Added the "Similarity check (PNSR and SSIM) on the GPU" tutorial. Corrected an highgu...
authorBernat Gabor <no@email>
Sun, 14 Aug 2011 15:05:56 +0000 (15:05 +0000)
committerBernat Gabor <no@email>
Sun, 14 Aug 2011 15:05:56 +0000 (15:05 +0000)
doc/conf.py
doc/tutorials/gpu/gpu-basics-similarity/gpu-basics-similarity.rst [new file with mode: 0644]
doc/tutorials/gpu/table_of_content_gpu/images/gpu-basics-similarity.png [new file with mode: 0644]
doc/tutorials/gpu/table_of_content_gpu/table_of_content_gpu.rst
doc/tutorials/highgui/table_of_content_highgui/table_of_content_highgui.rst
samples/cpp/tutorial_code/gpu/gpu-basics-similarity/gpu-basics-similarity.cpp [new file with mode: 0644]
samples/cpp/tutorial_code/images/Megamind.png [new file with mode: 0644]
samples/cpp/tutorial_code/images/Megamind_alt.png [new file with mode: 0644]

index 7d1ed1f..29408be 100644 (file)
@@ -357,21 +357,26 @@ extlinks = {'cvt_color': ('http://opencv.willowgarage.com/documentation/cpp/imgp
             'svms':('http://opencv.itseez.com/modules/ml/doc/support_vector_machines.html#%s', None),
             'xmlymlpers':('http://opencv.itseez.com/modules/core/doc/xml_yaml_persistence.html#%s', None),
                        'huivideo' : ('http://opencv.itseez.com/modules/highgui/doc/reading_and_writing_images_and_video.html#%s', None),
-            'filtering':('http://opencv.itseez.com/modules/imgproc/doc/filtering.html#%s', None),
+                       'gpuinit' : ('http://opencv.itseez.com/modules/gpu/doc/initalization_and_information.html#%s', None),
+                       'gpudatastructure' : ('http://opencv.itseez.com/modules/gpu/doc/data_structures.html#%s', None),
+                       'gpuopmatrices' : ('http://opencv.itseez.com/modules/gpu/doc/operations_on_matrices.html#%s', None),
+                       'gpuperelement' : ('http://opencv.itseez.com/modules/gpu/doc/per_element_operations.html#%s', None),
+                       'gpuimgproc' : ('http://opencv.itseez.com/modules/gpu/doc/image_processing.html#%s', None),
+                       'gpumatrixreduct' : ('http://opencv.itseez.com/modules/gpu/doc/matrix_reductions.html#%s', None),'filtering':('http://opencv.itseez.com/modules/imgproc/doc/filtering.html#%s', None),
             'point_polygon_test' : ('http://opencv.willowgarage.com/documentation/cpp/imgproc_structural_analysis_and_shape_descriptors.html#cv-pointpolygontest%s', None),
             'feature_detector' : ( 'http://opencv.willowgarage.com/documentation/cpp/features2d_common_interfaces_of_feature_detectors.html#featuredetector%s', None),
-           'feature_detector_detect' : ('http://opencv.willowgarage.com/documentation/cpp/features2d_common_interfaces_of_feature_detectors.html#cv-featuredetector-detect%s', None ),
-           'surf_feature_detector' : ('http://opencv.willowgarage.com/documentation/cpp/features2d_common_interfaces_of_feature_detectors.html#surffeaturedetector%s', None ),
-           'draw_keypoints' : ('http://opencv.willowgarage.com/documentation/cpp/features2d_drawing_function_of_keypoints_and_matches.html#cv-drawkeypoints%s', None ),
-           'descriptor_extractor': ( 'http://opencv.willowgarage.com/documentation/cpp/features2d_common_interfaces_of_descriptor_extractors.html#descriptorextractor%s', None ),
-           'descriptor_extractor_compute' : ( 'http://opencv.willowgarage.com/documentation/cpp/features2d_common_interfaces_of_descriptor_extractors.html#cv-descriptorextractor-compute%s', None ),
-           'surf_descriptor_extractor' : ( 'http://opencv.willowgarage.com/documentation/cpp/features2d_common_interfaces_of_descriptor_extractors.html#surfdescriptorextractor%s', None ),
-           'draw_matches' : ( 'http://opencv.willowgarage.com/documentation/cpp/features2d_drawing_function_of_keypoints_and_matches.html#cv-drawmatches%s', None ),
-           'find_homography' : ('http://opencv.willowgarage.com/documentation/cpp/calib3d_camera_calibration_and_3d_reconstruction.html?#findHomography%s', None),
-           'perspective_transform' : ('http://opencv.willowgarage.com/documentation/cpp/core_operations_on_arrays.html?#perspectiveTransform%s', None ),
-           'flann_based_matcher' : ('http://opencv.willowgarage.com/documentation/cpp/features2d_common_interfaces_of_descriptor_matchers.html?#FlannBasedMatcher%s', None),
-           'brute_force_matcher' : ('http://opencv.willowgarage.com/documentation/cpp/features2d_common_interfaces_of_descriptor_matchers.html?#BruteForceMatcher%s', None ),
-           'flann' : ('http://opencv.willowgarage.com/documentation/cpp/flann_fast_approximate_nearest_neighbor_search.html?%s', None )                                         
+               'feature_detector_detect' : ('http://opencv.willowgarage.com/documentation/cpp/features2d_common_interfaces_of_feature_detectors.html#cv-featuredetector-detect%s', None ),
+               'surf_feature_detector' : ('http://opencv.willowgarage.com/documentation/cpp/features2d_common_interfaces_of_feature_detectors.html#surffeaturedetector%s', None ),
+               'draw_keypoints' : ('http://opencv.willowgarage.com/documentation/cpp/features2d_drawing_function_of_keypoints_and_matches.html#cv-drawkeypoints%s', None ),
+               'descriptor_extractor': ( 'http://opencv.willowgarage.com/documentation/cpp/features2d_common_interfaces_of_descriptor_extractors.html#descriptorextractor%s', None ),
+               'descriptor_extractor_compute' : ( 'http://opencv.willowgarage.com/documentation/cpp/features2d_common_interfaces_of_descriptor_extractors.html#cv-descriptorextractor-compute%s', None ),
+               'surf_descriptor_extractor' : ( 'http://opencv.willowgarage.com/documentation/cpp/features2d_common_interfaces_of_descriptor_extractors.html#surfdescriptorextractor%s', None ),
+               'draw_matches' : ( 'http://opencv.willowgarage.com/documentation/cpp/features2d_drawing_function_of_keypoints_and_matches.html#cv-drawmatches%s', None ),
+               'find_homography' : ('http://opencv.willowgarage.com/documentation/cpp/calib3d_camera_calibration_and_3d_reconstruction.html?#findHomography%s', None),
+               'perspective_transform' : ('http://opencv.willowgarage.com/documentation/cpp/core_operations_on_arrays.html?#perspectiveTransform%s', None ),
+               'flann_based_matcher' : ('http://opencv.willowgarage.com/documentation/cpp/features2d_common_interfaces_of_descriptor_matchers.html?#FlannBasedMatcher%s', None),
+               'brute_force_matcher' : ('http://opencv.willowgarage.com/documentation/cpp/features2d_common_interfaces_of_descriptor_matchers.html?#BruteForceMatcher%s', None ),
+               'flann' : ('http://opencv.willowgarage.com/documentation/cpp/flann_fast_approximate_nearest_neighbor_search.html?%s', None )                                     
            }
 
 
diff --git a/doc/tutorials/gpu/gpu-basics-similarity/gpu-basics-similarity.rst b/doc/tutorials/gpu/gpu-basics-similarity/gpu-basics-similarity.rst
new file mode 100644 (file)
index 0000000..d81b9fa
--- /dev/null
@@ -0,0 +1 @@
+.. _gpuBasicsSimilarity:\r\rSimilarity check (PNSR and SSIM) on the GPU\r*******************************************\r\rGoal\r====\r\rIn the :ref:`videoInputPSNRMSSIM` tutorial I already presented the PSNR and SSIM methods for checking the similarity between the two images. And as you could see there performing these takes quite some time, especially in the case of the SSIM. However, if the performance numbers of an OpenCV implementation for the CPU do not satisfy you and you happen to have an NVidia CUDA GPU device in your system all is not lost. You may try to port or write your algorithm for the video card. \r\rThis tutorial will give a good grasp on how to approach coding by using the GPU module of OpenCV. As a prerequisite you should already know how to handle the core, highgui and imgproc modules. So, our goals are: \r\r.. container:: enumeratevisibleitemswithsquare\r\r   + What's different compared to the CPU?\r   + Create the GPU code for the PSNR and SSIM \r   + Optimize the code for maximal performance\r\rThe source code\r===============\r\rYou may also find the source code and these video file in the :file:`samples/cpp/tutorial_code/gpu/gpu-basics-similarity/gpu-basics-similarity` folder of the OpenCV source library or :download:`download it from here <../../../../samples/cpp/tutorial_code/gpu/gpu-basics-similarity/gpu-basics-similarity.cpp>`. The full source code is quite long (due to the controlling of the application via the command line arguments and performance measurement). Therefore, to avoid cluttering up these sections with those you'll find here only the functions itself. \r\rThe PSNR returns a float number, that if the two inputs are similar between 30 and 50 (higher is better). \r\r.. literalinclude:: ../../../../samples/cpp/tutorial_code/gpu/gpu-basics-similarity/gpu-basics-similarity.cpp\r   :language: cpp\r   :linenos:\r   :tab-width: 4\r   :lines: 165-210, 17-23, 211-233\r\rThe SSIM returns the MSSIM of the images. This is too a float number between zero and one (higher is better), however we have one for each channel. Therefore, we return a *Scalar* OpenCV data structure:\r\r.. literalinclude:: ../../../../samples/cpp/tutorial_code/gpu/gpu-basics-similarity/gpu-basics-similarity.cpp\r   :language: cpp\r   :linenos:\r   :tab-width: 4\r   :lines: 235-283, 25-42, 284-\r\rHow to do it? - The GPU\r=======================\r\rNow as you can see we have three types of functions for each operation. One for the CPU and two for the GPU. The reason I made two for the GPU is too illustrate that often simple porting your CPU to GPU will actually make it slower. If you want some performance gain you will need to remember a few rules, whose I'm going to detail later on.\r\rThe development of the GPU module was made so that it resembles as much as possible its CPU counterpart. This is to make porting easy. The first thing you need to do before writing any code is to link the GPU module to your project, and include the header file for the module. All the functions and data structures of the GPU are in a *gpu* sub namespace of the *cv* namespace. You may add this to the default one via the *use namespace* keyword, or mark it everywhere explicitly via the cv:: to avoid confusion. I'll do the later. \r\r.. code-block:: cpp\r\r   #include <opencv2/gpu/gpu.hpp>        // GPU structures and methods\r\rGPU stands for **g**\ raphics **p**\ rocessing **u**\ nit. It was originally build to render graphical scenes. These scenes somehow build on a lot of data. Nevertheless, these aren't all dependent one from another in a sequential way and as it is possible a parallel processing of them. Due to this a GPU will contain multiple smaller processing units. These aren't the state of the art processors and on a one on one test with a CPU it will fall behind. However, its strength lies in its numbers. In the last years there has been an increasing trend to harvest these massive parallel powers of the GPU in non-graphical scene rendering too. This gave birth to the general-purpose computation on graphics processing units (GPGPU). \r\rThe GPU has its own memory. When you read data from the hard drive with OpenCV into a *Mat* object that takes place in your systems memory. The CPU works somehow directly on this (via its cache), however the GPU cannot. He has too transferred the information he will use for calculations from the system memory to its own. This is done via an upload process and takes time. In the end the result will have to be downloaded back to your system memory for your CPU to see it and use it. Porting small functions to GPU is not recommended as the upload/download time will be larger than the amount you gain by a parallel execution. \r\r*Mat* objects are stored **only** in the system memory (or the CPU cache). For getting an OpenCV matrix to the GPU you'll need to use its GPU counterpart :gpudatastructure:`GpuMat <gpu-gpumat>`. It works similar to the *Mat* with a 2D only limitation and no reference returning for its functions (cannot mix GPU references with CPU ones). To upload a *Mat* object to the *GPU* you need to call the *upload* function after creating an instance of the class. To download you may use simple assignment to a *Mat* object or use the *download* function.\r\r.. code-block:: cpp \r\r   Mat I1;         // Main memory item - read image into with imread for example\r   gpu::GpuMat gI; // GPU matrix - for now empty\r   gI1.upload(I1); // Upload a data from the system memory to the GPU memory\r\r   I1 = gI1;       // Download, gI1.download(I1) will work too\r\rOnce you have your data up in the GPU memory you may call GPU enabled functions of OpenCV. Most of the functions keep the same name just as on the CPU, with the difference that they only accept *GpuMat* inputs. A full list of these you will find in the documentation: `online here <http://opencv.itseez.com/modules/gpu/doc/gpu.html>`_ or the OpenCV reference manual that comes with the source code.\r\rAnother thing to keep in mind is that not for all channel numbers you can make efficient algorithms on the GPU. Generally, I found that the input images for the GPU images need to be either one or four channel ones and one of the char or float type for the item sizes. No double support on the GPU, sorry. Passing other types of objects for some functions will result in an exception thrown, and an error message on the error output. The documentation details in most of the places the types accepted for the inputs. If you have three channel images as an input you can do two things: either adds a new channel (and use char elements) or split up the image and call the function for each image. The first one isn't really recommended as you waste memory. \r\rFor some functions, where the position of the elements (neighbor items) doesn't matter quick solution is to just reshape it into a single channel image. This is the case for the PSNR implementation where for the *absdiff* method the value of the neighbors is not important. However, for the *GaussianBlur* this isn't an option and such need to use the split method for the SSIM. With this knowledge you can already make a GPU viable code (like mine GPU one) and run it. You'll be surprised to see that it might turn out slower than your CPU implementation. \r\rOptimization\r============\r\rThe reason for this is that you're throwing out on the window the price for memory allocation and data transfer. And on the GPU this is damn high. Another possibility for optimization is to introduce asynchronous OpenCV GPU calls too with the help of the :gpudatastructure:`gpu::Stream <gpu-stream>`. \r\r1. Memory allocation on the GPU is considerable. Therefore, if it’s possible allocate new memory as few times as possible. If you create a function what you intend to call multiple times it is a good idea to allocate any local parameters for the function only once, during the first call. To do this you create a data structure containing all the local variables you will use. For instance in case of the PSNR these are: \r\r   .. code-block:: cpp \r\r      struct BufferPSNR                                     // Optimized GPU versions\r        {   // Data allocations are very expensive on GPU. Use a buffer to solve: allocate once reuse later.\r        gpu::GpuMat gI1, gI2, gs, t1,t2;\r\r        gpu::GpuMat buf;\r      };\r\r   Then create an instance of this in the main program: \r\r   .. code-block:: cpp\r\r      BufferPSNR bufferPSNR;\r\r   And finally pass this to the function each time you call it: \r\r   .. code-block:: cpp\r\r      double getPSNR_GPU_optimized(const Mat& I1, const Mat& I2, BufferPSNR& b)\r\r   Now you access these local parameters as: *b.gI1*, *b.buf* and so on. The GpuMat will only reallocate itself on a new call if the new matrix size is different from the previous one. \r\r#. Avoid unnecessary function data transfers. Any small data transfer will be significant one once you go to the GPU. Therefore, if possible make all calculations in-place (in other words do not create new memory objects - for reasons explained at the previous point). For example, although expressing arithmetical operations may be easier to express in one line formulas, it will be slower. In case of the SSIM at one point I need to calculate:\r\r   .. code-block:: cpp \r\r      b.t1 = 2 * b.mu1_mu2 + C1;  \r\r   Although the upper call will succeed observe that there is a hidden data transfer present. Before it makes the addition it needs to store somewhere the multiplication. Therefore, it will create a local matrix in the background, add to that the *C1* value and finally assign that to *t1*. To avoid this we use the gpu functions, instead of the arithmetic operators: \r\r   .. code-block:: cpp\r\r      gpu::multiply(b.mu1_mu2, 2, b.t1); //b.t1 = 2 * b.mu1_mu2 + C1; \r      gpu::add(b.t1, C1, b.t1);\r\r#. Use asynchronous calls (the :gpudatastructure:`gpu::Stream <gpu-stream>`). By default whenever you call a gpu function it will wait for the call to finish and return with the result afterwards. However, it is possible to make asynchronous calls, meaning it will call for the operation execution, make the costly data allocations for the algorithm and return back right away. Now you can call another function if you wish to do so. For the MSSIM this is a small optimization point. In our default implementation we split up the image into channels and call then for each channel the gpu functions. A small degree of parallelization is possible with the stream. By using a stream we can make the data allocation, upload operations while the GPU is already executing a given method. For example we need to upload two images. We queue these one after another and call already the function that processes it. The functions will wait for the upload to finish, however while that happens makes the output buffer allocations for the function to be executed next. \r\r   .. code-block:: cpp \r\r      gpu::Stream stream;\r\r      stream.enqueueConvert(b.gI1, b.t1, CV_32F);    // Upload\r\r      gpu::split(b.t1, b.vI1, stream);              // Methods (pass the stream as final parameter).\r      gpu::multiply(b.vI1[i], b.vI1[i], b.I1_2, stream);        // I1^2\r\rResult and conclusion\r=====================\r\rOn an Intel P8700 laptop CPU paired with a low end NVidia GT220M here are the performance numbers: \r\r.. code-block:: cpp\r\r   Time of PSNR CPU (averaged for 10 runs): 41.4122 milliseconds. With result of: 19.2506\r   Time of PSNR GPU (averaged for 10 runs): 158.977 milliseconds. With result of: 19.2506\r   Initial call GPU optimized:              31.3418 milliseconds. With result of: 19.2506\r   Time of PSNR GPU OPTIMIZED ( / 10 runs): 24.8171 milliseconds. With result of: 19.2506\r\r   Time of MSSIM CPU (averaged for 10 runs): 484.343 milliseconds. With result of B0.890964 G0.903845 R0.936934\r   Time of MSSIM GPU (averaged for 10 runs): 745.105 milliseconds. With result of B0.89922 G0.909051 R0.968223\r   Time of MSSIM GPU Initial Call            357.746 milliseconds. With result of B0.890964 G0.903845 R0.936934\r   Time of MSSIM GPU OPTIMIZED ( / 10 runs): 203.091 milliseconds. With result of B0.890964 G0.903845 R0.936934\r\rIn both cases we managed a performance increase of almost 100% compared to the CPU implementation. It may be just the improvement needed for your application to work. You may observe a runtime instance of this on the `YouTube here <https://www.youtube.com/watch?v=3_ESXmFlnvY>`_. \r\r.. raw:: html\r\r  <div align="center">\r  <iframe title="Similarity check (PNSR and SSIM) on the GPU" width="560" height="349" src="http://www.youtube.com/embed/3_ESXmFlnvY?rel=0&loop=1" frameborder="0" allowfullscreen align="middle"></iframe>\r  </div>\r
\ No newline at end of file
diff --git a/doc/tutorials/gpu/table_of_content_gpu/images/gpu-basics-similarity.png b/doc/tutorials/gpu/table_of_content_gpu/images/gpu-basics-similarity.png
new file mode 100644 (file)
index 0000000..3d6cca4
Binary files /dev/null and b/doc/tutorials/gpu/table_of_content_gpu/images/gpu-basics-similarity.png differ
index cadbcfd..a5935fb 100644 (file)
@@ -1,12 +1,36 @@
 .. _Table-Of-Content-GPU:\r
 \r
 *gpu* module. GPU-Accelerated Computer Vision\r
------------------------------------------------------------\r
+---------------------------------------------\r
 \r
 Squeeze out every little computation power from your system by using the power of your video card to run the OpenCV algorithms.\r
 \r
-.. include:: ../../definitions/noContent.rst\r
+.. include:: ../../definitions/tocDefinitions.rst\r
+\r
++ \r
+  .. tabularcolumns:: m{100pt} m{300pt}\r
+  .. cssclass:: toctableopencv\r
+\r
+  =============== ======================================================\r
+  |hVideoWrite|   *Title:* :ref:`gpuBasicsSimilarity`\r
+\r
+                  *Compatibility:* > OpenCV 2.0\r
+\r
+                  *Author:* |Author_BernatG|\r
+\r
+                  This will give a good grasp on how to approach coding on the GPU module, once you already know how to handle the other modules. As a test case it will port the similarity methods from the tutorial :ref:`videoInputPSNRMSSIM` to the GPU. \r
+\r
+  =============== ======================================================\r
+\r
+  .. |hVideoWrite| image:: images/gpu-basics-similarity.png\r
+                   :height: 90pt\r
+                   :width:  90pt\r
 \r
 .. raw:: latex\r
 \r
    \pagebreak\r
+\r
+.. toctree::\r
+   :hidden:\r
+\r
+   ../gpu-basics-similarity/gpu-basics-similarity\r
index 753ff34..0cd6c63 100644 (file)
@@ -1,7 +1,7 @@
 .. _Table-Of-Content-HighGui:\r
 \r
 *highgui* module. High Level GUI and Media\r
------------------------------------------------------------\r
+------------------------------------------\r
 \r
 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. \r
 \r
@@ -74,3 +74,4 @@ This section contains valuable tutorials about how to read/save your image/video
 \r
    ../trackbar/trackbar\r
    ../video-input-psnr-ssim/video-input-psnr-ssim\r
+   ../video-write/video-write
\ No newline at end of file
diff --git a/samples/cpp/tutorial_code/gpu/gpu-basics-similarity/gpu-basics-similarity.cpp b/samples/cpp/tutorial_code/gpu/gpu-basics-similarity/gpu-basics-similarity.cpp
new file mode 100644 (file)
index 0000000..b064f73
--- /dev/null
@@ -0,0 +1,352 @@
+#include <iostream>                   // Console I/O\r
+#include <sstream>                    // String to number conversion\r
+\r
+#include <opencv2/core/core.hpp>      // Basic OpenCV structures\r
+#include <opencv2/imgproc/imgproc.hpp>// Image processing methods for the CPU\r
+#include <opencv2/highgui/highgui.hpp>// Read images\r
+#include <opencv2/gpu/gpu.hpp>        // GPU structures and methods\r
+\r
+using namespace std;\r
+using namespace cv;\r
+\r
+double getPSNR(const Mat& I1, const Mat& I2);      // CPU versions\r
+Scalar getMSSIM( const Mat& I1, const Mat& I2);\r
+\r
+double getPSNR_GPU(const Mat& I1, const Mat& I2);  // Basic GPU versions\r
+Scalar getMSSIM_GPU( const Mat& I1, const Mat& I2);\r
+\r
+struct BufferPSNR                                     // Optimized GPU versions\r
+{   // Data allocations are very expensive on GPU. Use a buffer to solve: allocate once reuse later.\r
+    gpu::GpuMat gI1, gI2, gs, t1,t2;\r
+\r
+    gpu::GpuMat buf;\r
+};\r
+double getPSNR_GPU_optimized(const Mat& I1, const Mat& I2, BufferPSNR& b);\r
+\r
+struct BufferMSSIM                                     // Optimized GPU versions\r
+{   // Data allocations are very expensive on GPU. Use a buffer to solve: allocate once reuse later.\r
+    gpu::GpuMat gI1, gI2, gs, t1,t2;\r
+\r
+    gpu::GpuMat I1_2, I2_2, I1_I2;\r
+    vector<gpu::GpuMat> vI1, vI2;\r
+\r
+    gpu::GpuMat mu1, mu2; \r
+    gpu::GpuMat mu1_2, mu2_2, mu1_mu2; \r
+\r
+    gpu::GpuMat sigma1_2, sigma2_2, sigma12; \r
+    gpu::GpuMat t3; \r
+\r
+    gpu::GpuMat ssim_map;\r
+\r
+    gpu::GpuMat buf;\r
+};\r
+Scalar getMSSIM_GPU_optimized( const Mat& i1, const Mat& i2, BufferMSSIM& b);\r
+\r
+void help()\r
+{\r
+    cout\r
+        << "\n--------------------------------------------------------------------------" << endl\r
+        << "This program shows how to port your CPU code to GPU or write that from scratch." << endl\r
+        << "You can see the performance improvement for the similarity check methods (PSNR and SSIM)."  << endl\r
+        << "Usage:"                                                               << endl\r
+        << "./gpu-basics-similarity referenceImage comparedImage numberOfTimesToRunTest(like 10)." << endl\r
+        << "--------------------------------------------------------------------------"   << endl\r
+        << endl;\r
+}\r
+\r
+int main(int argc, char *argv[])\r
+{\r
+       help(); \r
+    Mat I1 = imread(argv[1]);           // Read the two images\r
+    Mat I2 = imread(argv[2]);\r
+\r
+    if (!I1.data || !I2.data)           // Check for success\r
+    {\r
+        cout << "Couldn't read the image";\r
+        return 0;\r
+    }\r
+\r
+    BufferPSNR bufferPSNR;\r
+    BufferMSSIM bufferMSSIM;\r
+    \r
+    int TIMES; \r
+    stringstream sstr(argv[3]); \r
+    sstr >> TIMES;\r
+    double time, result;\r
+\r
+    //------------------------------- PSNR CPU ----------------------------------------------------\r
+    time = (double)getTickCount();    \r
+\r
+    for (int i = 0; i < TIMES; ++i)\r
+        result = getPSNR(I1,I2);\r
+\r
+    time = 1000*((double)getTickCount() - time)/getTickFrequency();\r
+    time /= TIMES;\r
+\r
+    cout << "Time of PSNR CPU (averaged for " << TIMES << " runs): " << time << " milliseconds."\r
+        << " With result of: " <<  result << endl; \r
+\r
+    //------------------------------- PSNR GPU ----------------------------------------------------\r
+    time = (double)getTickCount();    \r
+\r
+    for (int i = 0; i < TIMES; ++i)\r
+        result = getPSNR_GPU(I1,I2);\r
+\r
+    time = 1000*((double)getTickCount() - time)/getTickFrequency();\r
+    time /= TIMES;\r
+\r
+    cout << "Time of PSNR GPU (averaged for " << TIMES << " runs): " << time << " milliseconds."\r
+        << " With result of: " <<  result << endl; \r
+\r
+    //------------------------------- PSNR GPU Optimized--------------------------------------------\r
+    time = (double)getTickCount();                                  // Initial call\r
+    result = getPSNR_GPU_optimized(I1, I2, bufferPSNR);\r
+    time = 1000*((double)getTickCount() - time)/getTickFrequency();\r
+    cout << "Initial call GPU optimized:              " << time  <<" milliseconds."\r
+         << " With result of: " << result << endl;\r
+\r
+    time = (double)getTickCount();    \r
+    for (int i = 0; i < TIMES; ++i)\r
+        result = getPSNR_GPU_optimized(I1, I2, bufferPSNR);\r
+\r
+    time = 1000*((double)getTickCount() - time)/getTickFrequency();\r
+    time /= TIMES;\r
+\r
+    cout << "Time of PSNR GPU OPTIMIZED ( / " << TIMES << " runs): " << time \r
+         << " milliseconds." << " With result of: " <<  result << endl << endl; \r
+\r
+\r
+    //------------------------------- SSIM CPU -----------------------------------------------------\r
+    Scalar x;\r
+    time = (double)getTickCount();    \r
+\r
+    for (int i = 0; i < TIMES; ++i)\r
+        x = getMSSIM(I1,I2);\r
+\r
+    time = 1000*((double)getTickCount() - time)/getTickFrequency();\r
+    time /= TIMES;\r
+\r
+    cout << "Time of MSSIM CPU (averaged for " << TIMES << " runs): " << time << " milliseconds."\r
+        << " With result of B" << x.val[0] << " G" << x.val[1] << " R" << x.val[2] << endl; \r
+\r
+    //------------------------------- SSIM GPU -----------------------------------------------------\r
+    time = (double)getTickCount();    \r
+\r
+    for (int i = 0; i < TIMES; ++i)\r
+        x = getMSSIM_GPU(I1,I2);\r
+\r
+    time = 1000*((double)getTickCount() - time)/getTickFrequency();\r
+    time /= TIMES;\r
+\r
+    cout << "Time of MSSIM GPU (averaged for " << TIMES << " runs): " << time << " milliseconds."\r
+        << " With result of B" << x.val[0] << " G" << x.val[1] << " R" << x.val[2] << endl; \r
+\r
+    //------------------------------- SSIM GPU Optimized--------------------------------------------\r
+    time = (double)getTickCount();    \r
+    x = getMSSIM_GPU_optimized(I1,I2, bufferMSSIM);\r
+    time = 1000*((double)getTickCount() - time)/getTickFrequency();\r
+    cout << "Time of MSSIM GPU Initial Call            " << time << " milliseconds."\r
+        << " With result of B" << x.val[0] << " G" << x.val[1] << " R" << x.val[2] << endl; \r
+\r
+    time = (double)getTickCount();    \r
+\r
+    for (int i = 0; i < TIMES; ++i)\r
+        x = getMSSIM_GPU_optimized(I1,I2, bufferMSSIM);\r
+\r
+    time = 1000*((double)getTickCount() - time)/getTickFrequency();\r
+    time /= TIMES;\r
+\r
+    cout << "Time of MSSIM GPU OPTIMIZED ( / " << TIMES << " runs): " << time << " milliseconds."\r
+        << " With result of B" << x.val[0] << " G" << x.val[1] << " R" << x.val[2] << endl << endl; \r
+    return 0;\r
+}\r
+\r
+\r
+double getPSNR(const Mat& I1, const Mat& I2)\r
+{\r
+    Mat s1; \r
+    absdiff(I1, I2, s1);       // |I1 - I2|\r
+    s1.convertTo(s1, CV_32F);  // cannot make a square on 8 bits\r
+    s1 = s1.mul(s1);           // |I1 - I2|^2\r
+\r
+    Scalar s = sum(s1);         // sum elements per channel\r
+\r
+    double sse = s.val[0] + s.val[1] + s.val[2]; // sum channels\r
+\r
+    if( sse <= 1e-10) // for small values return zero\r
+        return 0;\r
+    else\r
+    {\r
+        double  mse =sse /(double)(I1.channels() * I1.total());\r
+        double psnr = 10.0*log10((255*255)/mse);\r
+        return psnr;\r
+    }\r
+}\r
+\r
+double getPSNR_GPU(const Mat& I1, const Mat& I2)\r
+{\r
+    gpu::GpuMat gI1, gI2, gs, t1,t2; \r
+\r
+    gI1.upload(I1);\r
+    gI2.upload(I2);\r
+\r
+    gI1.convertTo(t1, CV_32F);\r
+    gI2.convertTo(t2, CV_32F);\r
+\r
+    gpu::absdiff(t1.reshape(1), t2.reshape(1), gs); \r
+    gpu::multiply(gs, gs, gs);\r
+    \r
+    Scalar s = gpu::sum(gs);\r
+    double sse = s.val[0] + s.val[1] + s.val[2];\r
+\r
+    if( sse <= 1e-10) // for small values return zero\r
+        return 0;\r
+    else\r
+    {\r
+        double  mse =sse /(double)(gI1.channels() * I1.total());\r
+        double psnr = 10.0*log10((255*255)/mse);\r
+        return psnr;\r
+    }\r
+}\r
+\r
+double getPSNR_GPU_optimized(const Mat& I1, const Mat& I2, BufferPSNR& b)\r
+{    \r
+    b.gI1.upload(I1);\r
+    b.gI2.upload(I2);\r
+\r
+    b.gI1.convertTo(b.t1, CV_32F);\r
+    b.gI2.convertTo(b.t2, CV_32F);\r
+\r
+    gpu::absdiff(b.t1.reshape(1), b.t2.reshape(1), b.gs);\r
+    gpu::multiply(b.gs, b.gs, b.gs);\r
+\r
+    double sse = gpu::sum(b.gs, b.buf)[0];\r
+\r
+    if( sse <= 1e-10) // for small values return zero\r
+        return 0;\r
+    else\r
+    {\r
+        double mse = sse /(double)(I1.channels() * I1.total());\r
+        double psnr = 10.0*log10((255*255)/mse);\r
+        return psnr;\r
+    }\r
+}\r
+\r
+Scalar getMSSIM( const Mat& i1, const Mat& i2)\r
+{ \r
+    const double C1 = 6.5025, C2 = 58.5225;\r
+    int d     = CV_32F;\r
+\r
+    Mat I1, I2; \r
+    i1.convertTo(I1, d);           // cannot calculate on one byte large values\r
+    i2.convertTo(I2, d); \r
+\r
+    Mat I2_2   = I2.mul(I2);        // I2^2\r
+    Mat I1_2   = I1.mul(I1);        // I1^2\r
+    Mat I1_I2  = I1.mul(I2);        // I1 * I2\r
+       \r
+    Mat mu1, mu2;  \r
+    GaussianBlur(I1, mu1, Size(11, 11), 1.5);\r
+    GaussianBlur(I2, mu2, Size(11, 11), 1.5);\r
+\r
+    Mat mu1_2   =   mu1.mul(mu1);    \r
+    Mat mu2_2   =   mu2.mul(mu2); \r
+    Mat mu1_mu2 =   mu1.mul(mu2);\r
+\r
+    Mat sigma1_2, sigma2_2, sigma12; \r
+\r
+    GaussianBlur(I1_2, sigma1_2, Size(11, 11), 1.5);\r
+    sigma1_2 -= mu1_2;\r
+\r
+    GaussianBlur(I2_2, sigma2_2, Size(11, 11), 1.5);\r
+    sigma2_2 -= mu2_2;\r
+\r
+    GaussianBlur(I1_I2, sigma12, Size(11, 11), 1.5);\r
+    sigma12 -= mu1_mu2;\r
+\r
+    ///////////////////////////////// FORMULA ////////////////////////////////\r
+    Mat t1, t2, t3; \r
+\r
+    t1 = 2 * mu1_mu2 + C1; \r
+    t2 = 2 * sigma12 + C2; \r
+    t3 = t1.mul(t2);              // t3 = ((2*mu1_mu2 + C1).*(2*sigma12 + C2))\r
+\r
+    t1 = mu1_2 + mu2_2 + C1; \r
+    t2 = sigma1_2 + sigma2_2 + C2;     \r
+    t1 = t1.mul(t2);               // t1 =((mu1_2 + mu2_2 + C1).*(sigma1_2 + sigma2_2 + C2))\r
+\r
+    Mat ssim_map;\r
+    divide(t3, t1, ssim_map);      // ssim_map =  t3./t1;\r
+\r
+    Scalar mssim = mean( ssim_map ); // mssim = average of ssim map\r
+    return mssim; \r
+}\r
+\r
+Scalar getMSSIM_GPU_optimized( const Mat& i1, const Mat& i2, BufferMSSIM& b)\r
+{ \r
+    int cn = i1.channels();\r
+    const float C1 = 6.5025f, C2 = 58.5225f;\r
+\r
+    b.gI1.upload(i1);\r
+    b.gI2.upload(i2);\r
+\r
+    gpu::Stream stream;\r
+\r
+    stream.enqueueConvert(b.gI1, b.t1, CV_32F);\r
+    stream.enqueueConvert(b.gI2, b.t2, CV_32F);      \r
+\r
+    gpu::split(b.t1, b.vI1, stream);\r
+    gpu::split(b.t2, b.vI2, stream);\r
+    Scalar mssim;\r
+\r
+    for( int i = 0; i < b.gI1.channels(); ++i )\r
+    {        \r
+        gpu::multiply(b.vI2[i], b.vI2[i], b.I2_2, stream);        // I2^2\r
+        gpu::multiply(b.vI1[i], b.vI1[i], b.I1_2, stream);        // I1^2\r
+        gpu::multiply(b.vI1[i], b.vI2[i], b.I1_I2, stream);       // I1 * I2\r
+\r
+        gpu::GaussianBlur(b.vI1[i], b.mu1, Size(11, 11), 1.5, 0, BORDER_DEFAULT, -1, stream);\r
+        gpu::GaussianBlur(b.vI2[i], b.mu2, Size(11, 11), 1.5, 0, BORDER_DEFAULT, -1, stream);\r
+\r
+        gpu::multiply(b.mu1, b.mu1, b.mu1_2, stream);   \r
+        gpu::multiply(b.mu2, b.mu2, b.mu2_2, stream);   \r
+        gpu::multiply(b.mu1, b.mu2, b.mu1_mu2, stream);   \r
+\r
+        gpu::GaussianBlur(b.I1_2, b.sigma1_2, Size(11, 11), 1.5, 0, BORDER_DEFAULT, -1, stream);\r
+        gpu::subtract(b.sigma1_2, b.mu1_2, b.sigma1_2, stream);\r
+        //b.sigma1_2 -= b.mu1_2;  - This would result in an extra data transfer operation\r
+\r
+        gpu::GaussianBlur(b.I2_2, b.sigma2_2, Size(11, 11), 1.5, 0, BORDER_DEFAULT, -1, stream);\r
+        gpu::subtract(b.sigma2_2, b.mu2_2, b.sigma2_2, stream);\r
+        //b.sigma2_2 -= b.mu2_2;\r
+\r
+        gpu::GaussianBlur(b.I1_I2, b.sigma12, Size(11, 11), 1.5, 0, BORDER_DEFAULT, -1, stream);\r
+        gpu::subtract(b.sigma12, b.mu1_mu2, b.sigma12, stream);\r
+        //b.sigma12 -= b.mu1_mu2;\r
+\r
+        //here too it would be an extra data transfer due to call of operator*(Scalar, Mat)\r
+        gpu::multiply(b.mu1_mu2, 2, b.t1, stream); //b.t1 = 2 * b.mu1_mu2 + C1; \r
+        gpu::add(b.t1, C1, b.t1, stream);\r
+        gpu::multiply(b.sigma12, 2, b.t2, stream); //b.t2 = 2 * b.sigma12 + C2; \r
+        gpu::add(b.t2, C2, b.t2, stream);     \r
+\r
+        gpu::multiply(b.t1, b.t2, b.t3, stream);     // t3 = ((2*mu1_mu2 + C1).*(2*sigma12 + C2))\r
+\r
+        gpu::add(b.mu1_2, b.mu2_2, b.t1, stream);\r
+        gpu::add(b.t1, C1, b.t1, stream);\r
+\r
+        gpu::add(b.sigma1_2, b.sigma2_2, b.t2, stream);\r
+        gpu::add(b.t2, C2, b.t2, stream);\r
+\r
+\r
+        gpu::multiply(b.t1, b.t2, b.t1, stream);     // t1 =((mu1_2 + mu2_2 + C1).*(sigma1_2 + sigma2_2 + C2))        \r
+        gpu::divide(b.t3, b.t1, b.ssim_map, stream);      // ssim_map =  t3./t1;\r
+\r
+        stream.waitForCompletion();\r
+\r
+        Scalar s = gpu::sum(b.ssim_map, b.buf);    \r
+        mssim.val[i] = s.val[0] / (b.ssim_map.rows * b.ssim_map.cols);\r
+\r
+    }\r
+    return mssim; \r
+}
\ No newline at end of file
diff --git a/samples/cpp/tutorial_code/images/Megamind.png b/samples/cpp/tutorial_code/images/Megamind.png
new file mode 100644 (file)
index 0000000..a1cc6d1
Binary files /dev/null and b/samples/cpp/tutorial_code/images/Megamind.png differ
diff --git a/samples/cpp/tutorial_code/images/Megamind_alt.png b/samples/cpp/tutorial_code/images/Megamind_alt.png
new file mode 100644 (file)
index 0000000..4e345f2
Binary files /dev/null and b/samples/cpp/tutorial_code/images/Megamind_alt.png differ