From fa2d79a15b441230ae9998d0856754aa955fc2ec Mon Sep 17 00:00:00 2001 From: Elena Gvozdeva Date: Mon, 24 Feb 2014 10:44:54 +0400 Subject: [PATCH 1/3] Added IPP Async converters, doc and sample --- CMakeLists.txt | 6 +- cmake/OpenCVFindIPPAsync.cmake | 45 +++++ cmake/OpenCVFindLibsPerf.cmake | 11 ++ cmake/templates/cvconfig.h.in | 3 + doc/conf.py | 4 +- .../how_to_use_ippa_conversion.rst | 156 ++++++++++++++++++ .../images/How_To_Use_IPPA_Result.jpg | Bin 0 -> 62891 bytes .../images/How_To_Use_IPPA.jpg | Bin 0 -> 6991 bytes .../table_of_content_core.rst | 21 +++ modules/core/doc/core.rst | 1 + modules/core/doc/ipp_async_converters.rst | 62 +++++++ .../core/include/opencv2/core/ippasync.hpp | 92 +++++++++++ modules/core/test/test_ippasync.cpp | 116 +++++++++++++ .../core/ippasync/ippasync_sample.cpp | 149 +++++++++++++++++ 14 files changed, 664 insertions(+), 2 deletions(-) create mode 100644 cmake/OpenCVFindIPPAsync.cmake create mode 100644 doc/tutorials/core/how_to_use_ippa_conversion/how_to_use_ippa_conversion.rst create mode 100644 doc/tutorials/core/how_to_use_ippa_conversion/images/How_To_Use_IPPA_Result.jpg create mode 100644 doc/tutorials/core/table_of_content_core/images/How_To_Use_IPPA.jpg create mode 100644 modules/core/doc/ipp_async_converters.rst create mode 100644 modules/core/include/opencv2/core/ippasync.hpp create mode 100644 modules/core/test/test_ippasync.cpp create mode 100644 samples/cpp/tutorial_code/core/ippasync/ippasync_sample.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 442edf32c3..6c36347b76 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -157,7 +157,7 @@ OCV_OPTION(WITH_OPENCLAMDFFT "Include AMD OpenCL FFT library support" ON OCV_OPTION(WITH_OPENCLAMDBLAS "Include AMD OpenCL BLAS library support" ON IF (NOT ANDROID AND NOT IOS) ) OCV_OPTION(WITH_DIRECTX "Include DirectX support" ON IF WIN32 ) OCV_OPTION(WITH_INTELPERC "Include Intel Perceptual Computing support" OFF IF WIN32 ) - +OCV_OPTION(WITH_IPP_A "Include Intel IPP_A support" OFF IF (MSVC OR X86 OR X86_64) ) # OpenCV build components # =================================================== @@ -923,6 +923,10 @@ else() status(" Use IPP:" WITH_IPP AND NOT IPP_FOUND THEN "IPP not found" ELSE NO) endif() +if(DEFINED WITH_IPP_A) +status(" Use IPP Async:" HAVE_IPP_A THEN "YES" ELSE NO) +endif(DEFINED WITH_IPP_A) + status(" Use Eigen:" HAVE_EIGEN THEN "YES (ver ${EIGEN_WORLD_VERSION}.${EIGEN_MAJOR_VERSION}.${EIGEN_MINOR_VERSION})" ELSE NO) status(" Use TBB:" HAVE_TBB THEN "YES (ver ${TBB_VERSION_MAJOR}.${TBB_VERSION_MINOR} interface ${TBB_INTERFACE_VERSION})" ELSE NO) status(" Use OpenMP:" HAVE_OPENMP THEN YES ELSE NO) diff --git a/cmake/OpenCVFindIPPAsync.cmake b/cmake/OpenCVFindIPPAsync.cmake new file mode 100644 index 0000000000..0b5751ba07 --- /dev/null +++ b/cmake/OpenCVFindIPPAsync.cmake @@ -0,0 +1,45 @@ +# Main variables: +# IPP_A_LIBRARIES and IPP_A_INCLUDE to use IPP Async +# HAVE_IPP_A for conditional compilation OpenCV with/without IPP Async + +# IPPAROOT - root of IPP Async installation + +if(X86_64) + find_path( + IPP_A_INCLUDE_DIR + NAMES ipp_async_defs.h + PATHS $ENV{IPPAROOT} + PATH_SUFFIXES include + DOC "Path to Intel IPP Async interface headers") + + find_file( + IPP_A_LIBRARIES + NAMES ipp_async_preview.lib + PATHS $ENV{IPPAROOT} + PATH_SUFFIXES lib/intel64 + DOC "Path to Intel IPP Async interface libraries") + +else() + find_path( + IPP_A_INCLUDE_DIR + NAMES ipp_async_defs.h + PATHS $ENV{IPPAROOT} + PATH_SUFFIXES include + DOC "Path to Intel IPP Async interface headers") + + find_file( + IPP_A_LIBRARIES + NAMES ipp_async_preview.lib + PATHS $ENV{IPPAROOT} + PATH_SUFFIXES lib/ia32 + DOC "Path to Intel IPP Async interface libraries") +endif() + +if(IPP_A_INCLUDE_DIR AND IPP_A_LIBRARIES) + set(HAVE_IPP_A TRUE) +else() + set(HAVE_IPP_A FALSE) + message(WARNING "Intel IPP Async library directory (set by IPP_A_LIBRARIES_DIR variable) is not found or does not have Intel IPP Async libraries.") +endif() + +mark_as_advanced(FORCE IPP_A_LIBRARIES IPP_A_INCLUDE_DIR) \ No newline at end of file diff --git a/cmake/OpenCVFindLibsPerf.cmake b/cmake/OpenCVFindLibsPerf.cmake index 4b80b1f786..7d7b23928c 100644 --- a/cmake/OpenCVFindLibsPerf.cmake +++ b/cmake/OpenCVFindLibsPerf.cmake @@ -19,6 +19,17 @@ if(WITH_IPP) endif() endif(WITH_IPP) +# --- IPP Async --- + +if(WITH_IPP_A) + include("${OpenCV_SOURCE_DIR}/cmake/OpenCVFindIPPAsync.cmake") + if(IPP_A_INCLUDE_DIR AND IPP_A_LIBRARIES) + ocv_include_directories(${IPP_A_INCLUDE_DIR}) + link_directories(${IPP_A_LIBRARIES}) + set(OPENCV_LINKER_LIBS ${OPENCV_LINKER_LIBS} ${IPP_A_LIBRARIES}) + endif() +endif(WITH_IPP_A) + # --- CUDA --- if(WITH_CUDA) include("${OpenCV_SOURCE_DIR}/cmake/OpenCVDetectCUDA.cmake") diff --git a/cmake/templates/cvconfig.h.in b/cmake/templates/cvconfig.h.in index 3f316da46e..c02d9f6127 100644 --- a/cmake/templates/cvconfig.h.in +++ b/cmake/templates/cvconfig.h.in @@ -94,6 +94,9 @@ /* Intel Integrated Performance Primitives */ #cmakedefine HAVE_IPP +/* Intel IPP Async */ +#cmakedefine HAVE_IPP_A + /* JPEG-2000 codec */ #cmakedefine HAVE_JASPER diff --git a/doc/conf.py b/doc/conf.py index 0112725b92..08c6d128ee 100755 --- a/doc/conf.py +++ b/doc/conf.py @@ -416,5 +416,7 @@ extlinks = { 'background_subtractor' : ('http://docs.opencv.org/modules/video/doc/motion_analysis_and_object_tracking.html?highlight=backgroundsubtractor#backgroundsubtractor%s', None), 'background_subtractor_mog' : ('http://docs.opencv.org/modules/video/doc/motion_analysis_and_object_tracking.html?highlight=backgroundsubtractorMOG#backgroundsubtractormog%s', None), 'background_subtractor_mog_two' : ('http://docs.opencv.org/modules/video/doc/motion_analysis_and_object_tracking.html?highlight=backgroundsubtractorMOG2#backgroundsubtractormog2%s', None), - 'video_capture' : ('http://docs.opencv.org/modules/highgui/doc/reading_and_writing_images_and_video.html?highlight=videocapture#videocapture%s', None) + 'video_capture' : ('http://docs.opencv.org/modules/highgui/doc/reading_and_writing_images_and_video.html?highlight=videocapture#videocapture%s', None), + 'ippa_convert': ('http://docs.opencv.org/modules/core/doc/ipp_async_converters.html#%s', None), + 'ptr':('http://docs.opencv.org/modules/core/doc/basic_structures.html?highlight=Ptr#Ptr%s', None) } diff --git a/doc/tutorials/core/how_to_use_ippa_conversion/how_to_use_ippa_conversion.rst b/doc/tutorials/core/how_to_use_ippa_conversion/how_to_use_ippa_conversion.rst new file mode 100644 index 0000000000..bbdaa24a66 --- /dev/null +++ b/doc/tutorials/core/how_to_use_ippa_conversion/how_to_use_ippa_conversion.rst @@ -0,0 +1,156 @@ +.. _howToUseIPPAconversion: + +Intel® IPP Asynchronous C/C++ library in OpenCV +*********************************************** + +Goal +==== + +.. _hppiSobel: http://software.intel.com/en-us/node/474701 +.. _hppiMatrix: http://software.intel.com/en-us/node/501660 + +The tutorial demonstrates the `Intel® IPP Asynchronous C/C++ `_ library usage with OpenCV. +The code example below illustrates implementation of the Sobel operation, accelerated with Intel® IPP Asynchronous C/C++ functions. +In this code example, :ippa_convert:`hpp::getMat <>` and :ippa_convert:`hpp::getHpp <>` functions are used for data conversion between hppiMatrix_ and ``Mat`` matrices. + +Code +==== + +You may also find the source code in the :file:`samples/cpp/tutorial_code/core/ippasync/ippasync_sample.cpp` +file of the OpenCV source library or :download:`download it from here +<../../../../samples/cpp/tutorial_code/core/ippasync/ippasync_sample.cpp>`. + +.. literalinclude:: ../../../../samples/cpp/tutorial_code/core/ippasync/ippasync_sample.cpp + :language: cpp + :linenos: + :tab-width: 4 + +Explanation +=========== + +#. Create parameters for OpenCV: + + .. code-block:: cpp + + VideoCapture cap; + Mat image, gray, result; + + and IPP Async: + + .. code-block:: cpp + + Ptr src, dst; + hppAccel accel = 0; + hppAccelType accelType; + hppStatus sts; + hppiVirtualMatrix * virtMatrix; + +#. Load input image or video. How to open and read video stream you can see in the :ref:`videoInputPSNRMSSIM` tutorial. + + .. code-block:: cpp + + if( useCamera ) + { + printf("used camera\n"); + cap.open(0); + } + else + { + printf("used image %s\n", file.c_str()); + cap.open(file.c_str()); + } + + if( !cap.isOpened() ) + { + printf("can not open camera or video file\n"); + return -1; + } + +#. Create accelerator instance using `hppCreateInstance `_: + + .. code-block:: cpp + + accelType = sAccel == "cpu" ? HPP_ACCEL_TYPE_CPU: + sAccel == "gpu" ? HPP_ACCEL_TYPE_GPU: + HPP_ACCEL_TYPE_ANY; + + //Create accelerator instance + sts = hppCreateInstance(accelType, 0, &accel); + CHECK_STATUS(sts, "hppCreateInstance"); + +#. Create an array of virtual matrices using `hppiCreateVirtualMatrices `_ function. + + .. code-block:: cpp + + virtMatrix = hppiCreateVirtualMatrices(accel, 1); + +#. Prepare a matrix for input and output data: + + .. code-block:: cpp + + cap >> image; + if(image.empty()) + break; + + cvtColor( image, gray, COLOR_BGR2GRAY ); + + result.create( image.rows, image.cols, CV_8U); + +#. Convert ``Mat`` to hppiMatrix_ using :ippa_convert:`getHpp <>` and call hppiSobel_ function. + + .. code-block:: cpp + + //convert Mat to hppiMatrix + src = getHpp(gray); + dst = getHpp(result); + + sts = hppiSobel(accel,src, HPP_MASK_SIZE_3X3,HPP_NORM_L1,virtMatrix[0]); + CHECK_STATUS(sts,"hppiSobel"); + + sts = hppiConvert(accel, virtMatrix[0], 0, HPP_RND_MODE_NEAR, dst, HPP_DATA_TYPE_8U); + CHECK_STATUS(sts,"hppiConvert"); + + // Wait for tasks to complete + sts = hppWait(accel, HPP_TIME_OUT_INFINITE); + CHECK_STATUS(sts, "hppWait"); + + We use `hppiConvert `_ because hppiSobel_ returns destination + matrix with ``HPP_DATA_TYPE_16S`` data type for source matrix with ``HPP_DATA_TYPE_8U`` type. + You should check ``hppStatus`` after each call IPP Async function. + +#. Create windows and show the images, the usual way. + + .. code-block:: cpp + + imshow("image", image); + imshow("rez", result); + + waitKey(15); + +#. Delete virtual matrices and accelerator instance. + + .. code-block:: cpp + + if (virtMatrix) + { + sts = hppiDeleteVirtualMatrices(accel, virtMatrix); + CHECK_DEL_STATUS(sts,"hppiDeleteVirtualMatrices"); + } + + if (accel) + { + sts = hppDeleteInstance(accel); + CHECK_DEL_STATUS(sts, "hppDeleteInstance"); + } + + We shouldn't delete hppiMatrix_ because we use :ptr:`Ptr <>` and so `hppiFreeMatrix `_ will be called implicitly. + +Result +======= + +After compiling the code above we can execute it giving an image or video path and accelerator type as an argument. +For this tutorial we use baboon.png image as input. The result is below. + + .. image:: images/How_To_Use_IPPA_Result.jpg + :alt: Final Result + :align: center \ No newline at end of file diff --git a/doc/tutorials/core/how_to_use_ippa_conversion/images/How_To_Use_IPPA_Result.jpg b/doc/tutorials/core/how_to_use_ippa_conversion/images/How_To_Use_IPPA_Result.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2bdeb830cb4309307fb476b860d44c6db1793b00 GIT binary patch literal 62891 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 OpenCV 2.0 + *Author:* |Author_ElenaG| + + You will see how to use the IPP Async with OpenCV. + + =============== ====================================================== + + .. |IPPIma| image:: images/How_To_Use_IPPA.jpg + :height: 90pt + :width: 90pt + .. |Author_ElenaG| unicode:: Elena U+0020 Gvozdeva + + =============== ====================================================== .. raw:: latex \pagebreak @@ -219,3 +239,4 @@ Here you will learn the about the basic building blocks of the library. A must r ../discrete_fourier_transform/discrete_fourier_transform ../file_input_output_with_xml_yml/file_input_output_with_xml_yml ../interoperability_with_OpenCV_1/interoperability_with_OpenCV_1 + ../how_to_use_ippa_conversion/how_to_use_ippa_conversion diff --git a/modules/core/doc/core.rst b/modules/core/doc/core.rst index 73bfc35367..669fddd46a 100644 --- a/modules/core/doc/core.rst +++ b/modules/core/doc/core.rst @@ -16,3 +16,4 @@ core. The Core Functionality clustering utility_and_system_functions_and_macros opengl_interop + ipp_async_converters diff --git a/modules/core/doc/ipp_async_converters.rst b/modules/core/doc/ipp_async_converters.rst new file mode 100644 index 0000000000..6fac234e2a --- /dev/null +++ b/modules/core/doc/ipp_async_converters.rst @@ -0,0 +1,62 @@ +Intel® IPP Asynchronous C/C++ Converters +======================================== + +.. highlight:: cpp + +General Information +------------------- + +This section describes conversion between OpenCV and `Intel® IPP Asynchronous C/C++ `_ library. +`Getting Started Guide `_ help you to install the library, configure header and library build paths. + +hpp::getHpp +----------- +Create ``hppiMatrix`` from ``Mat``. + +.. ocv:function:: Ptr hpp::getHpp(const Mat& src) + + :param src: input matrix. + +This function allocates and initializes the ``hppiMatrix`` that has the same size and type as input matrix, returns the ``Ptr``. +Supports ``CV_8U``, ``CV_16U``, ``CV_16S``, ``CV_32S``, ``CV_32F``, ``CV_64F``. + +.. note:: The ``hppiMatrix`` pointer to the image buffer in system memory refers to the ``src.data``. Control the lifetime of the matrix and don't change its data, if there is no special need. +.. seealso:: :ref:`howToUseIPPAconversion`, :ocv:func:`hpp::getMat` + + +hpp::getMat +----------- +Create ``Mat`` from ``hppiMatrix``. + +.. ocv:function:: Mat hpp::getMat(hppiMatrix* src, hppAccel accel, int cn) + + :param src: input hppiMatrix. + + :param accel: accelerator instance. + + :param cn: number of channels. + +This function allocates and initializes the ``Mat`` that has the same size and type as input matrix. +Supports ``CV_8U``, ``CV_16U``, ``CV_16S``, ``CV_32S``, ``CV_32F``, ``CV_64F``. + +.. seealso:: :ref:`howToUseIPPAconversion`, :ocv:func:`hpp::copyHppToMat`, :ocv:func:`hpp::getHpp`. + + +hpp::copyHppToMat +----------------- +Convert ``hppiMatrix`` to ``Mat``. + +.. ocv:function:: void hpp::copyHppToMat(hppiMatrix* src, Mat& dst, hppAccel accel, int cn) + + :param src: input hppiMatrix. + + :param dst: output matrix. + + :param accel: accelerator instance. + + :param cn: number of channels. + +This function allocates and initializes new matrix (if needed) that has the same size and type as input matrix. +Supports ``CV_8U``, ``CV_16U``, ``CV_16S``, ``CV_32S``, ``CV_32F``, ``CV_64F``. + +.. seealso:: :ref:`howToUseIPPAconversion`, :ocv:func:`hpp::getMat`, :ocv:func:`hpp::getHpp`. \ No newline at end of file diff --git a/modules/core/include/opencv2/core/ippasync.hpp b/modules/core/include/opencv2/core/ippasync.hpp new file mode 100644 index 0000000000..b17a803add --- /dev/null +++ b/modules/core/include/opencv2/core/ippasync.hpp @@ -0,0 +1,92 @@ +#ifndef __OPENCV_CORE_IPPASYNC_HPP__ +#define __OPENCV_CORE_IPPASYNC_HPP__ + +#include "opencv2/core.hpp" +#include +#include + +namespace cv +{ + void DefaultDeleter::operator () (hppiMatrix* p) const + { + hppiFreeMatrix(p); + } + +namespace hpp +{ + //convert OpenCV data type to hppDataType + inline int toHppType(const int cvType) + { + int depth = CV_MAT_DEPTH(cvType); + int hppType = depth == CV_8U ? HPP_DATA_TYPE_8U : + depth == CV_16U ? HPP_DATA_TYPE_16U : + depth == CV_16S ? HPP_DATA_TYPE_16S : + depth == CV_32S ? HPP_DATA_TYPE_32S : + depth == CV_32F ? HPP_DATA_TYPE_32F : + depth == CV_64F ? HPP_DATA_TYPE_64F : -1; + CV_Assert( hppType >= 0 ); + return hppType; + } + + //convert hppDataType to OpenCV data type + inline int toCvType(const int hppType) + { + int cvType = hppType == HPP_DATA_TYPE_8U ? CV_8U : + hppType == HPP_DATA_TYPE_16U ? CV_16U : + hppType == HPP_DATA_TYPE_16S ? CV_16S : + hppType == HPP_DATA_TYPE_32S ? CV_32S : + hppType == HPP_DATA_TYPE_32F ? CV_32F : + hppType == HPP_DATA_TYPE_64F ? CV_64F : -1; + CV_Assert( cvType >= 0 ); + return cvType; + } + + inline void copyHppToMat(hppiMatrix* src, Mat& dst, hppAccel accel, int cn) + { + hppDataType type; + hpp32u width, height; + hppStatus sts; + + CV_Assert(src!=NULL); + + sts = hppiInquireMatrix(src, &type, &width, &height); + + CV_Assert( sts == HPP_STATUS_NO_ERROR); + + int matType = CV_MAKETYPE(toCvType(type), cn); + + CV_Assert(width%cn == 0); + + width /= cn; + + dst.create((int)height, (int)width, (int)matType); + + size_t newSize = (size_t)(height*(hpp32u)(dst.step)); + + sts = hppiGetMatrixData(accel,src,(hpp32u)(dst.step),dst.data,&newSize); + + CV_Assert( sts == HPP_STATUS_NO_ERROR); + } + + //create cv::Mat from hppiMatrix + inline Mat getMat(hppiMatrix* src, hppAccel accel, int cn) + { + Mat dst; + copyHppToMat(src, dst, accel, cn); + return dst; + } + + //create hppiMatrix from cv::Mat + inline Ptr getHpp(const Mat& src) + { + int htype = toHppType(src.type()); + int cn = src.channels(); + + CV_Assert(src.data); + hppiMatrix *dst = hppiCreateMatrix(htype, src.cols*cn, src.rows, src.data, (hpp32s)(src.step)); + + return Ptr(dst); + } +}} + +#endif \ No newline at end of file diff --git a/modules/core/test/test_ippasync.cpp b/modules/core/test/test_ippasync.cpp new file mode 100644 index 0000000000..61e407a8fe --- /dev/null +++ b/modules/core/test/test_ippasync.cpp @@ -0,0 +1,116 @@ +#include "test_precomp.hpp" +#include "opencv2/ts/ocl_test.hpp" + +#include "opencv2/core/ippasync.hpp" + +using namespace cv; +using namespace std; +using namespace cvtest; + +namespace cvtest { +namespace ocl { + +PARAM_TEST_CASE(IPPAsync, MatDepth, Channels, hppAccelType) +{ + int type; + int cn; + int depth; + hppAccelType accelType; + + Mat matrix, result; + Ptr hppMat; + hppAccel accel; + hppiVirtualMatrix * virtMatrix; + hppStatus sts; + + virtual void SetUp() + { + type = CV_MAKE_TYPE(GET_PARAM(0), GET_PARAM(1)); + depth = GET_PARAM(0); + cn = GET_PARAM(1); + accelType = GET_PARAM(2); + } + + virtual void generateTestData() + { + Size matrix_Size = randomSize(2, 100); + const double upValue = 100; + + matrix = randomMat(matrix_Size, type, -upValue, upValue); + } + + void Near(double threshold = 0.0) + { + EXPECT_MAT_NEAR(matrix, result, threshold); + } +}; + +TEST_P(IPPAsync, accuracy) +{ + if (depth==CV_32S || depth==CV_64F) + return; + + sts = hppCreateInstance(accelType, 0, &accel); + CV_Assert(sts==HPP_STATUS_NO_ERROR); + virtMatrix = hppiCreateVirtualMatrices(accel, 2); + + for (int j = 0; j < test_loop_times; j++) + { + generateTestData(); + hppMat = hpp::getHpp(matrix); + + hppScalar a = 3; + + sts = hppiAddC(accel, hppMat, a, 0, virtMatrix[0]); + CV_Assert(sts==HPP_STATUS_NO_ERROR); + sts = hppiSubC(accel, virtMatrix[0], a, 0, virtMatrix[1]); + CV_Assert(sts==HPP_STATUS_NO_ERROR); + + sts = hppWait(accel, HPP_TIME_OUT_INFINITE); + CV_Assert(sts==HPP_STATUS_NO_ERROR); + + result = hpp::getMat(virtMatrix[1], accel, cn); + + Near(5.0e-6); + } + + sts = hppiDeleteVirtualMatrices(accel, virtMatrix); + CV_Assert(sts==HPP_STATUS_NO_ERROR); + sts = hppDeleteInstance(accel); + CV_Assert(sts==HPP_STATUS_NO_ERROR); +} + +TEST_P(IPPAsync, conversion) +{ + sts = hppCreateInstance(accelType, 0, &accel); + CV_Assert(sts==HPP_STATUS_NO_ERROR); + virtMatrix = hppiCreateVirtualMatrices(accel, 1); + + for (int j = 0; j < test_loop_times; j++) + { + generateTestData(); + hppMat = hpp::getHpp(matrix); + + sts = hppiCopy (accel, hppMat, virtMatrix[0]); + CV_Assert(sts==HPP_STATUS_NO_ERROR); + + sts = hppWait(accel, HPP_TIME_OUT_INFINITE); + CV_Assert(sts==HPP_STATUS_NO_ERROR); + + result = hpp::getMat(virtMatrix[0], accel, cn); + + Near(); + } + + sts = hppiDeleteVirtualMatrices(accel, virtMatrix); + CV_Assert(sts==HPP_STATUS_NO_ERROR); + sts = hppDeleteInstance(accel); + CV_Assert(sts==HPP_STATUS_NO_ERROR); +} + +INSTANTIATE_TEST_CASE_P(IppATest, IPPAsync, Combine(Values(CV_8U, CV_16U, CV_16S, CV_32S, CV_32F, CV_64F), + Values(1, 2, 3, 4), + Values( HPP_ACCEL_TYPE_CPU, HPP_ACCEL_TYPE_GPU))); + +} +} \ No newline at end of file diff --git a/samples/cpp/tutorial_code/core/ippasync/ippasync_sample.cpp b/samples/cpp/tutorial_code/core/ippasync/ippasync_sample.cpp new file mode 100644 index 0000000000..f7bc80ac4d --- /dev/null +++ b/samples/cpp/tutorial_code/core/ippasync/ippasync_sample.cpp @@ -0,0 +1,149 @@ +#include + +#include "opencv2/core/utility.hpp" +#include "opencv2/core/ippasync.hpp" +#include "opencv2/imgproc.hpp" +#include "opencv2/highgui.hpp" + +#define CHECK_STATUS(STATUS, NAME)\ + if(STATUS!=HPP_STATUS_NO_ERROR){ printf("%s error %d\n", NAME, STATUS);\ + if (virtMatrix) {hppStatus delSts = hppiDeleteVirtualMatrices(accel, virtMatrix); CHECK_DEL_STATUS(delSts,"hppiDeleteVirtualMatrices");}\ + if (accel) {hppStatus delSts = hppDeleteInstance(accel); CHECK_DEL_STATUS(delSts, "hppDeleteInstance");}\ + return -1;} + +#define CHECK_DEL_STATUS(STATUS, NAME)\ + if(STATUS!=HPP_STATUS_NO_ERROR){ printf("%s error %d\n", NAME, STATUS); return -1;} + +using namespace std; +using namespace cv; +using namespace hpp; + +static void help() +{ + printf("\nThis program shows how to use the conversion for IPP Async.\n" +"This example uses the Sobel filter.\n" +"You can use cv::Sobel or hppiSobel.\n" +"Usage: \n" +"./ipp_async_sobel [--camera]=, \n" +" [--file_name]=\n" +" [--accel]=\n\n"); +} + +const char* keys = +{ + "{c camera | | use camera or not}" + "{fn file_name|baboon.jpg | image file }" + "{a accel |cpu | accelerator type: auto (default), cpu, gpu}" +}; + +//this is a sample for hppiSobel functions +int main(int argc, const char** argv) +{ + help(); + + VideoCapture cap; + Mat image, gray, result; + + Ptr src, dst; + hppAccel accel = 0; + hppAccelType accelType; + hppStatus sts; + hppiVirtualMatrix * virtMatrix; + + CommandLineParser parser(argc, argv, keys); + bool useCamera = parser.has("camera"); + string file = parser.get("file_name"); + string sAccel = parser.get("accel"); + + parser.printMessage(); + + if( useCamera ) + { + printf("used camera\n"); + cap.open(0); + } + else + { + printf("used image %s\n", file.c_str()); + cap.open(file.c_str()); + } + + if( !cap.isOpened() ) + { + printf("can not open camera or video file\n"); + return -1; + } + + accelType = sAccel == "cpu" ? HPP_ACCEL_TYPE_CPU: + sAccel == "gpu" ? HPP_ACCEL_TYPE_GPU: + HPP_ACCEL_TYPE_ANY; + + //Create accelerator instance + sts = hppCreateInstance(accelType, 0, &accel); + CHECK_STATUS(sts, "hppCreateInstance"); + + accelType = hppQueryAccelType(accel); + + sAccel = accelType == HPP_ACCEL_TYPE_CPU ? "cpu": + accelType == HPP_ACCEL_TYPE_GPU ? "gpu": + accelType == HPP_ACCEL_TYPE_GPU_VIA_DX9 ? "gpu": + accelType == HPP_ACCEL_TYPE_OCL ? "ocl": "?"; + + printf("accelType %s\n", sAccel.c_str()); + + virtMatrix = hppiCreateVirtualMatrices(accel, 1); + + for(;;) + { + cap >> image; + if(image.empty()) + break; + + cvtColor( image, gray, COLOR_BGR2GRAY ); + + result.create( image.rows, image.cols, CV_8U); + + double execTime = (double)getTickCount(); + + //convert Mat to hppiMatrix + src = getHpp(gray); + dst = getHpp(result); + + sts = hppiSobel(accel,src, HPP_MASK_SIZE_3X3,HPP_NORM_L1,virtMatrix[0]); + CHECK_STATUS(sts,"hppiSobel"); + + sts = hppiConvert(accel, virtMatrix[0], 0, HPP_RND_MODE_NEAR, dst, HPP_DATA_TYPE_8U); + CHECK_STATUS(sts,"hppiConvert"); + + // Wait for tasks to complete + sts = hppWait(accel, HPP_TIME_OUT_INFINITE); + CHECK_STATUS(sts, "hppWait"); + + execTime = ((double)getTickCount() - execTime)*1000./getTickFrequency(); + + printf("Time : %0.3fms\n", execTime); + + imshow("image", image); + imshow("rez", result); + + waitKey(15); + } + + if (!useCamera) + waitKey(0); + + if (virtMatrix) + { + sts = hppiDeleteVirtualMatrices(accel, virtMatrix); + CHECK_DEL_STATUS(sts,"hppiDeleteVirtualMatrices"); + } + + if (accel) + { + sts = hppDeleteInstance(accel); + CHECK_DEL_STATUS(sts, "hppDeleteInstance"); + } + + printf("SUCCESS\n"); + return -1; +} \ No newline at end of file From 037ac27ec831baf6be65f85fad3765e53f0d3f0a Mon Sep 17 00:00:00 2001 From: Elena Gvozdeva Date: Thu, 27 Mar 2014 17:40:50 +0400 Subject: [PATCH 2/3] Added SharedMatrix --- cmake/OpenCVFindIPPAsync.cmake | 16 ++-- .../how_to_use_ippa_conversion.rst | 48 ++++++---- .../table_of_content_core.rst | 9 +- .../windows_install/windows_install.rst | 6 ++ modules/core/doc/ipp_async_converters.rst | 28 ++++-- .../core/include/opencv2/core/ippasync.hpp | 33 +++++-- modules/core/test/test_ippasync.cpp | 93 ++++++++++++++++--- .../core/ippasync/ippasync_sample.cpp | 42 ++++++--- 8 files changed, 198 insertions(+), 77 deletions(-) diff --git a/cmake/OpenCVFindIPPAsync.cmake b/cmake/OpenCVFindIPPAsync.cmake index 0b5751ba07..6f4765cbc9 100644 --- a/cmake/OpenCVFindIPPAsync.cmake +++ b/cmake/OpenCVFindIPPAsync.cmake @@ -2,35 +2,35 @@ # IPP_A_LIBRARIES and IPP_A_INCLUDE to use IPP Async # HAVE_IPP_A for conditional compilation OpenCV with/without IPP Async -# IPPAROOT - root of IPP Async installation +# IPP_ASYNC_ROOT - root of IPP Async installation if(X86_64) find_path( IPP_A_INCLUDE_DIR NAMES ipp_async_defs.h - PATHS $ENV{IPPAROOT} + PATHS $ENV{IPP_ASYNC_ROOT} PATH_SUFFIXES include DOC "Path to Intel IPP Async interface headers") - + find_file( IPP_A_LIBRARIES NAMES ipp_async_preview.lib - PATHS $ENV{IPPAROOT} + PATHS $ENV{IPP_ASYNC_ROOT} PATH_SUFFIXES lib/intel64 DOC "Path to Intel IPP Async interface libraries") - + else() find_path( IPP_A_INCLUDE_DIR NAMES ipp_async_defs.h - PATHS $ENV{IPPAROOT} + PATHS $ENV{IPP_ASYNC_ROOT} PATH_SUFFIXES include DOC "Path to Intel IPP Async interface headers") - + find_file( IPP_A_LIBRARIES NAMES ipp_async_preview.lib - PATHS $ENV{IPPAROOT} + PATHS $ENV{IPP_ASYNC_ROOT} PATH_SUFFIXES lib/ia32 DOC "Path to Intel IPP Async interface libraries") endif() diff --git a/doc/tutorials/core/how_to_use_ippa_conversion/how_to_use_ippa_conversion.rst b/doc/tutorials/core/how_to_use_ippa_conversion/how_to_use_ippa_conversion.rst index bbdaa24a66..43f1f22d30 100644 --- a/doc/tutorials/core/how_to_use_ippa_conversion/how_to_use_ippa_conversion.rst +++ b/doc/tutorials/core/how_to_use_ippa_conversion/how_to_use_ippa_conversion.rst @@ -9,14 +9,14 @@ Goal .. _hppiSobel: http://software.intel.com/en-us/node/474701 .. _hppiMatrix: http://software.intel.com/en-us/node/501660 -The tutorial demonstrates the `Intel® IPP Asynchronous C/C++ `_ library usage with OpenCV. +The tutorial demonstrates the `Intel® IPP Asynchronous C/C++ `_ library usage with OpenCV. The code example below illustrates implementation of the Sobel operation, accelerated with Intel® IPP Asynchronous C/C++ functions. In this code example, :ippa_convert:`hpp::getMat <>` and :ippa_convert:`hpp::getHpp <>` functions are used for data conversion between hppiMatrix_ and ``Mat`` matrices. Code ==== -You may also find the source code in the :file:`samples/cpp/tutorial_code/core/ippasync/ippasync_sample.cpp` +You may also find the source code in the :file:`samples/cpp/tutorial_code/core/ippasync/ippasync_sample.cpp` file of the OpenCV source library or :download:`download it from here <../../../../samples/cpp/tutorial_code/core/ippasync/ippasync_sample.cpp>`. @@ -24,7 +24,7 @@ file of the OpenCV source library or :download:`download it from here :language: cpp :linenos: :tab-width: 4 - + Explanation =========== @@ -38,17 +38,17 @@ Explanation and IPP Async: .. code-block:: cpp - - Ptr src, dst; + + hppiMatrix* src,* dst; hppAccel accel = 0; hppAccelType accelType; hppStatus sts; hppiVirtualMatrix * virtMatrix; #. Load input image or video. How to open and read video stream you can see in the :ref:`videoInputPSNRMSSIM` tutorial. - + .. code-block:: cpp - + if( useCamera ) { printf("used camera\n"); @@ -65,25 +65,25 @@ Explanation printf("can not open camera or video file\n"); return -1; } - + #. Create accelerator instance using `hppCreateInstance `_: .. code-block:: cpp - + accelType = sAccel == "cpu" ? HPP_ACCEL_TYPE_CPU: sAccel == "gpu" ? HPP_ACCEL_TYPE_GPU: HPP_ACCEL_TYPE_ANY; - + //Create accelerator instance sts = hppCreateInstance(accelType, 0, &accel); CHECK_STATUS(sts, "hppCreateInstance"); - + #. Create an array of virtual matrices using `hppiCreateVirtualMatrices `_ function. .. code-block:: cpp virtMatrix = hppiCreateVirtualMatrices(accel, 1); - + #. Prepare a matrix for input and output data: .. code-block:: cpp @@ -95,14 +95,14 @@ Explanation cvtColor( image, gray, COLOR_BGR2GRAY ); result.create( image.rows, image.cols, CV_8U); - + #. Convert ``Mat`` to hppiMatrix_ using :ippa_convert:`getHpp <>` and call hppiSobel_ function. .. code-block:: cpp //convert Mat to hppiMatrix - src = getHpp(gray); - dst = getHpp(result); + src = getHpp(gray, accel); + dst = getHpp(result, accel); sts = hppiSobel(accel,src, HPP_MASK_SIZE_3X3,HPP_NORM_L1,virtMatrix[0]); CHECK_STATUS(sts,"hppiSobel"); @@ -113,7 +113,7 @@ Explanation // Wait for tasks to complete sts = hppWait(accel, HPP_TIME_OUT_INFINITE); CHECK_STATUS(sts, "hppWait"); - + We use `hppiConvert `_ because hppiSobel_ returns destination matrix with ``HPP_DATA_TYPE_16S`` data type for source matrix with ``HPP_DATA_TYPE_8U`` type. You should check ``hppStatus`` after each call IPP Async function. @@ -126,7 +126,17 @@ Explanation imshow("rez", result); waitKey(15); - + +#. Delete hpp matrices. + + .. code-block:: cpp + + sts = hppiFreeMatrix(src); + CHECK_DEL_STATUS(sts,"hppiFreeMatrix"); + + sts = hppiFreeMatrix(dst); + CHECK_DEL_STATUS(sts,"hppiFreeMatrix"); + #. Delete virtual matrices and accelerator instance. .. code-block:: cpp @@ -143,12 +153,10 @@ Explanation CHECK_DEL_STATUS(sts, "hppDeleteInstance"); } - We shouldn't delete hppiMatrix_ because we use :ptr:`Ptr <>` and so `hppiFreeMatrix `_ will be called implicitly. - Result ======= -After compiling the code above we can execute it giving an image or video path and accelerator type as an argument. +After compiling the code above we can execute it giving an image or video path and accelerator type as an argument. For this tutorial we use baboon.png image as input. The result is below. .. image:: images/How_To_Use_IPPA_Result.jpg diff --git a/doc/tutorials/core/table_of_content_core/table_of_content_core.rst b/doc/tutorials/core/table_of_content_core/table_of_content_core.rst index 62a0e4f2f7..1fcf4ee2b1 100644 --- a/doc/tutorials/core/table_of_content_core/table_of_content_core.rst +++ b/doc/tutorials/core/table_of_content_core/table_of_content_core.rst @@ -204,7 +204,7 @@ Here you will learn the about the basic building blocks of the library. A must r + .. tabularcolumns:: m{100pt} m{300pt} .. cssclass:: toctableopencv - + =============== ====================================================== |IPPIma| **Title:** :ref:`howToUseIPPAconversion` @@ -213,15 +213,16 @@ Here you will learn the about the basic building blocks of the library. A must r *Author:* |Author_ElenaG| You will see how to use the IPP Async with OpenCV. - + =============== ====================================================== - + .. |IPPIma| image:: images/How_To_Use_IPPA.jpg :height: 90pt :width: 90pt - .. |Author_ElenaG| unicode:: Elena U+0020 Gvozdeva + .. |Author_ElenaG| unicode:: Elena U+0020 Gvozdeva =============== ====================================================== + .. raw:: latex \pagebreak diff --git a/doc/tutorials/introduction/windows_install/windows_install.rst b/doc/tutorials/introduction/windows_install/windows_install.rst index dd91027ba3..f4e9767e77 100644 --- a/doc/tutorials/introduction/windows_install/windows_install.rst +++ b/doc/tutorials/introduction/windows_install/windows_install.rst @@ -62,6 +62,8 @@ Building the OpenCV library from scratch requires a couple of tools installed be .. _IntelTBB: http://threadingbuildingblocks.org/file.php?fid=77 .. |IntelIIP| replace:: Intel |copy| Integrated Performance Primitives (*IPP*) .. _IntelIIP: http://software.intel.com/en-us/articles/intel-ipp/ +.. |IntelIIPA| replace:: Intel |copy| IPP Asynchronous C/C++ +.. _IntelIIPA: http://software.intel.com/en-us/intel-ipp-preview .. |qtframework| replace:: Qt framework .. _qtframework: http://qt.nokia.com/downloads .. |Eigen| replace:: Eigen @@ -97,6 +99,8 @@ OpenCV may come in multiple flavors. There is a "core" section that will work on + |IntelIIP|_ may be used to improve the performance of color conversion, Haar training and DFT functions of the OpenCV library. Watch out, since this isn't a free service. + + |IntelIIPA|_ is currently focused delivering Intel |copy| Graphics support for advanced image processing and computer vision functions. + + OpenCV offers a somewhat fancier and more useful graphical user interface, than the default one by using the |qtframework|_. For a quick overview of what this has to offer look into the documentations *highgui* module, under the *Qt New Functions* section. Version 4.6 or later of the framework is required. + |Eigen|_ is a C++ template library for linear algebra. @@ -168,6 +172,8 @@ Building the library :alt: The Miktex Install Screen :align: center + #) For the |IntelIIPA|_ download the source files and set environment variable **IPP_ASYNC_ROOT**. It should point to :file:`/Intel/IPP Preview */ipp directory`. Here ``*`` denotes the particular preview name. + #) In case of the |Eigen|_ library it is again a case of download and extract to the :file:`D:/OpenCV/dep` directory. #) Same as above with |OpenEXR|_. diff --git a/modules/core/doc/ipp_async_converters.rst b/modules/core/doc/ipp_async_converters.rst index 6fac234e2a..1be93d5584 100644 --- a/modules/core/doc/ipp_async_converters.rst +++ b/modules/core/doc/ipp_async_converters.rst @@ -6,21 +6,31 @@ Intel® IPP Asynchronous C/C++ Converters General Information ------------------- -This section describes conversion between OpenCV and `Intel® IPP Asynchronous C/C++ `_ library. +This section describes conversion between OpenCV and `Intel® IPP Asynchronous C/C++ `_ library. `Getting Started Guide `_ help you to install the library, configure header and library build paths. hpp::getHpp ----------- Create ``hppiMatrix`` from ``Mat``. -.. ocv:function:: Ptr hpp::getHpp(const Mat& src) +.. ocv:function:: hppiMatrix* hpp::getHpp(const Mat& src, hppAccel accel) :param src: input matrix. + :param accel: accelerator instance. Supports type: + + * **HPP_ACCEL_TYPE_CPU** - accelerated by optimized CPU instructions. + + * **HPP_ACCEL_TYPE_GPU** - accelerated by GPU programmable units or fixed-function accelerators. + + * **HPP_ACCEL_TYPE_ANY** - any acceleration or no acceleration available. + +This function allocates and initializes the ``hppiMatrix`` that has the same size and type as input matrix, returns the ``hppiMatrix*``. + +If you want to use zero-copy for GPU you should to have 4KB aligned matrix data. See details `hppiCreateSharedMatrix `_. -This function allocates and initializes the ``hppiMatrix`` that has the same size and type as input matrix, returns the ``Ptr``. Supports ``CV_8U``, ``CV_16U``, ``CV_16S``, ``CV_32S``, ``CV_32F``, ``CV_64F``. -.. note:: The ``hppiMatrix`` pointer to the image buffer in system memory refers to the ``src.data``. Control the lifetime of the matrix and don't change its data, if there is no special need. +.. note:: The ``hppiMatrix`` pointer to the image buffer in system memory refers to the ``src.data``. Control the lifetime of the matrix and don't change its data, if there is no special need. .. seealso:: :ref:`howToUseIPPAconversion`, :ocv:func:`hpp::getMat` @@ -32,11 +42,11 @@ Create ``Mat`` from ``hppiMatrix``. :param src: input hppiMatrix. - :param accel: accelerator instance. + :param accel: accelerator instance (see :ocv:func:`hpp::getHpp` for the list of acceleration framework types). :param cn: number of channels. -This function allocates and initializes the ``Mat`` that has the same size and type as input matrix. +This function allocates and initializes the ``Mat`` that has the same size and type as input matrix. Supports ``CV_8U``, ``CV_16U``, ``CV_16S``, ``CV_32S``, ``CV_32F``, ``CV_64F``. .. seealso:: :ref:`howToUseIPPAconversion`, :ocv:func:`hpp::copyHppToMat`, :ocv:func:`hpp::getHpp`. @@ -49,14 +59,14 @@ Convert ``hppiMatrix`` to ``Mat``. .. ocv:function:: void hpp::copyHppToMat(hppiMatrix* src, Mat& dst, hppAccel accel, int cn) :param src: input hppiMatrix. - + :param dst: output matrix. - :param accel: accelerator instance. + :param accel: accelerator instance (see :ocv:func:`hpp::getHpp` for the list of acceleration framework types). :param cn: number of channels. -This function allocates and initializes new matrix (if needed) that has the same size and type as input matrix. +This function allocates and initializes new matrix (if needed) that has the same size and type as input matrix. Supports ``CV_8U``, ``CV_16U``, ``CV_16S``, ``CV_32S``, ``CV_32F``, ``CV_64F``. .. seealso:: :ref:`howToUseIPPAconversion`, :ocv:func:`hpp::getMat`, :ocv:func:`hpp::getHpp`. \ No newline at end of file diff --git a/modules/core/include/opencv2/core/ippasync.hpp b/modules/core/include/opencv2/core/ippasync.hpp index b17a803add..e40a0aa2f1 100644 --- a/modules/core/include/opencv2/core/ippasync.hpp +++ b/modules/core/include/opencv2/core/ippasync.hpp @@ -1,16 +1,14 @@ #ifndef __OPENCV_CORE_IPPASYNC_HPP__ #define __OPENCV_CORE_IPPASYNC_HPP__ +#ifdef HAVE_IPP_A + #include "opencv2/core.hpp" #include #include namespace cv { - void DefaultDeleter::operator () (hppiMatrix* p) const - { - hppiFreeMatrix(p); - } namespace hpp { @@ -25,7 +23,7 @@ namespace hpp depth == CV_32F ? HPP_DATA_TYPE_32F : depth == CV_64F ? HPP_DATA_TYPE_64F : -1; CV_Assert( hppType >= 0 ); - return hppType; + return hppType; } //convert hppDataType to OpenCV data type @@ -47,7 +45,8 @@ namespace hpp hpp32u width, height; hppStatus sts; - CV_Assert(src!=NULL); + if (src == NULL) + return dst.release(); sts = hppiInquireMatrix(src, &type, &width, &height); @@ -76,17 +75,31 @@ namespace hpp return dst; } - //create hppiMatrix from cv::Mat - inline Ptr getHpp(const Mat& src) + //create hppiMatrix from cv::Mat + inline hppiMatrix* getHpp(const Mat& src, hppAccel accel) { int htype = toHppType(src.type()); int cn = src.channels(); CV_Assert(src.data); - hppiMatrix *dst = hppiCreateMatrix(htype, src.cols*cn, src.rows, src.data, (hpp32s)(src.step)); + hppAccelType accelType = hppQueryAccelType(accel); - return Ptr(dst); + if (accelType!=HPP_ACCEL_TYPE_CPU) + { + hpp32u pitch, size; + hppQueryMatrixAllocParams(accel, src.cols*cn, src.rows, htype, &pitch, &size); + if (pitch!=0 && size!=0) + if ((int)(src.data)%4096==0 && pitch==(hpp32u)(src.step)) + { + return hppiCreateSharedMatrix(htype, src.cols*cn, src.rows, src.data, pitch, size); + } + } + + return hppiCreateMatrix(htype, src.cols*cn, src.rows, src.data, (hpp32s)(src.step));; } + }} +#endif + #endif \ No newline at end of file diff --git a/modules/core/test/test_ippasync.cpp b/modules/core/test/test_ippasync.cpp index 61e407a8fe..2d30e7aa57 100644 --- a/modules/core/test/test_ippasync.cpp +++ b/modules/core/test/test_ippasync.cpp @@ -1,6 +1,7 @@ #include "test_precomp.hpp" #include "opencv2/ts/ocl_test.hpp" +#ifdef HAVE_IPP_A #include "opencv2/core/ippasync.hpp" using namespace cv; @@ -18,7 +19,7 @@ PARAM_TEST_CASE(IPPAsync, MatDepth, Channels, hppAccelType) hppAccelType accelType; Mat matrix, result; - Ptr hppMat; + hppiMatrix * hppMat; hppAccel accel; hppiVirtualMatrix * virtMatrix; hppStatus sts; @@ -47,17 +48,16 @@ PARAM_TEST_CASE(IPPAsync, MatDepth, Channels, hppAccelType) TEST_P(IPPAsync, accuracy) { - if (depth==CV_32S || depth==CV_64F) - return; - sts = hppCreateInstance(accelType, 0, &accel); + if (sts!=HPP_STATUS_NO_ERROR) printf("hppStatus= %d\n",sts); CV_Assert(sts==HPP_STATUS_NO_ERROR); + virtMatrix = hppiCreateVirtualMatrices(accel, 2); for (int j = 0; j < test_loop_times; j++) { generateTestData(); - hppMat = hpp::getHpp(matrix); + hppMat = hpp::getHpp(matrix,accel); hppScalar a = 3; @@ -68,10 +68,13 @@ TEST_P(IPPAsync, accuracy) sts = hppWait(accel, HPP_TIME_OUT_INFINITE); CV_Assert(sts==HPP_STATUS_NO_ERROR); - + result = hpp::getMat(virtMatrix[1], accel, cn); Near(5.0e-6); + + sts = hppiFreeMatrix(hppMat); + CV_Assert(sts==HPP_STATUS_NO_ERROR); } sts = hppiDeleteVirtualMatrices(accel, virtMatrix); @@ -80,26 +83,82 @@ TEST_P(IPPAsync, accuracy) CV_Assert(sts==HPP_STATUS_NO_ERROR); } -TEST_P(IPPAsync, conversion) +PARAM_TEST_CASE(IPPAsyncShared, Channels, hppAccelType) +{ + int cn; + int type; + hppAccelType accelType; + + Mat matrix, result; + hppiMatrix* hppMat; + hppAccel accel; + hppiVirtualMatrix * virtMatrix; + hppStatus sts; + + virtual void SetUp() + { + cn = GET_PARAM(0); + accelType = GET_PARAM(1); + type=CV_MAKE_TYPE(CV_8U, GET_PARAM(0)); + } + + virtual void generateTestData() + { + Size matrix_Size = randomSize(2, 100); + hpp32u pitch, size; + const int upValue = 100; + + sts = hppQueryMatrixAllocParams(accel, (hpp32u)(matrix_Size.width*cn), (hpp32u)matrix_Size.height, HPP_DATA_TYPE_8U, &pitch, &size); + + if (pitch!=0 && size!=0) + { + uchar *pData = (uchar*)_aligned_malloc(size, 4096); + + for (int j=0; j #include "opencv2/core/utility.hpp" -#include "opencv2/core/ippasync.hpp" #include "opencv2/imgproc.hpp" #include "opencv2/highgui.hpp" +#include "cvconfig.h" + +using namespace std; +using namespace cv; + +#ifdef HAVE_IPP_A +#include "opencv2/core/ippasync.hpp" #define CHECK_STATUS(STATUS, NAME)\ if(STATUS!=HPP_STATUS_NO_ERROR){ printf("%s error %d\n", NAME, STATUS);\ @@ -14,9 +20,7 @@ #define CHECK_DEL_STATUS(STATUS, NAME)\ if(STATUS!=HPP_STATUS_NO_ERROR){ printf("%s error %d\n", NAME, STATUS); return -1;} -using namespace std; -using namespace cv; -using namespace hpp; +#endif static void help() { @@ -42,15 +46,17 @@ int main(int argc, const char** argv) help(); VideoCapture cap; + CommandLineParser parser(argc, argv, keys); Mat image, gray, result; - Ptr src, dst; +#ifdef HAVE_IPP_A + + hppiMatrix* src,* dst; hppAccel accel = 0; hppAccelType accelType; hppStatus sts; hppiVirtualMatrix * virtMatrix; - CommandLineParser parser(argc, argv, keys); bool useCamera = parser.has("camera"); string file = parser.get("file_name"); string sAccel = parser.get("accel"); @@ -104,10 +110,10 @@ int main(int argc, const char** argv) result.create( image.rows, image.cols, CV_8U); double execTime = (double)getTickCount(); - + //convert Mat to hppiMatrix - src = getHpp(gray); - dst = getHpp(result); + src = hpp::getHpp(gray,accel); + dst = hpp::getHpp(result,accel); sts = hppiSobel(accel,src, HPP_MASK_SIZE_3X3,HPP_NORM_L1,virtMatrix[0]); CHECK_STATUS(sts,"hppiSobel"); @@ -127,6 +133,13 @@ int main(int argc, const char** argv) imshow("rez", result); waitKey(15); + + sts = hppiFreeMatrix(src); + CHECK_DEL_STATUS(sts,"hppiFreeMatrix"); + + sts = hppiFreeMatrix(dst); + CHECK_DEL_STATUS(sts,"hppiFreeMatrix"); + } if (!useCamera) @@ -145,5 +158,12 @@ int main(int argc, const char** argv) } printf("SUCCESS\n"); - return -1; -} \ No newline at end of file + +#else + + printf("IPP Async not supported\n"); + +#endif + + return 0; +} From f9c52894833cb4da02bb6f245409c3e20b4a67fd Mon Sep 17 00:00:00 2001 From: Elena Gvozdeva Date: Fri, 4 Apr 2014 12:08:22 +0400 Subject: [PATCH 3/3] fixed --- modules/core/test/test_ippasync.cpp | 4 ++-- samples/cpp/tutorial_code/core/ippasync/ippasync_sample.cpp | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/modules/core/test/test_ippasync.cpp b/modules/core/test/test_ippasync.cpp index 2d30e7aa57..bd37ffa63b 100644 --- a/modules/core/test/test_ippasync.cpp +++ b/modules/core/test/test_ippasync.cpp @@ -49,7 +49,7 @@ PARAM_TEST_CASE(IPPAsync, MatDepth, Channels, hppAccelType) TEST_P(IPPAsync, accuracy) { sts = hppCreateInstance(accelType, 0, &accel); - if (sts!=HPP_STATUS_NO_ERROR) printf("hppStatus= %d\n",sts); + if (sts!=HPP_STATUS_NO_ERROR) printf("hppStatus = %d\n",sts); CV_Assert(sts==HPP_STATUS_NO_ERROR); virtMatrix = hppiCreateVirtualMatrices(accel, 2); @@ -133,7 +133,7 @@ PARAM_TEST_CASE(IPPAsyncShared, Channels, hppAccelType) TEST_P(IPPAsyncShared, accuracy) { sts = hppCreateInstance(accelType, 0, &accel); - if (sts!=HPP_STATUS_NO_ERROR) printf("hppStatus= %d\n",sts); + if (sts!=HPP_STATUS_NO_ERROR) printf("hppStatus = %d\n",sts); CV_Assert(sts==HPP_STATUS_NO_ERROR); virtMatrix = hppiCreateVirtualMatrices(accel, 2); diff --git a/samples/cpp/tutorial_code/core/ippasync/ippasync_sample.cpp b/samples/cpp/tutorial_code/core/ippasync/ippasync_sample.cpp index 3c5e8d3029..0cfc5e6932 100644 --- a/samples/cpp/tutorial_code/core/ippasync/ippasync_sample.cpp +++ b/samples/cpp/tutorial_code/core/ippasync/ippasync_sample.cpp @@ -37,7 +37,7 @@ const char* keys = { "{c camera | | use camera or not}" "{fn file_name|baboon.jpg | image file }" - "{a accel |cpu | accelerator type: auto (default), cpu, gpu}" + "{a accel |auto | accelerator type: auto (default), cpu, gpu}" }; //this is a sample for hppiSobel functions @@ -92,8 +92,7 @@ int main(int argc, const char** argv) sAccel = accelType == HPP_ACCEL_TYPE_CPU ? "cpu": accelType == HPP_ACCEL_TYPE_GPU ? "gpu": - accelType == HPP_ACCEL_TYPE_GPU_VIA_DX9 ? "gpu": - accelType == HPP_ACCEL_TYPE_OCL ? "ocl": "?"; + accelType == HPP_ACCEL_TYPE_GPU_VIA_DX9 ? "gpu dx9": "?"; printf("accelType %s\n", sAccel.c_str());