From ef5578a7ce7e2b9a578dc17a02b36cbcac0756b2 Mon Sep 17 00:00:00 2001 From: ShengyinWu Date: Mon, 29 Apr 2013 12:16:42 +0800 Subject: [PATCH 001/178] Fixs: After scaling back to original image, some detected ROI will outside the original image ROI --- modules/objdetect/src/cascadedetect.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/objdetect/src/cascadedetect.cpp b/modules/objdetect/src/cascadedetect.cpp index 46a232ed6a..69f2d64181 100644 --- a/modules/objdetect/src/cascadedetect.cpp +++ b/modules/objdetect/src/cascadedetect.cpp @@ -1141,7 +1141,7 @@ void CascadeClassifier::detectMultiScale( const Mat& image, vector& object Size windowSize( cvRound(originalWindowSize.width*factor), cvRound(originalWindowSize.height*factor) ); Size scaledImageSize( cvRound( grayImage.cols/factor ), cvRound( grayImage.rows/factor ) ); - Size processingRectSize( scaledImageSize.width - originalWindowSize.width + 1, scaledImageSize.height - originalWindowSize.height + 1 ); + Size processingRectSize( scaledImageSize.width - originalWindowSize.width, scaledImageSize.height - originalWindowSize.height ); if( processingRectSize.width <= 0 || processingRectSize.height <= 0 ) break; From ac21cabda263776e2645796796afbd0ea84dc2e1 Mon Sep 17 00:00:00 2001 From: peng xiao Date: Tue, 14 May 2013 17:50:38 +0800 Subject: [PATCH 002/178] Copy ocl::queryDeviceInfo interface from master to 2.4. Affected functions surf.ocl, pyrlk.ocl and hog.ocl are updated with the change. --- modules/nonfree/src/surf.ocl.cpp | 49 ++++------ .../ocl/include/opencv2/ocl/private/util.hpp | 10 ++- modules/ocl/src/hog.cpp | 3 +- modules/ocl/src/initialization.cpp | 89 +++++++------------ modules/ocl/src/pyrlk.cpp | 3 +- 5 files changed, 62 insertions(+), 92 deletions(-) diff --git a/modules/nonfree/src/surf.ocl.cpp b/modules/nonfree/src/surf.ocl.cpp index acc188edf8..de7cac2fdc 100644 --- a/modules/nonfree/src/surf.ocl.cpp +++ b/modules/nonfree/src/surf.ocl.cpp @@ -60,27 +60,24 @@ namespace cv const char noImage2dOption [] = "-D DISABLE_IMAGE2D"; - static char SURF_OPTIONS [1024] = ""; - static bool USE_IMAGE2d = false; + static bool use_image2d = false; + static void openCLExecuteKernelSURF(Context *clCxt , const char **source, string kernelName, size_t globalThreads[3], size_t localThreads[3], vector< pair > &args, int channels, int depth) { - char * pSURF_OPTIONS = SURF_OPTIONS; - static bool OPTION_INIT = false; - if(!OPTION_INIT) + char optBuf [100] = {0}; + char * optBufPtr = optBuf; + if( !use_image2d ) { - if( !USE_IMAGE2d ) - { - strcat(pSURF_OPTIONS, noImage2dOption); - pSURF_OPTIONS += strlen(noImage2dOption); - } - - size_t wave_size = 0; - queryDeviceInfo(WAVEFRONT_SIZE, &wave_size); - std::sprintf(pSURF_OPTIONS, "-D WAVE_SIZE=%d", static_cast(wave_size)); - OPTION_INIT = true; + strcat(optBufPtr, noImage2dOption); + optBufPtr += strlen(noImage2dOption); } - openCLExecuteKernel(clCxt, source, kernelName, globalThreads, localThreads, args, channels, depth, SURF_OPTIONS); + cl_kernel kernel; + kernel = openCLGetKernelFromSource(clCxt, source, kernelName, optBufPtr); + size_t wave_size = queryDeviceInfo(kernel); + CV_Assert(clReleaseKernel(kernel) == CL_SUCCESS); + sprintf(optBufPtr, "-D WAVE_SIZE=%d", static_cast(wave_size)); + openCLExecuteKernel(clCxt, source, kernelName, globalThreads, localThreads, args, channels, depth, optBufPtr); } } } @@ -161,22 +158,12 @@ public: counters.setTo(Scalar::all(0)); integral(img, surf_.sum); - if(support_image2d()) + use_image2d = support_image2d(); + if(use_image2d) { - try - { - bindImgTex(img, imgTex); - bindImgTex(surf_.sum, sumTex); - USE_IMAGE2d = true; - } - catch (const cv::Exception& e) - { - USE_IMAGE2d = false; - if(e.code != CL_IMAGE_FORMAT_NOT_SUPPORTED && e.code != -217) - { - throw e; - } - } + bindImgTex(img, imgTex); + bindImgTex(surf_.sum, sumTex); + finish(); } maskSumTex = 0; diff --git a/modules/ocl/include/opencv2/ocl/private/util.hpp b/modules/ocl/include/opencv2/ocl/private/util.hpp index 081d2343dc..f3e582f483 100644 --- a/modules/ocl/include/opencv2/ocl/private/util.hpp +++ b/modules/ocl/include/opencv2/ocl/private/util.hpp @@ -128,11 +128,17 @@ namespace cv enum DEVICE_INFO { WAVEFRONT_SIZE, //in AMD speak - WARP_SIZE = WAVEFRONT_SIZE, //in nvidia speak IS_CPU_DEVICE //check if the device is CPU }; + template + _ty queryDeviceInfo(cl_kernel kernel = NULL); //info should have been pre-allocated - void CV_EXPORTS queryDeviceInfo(DEVICE_INFO info_type, void* info); + template<> + int CV_EXPORTS queryDeviceInfo(cl_kernel kernel); + template<> + size_t CV_EXPORTS queryDeviceInfo(cl_kernel kernel); + template<> + bool CV_EXPORTS queryDeviceInfo(cl_kernel kernel); }//namespace ocl diff --git a/modules/ocl/src/hog.cpp b/modules/ocl/src/hog.cpp index 7a13324077..9c8f315ec5 100644 --- a/modules/ocl/src/hog.cpp +++ b/modules/ocl/src/hog.cpp @@ -1578,8 +1578,7 @@ static void openCLExecuteKernel_hog(Context *clCxt , const char **source, string size_t globalThreads[3], size_t localThreads[3], vector< pair > &args) { - size_t wave_size = 0; - queryDeviceInfo(WAVEFRONT_SIZE, &wave_size); + size_t wave_size = queryDeviceInfo(); if (wave_size <= 16) { char build_options[64]; diff --git a/modules/ocl/src/initialization.cpp b/modules/ocl/src/initialization.cpp index 799c49c50c..fd462ad916 100644 --- a/modules/ocl/src/initialization.cpp +++ b/modules/ocl/src/initialization.cpp @@ -363,64 +363,43 @@ namespace cv clFinish(Context::getContext()->impl->clCmdQueue); } - void queryDeviceInfo(DEVICE_INFO info_type, void* info) + //template specializations of queryDeviceInfo + template<> + bool queryDeviceInfo(cl_kernel) { - static Info::Impl* impl = Context::getContext()->impl; - switch(info_type) - { - case WAVEFRONT_SIZE: - { - bool is_cpu = false; - queryDeviceInfo(IS_CPU_DEVICE, &is_cpu); - if(is_cpu) - { - *(int*)info = 1; - return; - } -#ifdef CL_DEVICE_WAVEFRONT_WIDTH_AMD - try - { - openCLSafeCall(clGetDeviceInfo(Context::getContext()->impl->devices[0], - CL_DEVICE_WAVEFRONT_WIDTH_AMD, sizeof(size_t), info, 0)); - } - catch(const cv::Exception&) -#elif defined (CL_DEVICE_WARP_SIZE_NV) - const int EXT_LEN = 4096 + 1 ; - char extends_set[EXT_LEN]; - size_t extends_size; - openCLSafeCall(clGetDeviceInfo(impl->devices[impl->devnum], CL_DEVICE_EXTENSIONS, EXT_LEN, (void *)extends_set, &extends_size)); - extends_set[EXT_LEN - 1] = 0; - if(std::string(extends_set).find("cl_nv_device_attribute_query") != std::string::npos) - { - openCLSafeCall(clGetDeviceInfo(Context::getContext()->impl->devices[0], - CL_DEVICE_WARP_SIZE_NV, sizeof(size_t), info, 0)); - } - else -#endif - { - // if no way left for us to query the warp size, we can get it from kernel group info - static const char * _kernel_string = "__kernel void test_func() {}"; - cl_kernel kernel; - kernel = openCLGetKernelFromSource(Context::getContext(), &_kernel_string, "test_func"); - openCLSafeCall(clGetKernelWorkGroupInfo(kernel, impl->devices[impl->devnum], - CL_KERNEL_PREFERRED_WORK_GROUP_SIZE_MULTIPLE, sizeof(size_t), info, NULL)); - } + Info::Impl* impl = Context::getContext()->impl; + cl_device_type devicetype; + openCLSafeCall(clGetDeviceInfo(impl->devices[impl->devnum], + CL_DEVICE_TYPE, sizeof(cl_device_type), + &devicetype, NULL)); + return (devicetype == CVCL_DEVICE_TYPE_CPU); + } - } - break; - case IS_CPU_DEVICE: - { - cl_device_type devicetype; - openCLSafeCall(clGetDeviceInfo(impl->devices[impl->devnum], - CL_DEVICE_TYPE, sizeof(cl_device_type), - &devicetype, NULL)); - *(bool*)info = (devicetype == CVCL_DEVICE_TYPE_CPU); - } - break; - default: - CV_Error(-1, "Invalid device info type"); - break; + template + static _ty queryWavesize(cl_kernel kernel) + { + size_t info = 0; + Info::Impl* impl = Context::getContext()->impl; + bool is_cpu = queryDeviceInfo(); + if(is_cpu) + { + return 1; } + CV_Assert(kernel != NULL); + openCLSafeCall(clGetKernelWorkGroupInfo(kernel, impl->devices[impl->devnum], + CL_KERNEL_PREFERRED_WORK_GROUP_SIZE_MULTIPLE, sizeof(size_t), &info, NULL)); + return static_cast<_ty>(info); + } + + template<> + size_t queryDeviceInfo(cl_kernel kernel) + { + return queryWavesize(kernel); + } + template<> + int queryDeviceInfo(cl_kernel kernel) + { + return queryWavesize(kernel); } void openCLReadBuffer(Context *clCxt, cl_mem dst_buffer, void *host_buffer, size_t size) diff --git a/modules/ocl/src/pyrlk.cpp b/modules/ocl/src/pyrlk.cpp index 4a6ce1c790..6de4f9786a 100644 --- a/modules/ocl/src/pyrlk.cpp +++ b/modules/ocl/src/pyrlk.cpp @@ -187,8 +187,7 @@ static void lkSparse_run(oclMat &I, oclMat &J, args.push_back( make_pair( sizeof(cl_int), (void *)&iters )); args.push_back( make_pair( sizeof(cl_char), (void *)&calcErr )); - bool is_cpu; - queryDeviceInfo(IS_CPU_DEVICE, &is_cpu); + bool is_cpu = queryDeviceInfo(); if (is_cpu) { openCLExecuteKernel(clCxt, &pyrlk, kernelName, globalThreads, localThreads, args, I.oclchannels(), I.depth(), (char*)" -D CPU"); From 9247ad634f70bb9bc3ba6d0bda9c66356d1a833a Mon Sep 17 00:00:00 2001 From: Dominik Rose Date: Tue, 14 May 2013 16:20:01 +0200 Subject: [PATCH 003/178] libd1394 2.x support for mingw on windows added --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 93549c9430..e23941c247 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -110,7 +110,7 @@ endif() # Optional 3rd party components # =================================================== -OCV_OPTION(WITH_1394 "Include IEEE1394 support" ON IF (UNIX AND NOT ANDROID AND NOT IOS) ) +OCV_OPTION(WITH_1394 "Include IEEE1394 support" ON IF (NOT ANDROID AND NOT IOS) ) OCV_OPTION(WITH_AVFOUNDATION "Use AVFoundation for Video I/O" ON IF IOS) OCV_OPTION(WITH_CARBON "Use Carbon for UI instead of Cocoa" OFF IF APPLE ) OCV_OPTION(WITH_CUDA "Include NVidia Cuda Runtime support" ON IF (CMAKE_VERSION VERSION_GREATER "2.8" AND NOT ANDROID AND NOT IOS) ) From 86ec9b79fd4c69c66fcd9ea9c600c7b60824b135 Mon Sep 17 00:00:00 2001 From: Dominik Rose Date: Tue, 14 May 2013 16:20:01 +0200 Subject: [PATCH 004/178] libd1394 2.x support for mingw on windows added --- CMakeLists.txt | 15 ++++++++++- cmake/OpenCVFindLibsVideo.cmake | 37 +++++++++++++++++++++++---- cmake/OpenCVUtils.cmake | 7 +++++ modules/highgui/src/cap_dc1394_v2.cpp | 11 +++++++- 4 files changed, 63 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 93549c9430..35bfaf2f3d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -103,6 +103,19 @@ if(UNIX AND NOT ANDROID) endif() endif() +# Add these standard paths to the search paths for FIND_PATH +# to find include files from these locations first +if(MINGW) + if(EXISTS /mingw) + list(APPEND CMAKE_LIBRARY_PATH /mingw) + endif() + if(EXISTS /mingw32) + list(APPEND CMAKE_LIBRARY_PATH /mingw32) + endif() + if(EXISTS /mingw64) + list(APPEND CMAKE_LIBRARY_PATH /mingw64) + endif() +endif() # ---------------------------------------------------------------------------- # OpenCV cmake options @@ -110,7 +123,7 @@ endif() # Optional 3rd party components # =================================================== -OCV_OPTION(WITH_1394 "Include IEEE1394 support" ON IF (UNIX AND NOT ANDROID AND NOT IOS) ) +OCV_OPTION(WITH_1394 "Include IEEE1394 support" ON IF (NOT ANDROID AND NOT IOS) ) OCV_OPTION(WITH_AVFOUNDATION "Use AVFoundation for Video I/O" ON IF IOS) OCV_OPTION(WITH_CARBON "Use Carbon for UI instead of Cocoa" OFF IF APPLE ) OCV_OPTION(WITH_CUDA "Include NVidia Cuda Runtime support" ON IF (CMAKE_VERSION VERSION_GREATER "2.8" AND NOT ANDROID AND NOT IOS) ) diff --git a/cmake/OpenCVFindLibsVideo.cmake b/cmake/OpenCVFindLibsVideo.cmake index fbb47d4861..599bd9ff69 100644 --- a/cmake/OpenCVFindLibsVideo.cmake +++ b/cmake/OpenCVFindLibsVideo.cmake @@ -81,10 +81,37 @@ endif(WITH_GIGEAPI) # --- Dc1394 --- ocv_clear_vars(HAVE_DC1394 HAVE_DC1394_2) if(WITH_1394) - CHECK_MODULE(libdc1394-2 HAVE_DC1394_2) - if(NOT HAVE_DC1394_2) - CHECK_MODULE(libdc1394 HAVE_DC1394) - endif() + if(WIN32) + if(MINGW) + find_path(CMU1394_INCLUDE_PATH "/1394common.h" + PATH_SUFFIXES include + DOC "The path to cmu1394 headers") + find_path(DC1394_2_INCLUDE_PATH "/dc1394/dc1394.h" + PATH_SUFFIXES include + DOC "The path to DC1394 2.x headers") + if(CMU1394_INCLUDE_PATH AND DC1394_2_INCLUDE_PATH) + set(CMU1394_LIB_DIR "${CMU1394_INCLUDE_PATH}/../lib" CACHE PATH "Full path of CMU1394 library directory") + set(DC1394_2_LIB_DIR "${DC1394_2_INCLUDE_PATH}/../lib" CACHE PATH "Full path of DC1394 2.x library directory") + if(EXISTS "${CMU1394_LIB_DIR}/lib1394camera.a" AND EXISTS "${DC1394_2_LIB_DIR}/libdc1394.a") + set(HAVE_DC1394_2 TRUE) + endif() + endif() + if(HAVE_DC1394_2) + ocv_parse_pkg("libdc1394-2" "${DC1394_2_LIB_DIR}/pkgconfig" "") + ocv_include_directories(${DC1394_2_INCLUDE_PATH}) + set(HIGHGUI_LIBRARIES ${HIGHGUI_LIBRARIES} + "${DC1394_2_LIB_DIR}/libdc1394.a" + "${CMU1394_LIB_DIR}/lib1394camera.a") + endif(HAVE_DC1394_2) + else(MINGW) + message(STATUS "libdc1394 compilation is disabled (due to only MinGW compiler supported on your platform).") + endif(MINGW) + else(WIN32) + CHECK_MODULE(libdc1394-2 HAVE_DC1394_2) + if(NOT HAVE_DC1394_2) + CHECK_MODULE(libdc1394 HAVE_DC1394) + endif() + endif(WIN32) endif(WITH_1394) # --- xine --- @@ -197,7 +224,7 @@ endif(WITH_MSMF) # --- Extra HighGUI libs on Windows --- if(WIN32) - list(APPEND HIGHGUI_LIBRARIES comctl32 gdi32 ole32 vfw32) + list(APPEND HIGHGUI_LIBRARIES comctl32 gdi32 ole32 setupapi ws2_32 vfw32) if(MINGW64) list(APPEND HIGHGUI_LIBRARIES avifil32 avicap32 winmm msvfw32) list(REMOVE_ITEM HIGHGUI_LIBRARIES vfw32) diff --git a/cmake/OpenCVUtils.cmake b/cmake/OpenCVUtils.cmake index db24c99708..e6fcda6b2e 100644 --- a/cmake/OpenCVUtils.cmake +++ b/cmake/OpenCVUtils.cmake @@ -511,6 +511,13 @@ macro(ocv_parse_header2 LIBNAME HDR_PATH VARNAME) endif() endmacro() +# read single version info from the pkg file +macro(ocv_parse_pkg LIBNAME PKG_PATH SCOPE) + if(EXISTS "${PKG_PATH}/${LIBNAME}.pc") + file(STRINGS "${PKG_PATH}/${LIBNAME}.pc" line_to_parse REGEX "^Version:[ \t]+[0-9.]*.*$" LIMIT_COUNT 1) + STRING(REGEX REPLACE ".*Version: ([^ ]+).*" "\\1" ALIASOF_${LIBNAME}_VERSION "${line_to_parse}" ) + endif() +endmacro() ################################################################################################ # short command to setup source group diff --git a/modules/highgui/src/cap_dc1394_v2.cpp b/modules/highgui/src/cap_dc1394_v2.cpp index 2aa494fac7..ea7e4b2b86 100644 --- a/modules/highgui/src/cap_dc1394_v2.cpp +++ b/modules/highgui/src/cap_dc1394_v2.cpp @@ -45,7 +45,16 @@ #include #include -#include +#ifdef WIN32 + // On Windows, we have no sys/select.h, but we need to pick up + // select() which is in winsock2. + #ifndef __SYS_SELECT_H__ + #define __SYS_SELECT_H__ 1 + #include + #endif +#else + #include +#endif /*WIN32*/ #include #include #include From df3997b108600d95835944c26cb98e7bbdcdbf18 Mon Sep 17 00:00:00 2001 From: peng xiao Date: Wed, 15 May 2013 08:51:21 +0800 Subject: [PATCH 005/178] Fix ocl::pyrUp Use predefined OpenCL function to convert integers to floating points. This is more accurate than before as it enables: 1. saturate cast 2. customized rounding --- modules/ocl/src/opencl/pyr_up.cl | 82 +++++++------------------------- 1 file changed, 16 insertions(+), 66 deletions(-) diff --git a/modules/ocl/src/opencl/pyr_up.cl b/modules/ocl/src/opencl/pyr_up.cl index 0b7f0c9025..ef41c9408d 100644 --- a/modules/ocl/src/opencl/pyr_up.cl +++ b/modules/ocl/src/opencl/pyr_up.cl @@ -18,6 +18,7 @@ // Zhang Chunpeng chunpeng@multicorewareinc.com // Dachuan Zhao, dachuan@multicorewareinc.com // Yao Wang, yao@multicorewareinc.com +// Peng Xiao, pengxiao@outlook.com // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: @@ -47,7 +48,7 @@ //#pragma OPENCL EXTENSION cl_amd_printf : enable -uchar get_valid_uchar(uchar data) +uchar get_valid_uchar(float data) { return (uchar)(data <= 255 ? data : data > 0 ? 255 : 0); } @@ -142,7 +143,7 @@ __kernel void pyrUp_C1_D0(__global uchar* src,__global uchar* dst, sum = sum + 0.0625f * s_dstPatch[2 + tidy + 2][tidx]; if ((x < dstCols) && (y < dstRows)) - dst[x + y * dstStep] = (float)(4.0f * sum); + dst[x + y * dstStep] = convert_uchar_sat_rte(4.0f * sum); } @@ -244,7 +245,7 @@ __kernel void pyrUp_C1_D2(__global ushort* src,__global ushort* dst, sum = sum + 0.0625f * s_dstPatch[2 + tidy + 2][get_local_id(0)]; if ((x < dstCols) && (y < dstRows)) - dst[x + y * dstStep] = (float)(4.0f * sum); + dst[x + y * dstStep] = convert_short_sat_rte(4.0f * sum); } @@ -351,31 +352,6 @@ __kernel void pyrUp_C1_D5(__global float* src,__global float* dst, /////////////////////////////////////////////////////////////////////// ////////////////////////// CV_8UC4 ////////////////////////////////// /////////////////////////////////////////////////////////////////////// -float4 covert_uchar4_to_float4(uchar4 data) -{ - float4 f4Data = {0,0,0,0}; - - f4Data.x = (float)data.x; - f4Data.y = (float)data.y; - f4Data.z = (float)data.z; - f4Data.w = (float)data.w; - - return f4Data; -} - - -uchar4 convert_float4_to_uchar4(float4 data) -{ - uchar4 u4Data; - - u4Data.x = get_valid_uchar(data.x); - u4Data.y = get_valid_uchar(data.y); - u4Data.z = get_valid_uchar(data.z); - u4Data.w = get_valid_uchar(data.w); - - return u4Data; -} - __kernel void pyrUp_C4_D0(__global uchar4* src,__global uchar4* dst, int srcRows,int dstRows,int srcCols,int dstCols, int srcOffset,int dstOffset,int srcStep,int dstStep) @@ -406,7 +382,7 @@ __kernel void pyrUp_C4_D0(__global uchar4* src,__global uchar4* dst, srcy = abs(srcy); srcy = min(srcRows -1 ,srcy); - s_srcPatch[tidy][tidx] = covert_uchar4_to_float4(src[srcx + srcy * srcStep]); + s_srcPatch[tidy][tidx] = convert_float4(src[srcx + srcy * srcStep]); } barrier(CLK_LOCAL_MEM_FENCE); @@ -476,38 +452,12 @@ __kernel void pyrUp_C4_D0(__global uchar4* src,__global uchar4* dst, if ((x < dstCols) && (y < dstRows)) { - dst[x + y * dstStep] = convert_float4_to_uchar4(4.0f * sum); + dst[x + y * dstStep] = convert_uchar4_sat_rte(4.0f * sum); } } /////////////////////////////////////////////////////////////////////// ////////////////////////// CV_16UC4 ////////////////////////////////// /////////////////////////////////////////////////////////////////////// -float4 covert_ushort4_to_float4(ushort4 data) -{ - float4 f4Data = {0,0,0,0}; - - f4Data.x = (float)data.x; - f4Data.y = (float)data.y; - f4Data.z = (float)data.z; - f4Data.w = (float)data.w; - - return f4Data; -} - - -ushort4 convert_float4_to_ushort4(float4 data) -{ - ushort4 u4Data; - - u4Data.x = (float)data.x; - u4Data.y = (float)data.y; - u4Data.z = (float)data.z; - u4Data.w = (float)data.w; - - return u4Data; -} - - __kernel void pyrUp_C4_D2(__global ushort4* src,__global ushort4* dst, int srcRows,int dstRows,int srcCols,int dstCols, int srcOffset,int dstOffset,int srcStep,int dstStep) @@ -535,7 +485,7 @@ __kernel void pyrUp_C4_D2(__global ushort4* src,__global ushort4* dst, srcy = abs(srcy); srcy = min(srcRows -1 ,srcy); - s_srcPatch[get_local_id(1)][get_local_id(0)] = covert_ushort4_to_float4(src[srcx + srcy * srcStep]); + s_srcPatch[get_local_id(1)][get_local_id(0)] = convert_float4(src[srcx + srcy * srcStep]); } barrier(CLK_LOCAL_MEM_FENCE); @@ -570,11 +520,11 @@ __kernel void pyrUp_C4_D2(__global ushort4* src,__global ushort4* dst, if (eveny) { - sum = sum + (evenFlag * co3) * s_srcPatch[0][1 + ((tidx - 2) >> 1)]; - sum = sum + ( oddFlag * co2 ) * s_srcPatch[0][1 + ((tidx - 1) >> 1)]; + sum = sum + (evenFlag * co3 ) * s_srcPatch[0][1 + ((tidx - 2) >> 1)]; + sum = sum + (oddFlag * co2 ) * s_srcPatch[0][1 + ((tidx - 1) >> 1)]; sum = sum + (evenFlag * co1 ) * s_srcPatch[0][1 + ((tidx ) >> 1)]; - sum = sum + ( oddFlag * co2 ) * s_srcPatch[0][1 + ((tidx + 1) >> 1)]; - sum = sum + (evenFlag * co3) * s_srcPatch[0][1 + ((tidx + 2) >> 1)]; + sum = sum + (oddFlag * co2 ) * s_srcPatch[0][1 + ((tidx + 1) >> 1)]; + sum = sum + (evenFlag * co3 ) * s_srcPatch[0][1 + ((tidx + 2) >> 1)]; } s_dstPatch[get_local_id(1)][get_local_id(0)] = sum; @@ -610,7 +560,7 @@ __kernel void pyrUp_C4_D2(__global ushort4* src,__global ushort4* dst, if ((x < dstCols) && (y < dstRows)) { - dst[x + y * dstStep] = convert_float4_to_ushort4(4.0f * sum); + dst[x + y * dstStep] = convert_ushort4_sat_rte(4.0f * sum); } } @@ -681,11 +631,11 @@ __kernel void pyrUp_C4_D5(__global float4* src,__global float4* dst, if (eveny) { - sum = sum + (evenFlag * co3) * s_srcPatch[lsizey-16][1 + ((tidx - 2) >> 1)]; - sum = sum + ( oddFlag * co2 ) * s_srcPatch[lsizey-16][1 + ((tidx - 1) >> 1)]; + sum = sum + (evenFlag * co3 ) * s_srcPatch[lsizey-16][1 + ((tidx - 2) >> 1)]; + sum = sum + (oddFlag * co2 ) * s_srcPatch[lsizey-16][1 + ((tidx - 1) >> 1)]; sum = sum + (evenFlag * co1 ) * s_srcPatch[lsizey-16][1 + ((tidx ) >> 1)]; - sum = sum + ( oddFlag * co2 ) * s_srcPatch[lsizey-16][1 + ((tidx + 1) >> 1)]; - sum = sum + (evenFlag * co3) * s_srcPatch[lsizey-16][1 + ((tidx + 2) >> 1)]; + sum = sum + ( oddFlag * co2 ) * s_srcPatch[lsizey-16][1 + ((tidx + 1) >> 1)]; + sum = sum + (evenFlag * co3 ) * s_srcPatch[lsizey-16][1 + ((tidx + 2) >> 1)]; } s_dstPatch[tidy][tidx] = sum; From 3f93c3cc4e7d106fb5386695173f34578191745b Mon Sep 17 00:00:00 2001 From: peng xiao Date: Wed, 15 May 2013 10:43:47 +0800 Subject: [PATCH 006/178] Clean up spaces in ocl.hpp --- modules/ocl/include/opencv2/ocl/ocl.hpp | 372 ------------------------ 1 file changed, 372 deletions(-) diff --git a/modules/ocl/include/opencv2/ocl/ocl.hpp b/modules/ocl/include/opencv2/ocl/ocl.hpp index 6f29377f48..5c6a39ee12 100644 --- a/modules/ocl/include/opencv2/ocl/ocl.hpp +++ b/modules/ocl/include/opencv2/ocl/ocl.hpp @@ -866,7 +866,6 @@ namespace cv std::vector image_sqsums; }; - //! computes the proximity map for the raster template and the image where the template is searched for // Supports TM_SQDIFF, TM_SQDIFF_NORMED, TM_CCORR, TM_CCORR_NORMED, TM_CCOEFF, TM_CCOEFF_NORMED for type 8UC1 and 8UC4 // Supports TM_SQDIFF, TM_CCORR for type 32FC1 and 32FC4 @@ -877,71 +876,36 @@ namespace cv // Supports TM_SQDIFF, TM_CCORR for type 32FC1 and 32FC4 CV_EXPORTS void matchTemplate(const oclMat &image, const oclMat &templ, oclMat &result, int method, MatchTemplateBuf &buf); - - ///////////////////////////////////////////// Canny ///////////////////////////////////////////// - struct CV_EXPORTS CannyBuf; - - - //! compute edges of the input image using Canny operator - // Support CV_8UC1 only - CV_EXPORTS void Canny(const oclMat &image, oclMat &edges, double low_thresh, double high_thresh, int apperture_size = 3, bool L2gradient = false); - CV_EXPORTS void Canny(const oclMat &image, CannyBuf &buf, oclMat &edges, double low_thresh, double high_thresh, int apperture_size = 3, bool L2gradient = false); - CV_EXPORTS void Canny(const oclMat &dx, const oclMat &dy, oclMat &edges, double low_thresh, double high_thresh, bool L2gradient = false); - CV_EXPORTS void Canny(const oclMat &dx, const oclMat &dy, CannyBuf &buf, oclMat &edges, double low_thresh, double high_thresh, bool L2gradient = false); - - struct CV_EXPORTS CannyBuf - { - CannyBuf() : counter(NULL) {} - ~CannyBuf() { release(); } - explicit CannyBuf(const Size &image_size, int apperture_size = 3) : counter(NULL) - { - create(image_size, apperture_size); - } - CannyBuf(const oclMat &dx_, const oclMat &dy_); - - void create(const Size &image_size, int apperture_size = 3); - - - void release(); - - - oclMat dx, dy; - oclMat dx_buf, dy_buf; - oclMat edgeBuf; - oclMat trackBuf1, trackBuf2; - void *counter; - Ptr filterDX, filterDY; - }; ///////////////////////////////////////// clAmdFft related ///////////////////////////////////////// @@ -966,159 +930,69 @@ namespace cv const oclMat &src3, double beta, oclMat &dst, int flags = 0); //////////////// HOG (Histogram-of-Oriented-Gradients) Descriptor and Object Detector ////////////// - struct CV_EXPORTS HOGDescriptor - { - enum { DEFAULT_WIN_SIGMA = -1 }; - enum { DEFAULT_NLEVELS = 64 }; - enum { DESCR_FORMAT_ROW_BY_ROW, DESCR_FORMAT_COL_BY_COL }; - - - HOGDescriptor(Size win_size = Size(64, 128), Size block_size = Size(16, 16), - Size block_stride = Size(8, 8), Size cell_size = Size(8, 8), - int nbins = 9, double win_sigma = DEFAULT_WIN_SIGMA, - double threshold_L2hys = 0.2, bool gamma_correction = true, - int nlevels = DEFAULT_NLEVELS); - - size_t getDescriptorSize() const; - size_t getBlockHistogramSize() const; - - - void setSVMDetector(const vector &detector); - - - static vector getDefaultPeopleDetector(); - static vector getPeopleDetector48x96(); - static vector getPeopleDetector64x128(); - - - void detect(const oclMat &img, vector &found_locations, - double hit_threshold = 0, Size win_stride = Size(), - Size padding = Size()); - - - void detectMultiScale(const oclMat &img, vector &found_locations, - double hit_threshold = 0, Size win_stride = Size(), - Size padding = Size(), double scale0 = 1.05, - int group_threshold = 2); - - - void getDescriptors(const oclMat &img, Size win_stride, - oclMat &descriptors, - int descr_format = DESCR_FORMAT_COL_BY_COL); - - - Size win_size; - Size block_size; - Size block_stride; - Size cell_size; int nbins; - double win_sigma; - double threshold_L2hys; - bool gamma_correction; - int nlevels; - - protected: - // initialize buffers; only need to do once in case of multiscale detection - void init_buffer(const oclMat &img, Size win_stride); - - - void computeBlockHistograms(const oclMat &img); - void computeGradient(const oclMat &img, oclMat &grad, oclMat &qangle); - - - double getWinSigma() const; - bool checkDetectorSize() const; - - static int numPartsWithin(int size, int part_size, int stride); - static Size numPartsWithin(Size size, Size part_size, Size stride); - - // Coefficients of the separating plane - float free_coef; - oclMat detector; - - - // Results of the last classification step - oclMat labels; - Mat labels_host; - - - // Results of the last histogram evaluation step - oclMat block_hists; - - - // Gradients conputation results - oclMat grad, qangle; - - - // scaled image - oclMat image_scale; - - - // effect size of input image (might be different from original size after scaling) - Size effect_size; - }; @@ -1126,13 +1000,11 @@ namespace cv /****************************************************************************************\ * Distance * \****************************************************************************************/ - template struct CV_EXPORTS Accumulator { typedef T Type; }; - template<> struct Accumulator { typedef float Type; @@ -1206,469 +1078,225 @@ namespace cv { public: enum DistType {L1Dist = 0, L2Dist, HammingDist}; - explicit BruteForceMatcher_OCL_base(DistType distType = L2Dist); - - - // Add descriptors to train descriptor collection - void add(const std::vector &descCollection); - - - // Get train descriptors collection - const std::vector &getTrainDescriptors() const; - - - // Clear train descriptors collection - void clear(); - - - // Return true if there are not train descriptors in collection - bool empty() const; - - // Return true if the matcher supports mask in match methods - bool isMaskSupported() const; - - // Find one best match for each query descriptor - void matchSingle(const oclMat &query, const oclMat &train, - oclMat &trainIdx, oclMat &distance, - const oclMat &mask = oclMat()); - - // Download trainIdx and distance and convert it to CPU vector with DMatch - static void matchDownload(const oclMat &trainIdx, const oclMat &distance, std::vector &matches); - // Convert trainIdx and distance to vector with DMatch - static void matchConvert(const Mat &trainIdx, const Mat &distance, std::vector &matches); - - // Find one best match for each query descriptor - void match(const oclMat &query, const oclMat &train, std::vector &matches, const oclMat &mask = oclMat()); - - // Make gpu collection of trains and masks in suitable format for matchCollection function - void makeGpuCollection(oclMat &trainCollection, oclMat &maskCollection, const std::vector &masks = std::vector()); - // Find one best match from train collection for each query descriptor - void matchCollection(const oclMat &query, const oclMat &trainCollection, - oclMat &trainIdx, oclMat &imgIdx, oclMat &distance, - const oclMat &masks = oclMat()); - - // Download trainIdx, imgIdx and distance and convert it to vector with DMatch - static void matchDownload(const oclMat &trainIdx, const oclMat &imgIdx, const oclMat &distance, std::vector &matches); - // Convert trainIdx, imgIdx and distance to vector with DMatch - static void matchConvert(const Mat &trainIdx, const Mat &imgIdx, const Mat &distance, std::vector &matches); - - // Find one best match from train collection for each query descriptor. - void match(const oclMat &query, std::vector &matches, const std::vector &masks = std::vector()); - - // Find k best matches for each query descriptor (in increasing order of distances) - void knnMatchSingle(const oclMat &query, const oclMat &train, - oclMat &trainIdx, oclMat &distance, oclMat &allDist, int k, - const oclMat &mask = oclMat()); - - // Download trainIdx and distance and convert it to vector with DMatch - // compactResult is used when mask is not empty. If compactResult is false matches - // vector will have the same size as queryDescriptors rows. If compactResult is true - // matches vector will not contain matches for fully masked out query descriptors. - static void knnMatchDownload(const oclMat &trainIdx, const oclMat &distance, - std::vector< std::vector > &matches, bool compactResult = false); // Convert trainIdx and distance to vector with DMatch - static void knnMatchConvert(const Mat &trainIdx, const Mat &distance, - std::vector< std::vector > &matches, bool compactResult = false); - - // Find k best matches for each query descriptor (in increasing order of distances). - // compactResult is used when mask is not empty. If compactResult is false matches - // vector will have the same size as queryDescriptors rows. If compactResult is true - // matches vector will not contain matches for fully masked out query descriptors. - void knnMatch(const oclMat &query, const oclMat &train, - std::vector< std::vector > &matches, int k, const oclMat &mask = oclMat(), - bool compactResult = false); - - // Find k best matches from train collection for each query descriptor (in increasing order of distances) - void knnMatch2Collection(const oclMat &query, const oclMat &trainCollection, - oclMat &trainIdx, oclMat &imgIdx, oclMat &distance, - const oclMat &maskCollection = oclMat()); - - // Download trainIdx and distance and convert it to vector with DMatch - // compactResult is used when mask is not empty. If compactResult is false matches - // vector will have the same size as queryDescriptors rows. If compactResult is true - // matches vector will not contain matches for fully masked out query descriptors. - static void knnMatch2Download(const oclMat &trainIdx, const oclMat &imgIdx, const oclMat &distance, - std::vector< std::vector > &matches, bool compactResult = false); // Convert trainIdx and distance to vector with DMatch - static void knnMatch2Convert(const Mat &trainIdx, const Mat &imgIdx, const Mat &distance, - std::vector< std::vector > &matches, bool compactResult = false); - - // Find k best matches for each query descriptor (in increasing order of distances). - // compactResult is used when mask is not empty. If compactResult is false matches - // vector will have the same size as queryDescriptors rows. If compactResult is true - // matches vector will not contain matches for fully masked out query descriptors. - void knnMatch(const oclMat &query, std::vector< std::vector > &matches, int k, - const std::vector &masks = std::vector(), bool compactResult = false); - - // Find best matches for each query descriptor which have distance less than maxDistance. - // nMatches.at(0, queryIdx) will contain matches count for queryIdx. - // carefully nMatches can be greater than trainIdx.cols - it means that matcher didn't find all matches, - // because it didn't have enough memory. - // If trainIdx is empty, then trainIdx and distance will be created with size nQuery x max((nTrain / 100), 10), - // otherwize user can pass own allocated trainIdx and distance with size nQuery x nMaxMatches - // Matches doesn't sorted. - void radiusMatchSingle(const oclMat &query, const oclMat &train, - oclMat &trainIdx, oclMat &distance, oclMat &nMatches, float maxDistance, - const oclMat &mask = oclMat()); - - // Download trainIdx, nMatches and distance and convert it to vector with DMatch. - // matches will be sorted in increasing order of distances. - // compactResult is used when mask is not empty. If compactResult is false matches - // vector will have the same size as queryDescriptors rows. If compactResult is true - // matches vector will not contain matches for fully masked out query descriptors. - static void radiusMatchDownload(const oclMat &trainIdx, const oclMat &distance, const oclMat &nMatches, - std::vector< std::vector > &matches, bool compactResult = false); - // Convert trainIdx, nMatches and distance to vector with DMatch. - static void radiusMatchConvert(const Mat &trainIdx, const Mat &distance, const Mat &nMatches, - std::vector< std::vector > &matches, bool compactResult = false); - - - // Find best matches for each query descriptor which have distance less than maxDistance - // in increasing order of distances). - void radiusMatch(const oclMat &query, const oclMat &train, - std::vector< std::vector > &matches, float maxDistance, - const oclMat &mask = oclMat(), bool compactResult = false); - - - // Find best matches for each query descriptor which have distance less than maxDistance. - // If trainIdx is empty, then trainIdx and distance will be created with size nQuery x max((nQuery / 100), 10), - // otherwize user can pass own allocated trainIdx and distance with size nQuery x nMaxMatches - // Matches doesn't sorted. - void radiusMatchCollection(const oclMat &query, oclMat &trainIdx, oclMat &imgIdx, oclMat &distance, oclMat &nMatches, float maxDistance, - const std::vector &masks = std::vector()); - - - // Download trainIdx, imgIdx, nMatches and distance and convert it to vector with DMatch. - // matches will be sorted in increasing order of distances. - // compactResult is used when mask is not empty. If compactResult is false matches - // vector will have the same size as queryDescriptors rows. If compactResult is true - // matches vector will not contain matches for fully masked out query descriptors. - static void radiusMatchDownload(const oclMat &trainIdx, const oclMat &imgIdx, const oclMat &distance, const oclMat &nMatches, - std::vector< std::vector > &matches, bool compactResult = false); - // Convert trainIdx, nMatches and distance to vector with DMatch. - static void radiusMatchConvert(const Mat &trainIdx, const Mat &imgIdx, const Mat &distance, const Mat &nMatches, - std::vector< std::vector > &matches, bool compactResult = false); - - - // Find best matches from train collection for each query descriptor which have distance less than - // maxDistance (in increasing order of distances). - void radiusMatch(const oclMat &query, std::vector< std::vector > &matches, float maxDistance, - const std::vector &masks = std::vector(), bool compactResult = false); - - - DistType distType; - - - private: - std::vector trainDescCollection; - }; - - template - class CV_EXPORTS BruteForceMatcher_OCL; - - template - class CV_EXPORTS BruteForceMatcher_OCL< L1 > : public BruteForceMatcher_OCL_base - { - public: - explicit BruteForceMatcher_OCL() : BruteForceMatcher_OCL_base(L1Dist) {} - explicit BruteForceMatcher_OCL(L1 /*d*/) : BruteForceMatcher_OCL_base(L1Dist) {} - }; template - class CV_EXPORTS BruteForceMatcher_OCL< L2 > : public BruteForceMatcher_OCL_base - { - public: - explicit BruteForceMatcher_OCL() : BruteForceMatcher_OCL_base(L2Dist) {} - explicit BruteForceMatcher_OCL(L2 /*d*/) : BruteForceMatcher_OCL_base(L2Dist) {} - }; template <> class CV_EXPORTS BruteForceMatcher_OCL< Hamming > : public BruteForceMatcher_OCL_base - { - public: - explicit BruteForceMatcher_OCL() : BruteForceMatcher_OCL_base(HammingDist) {} - explicit BruteForceMatcher_OCL(Hamming /*d*/) : BruteForceMatcher_OCL_base(HammingDist) {} - }; - - /////////////////////////////// PyrLKOpticalFlow ///////////////////////////////////// - class CV_EXPORTS PyrLKOpticalFlow - { - public: - PyrLKOpticalFlow() - { - winSize = Size(21, 21); - maxLevel = 3; - iters = 30; - derivLambda = 0.5; - useInitialFlow = false; - minEigThreshold = 1e-4f; - getMinEigenVals = false; - isDeviceArch11_ = false; - } - - void sparse(const oclMat &prevImg, const oclMat &nextImg, const oclMat &prevPts, oclMat &nextPts, - oclMat &status, oclMat *err = 0); - - - void dense(const oclMat &prevImg, const oclMat &nextImg, oclMat &u, oclMat &v, oclMat *err = 0); - - - Size winSize; - int maxLevel; - int iters; - double derivLambda; - bool useInitialFlow; - float minEigThreshold; - bool getMinEigenVals; - - - void releaseMemory() - { - dx_calcBuf_.release(); - dy_calcBuf_.release(); - - prevPyr_.clear(); - nextPyr_.clear(); - - dx_buf_.release(); - dy_buf_.release(); - } - - - private: - void calcSharrDeriv(const oclMat &src, oclMat &dx, oclMat &dy); - - - void buildImagePyramid(const oclMat &img0, vector &pyr, bool withBorder); - - oclMat dx_calcBuf_; - oclMat dy_calcBuf_; - - vector prevPyr_; - vector nextPyr_; - - oclMat dx_buf_; - oclMat dy_buf_; - - - oclMat uPyr_[2]; - oclMat vPyr_[2]; - - - bool isDeviceArch11_; - }; //////////////// build warping maps //////////////////// //! builds plane warping maps From d053f2165d12e6044b51a713c0b586a5cf2d6f27 Mon Sep 17 00:00:00 2001 From: peng xiao Date: Wed, 15 May 2013 10:47:17 +0800 Subject: [PATCH 007/178] Add BFMatcher_OCL class alias for BruteForceMatcher_OCL. This adds a similar interface with pure-cpp and gpu versions. --- modules/ocl/include/opencv2/ocl/ocl.hpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/ocl/include/opencv2/ocl/ocl.hpp b/modules/ocl/include/opencv2/ocl/ocl.hpp index 5c6a39ee12..17210699b0 100644 --- a/modules/ocl/include/opencv2/ocl/ocl.hpp +++ b/modules/ocl/include/opencv2/ocl/ocl.hpp @@ -1245,6 +1245,11 @@ namespace cv explicit BruteForceMatcher_OCL(Hamming /*d*/) : BruteForceMatcher_OCL_base(HammingDist) {} }; + class CV_EXPORTS BFMatcher_OCL : public BruteForceMatcher_OCL_base + { + public: + explicit BFMatcher_OCL(int norm = NORM_L2) : BruteForceMatcher_OCL_base(norm == NORM_L1 ? L1Dist : norm == NORM_L2 ? L2Dist : HammingDist) {} + }; /////////////////////////////// PyrLKOpticalFlow ///////////////////////////////////// class CV_EXPORTS PyrLKOpticalFlow { From 66c9029fd5937e368e31ea1e2cbcafdb525f94da Mon Sep 17 00:00:00 2001 From: Dominik Rose Date: Wed, 15 May 2013 12:15:16 +0200 Subject: [PATCH 008/178] libdc1394 - removed validation for msvc compiler in CMakeLists.txt --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 37f1248240..e941f59a34 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -123,7 +123,7 @@ endif() # Optional 3rd party components # =================================================== -OCV_OPTION(WITH_1394 "Include IEEE1394 support" ON IF (NOT ANDROID AND NOT IOS AND NOT MSVC) ) +OCV_OPTION(WITH_1394 "Include IEEE1394 support" ON IF (NOT ANDROID AND NOT IOS) ) OCV_OPTION(WITH_AVFOUNDATION "Use AVFoundation for Video I/O" ON IF IOS) OCV_OPTION(WITH_CARBON "Use Carbon for UI instead of Cocoa" OFF IF APPLE ) OCV_OPTION(WITH_CUDA "Include NVidia Cuda Runtime support" ON IF (CMAKE_VERSION VERSION_GREATER "2.8" AND NOT ANDROID AND NOT IOS) ) From ec52096e30860d59717f81816fafe0c9e4c6fdc0 Mon Sep 17 00:00:00 2001 From: Vladislav Vinogradov Date: Thu, 16 May 2013 13:47:24 +0400 Subject: [PATCH 009/178] removed VIBE implementation --- .../nonfree/doc/background_subtraction.rst | 79 ----- modules/nonfree/doc/nonfree.rst | 1 - .../nonfree/include/opencv2/nonfree/gpu.hpp | 35 --- modules/nonfree/perf/perf_gpu.cpp | 83 ------ modules/nonfree/src/cuda/vibe.cu | 271 ------------------ modules/nonfree/src/vibe_gpu.cpp | 141 --------- modules/nonfree/test/test_gpu.cpp | 38 --- samples/gpu/bgfg_segm.cpp | 37 +-- 8 files changed, 2 insertions(+), 683 deletions(-) delete mode 100644 modules/nonfree/doc/background_subtraction.rst delete mode 100644 modules/nonfree/src/cuda/vibe.cu delete mode 100644 modules/nonfree/src/vibe_gpu.cpp diff --git a/modules/nonfree/doc/background_subtraction.rst b/modules/nonfree/doc/background_subtraction.rst deleted file mode 100644 index 11603ca566..0000000000 --- a/modules/nonfree/doc/background_subtraction.rst +++ /dev/null @@ -1,79 +0,0 @@ -Background Subtraction -====================== - -.. highlight:: cpp - - - -gpu::VIBE_GPU -------------- -.. ocv:class:: gpu::VIBE_GPU - -Class used for background/foreground segmentation. :: - - class VIBE_GPU - { - public: - explicit VIBE_GPU(unsigned long rngSeed = 1234567); - - void initialize(const GpuMat& firstFrame, Stream& stream = Stream::Null()); - - void operator()(const GpuMat& frame, GpuMat& fgmask, Stream& stream = Stream::Null()); - - void release(); - - ... - }; - -The class discriminates between foreground and background pixels by building and maintaining a model of the background. Any pixel which does not fit this model is then deemed to be foreground. The class implements algorithm described in [VIBE2011]_. - - - -gpu::VIBE_GPU::VIBE_GPU ------------------------ -The constructor. - -.. ocv:function:: gpu::VIBE_GPU::VIBE_GPU(unsigned long rngSeed = 1234567) - - :param rngSeed: Value used to initiate a random sequence. - -Default constructor sets all parameters to default values. - - - -gpu::VIBE_GPU::initialize -------------------------- -Initialize background model and allocates all inner buffers. - -.. ocv:function:: void gpu::VIBE_GPU::initialize(const GpuMat& firstFrame, Stream& stream = Stream::Null()) - - :param firstFrame: First frame from video sequence. - - :param stream: Stream for the asynchronous version. - - - -gpu::VIBE_GPU::operator() -------------------------- -Updates the background model and returns the foreground mask - -.. ocv:function:: void gpu::VIBE_GPU::operator()(const GpuMat& frame, GpuMat& fgmask, Stream& stream = Stream::Null()) - - :param frame: Next video frame. - - :param fgmask: The output foreground mask as an 8-bit binary image. - - :param stream: Stream for the asynchronous version. - - - -gpu::VIBE_GPU::release ----------------------- -Releases all inner buffer's memory. - -.. ocv:function:: void gpu::VIBE_GPU::release() - - - - -.. [VIBE2011] O. Barnich and M. Van D Roogenbroeck. *ViBe: A universal background subtraction algorithm for video sequences*. IEEE Transactions on Image Processing, 20(6) :1709-1724, June 2011 diff --git a/modules/nonfree/doc/nonfree.rst b/modules/nonfree/doc/nonfree.rst index f8fa1d6eba..e524ea82f8 100644 --- a/modules/nonfree/doc/nonfree.rst +++ b/modules/nonfree/doc/nonfree.rst @@ -8,4 +8,3 @@ The module contains algorithms that may be patented in some countries or have so :maxdepth: 2 feature_detection - background_subtraction diff --git a/modules/nonfree/include/opencv2/nonfree/gpu.hpp b/modules/nonfree/include/opencv2/nonfree/gpu.hpp index c8a24e01ec..3cb0b47621 100644 --- a/modules/nonfree/include/opencv2/nonfree/gpu.hpp +++ b/modules/nonfree/include/opencv2/nonfree/gpu.hpp @@ -125,41 +125,6 @@ public: GpuMat maxPosBuffer; }; -/*! - * The class implements the following algorithm: - * "ViBe: A universal background subtraction algorithm for video sequences" - * O. Barnich and M. Van D Roogenbroeck - * IEEE Transactions on Image Processing, 20(6) :1709-1724, June 2011 - */ -class CV_EXPORTS VIBE_GPU -{ -public: - //! the default constructor - explicit VIBE_GPU(unsigned long rngSeed = 1234567); - - //! re-initiaization method - void initialize(const GpuMat& firstFrame, Stream& stream = Stream::Null()); - - //! the update operator - void operator()(const GpuMat& frame, GpuMat& fgmask, Stream& stream = Stream::Null()); - - //! releases all inner buffers - void release(); - - int nbSamples; // number of samples per pixel - int reqMatches; // #_min - int radius; // R - int subsamplingFactor; // amount of random subsampling - -private: - Size frameSize_; - - unsigned long rngSeed_; - GpuMat randStates_; - - GpuMat samples_; -}; - } // namespace gpu } // namespace cv diff --git a/modules/nonfree/perf/perf_gpu.cpp b/modules/nonfree/perf/perf_gpu.cpp index aa8516b1c4..9f451deaba 100644 --- a/modules/nonfree/perf/perf_gpu.cpp +++ b/modules/nonfree/perf/perf_gpu.cpp @@ -50,18 +50,6 @@ using namespace std; using namespace testing; using namespace perf; -#if defined(HAVE_XINE) || \ - defined(HAVE_GSTREAMER) || \ - defined(HAVE_QUICKTIME) || \ - defined(HAVE_AVFOUNDATION) || \ - defined(HAVE_FFMPEG) || \ - defined(WIN32) /* assume that we have ffmpeg */ - -# define BUILD_WITH_VIDEO_INPUT_SUPPORT 1 -#else -# define BUILD_WITH_VIDEO_INPUT_SUPPORT 0 -#endif - ////////////////////////////////////////////////////////////////////// // SURF @@ -108,75 +96,4 @@ PERF_TEST_P(Image, GPU_SURF, } } -////////////////////////////////////////////////////// -// VIBE - -#if BUILD_WITH_VIDEO_INPUT_SUPPORT - -DEF_PARAM_TEST(Video_Cn, string, int); - -PERF_TEST_P(Video_Cn, GPU_VIBE, - Combine(Values("gpu/video/768x576.avi", "gpu/video/1920x1080.avi"), - GPU_CHANNELS_1_3_4)) -{ - const string inputFile = perf::TestBase::getDataPath(GET_PARAM(0)); - const int cn = GET_PARAM(1); - - cv::VideoCapture cap(inputFile); - ASSERT_TRUE(cap.isOpened()); - - cv::Mat frame; - cap >> frame; - ASSERT_FALSE(frame.empty()); - - if (cn != 3) - { - cv::Mat temp; - if (cn == 1) - cv::cvtColor(frame, temp, cv::COLOR_BGR2GRAY); - else - cv::cvtColor(frame, temp, cv::COLOR_BGR2BGRA); - cv::swap(temp, frame); - } - - if (PERF_RUN_GPU()) - { - cv::gpu::GpuMat d_frame(frame); - cv::gpu::VIBE_GPU vibe; - cv::gpu::GpuMat foreground; - - vibe(d_frame, foreground); - - for (int i = 0; i < 10; ++i) - { - cap >> frame; - ASSERT_FALSE(frame.empty()); - - if (cn != 3) - { - cv::Mat temp; - if (cn == 1) - cv::cvtColor(frame, temp, cv::COLOR_BGR2GRAY); - else - cv::cvtColor(frame, temp, cv::COLOR_BGR2BGRA); - cv::swap(temp, frame); - } - - d_frame.upload(frame); - - startTimer(); next(); - vibe(d_frame, foreground); - stopTimer(); - } - - GPU_SANITY_CHECK(foreground); - } - else - { - FAIL_NO_CPU(); - } -} - -#endif - #endif diff --git a/modules/nonfree/src/cuda/vibe.cu b/modules/nonfree/src/cuda/vibe.cu deleted file mode 100644 index ba678abae2..0000000000 --- a/modules/nonfree/src/cuda/vibe.cu +++ /dev/null @@ -1,271 +0,0 @@ -/*M/////////////////////////////////////////////////////////////////////////////////////// -// -// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. -// -// By downloading, copying, installing or using the software you agree to this license. -// If you do not agree to this license, do not download, install, -// copy or use the software. -// -// -// License Agreement -// For Open Source Computer Vision Library -// -// Copyright (C) 2000-2008, Intel Corporation, all rights reserved. -// Copyright (C) 2009, Willow Garage Inc., all rights reserved. -// Third party copyrights are property of their respective owners. -// -// Redistribution and use in source and binary forms, with or without modification, -// are permitted provided that the following conditions are met: -// -// * Redistribution's of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// * Redistribution's in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// * The name of the copyright holders may not be used to endorse or promote products -// derived from this software without specific prior written permission. -// -// This software is provided by the copyright holders and contributors "as is" and -// any express or implied warranties, including, but not limited to, the implied -// warranties of merchantability and fitness for a particular purpose are disclaimed. -// In no event shall the Intel Corporation or contributors be liable for any direct, -// indirect, incidental, special, exemplary, or consequential damages -// (including, but not limited to, procurement of substitute goods or services; -// loss of use, data, or profits; or business interruption) however caused -// and on any theory of liability, whether in contract, strict liability, -// or tort (including negligence or otherwise) arising in any way out of -// the use of this software, even if advised of the possibility of such damage. -// -//M*/ - -#include "opencv2/opencv_modules.hpp" - -#ifdef HAVE_OPENCV_GPU - -#include "opencv2/gpu/device/common.hpp" - -namespace cv { namespace gpu { namespace device -{ - namespace vibe - { - void loadConstants(int nbSamples, int reqMatches, int radius, int subsamplingFactor); - - void init_gpu(PtrStepSzb frame, int cn, PtrStepSzb samples, PtrStepSz randStates, cudaStream_t stream); - - void update_gpu(PtrStepSzb frame, int cn, PtrStepSzb fgmask, PtrStepSzb samples, PtrStepSz randStates, cudaStream_t stream); - } -}}} - -namespace cv { namespace gpu { namespace device -{ - namespace vibe - { - __constant__ int c_nbSamples; - __constant__ int c_reqMatches; - __constant__ int c_radius; - __constant__ int c_subsamplingFactor; - - void loadConstants(int nbSamples, int reqMatches, int radius, int subsamplingFactor) - { - cudaSafeCall( cudaMemcpyToSymbol(c_nbSamples, &nbSamples, sizeof(int)) ); - cudaSafeCall( cudaMemcpyToSymbol(c_reqMatches, &reqMatches, sizeof(int)) ); - cudaSafeCall( cudaMemcpyToSymbol(c_radius, &radius, sizeof(int)) ); - cudaSafeCall( cudaMemcpyToSymbol(c_subsamplingFactor, &subsamplingFactor, sizeof(int)) ); - } - - __device__ __forceinline__ uint nextRand(uint& state) - { - const unsigned int CV_RNG_COEFF = 4164903690U; - state = state * CV_RNG_COEFF + (state >> 16); - return state; - } - - __constant__ int c_xoff[9] = {-1, 0, 1, -1, 1, -1, 0, 1, 0}; - __constant__ int c_yoff[9] = {-1, -1, -1, 0, 0, 1, 1, 1, 0}; - - __device__ __forceinline__ int2 chooseRandomNeighbor(int x, int y, uint& randState, int count = 8) - { - int idx = nextRand(randState) % count; - - return make_int2(x + c_xoff[idx], y + c_yoff[idx]); - } - - __device__ __forceinline__ uchar cvt(uchar val) - { - return val; - } - __device__ __forceinline__ uchar4 cvt(const uchar3& val) - { - return make_uchar4(val.x, val.y, val.z, 0); - } - __device__ __forceinline__ uchar4 cvt(const uchar4& val) - { - return val; - } - - template - __global__ void init(const PtrStepSz frame, PtrStep samples, PtrStep randStates) - { - const int x = blockIdx.x * blockDim.x + threadIdx.x; - const int y = blockIdx.y * blockDim.y + threadIdx.y; - - if (x >= frame.cols || y >= frame.rows) - return; - - uint localState = randStates(y, x); - - for (int k = 0; k < c_nbSamples; ++k) - { - int2 np = chooseRandomNeighbor(x, y, localState, 9); - - np.x = ::max(0, ::min(np.x, frame.cols - 1)); - np.y = ::max(0, ::min(np.y, frame.rows - 1)); - - SrcT pix = frame(np.y, np.x); - - samples(k * frame.rows + y, x) = cvt(pix); - } - - randStates(y, x) = localState; - } - - template - void init_caller(PtrStepSzb frame, PtrStepSzb samples, PtrStepSz randStates, cudaStream_t stream) - { - dim3 block(32, 8); - dim3 grid(divUp(frame.cols, block.x), divUp(frame.rows, block.y)); - - cudaSafeCall( cudaFuncSetCacheConfig(init, cudaFuncCachePreferL1) ); - - init<<>>((PtrStepSz) frame, (PtrStepSz) samples, randStates); - cudaSafeCall( cudaGetLastError() ); - - if (stream == 0) - cudaSafeCall( cudaDeviceSynchronize() ); - } - - void init_gpu(PtrStepSzb frame, int cn, PtrStepSzb samples, PtrStepSz randStates, cudaStream_t stream) - { - typedef void (*func_t)(PtrStepSzb frame, PtrStepSzb samples, PtrStepSz randStates, cudaStream_t stream); - static const func_t funcs[] = - { - 0, init_caller, 0, init_caller, init_caller - }; - - funcs[cn](frame, samples, randStates, stream); - } - - __device__ __forceinline__ int calcDist(uchar a, uchar b) - { - return ::abs(a - b); - } - __device__ __forceinline__ int calcDist(const uchar3& a, const uchar4& b) - { - return (::abs(a.x - b.x) + ::abs(a.y - b.y) + ::abs(a.z - b.z)) / 3; - } - __device__ __forceinline__ int calcDist(const uchar4& a, const uchar4& b) - { - return (::abs(a.x - b.x) + ::abs(a.y - b.y) + ::abs(a.z - b.z)) / 3; - } - - template - __global__ void update(const PtrStepSz frame, PtrStepb fgmask, PtrStep samples, PtrStep randStates) - { - const int x = blockIdx.x * blockDim.x + threadIdx.x; - const int y = blockIdx.y * blockDim.y + threadIdx.y; - - if (x >= frame.cols || y >= frame.rows) - return; - - uint localState = randStates(y, x); - - SrcT imgPix = frame(y, x); - - // comparison with the model - - int count = 0; - for (int k = 0; (count < c_reqMatches) && (k < c_nbSamples); ++k) - { - SampleT samplePix = samples(k * frame.rows + y, x); - - int distance = calcDist(imgPix, samplePix); - - if (distance < c_radius) - ++count; - } - - // pixel classification according to reqMatches - - fgmask(y, x) = (uchar) (-(count < c_reqMatches)); - - if (count >= c_reqMatches) - { - // the pixel belongs to the background - - // gets a random number between 0 and subsamplingFactor-1 - int randomNumber = nextRand(localState) % c_subsamplingFactor; - - // update of the current pixel model - if (randomNumber == 0) - { - // random subsampling - - int k = nextRand(localState) % c_nbSamples; - - samples(k * frame.rows + y, x) = cvt(imgPix); - } - - // update of a neighboring pixel model - randomNumber = nextRand(localState) % c_subsamplingFactor; - - if (randomNumber == 0) - { - // random subsampling - - // chooses a neighboring pixel randomly - int2 np = chooseRandomNeighbor(x, y, localState); - - np.x = ::max(0, ::min(np.x, frame.cols - 1)); - np.y = ::max(0, ::min(np.y, frame.rows - 1)); - - // chooses the value to be replaced randomly - int k = nextRand(localState) % c_nbSamples; - - samples(k * frame.rows + np.y, np.x) = cvt(imgPix); - } - } - - randStates(y, x) = localState; - } - - template - void update_caller(PtrStepSzb frame, PtrStepSzb fgmask, PtrStepSzb samples, PtrStepSz randStates, cudaStream_t stream) - { - dim3 block(32, 8); - dim3 grid(divUp(frame.cols, block.x), divUp(frame.rows, block.y)); - - cudaSafeCall( cudaFuncSetCacheConfig(update, cudaFuncCachePreferL1) ); - - update<<>>((PtrStepSz) frame, fgmask, (PtrStepSz) samples, randStates); - cudaSafeCall( cudaGetLastError() ); - - if (stream == 0) - cudaSafeCall( cudaDeviceSynchronize() ); - } - - void update_gpu(PtrStepSzb frame, int cn, PtrStepSzb fgmask, PtrStepSzb samples, PtrStepSz randStates, cudaStream_t stream) - { - typedef void (*func_t)(PtrStepSzb frame, PtrStepSzb fgmask, PtrStepSzb samples, PtrStepSz randStates, cudaStream_t stream); - static const func_t funcs[] = - { - 0, update_caller, 0, update_caller, update_caller - }; - - funcs[cn](frame, fgmask, samples, randStates, stream); - } - } -}}} - -#endif /* HAVE_OPENCV_GPU */ diff --git a/modules/nonfree/src/vibe_gpu.cpp b/modules/nonfree/src/vibe_gpu.cpp deleted file mode 100644 index e34862765d..0000000000 --- a/modules/nonfree/src/vibe_gpu.cpp +++ /dev/null @@ -1,141 +0,0 @@ -/*M/////////////////////////////////////////////////////////////////////////////////////// -// -// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. -// -// By downloading, copying, installing or using the software you agree to this license. -// If you do not agree to this license, do not download, install, -// copy or use the software. -// -// -// License Agreement -// For Open Source Computer Vision Library -// -// Copyright (C) 2000-2008, Intel Corporation, all rights reserved. -// Copyright (C) 2009, Willow Garage Inc., all rights reserved. -// Third party copyrights are property of their respective owners. -// -// Redistribution and use in source and binary forms, with or without modification, -// are permitted provided that the following conditions are met: -// -// * Redistribution's of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// * Redistribution's in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// * The name of the copyright holders may not be used to endorse or promote products -// derived from this software without specific prior written permission. -// -// This software is provided by the copyright holders and contributors "as is" and -// any express or implied warranties, including, but not limited to, the implied -// warranties of merchantability and fitness for a particular purpose are disclaimed. -// In no event shall the Intel Corporation or contributors be liable for any direct, -// indirect, incidental, special, exemplary, or consequential damages -// (including, but not limited to, procurement of substitute goods or services; -// loss of use, data, or profits; or business interruption) however caused -// and on any theory of liability, whether in contract, strict liability, -// or tort (including negligence or otherwise) arising in any way out of -// the use of this software, even if advised of the possibility of such damage. -// -//M*/ - -#include "precomp.hpp" - -#if defined(HAVE_OPENCV_GPU) - -#if !defined HAVE_CUDA || defined(CUDA_DISABLER) - -cv::gpu::VIBE_GPU::VIBE_GPU(unsigned long) { throw_nogpu(); } -void cv::gpu::VIBE_GPU::initialize(const GpuMat&, Stream&) { throw_nogpu(); } -void cv::gpu::VIBE_GPU::operator()(const GpuMat&, GpuMat&, Stream&) { throw_nogpu(); } -void cv::gpu::VIBE_GPU::release() {} - -#else - -namespace cv { namespace gpu { namespace device -{ - namespace vibe - { - void loadConstants(int nbSamples, int reqMatches, int radius, int subsamplingFactor); - - void init_gpu(PtrStepSzb frame, int cn, PtrStepSzb samples, PtrStepSz randStates, cudaStream_t stream); - - void update_gpu(PtrStepSzb frame, int cn, PtrStepSzb fgmask, PtrStepSzb samples, PtrStepSz randStates, cudaStream_t stream); - } -}}} - -namespace -{ - const int defaultNbSamples = 20; - const int defaultReqMatches = 2; - const int defaultRadius = 20; - const int defaultSubsamplingFactor = 16; -} - -cv::gpu::VIBE_GPU::VIBE_GPU(unsigned long rngSeed) : - frameSize_(0, 0), rngSeed_(rngSeed) -{ - nbSamples = defaultNbSamples; - reqMatches = defaultReqMatches; - radius = defaultRadius; - subsamplingFactor = defaultSubsamplingFactor; -} - -void cv::gpu::VIBE_GPU::initialize(const GpuMat& firstFrame, Stream& s) -{ - using namespace cv::gpu::device::vibe; - - CV_Assert(firstFrame.type() == CV_8UC1 || firstFrame.type() == CV_8UC3 || firstFrame.type() == CV_8UC4); - - cudaStream_t stream = StreamAccessor::getStream(s); - - loadConstants(nbSamples, reqMatches, radius, subsamplingFactor); - - frameSize_ = firstFrame.size(); - - if (randStates_.size() != frameSize_) - { - cv::RNG rng(rngSeed_); - cv::Mat h_randStates(frameSize_, CV_8UC4); - rng.fill(h_randStates, cv::RNG::UNIFORM, 0, 255); - randStates_.upload(h_randStates); - } - - int ch = firstFrame.channels(); - int sample_ch = ch == 1 ? 1 : 4; - - samples_.create(nbSamples * frameSize_.height, frameSize_.width, CV_8UC(sample_ch)); - - init_gpu(firstFrame, ch, samples_, randStates_, stream); -} - -void cv::gpu::VIBE_GPU::operator()(const GpuMat& frame, GpuMat& fgmask, Stream& s) -{ - using namespace cv::gpu::device::vibe; - - CV_Assert(frame.depth() == CV_8U); - - int ch = frame.channels(); - int sample_ch = ch == 1 ? 1 : 4; - - if (frame.size() != frameSize_ || sample_ch != samples_.channels()) - initialize(frame); - - fgmask.create(frameSize_, CV_8UC1); - - update_gpu(frame, ch, fgmask, samples_, randStates_, StreamAccessor::getStream(s)); -} - -void cv::gpu::VIBE_GPU::release() -{ - frameSize_ = Size(0, 0); - - randStates_.release(); - - samples_.release(); -} - -#endif - -#endif // defined(HAVE_OPENCV_GPU) diff --git a/modules/nonfree/test/test_gpu.cpp b/modules/nonfree/test/test_gpu.cpp index 30aec352cd..3f63eeddf2 100644 --- a/modules/nonfree/test/test_gpu.cpp +++ b/modules/nonfree/test/test_gpu.cpp @@ -191,42 +191,4 @@ INSTANTIATE_TEST_CASE_P(GPU_Features2D, SURF, testing::Combine( testing::Values(SURF_Extended(false), SURF_Extended(true)), testing::Values(SURF_Upright(false), SURF_Upright(true)))); -////////////////////////////////////////////////////// -// VIBE - -PARAM_TEST_CASE(VIBE, cv::Size, MatType, UseRoi) -{ -}; - -GPU_TEST_P(VIBE, Accuracy) -{ - const cv::Size size = GET_PARAM(0); - const int type = GET_PARAM(1); - const bool useRoi = GET_PARAM(2); - - const cv::Mat fullfg(size, CV_8UC1, cv::Scalar::all(255)); - - cv::Mat frame = randomMat(size, type, 0.0, 100); - cv::gpu::GpuMat d_frame = loadMat(frame, useRoi); - - cv::gpu::VIBE_GPU vibe; - cv::gpu::GpuMat d_fgmask = createMat(size, CV_8UC1, useRoi); - vibe.initialize(d_frame); - - for (int i = 0; i < 20; ++i) - vibe(d_frame, d_fgmask); - - frame = randomMat(size, type, 160, 255); - d_frame = loadMat(frame, useRoi); - vibe(d_frame, d_fgmask); - - // now fgmask should be entirely foreground - ASSERT_MAT_NEAR(fullfg, d_fgmask, 0); -} - -INSTANTIATE_TEST_CASE_P(GPU_Video, VIBE, testing::Combine( - DIFFERENT_SIZES, - testing::Values(MatType(CV_8UC1), MatType(CV_8UC3), MatType(CV_8UC4)), - WHOLE_SUBMAT)); - #endif diff --git a/samples/gpu/bgfg_segm.cpp b/samples/gpu/bgfg_segm.cpp index a77d336a9e..6963e75ff8 100644 --- a/samples/gpu/bgfg_segm.cpp +++ b/samples/gpu/bgfg_segm.cpp @@ -1,15 +1,10 @@ #include #include -#include "opencv2/opencv_modules.hpp" #include "opencv2/core/core.hpp" #include "opencv2/gpu/gpu.hpp" #include "opencv2/highgui/highgui.hpp" -#ifdef HAVE_OPENCV_NONFREE -#include "opencv2/nonfree/gpu.hpp" -#endif - using namespace std; using namespace cv; using namespace cv::gpu; @@ -19,9 +14,6 @@ enum Method FGD_STAT, MOG, MOG2, -#ifdef HAVE_OPENCV_NONFREE - VIBE, -#endif GMG }; @@ -30,7 +22,7 @@ int main(int argc, const char** argv) cv::CommandLineParser cmd(argc, argv, "{ c | camera | false | use camera }" "{ f | file | 768x576.avi | input video file }" - "{ m | method | mog | method (fgd, mog, mog2, vibe, gmg) }" + "{ m | method | mog | method (fgd, mog, mog2, gmg) }" "{ h | help | false | print help message }"); if (cmd.get("help")) @@ -48,9 +40,6 @@ int main(int argc, const char** argv) if (method != "fgd" && method != "mog" && method != "mog2" - #ifdef HAVE_OPENCV_NONFREE - && method != "vibe" - #endif && method != "gmg") { cerr << "Incorrect method" << endl; @@ -60,9 +49,6 @@ int main(int argc, const char** argv) Method m = method == "fgd" ? FGD_STAT : method == "mog" ? MOG : method == "mog2" ? MOG2 : - #ifdef HAVE_OPENCV_NONFREE - method == "vibe" ? VIBE : - #endif GMG; VideoCapture cap; @@ -86,9 +72,6 @@ int main(int argc, const char** argv) FGDStatModel fgd_stat; MOG_GPU mog; MOG2_GPU mog2; -#ifdef HAVE_OPENCV_NONFREE - VIBE_GPU vibe; -#endif GMG_GPU gmg; gmg.numInitializationFrames = 40; @@ -114,12 +97,6 @@ int main(int argc, const char** argv) mog2(d_frame, d_fgmask); break; -#ifdef HAVE_OPENCV_NONFREE - case VIBE: - vibe.initialize(d_frame); - break; -#endif - case GMG: gmg.initialize(d_frame.size()); break; @@ -128,11 +105,7 @@ int main(int argc, const char** argv) namedWindow("image", WINDOW_NORMAL); namedWindow("foreground mask", WINDOW_NORMAL); namedWindow("foreground image", WINDOW_NORMAL); - if (m != GMG - #ifdef HAVE_OPENCV_NONFREE - && m != VIBE - #endif - ) + if (m != GMG) { namedWindow("mean background image", WINDOW_NORMAL); } @@ -165,12 +138,6 @@ int main(int argc, const char** argv) mog2.getBackgroundImage(d_bgimg); break; -#ifdef HAVE_OPENCV_NONFREE - case VIBE: - vibe(d_frame, d_fgmask); - break; -#endif - case GMG: gmg(d_frame, d_fgmask); break; From 5f20fce6fddf46d5f1665632015c444aebc7c570 Mon Sep 17 00:00:00 2001 From: yao Date: Fri, 17 May 2013 13:18:46 +0800 Subject: [PATCH 010/178] add accuracy tests while running perf --- modules/ocl/CMakeLists.txt | 2 +- modules/ocl/perf/perf_arithm.cpp | 430 ++++++++++------- modules/ocl/perf/perf_blend.cpp | 8 +- modules/ocl/perf/perf_brute_force_matcher.cpp | 18 +- modules/ocl/perf/perf_canny.cpp | 6 +- modules/ocl/perf/perf_color.cpp | 8 +- modules/ocl/perf/perf_columnsum.cpp | 15 +- modules/ocl/perf/perf_fft.cpp | 10 +- modules/ocl/perf/perf_filters.cpp | 46 +- modules/ocl/perf/perf_gemm.cpp | 5 +- modules/ocl/perf/perf_haar.cpp | 17 +- modules/ocl/perf/perf_hog.cpp | 80 +++- modules/ocl/perf/perf_imgproc.cpp | 293 ++++++++++-- modules/ocl/perf/perf_match_template.cpp | 9 +- modules/ocl/perf/perf_matrix_operation.cpp | 19 +- modules/ocl/perf/perf_norm.cpp | 6 +- modules/ocl/perf/perf_pyrdown.cpp | 7 +- modules/ocl/perf/perf_pyrlk.cpp | 10 +- modules/ocl/perf/perf_pyrup.cpp | 6 +- modules/ocl/perf/perf_split_merge.cpp | 24 +- modules/ocl/perf/precomp.cpp | 451 +++++++++++++++--- modules/ocl/perf/precomp.hpp | 88 +++- 22 files changed, 1185 insertions(+), 373 deletions(-) diff --git a/modules/ocl/CMakeLists.txt b/modules/ocl/CMakeLists.txt index a7cd3a0715..05b28b83fe 100644 --- a/modules/ocl/CMakeLists.txt +++ b/modules/ocl/CMakeLists.txt @@ -3,5 +3,5 @@ if(NOT HAVE_OPENCL) endif() set(the_description "OpenCL-accelerated Computer Vision") -ocv_define_module(ocl opencv_core opencv_imgproc opencv_features2d opencv_objdetect opencv_video) +ocv_define_module(ocl opencv_core opencv_imgproc opencv_features2d opencv_objdetect opencv_video opencv_calib3d) ocv_warnings_disable(CMAKE_CXX_FLAGS -Wshadow) diff --git a/modules/ocl/perf/perf_arithm.cpp b/modules/ocl/perf/perf_arithm.cpp index e6e957641b..e69fecd647 100644 --- a/modules/ocl/perf/perf_arithm.cpp +++ b/modules/ocl/perf/perf_arithm.cpp @@ -16,6 +16,7 @@ // // @Authors // Fangfang Bai, fangfang@multicorewareinc.com +// Jin Ma, jin@multicorewareinc.com // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: @@ -45,7 +46,7 @@ #include "precomp.hpp" ///////////// Lut //////////////////////// -TEST(lut) +PERFTEST(lut) { Mat src, lut, dst; ocl::oclMat d_src, d_lut, d_dst; @@ -61,7 +62,7 @@ TEST(lut) gen(src, size, size, all_type[j], 0, 256); gen(lut, 1, 256, CV_8UC1, 0, 1); - gen(dst, size, size, all_type[j], 0, 256); + dst = src; LUT(src, lut, dst); @@ -76,9 +77,13 @@ TEST(lut) ocl::LUT(d_src, d_lut, d_dst); WARMUP_OFF; + cv::Mat ocl_mat_dst; + d_dst.download(ocl_mat_dst); + + TestSystem::instance().setAccurate(ExpectedMatNear(ocl_mat_dst, dst, 0)); + GPU_ON; ocl::LUT(d_src, d_lut, d_dst); - ; GPU_OFF; GPU_FULL_ON; @@ -94,7 +99,7 @@ TEST(lut) } ///////////// Exp //////////////////////// -TEST(Exp) +PERFTEST(Exp) { Mat src, dst; ocl::oclMat d_src, d_dst; @@ -103,8 +108,7 @@ TEST(Exp) { SUBTEST << size << 'x' << size << "; CV_32FC1"; - gen(src, size, size, CV_32FC1, 0, 256); - gen(dst, size, size, CV_32FC1, 0, 256); + gen(src, size, size, CV_32FC1, 5, 16); exp(src, dst); @@ -117,9 +121,13 @@ TEST(Exp) ocl::exp(d_src, d_dst); WARMUP_OFF; + cv::Mat ocl_mat_dst; + d_dst.download(ocl_mat_dst); + + TestSystem::instance().setAccurate(ExpectedMatNear(ocl_mat_dst, dst, 2)); + GPU_ON; ocl::exp(d_src, d_dst); - ; GPU_OFF; GPU_FULL_ON; @@ -131,7 +139,7 @@ TEST(Exp) } ///////////// LOG //////////////////////// -TEST(Log) +PERFTEST(Log) { Mat src, dst; ocl::oclMat d_src, d_dst; @@ -153,9 +161,13 @@ TEST(Log) ocl::log(d_src, d_dst); WARMUP_OFF; + cv::Mat ocl_mat_dst; + d_dst.download(ocl_mat_dst); + + TestSystem::instance().setAccurate(ExpectedMatNear(ocl_mat_dst, dst, 1)); + GPU_ON; ocl::log(d_src, d_dst); - ; GPU_OFF; GPU_FULL_ON; @@ -167,7 +179,7 @@ TEST(Log) } ///////////// Add //////////////////////// -TEST(Add) +PERFTEST(Add) { Mat src1, src2, dst; ocl::oclMat d_src1, d_src2, d_dst; @@ -196,9 +208,13 @@ TEST(Add) ocl::add(d_src1, d_src2, d_dst); WARMUP_OFF; + cv::Mat ocl_mat_dst; + d_dst.download(ocl_mat_dst); + + TestSystem::instance().setAccurate(ExpectedMatNear(ocl_mat_dst, dst, 0.0)); + GPU_ON; ocl::add(d_src1, d_src2, d_dst); - ; GPU_OFF; GPU_FULL_ON; @@ -213,7 +229,7 @@ TEST(Add) } ///////////// Mul //////////////////////// -TEST(Mul) +PERFTEST(Mul) { Mat src1, src2, dst; ocl::oclMat d_src1, d_src2, d_dst; @@ -229,8 +245,8 @@ TEST(Mul) gen(src1, size, size, all_type[j], 0, 256); gen(src2, size, size, all_type[j], 0, 256); - gen(dst, size, size, all_type[j], 0, 256); - + dst = src1; + dst.setTo(0); multiply(src1, src2, dst); @@ -244,9 +260,13 @@ TEST(Mul) ocl::multiply(d_src1, d_src2, d_dst); WARMUP_OFF; + cv::Mat ocl_mat_dst; + d_dst.download(ocl_mat_dst); + + TestSystem::instance().setAccurate(ExpectedMatNear(ocl_mat_dst, dst, 0.0)); + GPU_ON; ocl::multiply(d_src1, d_src2, d_dst); - ; GPU_OFF; GPU_FULL_ON; @@ -261,7 +281,7 @@ TEST(Mul) } ///////////// Div //////////////////////// -TEST(Div) +PERFTEST(Div) { Mat src1, src2, dst; ocl::oclMat d_src1, d_src2, d_dst; @@ -276,8 +296,8 @@ TEST(Div) gen(src1, size, size, all_type[j], 0, 256); gen(src2, size, size, all_type[j], 0, 256); - gen(dst, size, size, all_type[j], 0, 256); - + dst = src1; + dst.setTo(0); divide(src1, src2, dst); @@ -291,9 +311,13 @@ TEST(Div) ocl::divide(d_src1, d_src2, d_dst); WARMUP_OFF; + cv::Mat ocl_mat_dst; + d_dst.download(ocl_mat_dst); + + TestSystem::instance().setAccurate(ExpectedMatNear(ocl_mat_dst, dst, 1)); + GPU_ON; ocl::divide(d_src1, d_src2, d_dst); - ; GPU_OFF; GPU_FULL_ON; @@ -308,7 +332,7 @@ TEST(Div) } ///////////// Absdiff //////////////////////// -TEST(Absdiff) +PERFTEST(Absdiff) { Mat src1, src2, dst; ocl::oclMat d_src1, d_src2, d_dst; @@ -326,7 +350,6 @@ TEST(Absdiff) gen(src2, size, size, all_type[j], 0, 256); gen(dst, size, size, all_type[j], 0, 256); - absdiff(src1, src2, dst); CPU_ON; @@ -339,9 +362,13 @@ TEST(Absdiff) ocl::absdiff(d_src1, d_src2, d_dst); WARMUP_OFF; + cv::Mat ocl_mat_dst; + d_dst.download(ocl_mat_dst); + + TestSystem::instance().setAccurate(ExpectedMatNear(ocl_mat_dst, dst, 0.0)); + GPU_ON; ocl::absdiff(d_src1, d_src2, d_dst); - ; GPU_OFF; GPU_FULL_ON; @@ -356,7 +383,7 @@ TEST(Absdiff) } ///////////// CartToPolar //////////////////////// -TEST(CartToPolar) +PERFTEST(CartToPolar) { Mat src1, src2, dst, dst1; ocl::oclMat d_src1, d_src2, d_dst, d_dst1; @@ -388,9 +415,16 @@ TEST(CartToPolar) ocl::cartToPolar(d_src1, d_src2, d_dst, d_dst1, 1); WARMUP_OFF; + cv::Mat ocl_mat_dst; + d_dst.download(ocl_mat_dst); + + cv::Mat ocl_mat_dst1; + d_dst1.download(ocl_mat_dst1); + + TestSystem::instance().setAccurate(ExpectedMatNear(ocl_mat_dst1, dst1, 0.5)&&ExpectedMatNear(ocl_mat_dst, dst, 0.5)); + GPU_ON; ocl::cartToPolar(d_src1, d_src2, d_dst, d_dst1, 1); - ; GPU_OFF; GPU_FULL_ON; @@ -406,7 +440,7 @@ TEST(CartToPolar) } ///////////// PolarToCart //////////////////////// -TEST(PolarToCart) +PERFTEST(PolarToCart) { Mat src1, src2, dst, dst1; ocl::oclMat d_src1, d_src2, d_dst, d_dst1; @@ -438,9 +472,16 @@ TEST(PolarToCart) ocl::polarToCart(d_src1, d_src2, d_dst, d_dst1, 1); WARMUP_OFF; + cv::Mat ocl_mat_dst; + d_dst.download(ocl_mat_dst); + + cv::Mat ocl_mat_dst1; + d_dst1.download(ocl_mat_dst1); + + TestSystem::instance().setAccurate(ExpectedMatNear(ocl_mat_dst1, dst1, 0.5)&&ExpectedMatNear(ocl_mat_dst, dst, 0.5)); + GPU_ON; ocl::polarToCart(d_src1, d_src2, d_dst, d_dst1, 1); - ; GPU_OFF; GPU_FULL_ON; @@ -456,7 +497,7 @@ TEST(PolarToCart) } ///////////// Magnitude //////////////////////// -TEST(magnitude) +PERFTEST(magnitude) { Mat x, y, mag; ocl::oclMat d_x, d_y, d_mag; @@ -485,9 +526,13 @@ TEST(magnitude) ocl::magnitude(d_x, d_y, d_mag); WARMUP_OFF; + cv::Mat ocl_mat_dst; + d_mag.download(ocl_mat_dst); + + TestSystem::instance().setAccurate(ExpectedMatNear(ocl_mat_dst, mag, 1e-5)); + GPU_ON; ocl::magnitude(d_x, d_y, d_mag); - ; GPU_OFF; GPU_FULL_ON; @@ -502,7 +547,7 @@ TEST(magnitude) } ///////////// Transpose //////////////////////// -TEST(Transpose) +PERFTEST(Transpose) { Mat src, dst; ocl::oclMat d_src, d_dst; @@ -530,9 +575,13 @@ TEST(Transpose) ocl::transpose(d_src, d_dst); WARMUP_OFF; + cv::Mat ocl_mat_dst; + d_dst.download(ocl_mat_dst); + + TestSystem::instance().setAccurate(ExpectedMatNear(ocl_mat_dst, dst, 1e-5)); + GPU_ON; ocl::transpose(d_src, d_dst); - ; GPU_OFF; GPU_FULL_ON; @@ -546,7 +595,7 @@ TEST(Transpose) } ///////////// Flip //////////////////////// -TEST(Flip) +PERFTEST(Flip) { Mat src, dst; ocl::oclMat d_src, d_dst; @@ -574,9 +623,13 @@ TEST(Flip) ocl::flip(d_src, d_dst, 0); WARMUP_OFF; + cv::Mat ocl_mat_dst; + d_dst.download(ocl_mat_dst); + + TestSystem::instance().setAccurate(ExpectedMatNear(ocl_mat_dst, dst, 1e-5)); + GPU_ON; ocl::flip(d_src, d_dst, 0); - ; GPU_OFF; GPU_FULL_ON; @@ -590,12 +643,13 @@ TEST(Flip) } ///////////// minMax //////////////////////// -TEST(minMax) +PERFTEST(minMax) { Mat src; ocl::oclMat d_src; - double min_val, max_val; + double min_val = 0.0, max_val = 0.0; + double min_val_ = 0.0, max_val_ = 0.0; Point min_loc, max_loc; int all_type[] = {CV_8UC1, CV_32FC1}; std::string type_name[] = {"CV_8UC1", "CV_32FC1"}; @@ -614,12 +668,13 @@ TEST(minMax) d_src.upload(src); WARMUP_ON; - ocl::minMax(d_src, &min_val, &max_val); + ocl::minMax(d_src, &min_val_, &max_val_); WARMUP_OFF; + TestSystem::instance().setAccurate(EeceptDoubleEQ(max_val_, max_val)&&EeceptDoubleEQ(min_val_, min_val)); + GPU_ON; ocl::minMax(d_src, &min_val, &max_val); - ; GPU_OFF; GPU_FULL_ON; @@ -633,13 +688,15 @@ TEST(minMax) } ///////////// minMaxLoc //////////////////////// -TEST(minMaxLoc) +PERFTEST(minMaxLoc) { Mat src; ocl::oclMat d_src; - double min_val, max_val; + double min_val = 0.0, max_val = 0.0; + double min_val_ = 0.0, max_val_ = 0.0; Point min_loc, max_loc; + Point min_loc_, max_loc_; int all_type[] = {CV_8UC1, CV_32FC1}; std::string type_name[] = {"CV_8UC1", "CV_32FC1"}; @@ -657,12 +714,83 @@ TEST(minMaxLoc) d_src.upload(src); WARMUP_ON; - ocl::minMaxLoc(d_src, &min_val, &max_val, &min_loc, &max_loc); + ocl::minMaxLoc(d_src, &min_val_, &max_val_, &min_loc_, &max_loc_); WARMUP_OFF; + double error0 = 0., error1 = 0., minlocVal = 0., minlocVal_ = 0., maxlocVal = 0., maxlocVal_ = 0.; + if(src.depth() == 0) + { + minlocVal = src.at(min_loc); + minlocVal_ = src.at(min_loc_); + maxlocVal = src.at(max_loc); + maxlocVal_ = src.at(max_loc_); + error0 = ::abs(src.at(min_loc_) - src.at(min_loc)); + error1 = ::abs(src.at(max_loc_) - src.at(max_loc)); + } + if(src.depth() == 1) + { + minlocVal = src.at(min_loc); + minlocVal_ = src.at(min_loc_); + maxlocVal = src.at(max_loc); + maxlocVal_ = src.at(max_loc_); + error0 = ::abs(src.at(min_loc_) - src.at(min_loc)); + error1 = ::abs(src.at(max_loc_) - src.at(max_loc)); + } + if(src.depth() == 2) + { + minlocVal = src.at(min_loc); + minlocVal_ = src.at(min_loc_); + maxlocVal = src.at(max_loc); + maxlocVal_ = src.at(max_loc_); + error0 = ::abs(src.at(min_loc_) - src.at(min_loc)); + error1 = ::abs(src.at(max_loc_) - src.at(max_loc)); + } + if(src.depth() == 3) + { + minlocVal = src.at(min_loc); + minlocVal_ = src.at(min_loc_); + maxlocVal = src.at(max_loc); + maxlocVal_ = src.at(max_loc_); + error0 = ::abs(src.at(min_loc_) - src.at(min_loc)); + error1 = ::abs(src.at(max_loc_) - src.at(max_loc)); + } + if(src.depth() == 4) + { + minlocVal = src.at(min_loc); + minlocVal_ = src.at(min_loc_); + maxlocVal = src.at(max_loc); + maxlocVal_ = src.at(max_loc_); + error0 = ::abs(src.at(min_loc_) - src.at(min_loc)); + error1 = ::abs(src.at(max_loc_) - src.at(max_loc)); + } + if(src.depth() == 5) + { + minlocVal = src.at(min_loc); + minlocVal_ = src.at(min_loc_); + maxlocVal = src.at(max_loc); + maxlocVal_ = src.at(max_loc_); + error0 = ::abs(src.at(min_loc_) - src.at(min_loc)); + error1 = ::abs(src.at(max_loc_) - src.at(max_loc)); + } + if(src.depth() == 6) + { + minlocVal = src.at(min_loc); + minlocVal_ = src.at(min_loc_); + maxlocVal = src.at(max_loc); + maxlocVal_ = src.at(max_loc_); + error0 = ::abs(src.at(min_loc_) - src.at(min_loc)); + error1 = ::abs(src.at(max_loc_) - src.at(max_loc)); + } + + TestSystem::instance().setAccurate(EeceptDoubleEQ(error1, 0.0) + &&EeceptDoubleEQ(error0, 0.0) + &&EeceptDoubleEQ(maxlocVal_, maxlocVal) + &&EeceptDoubleEQ(minlocVal_, minlocVal) + &&EeceptDoubleEQ(max_val_, max_val) + &&EeceptDoubleEQ(min_val_, min_val)); + GPU_ON; ocl::minMaxLoc(d_src, &min_val, &max_val, &min_loc, &max_loc); - ; GPU_OFF; GPU_FULL_ON; @@ -675,7 +803,7 @@ TEST(minMaxLoc) } ///////////// Sum //////////////////////// -TEST(Sum) +PERFTEST(Sum) { Mat src; Scalar cpures, gpures; @@ -690,7 +818,7 @@ TEST(Sum) { SUBTEST << size << 'x' << size << "; " << type_name[j] ; - gen(src, size, size, all_type[j], 0, 256); + gen(src, size, size, all_type[j], 0, 60); cpures = sum(src); @@ -703,9 +831,14 @@ TEST(Sum) gpures = ocl::sum(d_src); WARMUP_OFF; + TestSystem::instance().setAccurate(ExceptDoubleNear(cpures[3], gpures[3], 0.1) + &&ExceptDoubleNear(cpures[2], gpures[2], 0.1) + &&ExceptDoubleNear(cpures[1], gpures[1], 0.1) + &&ExceptDoubleNear(cpures[0], gpures[0], 0.1)); + + GPU_ON; gpures = ocl::sum(d_src); - ; GPU_OFF; GPU_FULL_ON; @@ -718,7 +851,7 @@ TEST(Sum) } ///////////// countNonZero //////////////////////// -TEST(countNonZero) +PERFTEST(countNonZero) { Mat src; ocl::oclMat d_src; @@ -736,18 +869,20 @@ TEST(countNonZero) countNonZero(src); + int cpures = 0, gpures = 0; CPU_ON; - countNonZero(src); + cpures = countNonZero(src); CPU_OFF; d_src.upload(src); WARMUP_ON; - ocl::countNonZero(d_src); + gpures = ocl::countNonZero(d_src); WARMUP_OFF; + TestSystem::instance().setAccurate((EeceptDoubleEQ((double)cpures, (double)gpures))); + GPU_ON; ocl::countNonZero(d_src); - ; GPU_OFF; GPU_FULL_ON; @@ -760,7 +895,7 @@ TEST(countNonZero) } ///////////// Phase //////////////////////// -TEST(Phase) +PERFTEST(Phase) { Mat src1, src2, dst; ocl::oclMat d_src1, d_src2, d_dst; @@ -791,9 +926,13 @@ TEST(Phase) ocl::phase(d_src1, d_src2, d_dst, 1); WARMUP_OFF; + cv::Mat ocl_mat_dst; + d_dst.download(ocl_mat_dst); + + TestSystem::instance().setAccurate(ExpectedMatNear(ocl_mat_dst, dst, 1e-2)); + GPU_ON; ocl::phase(d_src1, d_src2, d_dst, 1); - ; GPU_OFF; GPU_FULL_ON; @@ -808,7 +947,7 @@ TEST(Phase) } ///////////// bitwise_and//////////////////////// -TEST(bitwise_and) +PERFTEST(bitwise_and) { Mat src1, src2, dst; ocl::oclMat d_src1, d_src2, d_dst; @@ -839,9 +978,13 @@ TEST(bitwise_and) ocl::bitwise_and(d_src1, d_src2, d_dst); WARMUP_OFF; + cv::Mat ocl_mat_dst; + d_dst.download(ocl_mat_dst); + + TestSystem::instance().setAccurate(ExpectedMatNear(ocl_mat_dst, dst, 0.0)); + GPU_ON; ocl::bitwise_and(d_src1, d_src2, d_dst); - ; GPU_OFF; GPU_FULL_ON; @@ -855,104 +998,8 @@ TEST(bitwise_and) } } -///////////// bitwise_or//////////////////////// -TEST(bitwise_or) -{ - Mat src1, src2, dst; - ocl::oclMat d_src1, d_src2, d_dst; - - int all_type[] = {CV_8UC1, CV_32SC1}; - std::string type_name[] = {"CV_8UC1", "CV_32SC1"}; - - for (int size = Min_Size; size <= Max_Size; size *= Multiple) - { - for (size_t j = 0; j < sizeof(all_type) / sizeof(int); j++) - { - SUBTEST << size << 'x' << size << "; " << type_name[j]; - - gen(src1, size, size, all_type[j], 0, 256); - gen(src2, size, size, all_type[j], 0, 256); - gen(dst, size, size, all_type[j], 0, 256); - - - bitwise_or(src1, src2, dst); - - CPU_ON; - bitwise_or(src1, src2, dst); - CPU_OFF; - d_src1.upload(src1); - d_src2.upload(src2); - - WARMUP_ON; - ocl::bitwise_or(d_src1, d_src2, d_dst); - WARMUP_OFF; - - GPU_ON; - ocl::bitwise_or(d_src1, d_src2, d_dst); - ; - GPU_OFF; - - GPU_FULL_ON; - d_src1.upload(src1); - d_src2.upload(src2); - ocl::bitwise_or(d_src1, d_src2, d_dst); - d_dst.download(dst); - GPU_FULL_OFF; - } - - } -} - -///////////// bitwise_xor//////////////////////// -TEST(bitwise_xor) -{ - Mat src1, src2, dst; - ocl::oclMat d_src1, d_src2, d_dst; - - int all_type[] = {CV_8UC1, CV_32SC1}; - std::string type_name[] = {"CV_8UC1", "CV_32SC1"}; - - for (int size = Min_Size; size <= Max_Size; size *= Multiple) - { - for (size_t j = 0; j < sizeof(all_type) / sizeof(int); j++) - { - SUBTEST << size << 'x' << size << "; " << type_name[j]; - - gen(src1, size, size, all_type[j], 0, 256); - gen(src2, size, size, all_type[j], 0, 256); - gen(dst, size, size, all_type[j], 0, 256); - - - bitwise_xor(src1, src2, dst); - - CPU_ON; - bitwise_xor(src1, src2, dst); - CPU_OFF; - d_src1.upload(src1); - d_src2.upload(src2); - - WARMUP_ON; - ocl::bitwise_xor(d_src1, d_src2, d_dst); - WARMUP_OFF; - - GPU_ON; - ocl::bitwise_xor(d_src1, d_src2, d_dst); - ; - GPU_OFF; - - GPU_FULL_ON; - d_src1.upload(src1); - d_src2.upload(src2); - ocl::bitwise_xor(d_src1, d_src2, d_dst); - d_dst.download(dst); - GPU_FULL_OFF; - } - - } -} - ///////////// bitwise_not//////////////////////// -TEST(bitwise_not) +PERFTEST(bitwise_not) { Mat src1, dst; ocl::oclMat d_src1, d_dst; @@ -981,9 +1028,13 @@ TEST(bitwise_not) ocl::bitwise_not(d_src1, d_dst); WARMUP_OFF; + cv::Mat ocl_mat_dst; + d_dst.download(ocl_mat_dst); + + TestSystem::instance().setAccurate(ExpectedMatNear(ocl_mat_dst, dst, 0.0)); + GPU_ON; ocl::bitwise_not(d_src1, d_dst); - ; GPU_OFF; GPU_FULL_ON; @@ -997,7 +1048,7 @@ TEST(bitwise_not) } ///////////// compare//////////////////////// -TEST(compare) +PERFTEST(compare) { Mat src1, src2, dst; ocl::oclMat d_src1, d_src2, d_dst; @@ -1029,9 +1080,13 @@ TEST(compare) ocl::compare(d_src1, d_src2, d_dst, CMP_EQ); WARMUP_OFF; + cv::Mat ocl_mat_dst; + d_dst.download(ocl_mat_dst); + + TestSystem::instance().setAccurate(ExpectedMatNear(ocl_mat_dst, dst, 0.0)); + GPU_ON; ocl::compare(d_src1, d_src2, d_dst, CMP_EQ); - ; GPU_OFF; GPU_FULL_ON; @@ -1046,7 +1101,7 @@ TEST(compare) } ///////////// pow //////////////////////// -TEST(pow) +PERFTEST(pow) { Mat src, dst; ocl::oclMat d_src, d_dst; @@ -1060,8 +1115,7 @@ TEST(pow) { SUBTEST << size << 'x' << size << "; " << type_name[j] ; - gen(src, size, size, all_type[j], 0, 100); - gen(dst, size, size, all_type[j], 0, 100); + gen(src, size, size, all_type[j], 5, 16); pow(src, -2.0, dst); @@ -1075,9 +1129,13 @@ TEST(pow) ocl::pow(d_src, -2.0, d_dst); WARMUP_OFF; + cv::Mat ocl_mat_dst; + d_dst.download(ocl_mat_dst); + + TestSystem::instance().setAccurate(ExpectedMatNear(ocl_mat_dst, dst, 1.0)); + GPU_ON; ocl::pow(d_src, -2.0, d_dst); - ; GPU_OFF; GPU_FULL_ON; @@ -1091,7 +1149,7 @@ TEST(pow) } ///////////// MagnitudeSqr//////////////////////// -TEST(MagnitudeSqr) +PERFTEST(MagnitudeSqr) { Mat src1, src2, dst; ocl::oclMat d_src1, d_src2, d_dst; @@ -1121,44 +1179,48 @@ TEST(MagnitudeSqr) } - CPU_ON; + CPU_ON; - for (int i = 0; i < src1.rows; ++i) - for (int j = 0; j < src1.cols; ++j) - { - float val1 = src1.at(i, j); - float val2 = src2.at(i, j); + for (int i = 0; i < src1.rows; ++i) + for (int j = 0; j < src1.cols; ++j) + { + float val1 = src1.at(i, j); + float val2 = src2.at(i, j); - ((float *)(dst.data))[i * dst.step / 4 + j] = val1 * val1 + val2 * val2; + ((float *)(dst.data))[i * dst.step / 4 + j] = val1 * val1 + val2 * val2; - } + } - CPU_OFF; - d_src1.upload(src1); - d_src2.upload(src2); + CPU_OFF; + d_src1.upload(src1); + d_src2.upload(src2); - WARMUP_ON; - ocl::magnitudeSqr(d_src1, d_src2, d_dst); - WARMUP_OFF; + WARMUP_ON; + ocl::magnitudeSqr(d_src1, d_src2, d_dst); + WARMUP_OFF; - GPU_ON; - ocl::magnitudeSqr(d_src1, d_src2, d_dst); - ; - GPU_OFF; + cv::Mat ocl_mat_dst; + d_dst.download(ocl_mat_dst); - GPU_FULL_ON; - d_src1.upload(src1); - d_src2.upload(src2); - ocl::magnitudeSqr(d_src1, d_src2, d_dst); - d_dst.download(dst); - GPU_FULL_OFF; + TestSystem::instance().setAccurate(ExpectedMatNear(ocl_mat_dst, dst, 1.0)); + + GPU_ON; + ocl::magnitudeSqr(d_src1, d_src2, d_dst); + GPU_OFF; + + GPU_FULL_ON; + d_src1.upload(src1); + d_src2.upload(src2); + ocl::magnitudeSqr(d_src1, d_src2, d_dst); + d_dst.download(dst); + GPU_FULL_OFF; } } } ///////////// AddWeighted//////////////////////// -TEST(AddWeighted) +PERFTEST(AddWeighted) { Mat src1, src2, dst; ocl::oclMat d_src1, d_src2, d_dst; @@ -1190,9 +1252,13 @@ TEST(AddWeighted) ocl::addWeighted(d_src1, alpha, d_src2, beta, gama, d_dst); WARMUP_OFF; + cv::Mat ocl_mat_dst; + d_dst.download(ocl_mat_dst); + + TestSystem::instance().setAccurate(ExpectedMatNear(ocl_mat_dst, dst, 1e-5)); + GPU_ON; ocl::addWeighted(d_src1, alpha, d_src2, beta, gama, d_dst); - ; GPU_OFF; GPU_FULL_ON; diff --git a/modules/ocl/perf/perf_blend.cpp b/modules/ocl/perf/perf_blend.cpp index 00034700b4..6dda464bd7 100644 --- a/modules/ocl/perf/perf_blend.cpp +++ b/modules/ocl/perf/perf_blend.cpp @@ -16,6 +16,7 @@ // // @Authors // Fangfang Bai, fangfang@multicorewareinc.com +// Jin Ma, jin@multicorewareinc.com // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: @@ -68,7 +69,7 @@ void blendLinearGold(const cv::Mat &img1, const cv::Mat &img2, const cv::Mat &we } } } -TEST(blend) +PERFTEST(blend) { Mat src1, src2, weights1, weights2, dst; ocl::oclMat d_src1, d_src2, d_weights1, d_weights2, d_dst; @@ -102,9 +103,12 @@ TEST(blend) ocl::blendLinear(d_src1, d_src2, d_weights1, d_weights2, d_dst); WARMUP_OFF; + cv::Mat ocl_mat; + d_dst.download(ocl_mat); + TestSystem::instance().setAccurate(ExpectedMatNear(dst, ocl_mat, 1.f)); + GPU_ON; ocl::blendLinear(d_src1, d_src2, d_weights1, d_weights2, d_dst); - ; GPU_OFF; GPU_FULL_ON; diff --git a/modules/ocl/perf/perf_brute_force_matcher.cpp b/modules/ocl/perf/perf_brute_force_matcher.cpp index 6562f91e43..ba87bd8924 100644 --- a/modules/ocl/perf/perf_brute_force_matcher.cpp +++ b/modules/ocl/perf/perf_brute_force_matcher.cpp @@ -16,6 +16,7 @@ // // @Authors // Fangfang Bai, fangfang@multicorewareinc.com +// Jin Ma, jin@multicorewareinc.com // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: @@ -45,7 +46,7 @@ #include "precomp.hpp" //////////////////// BruteForceMatch ///////////////// -TEST(BruteForceMatcher) +PERFTEST(BruteForceMatcher) { Mat trainIdx_cpu; Mat distance_cpu; @@ -66,6 +67,7 @@ TEST(BruteForceMatcher) gen(train, size, desc_len, CV_32F, 0, 1); // Output vector< vector > matches(2); + vector< vector > d_matches(2); // Init GPU matcher ocl::BruteForceMatcher_OCL_base d_matcher(ocl::BruteForceMatcher_OCL_base::L2Dist); @@ -86,9 +88,11 @@ TEST(BruteForceMatcher) d_matcher.matchSingle(d_query, d_train, d_trainIdx, d_distance); WARMUP_OFF; + d_matcher.match(d_query, d_train, d_matches[0]); + TestSystem::instance().setAccurate(AssertEQ(d_matches[0].size(), matches[0].size())); + GPU_ON; d_matcher.matchSingle(d_query, d_train, d_trainIdx, d_distance); - ; GPU_OFF; GPU_FULL_ON; @@ -111,15 +115,16 @@ TEST(BruteForceMatcher) GPU_ON; d_matcher.knnMatchSingle(d_query, d_train, d_trainIdx, d_distance, d_allDist, 2); - ; GPU_OFF; GPU_FULL_ON; d_query.upload(query); d_train.upload(train); - d_matcher.knnMatch(d_query, d_train, matches, 2); + d_matcher.knnMatch(d_query, d_train, d_matches, 2); GPU_FULL_OFF; + TestSystem::instance().setAccurate(AssertEQ(d_matches[0].size(), matches[0].size())); + SUBTEST << size << "; radiusMatch"; float max_distance = 2.0f; @@ -138,13 +143,14 @@ TEST(BruteForceMatcher) GPU_ON; d_matcher.radiusMatchSingle(d_query, d_train, d_trainIdx, d_distance, d_nMatches, max_distance); - ; GPU_OFF; GPU_FULL_ON; d_query.upload(query); d_train.upload(train); - d_matcher.radiusMatch(d_query, d_train, matches, max_distance); + d_matcher.radiusMatch(d_query, d_train, d_matches, max_distance); GPU_FULL_OFF; + + TestSystem::instance().setAccurate(AssertEQ(d_matches[0].size(), matches[0].size())); } } \ No newline at end of file diff --git a/modules/ocl/perf/perf_canny.cpp b/modules/ocl/perf/perf_canny.cpp index 428e036d0c..2acb2f611c 100644 --- a/modules/ocl/perf/perf_canny.cpp +++ b/modules/ocl/perf/perf_canny.cpp @@ -16,6 +16,7 @@ // // @Authors // Fangfang Bai, fangfang@multicorewareinc.com +// Jin Ma, jin@multicorewareinc.com // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: @@ -45,7 +46,7 @@ #include "precomp.hpp" ///////////// Canny //////////////////////// -TEST(Canny) +PERFTEST(Canny) { Mat img = imread(abspath("aloeL.jpg"), CV_LOAD_IMAGE_GRAYSCALE); @@ -70,9 +71,10 @@ TEST(Canny) ocl::Canny(d_img, d_buf, d_edges, 50.0, 100.0); WARMUP_OFF; + TestSystem::instance().setAccurate(ExceptedMatSimilar(edges, d_edges, 2e-2)); + GPU_ON; ocl::Canny(d_img, d_buf, d_edges, 50.0, 100.0); - ; GPU_OFF; GPU_FULL_ON; diff --git a/modules/ocl/perf/perf_color.cpp b/modules/ocl/perf/perf_color.cpp index e32a1839d8..3ebd32ee4a 100644 --- a/modules/ocl/perf/perf_color.cpp +++ b/modules/ocl/perf/perf_color.cpp @@ -16,6 +16,7 @@ // // @Authors // Fangfang Bai, fangfang@multicorewareinc.com +// Jin Ma, jin@multicorewareinc.com // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: @@ -45,7 +46,7 @@ #include "precomp.hpp" ///////////// cvtColor//////////////////////// -TEST(cvtColor) +PERFTEST(cvtColor) { Mat src, dst; ocl::oclMat d_src, d_dst; @@ -72,9 +73,12 @@ TEST(cvtColor) ocl::cvtColor(d_src, d_dst, CV_RGBA2GRAY, 4); WARMUP_OFF; + cv::Mat ocl_mat; + d_dst.download(ocl_mat); + TestSystem::instance().setAccurate(ExceptedMatSimilar(dst, ocl_mat, 1e-5)); + GPU_ON; ocl::cvtColor(d_src, d_dst, CV_RGBA2GRAY, 4); - ; GPU_OFF; GPU_FULL_ON; diff --git a/modules/ocl/perf/perf_columnsum.cpp b/modules/ocl/perf/perf_columnsum.cpp index d2e3b45e53..a07af17793 100644 --- a/modules/ocl/perf/perf_columnsum.cpp +++ b/modules/ocl/perf/perf_columnsum.cpp @@ -16,6 +16,7 @@ // // @Authors // Fangfang Bai, fangfang@multicorewareinc.com +// Jin Ma, jin@multicorewareinc.com // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: @@ -45,7 +46,7 @@ #include "precomp.hpp" ///////////// columnSum//////////////////////// -TEST(columnSum) +PERFTEST(columnSum) { Mat src, dst; ocl::oclMat d_src, d_dst; @@ -58,12 +59,13 @@ TEST(columnSum) CPU_ON; dst.create(src.size(), src.type()); + for (int j = 0; j < src.cols; j++) + dst.at(0, j) = src.at(0, j); for (int i = 1; i < src.rows; ++i) - { - for (int j = 0; j < src.cols; ++j) + {for (int j = 0; j < src.cols; ++j) { - dst.at(i, j) = src.at(i, j) += src.at(i - 1, j); + dst.at(i, j) = dst.at(i - 1 , j) + src.at(i , j); } } @@ -74,9 +76,12 @@ TEST(columnSum) ocl::columnSum(d_src, d_dst); WARMUP_OFF; + cv::Mat ocl_mat; + d_dst.download(ocl_mat); + TestSystem::instance().setAccurate(ExpectedMatNear(dst, ocl_mat, 5e-1)); + GPU_ON; ocl::columnSum(d_src, d_dst); - ; GPU_OFF; GPU_FULL_ON; diff --git a/modules/ocl/perf/perf_fft.cpp b/modules/ocl/perf/perf_fft.cpp index 50be2546ee..49c88821dd 100644 --- a/modules/ocl/perf/perf_fft.cpp +++ b/modules/ocl/perf/perf_fft.cpp @@ -16,6 +16,7 @@ // // @Authors // Fangfang Bai, fangfang@multicorewareinc.com +// Jin Ma, jin@multicorewareinc.com // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: @@ -45,13 +46,13 @@ #include "precomp.hpp" ///////////// dft //////////////////////// -TEST(dft) +PERFTEST(dft) { Mat src, dst; ocl::oclMat d_src, d_dst; - int all_type[] = {CV_32FC1, CV_32FC2}; - std::string type_name[] = {"CV_32FC1", "CV_32FC2"}; + int all_type[] = {CV_32FC2}; + std::string type_name[] = {"CV_32FC2"}; for (int size = Min_Size; size <= Max_Size; size *= Multiple) { @@ -73,9 +74,10 @@ TEST(dft) ocl::dft(d_src, d_dst, Size(size, size)); WARMUP_OFF; + TestSystem::instance().setAccurate(ExpectedMatNear(dst, cv::Mat(d_dst), src.size().area() * 1e-4)); + GPU_ON; ocl::dft(d_src, d_dst, Size(size, size)); - ; GPU_OFF; GPU_FULL_ON; diff --git a/modules/ocl/perf/perf_filters.cpp b/modules/ocl/perf/perf_filters.cpp index e9646c77e2..c1cf19eefc 100644 --- a/modules/ocl/perf/perf_filters.cpp +++ b/modules/ocl/perf/perf_filters.cpp @@ -16,6 +16,7 @@ // // @Authors // Fangfang Bai, fangfang@multicorewareinc.com +// Jin Ma, jin@multicorewareinc.com // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: @@ -45,7 +46,7 @@ #include "precomp.hpp" ///////////// Blur//////////////////////// -TEST(Blur) +PERFTEST(Blur) { Mat src1, dst; ocl::oclMat d_src1, d_dst; @@ -77,9 +78,10 @@ TEST(Blur) ocl::blur(d_src1, d_dst, ksize, Point(-1, -1), bordertype); WARMUP_OFF; + TestSystem::instance().setAccurate(ExpectedMatNear(cv::Mat(d_dst), dst, 1.0)); + GPU_ON; ocl::blur(d_src1, d_dst, ksize, Point(-1, -1), bordertype); - ; GPU_OFF; GPU_FULL_ON; @@ -92,7 +94,7 @@ TEST(Blur) } } ///////////// Laplacian//////////////////////// -TEST(Laplacian) +PERFTEST(Laplacian) { Mat src1, dst; ocl::oclMat d_src1, d_dst; @@ -123,9 +125,10 @@ TEST(Laplacian) ocl::Laplacian(d_src1, d_dst, -1, ksize, 1); WARMUP_OFF; + TestSystem::instance().setAccurate(ExpectedMatNear(cv::Mat(d_dst), dst, 1e-5)); + GPU_ON; ocl::Laplacian(d_src1, d_dst, -1, ksize, 1); - ; GPU_OFF; GPU_FULL_ON; @@ -139,7 +142,7 @@ TEST(Laplacian) } ///////////// Erode //////////////////// -TEST(Erode) +PERFTEST(Erode) { Mat src, dst, ker; ocl::oclMat d_src, d_dst; @@ -168,9 +171,10 @@ TEST(Erode) ocl::erode(d_src, d_dst, ker); WARMUP_OFF; + TestSystem::instance().setAccurate(ExpectedMatNear(cv::Mat(d_dst), dst, 1e-5)); + GPU_ON; ocl::erode(d_src, d_dst, ker); - ; GPU_OFF; GPU_FULL_ON; @@ -184,7 +188,7 @@ TEST(Erode) } ///////////// Sobel //////////////////////// -TEST(Sobel) +PERFTEST(Sobel) { Mat src, dst; ocl::oclMat d_src, d_dst; @@ -214,9 +218,10 @@ TEST(Sobel) ocl::Sobel(d_src, d_dst, -1, dx, dy); WARMUP_OFF; + TestSystem::instance().setAccurate(ExpectedMatNear(cv::Mat(d_dst), dst, 1)); + GPU_ON; ocl::Sobel(d_src, d_dst, -1, dx, dy); - ; GPU_OFF; GPU_FULL_ON; @@ -229,7 +234,7 @@ TEST(Sobel) } } ///////////// Scharr //////////////////////// -TEST(Scharr) +PERFTEST(Scharr) { Mat src, dst; ocl::oclMat d_src, d_dst; @@ -259,9 +264,10 @@ TEST(Scharr) ocl::Scharr(d_src, d_dst, -1, dx, dy); WARMUP_OFF; + TestSystem::instance().setAccurate(ExpectedMatNear(cv::Mat(d_dst), dst, 1)); + GPU_ON; ocl::Scharr(d_src, d_dst, -1, dx, dy); - ; GPU_OFF; GPU_FULL_ON; @@ -275,7 +281,7 @@ TEST(Scharr) } ///////////// GaussianBlur //////////////////////// -TEST(GaussianBlur) +PERFTEST(GaussianBlur) { Mat src, dst; int all_type[] = {CV_8UC1, CV_8UC4, CV_32FC1, CV_32FC4}; @@ -288,6 +294,8 @@ TEST(GaussianBlur) SUBTEST << size << 'x' << size << "; " << type_name[j] ; gen(src, size, size, all_type[j], 0, 256); + dst = src; + dst.setTo(0); GaussianBlur(src, dst, Size(9, 9), 0); @@ -303,9 +311,11 @@ TEST(GaussianBlur) ocl::GaussianBlur(d_src, d_dst, Size(9, 9), 0); WARMUP_OFF; + TestSystem::instance().setAccurate(ExpectedMatNear(cv::Mat(d_dst), dst, 1.0)); + + GPU_ON; ocl::GaussianBlur(d_src, d_dst, Size(9, 9), 0); - ; GPU_OFF; GPU_FULL_ON; @@ -319,7 +329,7 @@ TEST(GaussianBlur) } ///////////// filter2D//////////////////////// -TEST(filter2D) +PERFTEST(filter2D) { Mat src; @@ -339,7 +349,8 @@ TEST(filter2D) Mat kernel; gen(kernel, ksize, ksize, CV_32FC1, 0.0, 1.0); - Mat dst; + Mat dst(src); + dst.setTo(0); cv::filter2D(src, dst, -1, kernel); CPU_ON; @@ -347,15 +358,18 @@ TEST(filter2D) CPU_OFF; ocl::oclMat d_src(src); - ocl::oclMat d_dst; + ocl::oclMat d_dst(d_src); + d_dst.setTo(0); WARMUP_ON; ocl::filter2D(d_src, d_dst, -1, kernel); WARMUP_OFF; + TestSystem::instance().setAccurate(ExpectedMatNear(cv::Mat(d_dst), dst, 1e-5)); + + GPU_ON; ocl::filter2D(d_src, d_dst, -1, kernel); - ; GPU_OFF; GPU_FULL_ON; diff --git a/modules/ocl/perf/perf_gemm.cpp b/modules/ocl/perf/perf_gemm.cpp index 930ecb0464..280a0394ce 100644 --- a/modules/ocl/perf/perf_gemm.cpp +++ b/modules/ocl/perf/perf_gemm.cpp @@ -16,6 +16,7 @@ // // @Authors // Fangfang Bai, fangfang@multicorewareinc.com +// Jin Ma, jin@multicorewareinc.com // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: @@ -45,7 +46,7 @@ #include "precomp.hpp" ///////////// gemm //////////////////////// -TEST(gemm) +PERFTEST(gemm) { Mat src1, src2, src3, dst; ocl::oclMat d_src1, d_src2, d_src3, d_dst; @@ -71,10 +72,10 @@ TEST(gemm) WARMUP_ON; ocl::gemm(d_src1, d_src2, 1.0, d_src3, 1.0, d_dst); WARMUP_OFF; + TestSystem::instance().setAccurate(ExpectedMatNear(cv::Mat(d_dst), dst, src1.cols * src1.rows * 1e-4)); GPU_ON; ocl::gemm(d_src1, d_src2, 1.0, d_src3, 1.0, d_dst); - ; GPU_OFF; GPU_FULL_ON; diff --git a/modules/ocl/perf/perf_haar.cpp b/modules/ocl/perf/perf_haar.cpp index 5a909ace4e..792ead1f09 100644 --- a/modules/ocl/perf/perf_haar.cpp +++ b/modules/ocl/perf/perf_haar.cpp @@ -16,6 +16,7 @@ // // @Authors // Fangfang Bai, fangfang@multicorewareinc.com +// Jin Ma, jin@multicorewareinc.com // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: @@ -82,7 +83,7 @@ public: } } -TEST(Haar) +PERFTEST(Haar) { Mat img = imread(abspath("basketball1.png"), CV_LOAD_IMAGE_GRAYSCALE); @@ -106,6 +107,8 @@ TEST(Haar) 1.1, 2, 0 | CV_HAAR_SCALE_IMAGE, Size(30, 30)); CPU_OFF; + + vector oclfaces; ocl::CascadeClassifier_GPU faceCascade; if (!faceCascade.load(abspath("haarcascade_frontalface_alt.xml"))) @@ -115,24 +118,24 @@ TEST(Haar) ocl::oclMat d_img(img); - faces.clear(); - WARMUP_ON; - faceCascade.detectMultiScale(d_img, faces, + faceCascade.detectMultiScale(d_img, oclfaces, 1.1, 2, 0 | CV_HAAR_SCALE_IMAGE, Size(30, 30)); WARMUP_OFF; + //Testing whether the expected is equal to the actual. + TestSystem::instance().setAccurate(ExpectedEQ::size_type, vector::size_type>(faces.size(), oclfaces.size())); + faces.clear(); GPU_ON; - faceCascade.detectMultiScale(d_img, faces, + faceCascade.detectMultiScale(d_img, oclfaces, 1.1, 2, 0 | CV_HAAR_SCALE_IMAGE, Size(30, 30)); - ; GPU_OFF; GPU_FULL_ON; d_img.upload(img); - faceCascade.detectMultiScale(d_img, faces, + faceCascade.detectMultiScale(d_img, oclfaces, 1.1, 2, 0 | CV_HAAR_SCALE_IMAGE, Size(30, 30)); GPU_FULL_OFF; } \ No newline at end of file diff --git a/modules/ocl/perf/perf_hog.cpp b/modules/ocl/perf/perf_hog.cpp index b74077ff40..c425ef4848 100644 --- a/modules/ocl/perf/perf_hog.cpp +++ b/modules/ocl/perf/perf_hog.cpp @@ -16,6 +16,7 @@ // // @Authors // Fangfang Bai, fangfang@multicorewareinc.com +// Jin Ma, jin@multicorewareinc.com // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: @@ -45,7 +46,13 @@ #include "precomp.hpp" ///////////// HOG//////////////////////// -TEST(HOG) +bool match_rect(cv::Rect r1, cv::Rect r2, int threshold) +{ + return ((abs(r1.x - r2.x) < threshold) && (abs(r1.y - r2.y) < threshold) && + (abs(r1.width - r2.width) < threshold) && (abs(r1.height - r2.height) < threshold)); +} + +PERFTEST(HOG) { Mat src = imread(abspath("road.png"), cv::IMREAD_GRAYSCALE); @@ -58,6 +65,7 @@ TEST(HOG) cv::HOGDescriptor hog; hog.setSVMDetector(hog.getDefaultPeopleDetector()); std::vector found_locations; + std::vector d_found_locations; SUBTEST << 768 << 'x' << 576 << "; road.png"; @@ -73,12 +81,78 @@ TEST(HOG) d_src.upload(src); WARMUP_ON; - ocl_hog.detectMultiScale(d_src, found_locations); + ocl_hog.detectMultiScale(d_src, d_found_locations); WARMUP_OFF; + + // Ground-truth rectangular people window + cv::Rect win1_64x128(231, 190, 72, 144); + cv::Rect win2_64x128(621, 156, 97, 194); + cv::Rect win1_48x96(238, 198, 63, 126); + cv::Rect win2_48x96(619, 161, 92, 185); + cv::Rect win3_48x96(488, 136, 56, 112); + + // Compare whether ground-truth windows are detected and compare the number of windows detected. + std::vector d_comp(4); + std::vector comp(4); + for(int i = 0; i < (int)d_comp.size(); i++) + { + d_comp[i] = 0; + comp[i] = 0; + } + + int threshold = 10; + int val = 32; + d_comp[0] = (int)d_found_locations.size(); + comp[0] = (int)found_locations.size(); + + cv::Size winSize = hog.winSize; + + if (winSize == cv::Size(48, 96)) + { + for(int i = 0; i < (int)d_found_locations.size(); i++) + { + if (match_rect(d_found_locations[i], win1_48x96, threshold)) + d_comp[1] = val; + if (match_rect(d_found_locations[i], win2_48x96, threshold)) + d_comp[2] = val; + if (match_rect(d_found_locations[i], win3_48x96, threshold)) + d_comp[3] = val; + } + for(int i = 0; i < (int)found_locations.size(); i++) + { + if (match_rect(found_locations[i], win1_48x96, threshold)) + comp[1] = val; + if (match_rect(found_locations[i], win2_48x96, threshold)) + comp[2] = val; + if (match_rect(found_locations[i], win3_48x96, threshold)) + comp[3] = val; + } + } + else if (winSize == cv::Size(64, 128)) + { + for(int i = 0; i < (int)d_found_locations.size(); i++) + { + if (match_rect(d_found_locations[i], win1_64x128, threshold)) + d_comp[1] = val; + if (match_rect(d_found_locations[i], win2_64x128, threshold)) + d_comp[2] = val; + } + for(int i = 0; i < (int)found_locations.size(); i++) + { + if (match_rect(found_locations[i], win1_64x128, threshold)) + comp[1] = val; + if (match_rect(found_locations[i], win2_64x128, threshold)) + comp[2] = val; + } + } + + cv::Mat ocl_mat; + ocl_mat = cv::Mat(d_comp); + ocl_mat.convertTo(ocl_mat, cv::Mat(comp).type()); + TestSystem::instance().setAccurate(ExpectedMatNear(ocl_mat, cv::Mat(comp), 3)); GPU_ON; ocl_hog.detectMultiScale(d_src, found_locations); - ; GPU_OFF; GPU_FULL_ON; diff --git a/modules/ocl/perf/perf_imgproc.cpp b/modules/ocl/perf/perf_imgproc.cpp index 756f69556f..980d3be40b 100644 --- a/modules/ocl/perf/perf_imgproc.cpp +++ b/modules/ocl/perf/perf_imgproc.cpp @@ -16,6 +16,7 @@ // // @Authors // Fangfang Bai, fangfang@multicorewareinc.com +// Jin Ma, jin@multicorewareinc.com // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: @@ -45,7 +46,7 @@ #include "precomp.hpp" ///////////// equalizeHist //////////////////////// -TEST(equalizeHist) +PERFTEST(equalizeHist) { Mat src, dst; int all_type[] = {CV_8UC1}; @@ -74,9 +75,11 @@ TEST(equalizeHist) ocl::equalizeHist(d_src, d_dst); WARMUP_OFF; + TestSystem::instance().setAccurate(ExpectedMatNear(dst, cv::Mat(d_dst), 1.1)); + + GPU_ON; ocl::equalizeHist(d_src, d_dst); - ; GPU_OFF; GPU_FULL_ON; @@ -89,7 +92,7 @@ TEST(equalizeHist) } } /////////// CopyMakeBorder ////////////////////// -TEST(CopyMakeBorder) +PERFTEST(CopyMakeBorder) { Mat src, dst; ocl::oclMat d_dst; @@ -119,9 +122,11 @@ TEST(CopyMakeBorder) ocl::copyMakeBorder(d_src, d_dst, 7, 5, 5, 7, bordertype, cv::Scalar(1.0)); WARMUP_OFF; + TestSystem::instance().setAccurate(ExpectedMatNear(dst, cv::Mat(d_dst), 0.0)); + + GPU_ON; ocl::copyMakeBorder(d_src, d_dst, 7, 5, 5, 7, bordertype, cv::Scalar(1.0)); - ; GPU_OFF; GPU_FULL_ON; @@ -134,7 +139,7 @@ TEST(CopyMakeBorder) } } ///////////// cornerMinEigenVal //////////////////////// -TEST(cornerMinEigenVal) +PERFTEST(cornerMinEigenVal) { Mat src, dst; ocl::oclMat d_dst; @@ -165,9 +170,11 @@ TEST(cornerMinEigenVal) ocl::cornerMinEigenVal(d_src, d_dst, blockSize, apertureSize, borderType); WARMUP_OFF; + TestSystem::instance().setAccurate(ExpectedMatNear(dst, cv::Mat(d_dst), 1.0)); + + GPU_ON; ocl::cornerMinEigenVal(d_src, d_dst, blockSize, apertureSize, borderType); - ; GPU_OFF; GPU_FULL_ON; @@ -180,7 +187,7 @@ TEST(cornerMinEigenVal) } } ///////////// cornerHarris //////////////////////// -TEST(cornerHarris) +PERFTEST(cornerHarris) { Mat src, dst; ocl::oclMat d_src, d_dst; @@ -208,9 +215,10 @@ TEST(cornerHarris) ocl::cornerHarris(d_src, d_dst, 5, 7, 0.1, BORDER_REFLECT); WARMUP_OFF; + TestSystem::instance().setAccurate(ExpectedMatNear(dst, cv::Mat(d_dst), 1.0)); + GPU_ON; ocl::cornerHarris(d_src, d_dst, 5, 7, 0.1, BORDER_REFLECT); - ; GPU_OFF; GPU_FULL_ON; @@ -224,7 +232,7 @@ TEST(cornerHarris) } } ///////////// integral //////////////////////// -TEST(integral) +PERFTEST(integral) { Mat src, sum; ocl::oclMat d_src, d_sum, d_buf; @@ -252,9 +260,14 @@ TEST(integral) ocl::integral(d_src, d_sum); WARMUP_OFF; + cv::Mat ocl_mat; + d_sum.download(ocl_mat); + if(sum.type() == ocl_mat.type()) //we won't test accuracy when cpu function overlow + TestSystem::instance().setAccurate(ExpectedMatNear(sum, ocl_mat, 0.0)); + + GPU_ON; ocl::integral(d_src, d_sum); - ; GPU_OFF; GPU_FULL_ON; @@ -267,15 +280,15 @@ TEST(integral) } } ///////////// WarpAffine //////////////////////// -TEST(WarpAffine) +PERFTEST(WarpAffine) { Mat src, dst; ocl::oclMat d_src, d_dst; static const double coeffs[2][3] = { - {cos(3.14 / 6), -sin(3.14 / 6), 100.0}, - {sin(3.14 / 6), cos(3.14 / 6), -100.0} + {cos(CV_PI / 6), -sin(CV_PI / 6), 100.0}, + {sin(CV_PI / 6), cos(CV_PI / 6), -100.0} }; Mat M(2, 3, CV_64F, (void *)coeffs); int interpolation = INTER_NEAREST; @@ -306,9 +319,10 @@ TEST(WarpAffine) ocl::warpAffine(d_src, d_dst, M, size1, interpolation); WARMUP_OFF; + TestSystem::instance().setAccurate(ExpectedMatNear(dst, cv::Mat(d_dst), 1.0)); + GPU_ON; ocl::warpAffine(d_src, d_dst, M, size1, interpolation); - ; GPU_OFF; GPU_FULL_ON; @@ -321,19 +335,19 @@ TEST(WarpAffine) } } ///////////// WarpPerspective //////////////////////// -TEST(WarpPerspective) +PERFTEST(WarpPerspective) { Mat src, dst; ocl::oclMat d_src, d_dst; static const double coeffs[3][3] = { - {cos(3.14 / 6), -sin(3.14 / 6), 100.0}, - {sin(3.14 / 6), cos(3.14 / 6), -100.0}, + {cos(CV_PI / 6), -sin(CV_PI / 6), 100.0}, + {sin(CV_PI / 6), cos(CV_PI / 6), -100.0}, {0.0, 0.0, 1.0} }; Mat M(3, 3, CV_64F, (void *)coeffs); - int interpolation = INTER_NEAREST; + int interpolation = INTER_LINEAR; int all_type[] = {CV_8UC1, CV_8UC4}; std::string type_name[] = {"CV_8UC1", "CV_8UC4"}; @@ -360,9 +374,10 @@ TEST(WarpPerspective) ocl::warpPerspective(d_src, d_dst, M, size1, interpolation); WARMUP_OFF; + TestSystem::instance().setAccurate(ExpectedMatNear(dst, cv::Mat(d_dst), 1.0)); + GPU_ON; ocl::warpPerspective(d_src, d_dst, M, size1, interpolation); - ; GPU_OFF; GPU_FULL_ON; @@ -376,7 +391,7 @@ TEST(WarpPerspective) } ///////////// resize //////////////////////// -TEST(resize) +PERFTEST(resize) { Mat src, dst; ocl::oclMat d_src, d_dst; @@ -405,9 +420,11 @@ TEST(resize) ocl::resize(d_src, d_dst, Size(), 2.0, 2.0); WARMUP_OFF; + TestSystem::instance().setAccurate(ExpectedMatNear(dst, cv::Mat(d_dst), 1.0)); + + GPU_ON; ocl::resize(d_src, d_dst, Size(), 2.0, 2.0); - ; GPU_OFF; GPU_FULL_ON; @@ -439,9 +456,10 @@ TEST(resize) ocl::resize(d_src, d_dst, Size(), 0.5, 0.5); WARMUP_OFF; + TestSystem::instance().setAccurate(ExpectedMatNear(dst, cv::Mat(d_dst), 1.0)); + GPU_ON; ocl::resize(d_src, d_dst, Size(), 0.5, 0.5); - ; GPU_OFF; GPU_FULL_ON; @@ -454,7 +472,7 @@ TEST(resize) } } ///////////// threshold//////////////////////// -TEST(threshold) +PERFTEST(threshold) { Mat src, dst; ocl::oclMat d_src, d_dst; @@ -478,9 +496,11 @@ TEST(threshold) ocl::threshold(d_src, d_dst, 50.0, 0.0, THRESH_BINARY); WARMUP_OFF; + TestSystem::instance().setAccurate(ExpectedMatNear(dst, cv::Mat(d_dst), 1.0)); + + GPU_ON; ocl::threshold(d_src, d_dst, 50.0, 0.0, THRESH_BINARY); - ; GPU_OFF; GPU_FULL_ON; @@ -509,9 +529,10 @@ TEST(threshold) ocl::threshold(d_src, d_dst, 50.0, 0.0, THRESH_TRUNC); WARMUP_OFF; + TestSystem::instance().setAccurate(ExpectedMatNear(dst, cv::Mat(d_dst), 1.0)); + GPU_ON; ocl::threshold(d_src, d_dst, 50.0, 0.0, THRESH_TRUNC); - ; GPU_OFF; GPU_FULL_ON; @@ -522,9 +543,189 @@ TEST(threshold) } } ///////////// meanShiftFiltering//////////////////////// -TEST(meanShiftFiltering) +COOR do_meanShift(int x0, int y0, uchar *sptr, uchar *dptr, int sstep, cv::Size size, int sp, int sr, int maxIter, float eps, int *tab) { - int sp = 10, sr = 10; + + int isr2 = sr * sr; + int c0, c1, c2, c3; + int iter; + uchar *ptr = NULL; + uchar *pstart = NULL; + int revx = 0, revy = 0; + c0 = sptr[0]; + c1 = sptr[1]; + c2 = sptr[2]; + c3 = sptr[3]; + // iterate meanshift procedure + for(iter = 0; iter < maxIter; iter++ ) + { + int count = 0; + int s0 = 0, s1 = 0, s2 = 0, sx = 0, sy = 0; + + //mean shift: process pixels in window (p-sigmaSp)x(p+sigmaSp) + int minx = x0 - sp; + int miny = y0 - sp; + int maxx = x0 + sp; + int maxy = y0 + sp; + + //deal with the image boundary + if(minx < 0) minx = 0; + if(miny < 0) miny = 0; + if(maxx >= size.width) maxx = size.width - 1; + if(maxy >= size.height) maxy = size.height - 1; + if(iter == 0) + { + pstart = sptr; + } + else + { + pstart = pstart + revy * sstep + (revx << 2); //point to the new position + } + ptr = pstart; + ptr = ptr + (miny - y0) * sstep + ((minx - x0) << 2); //point to the start in the row + + for( int y = miny; y <= maxy; y++, ptr += sstep - ((maxx - minx + 1) << 2)) + { + int rowCount = 0; + int x = minx; +#if CV_ENABLE_UNROLLED + for( ; x + 4 <= maxx; x += 4, ptr += 16) + { + int t0, t1, t2; + t0 = ptr[0], t1 = ptr[1], t2 = ptr[2]; + if(tab[t0 - c0 + 255] + tab[t1 - c1 + 255] + tab[t2 - c2 + 255] <= isr2) + { + s0 += t0; + s1 += t1; + s2 += t2; + sx += x; + rowCount++; + } + t0 = ptr[4], t1 = ptr[5], t2 = ptr[6]; + if(tab[t0 - c0 + 255] + tab[t1 - c1 + 255] + tab[t2 - c2 + 255] <= isr2) + { + s0 += t0; + s1 += t1; + s2 += t2; + sx += x + 1; + rowCount++; + } + t0 = ptr[8], t1 = ptr[9], t2 = ptr[10]; + if(tab[t0 - c0 + 255] + tab[t1 - c1 + 255] + tab[t2 - c2 + 255] <= isr2) + { + s0 += t0; + s1 += t1; + s2 += t2; + sx += x + 2; + rowCount++; + } + t0 = ptr[12], t1 = ptr[13], t2 = ptr[14]; + if(tab[t0 - c0 + 255] + tab[t1 - c1 + 255] + tab[t2 - c2 + 255] <= isr2) + { + s0 += t0; + s1 += t1; + s2 += t2; + sx += x + 3; + rowCount++; + } + } +#endif + for(; x <= maxx; x++, ptr += 4) + { + int t0 = ptr[0], t1 = ptr[1], t2 = ptr[2]; + if(tab[t0 - c0 + 255] + tab[t1 - c1 + 255] + tab[t2 - c2 + 255] <= isr2) + { + s0 += t0; + s1 += t1; + s2 += t2; + sx += x; + rowCount++; + } + } + if(rowCount == 0) + continue; + count += rowCount; + sy += y * rowCount; + } + + if( count == 0 ) + break; + + int x1 = sx / count; + int y1 = sy / count; + s0 = s0 / count; + s1 = s1 / count; + s2 = s2 / count; + + bool stopFlag = (x0 == x1 && y0 == y1) || (abs(x1 - x0) + abs(y1 - y0) + + tab[s0 - c0 + 255] + tab[s1 - c1 + 255] + tab[s2 - c2 + 255] <= eps); + + //revise the pointer corresponding to the new (y0,x0) + revx = x1 - x0; + revy = y1 - y0; + + x0 = x1; + y0 = y1; + c0 = s0; + c1 = s1; + c2 = s2; + + if( stopFlag ) + break; + } //for iter + + dptr[0] = (uchar)c0; + dptr[1] = (uchar)c1; + dptr[2] = (uchar)c2; + dptr[3] = (uchar)c3; + + COOR coor; + coor.x = static_cast(x0); + coor.y = static_cast(y0); + return coor; +} +void meanShiftFiltering_(const Mat &src_roi, Mat &dst_roi, int sp, int sr, cv::TermCriteria crit); +void meanShiftFiltering_(const Mat &src_roi, Mat &dst_roi, int sp, int sr, cv::TermCriteria crit) +{ + if( src_roi.empty() ) + CV_Error( CV_StsBadArg, "The input image is empty" ); + + if( src_roi.depth() != CV_8U || src_roi.channels() != 4 ) + CV_Error( CV_StsUnsupportedFormat, "Only 8-bit, 4-channel images are supported" ); + + CV_Assert( (src_roi.cols == dst_roi.cols) && (src_roi.rows == dst_roi.rows) ); + CV_Assert( !(dst_roi.step & 0x3) ); + + if( !(crit.type & cv::TermCriteria::MAX_ITER) ) + crit.maxCount = 5; + int maxIter = std::min(std::max(crit.maxCount, 1), 100); + float eps; + if( !(crit.type & cv::TermCriteria::EPS) ) + eps = 1.f; + eps = (float)std::max(crit.epsilon, 0.0); + + int tab[512]; + for(int i = 0; i < 512; i++) + tab[i] = (i - 255) * (i - 255); + uchar *sptr = src_roi.data; + uchar *dptr = dst_roi.data; + int sstep = (int)src_roi.step; + int dstep = (int)dst_roi.step; + cv::Size size = src_roi.size(); + + for(int i = 0; i < size.height; i++, sptr += sstep - (size.width << 2), + dptr += dstep - (size.width << 2)) + { + for(int j = 0; j < size.width; j++, sptr += 4, dptr += 4) + { + do_meanShift(j, i, sptr, dptr, sstep, size, sp, sr, maxIter, eps, tab); + } + } +} + +PERFTEST(meanShiftFiltering) +{ + int sp = 5, sr = 6; Mat src, dst; ocl::oclMat d_src, d_dst; @@ -533,25 +734,32 @@ TEST(meanShiftFiltering) { SUBTEST << size << 'x' << size << "; 8UC3 vs 8UC4"; - gen(src, size, size, CV_8UC3, Scalar::all(0), Scalar::all(256)); + gen(src, size, size, CV_8UC4, Scalar::all(0), Scalar::all(256)); + //gen(dst, size, size, CV_8UC4, Scalar::all(0), Scalar::all(256)); + dst = src; + dst.setTo(0); - pyrMeanShiftFiltering(src, dst, sp, sr); + cv::TermCriteria crit(cv::TermCriteria::COUNT + cv::TermCriteria::EPS, 5, 1); + + meanShiftFiltering_(src, dst, sp, sr, crit); CPU_ON; - pyrMeanShiftFiltering(src, dst, sp, sr); + meanShiftFiltering_(src, dst, sp, sr, crit); CPU_OFF; - gen(src, size, size, CV_8UC4, Scalar::all(0), Scalar::all(256)); - d_src.upload(src); WARMUP_ON; - ocl::meanShiftFiltering(d_src, d_dst, sp, sr); + ocl::meanShiftFiltering(d_src, d_dst, sp, sr, crit); WARMUP_OFF; + cv::Mat ocl_mat; + d_dst.download(ocl_mat); + + TestSystem::instance().setAccurate(ExpectedMatNear(dst, ocl_mat, 0.0)); + GPU_ON; ocl::meanShiftFiltering(d_src, d_dst, sp, sr); - ; GPU_OFF; GPU_FULL_ON; @@ -562,6 +770,7 @@ TEST(meanShiftFiltering) } } ///////////// meanShiftProc//////////////////////// +#if 0 COOR do_meanShift(int x0, int y0, uchar *sptr, uchar *dptr, int sstep, cv::Size size, int sp, int sr, int maxIter, float eps, int *tab) { @@ -740,6 +949,7 @@ COOR do_meanShift(int x0, int y0, uchar *sptr, uchar *dptr, int sstep, cv::Size coor.y = static_cast(y0); return coor; } +#endif void meanShiftProc_(const Mat &src_roi, Mat &dst_roi, Mat &dstCoor_roi, int sp, int sr, cv::TermCriteria crit) { @@ -798,7 +1008,7 @@ void meanShiftProc_(const Mat &src_roi, Mat &dst_roi, Mat &dstCoor_roi, int sp, } } -TEST(meanShiftProc) +PERFTEST(meanShiftProc) { Mat src, dst, dstCoor_roi; ocl::oclMat d_src, d_dst, d_dstCoor_roi; @@ -825,9 +1035,11 @@ TEST(meanShiftProc) ocl::meanShiftProc(d_src, d_dst, d_dstCoor_roi, 5, 6, crit); WARMUP_OFF; + TestSystem::instance().setAccurate(ExpectedMatNear(dstCoor_roi, cv::Mat(d_dstCoor_roi), 0.0) + &&ExpectedMatNear(dst, cv::Mat(d_dst), 0.0)); + GPU_ON; ocl::meanShiftProc(d_src, d_dst, d_dstCoor_roi, 5, 6, crit); - ; GPU_OFF; GPU_FULL_ON; @@ -841,7 +1053,7 @@ TEST(meanShiftProc) } ///////////// remap//////////////////////// -TEST(remap) +PERFTEST(remap) { Mat src, dst, xmap, ymap; ocl::oclMat d_src, d_dst, d_xmap, d_ymap; @@ -892,9 +1104,14 @@ TEST(remap) ocl::remap(d_src, d_dst, d_xmap, d_ymap, interpolation, borderMode); WARMUP_OFF; + if(interpolation == 0) + TestSystem::instance().setAccurate(ExpectedMatNear(dst, cv::Mat(d_dst), 1.0)); + else + TestSystem::instance().setAccurate(ExpectedMatNear(dst, cv::Mat(d_dst), 2.0)); + + GPU_ON; ocl::remap(d_src, d_dst, d_xmap, d_ymap, interpolation, borderMode); - ; GPU_OFF; GPU_FULL_ON; diff --git a/modules/ocl/perf/perf_match_template.cpp b/modules/ocl/perf/perf_match_template.cpp index 2828efe01a..f9f0f6a4d6 100644 --- a/modules/ocl/perf/perf_match_template.cpp +++ b/modules/ocl/perf/perf_match_template.cpp @@ -16,6 +16,7 @@ // // @Authors // Fangfang Bai, fangfang@multicorewareinc.com +// Jin Ma, jin@multicorewareinc.com // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: @@ -52,7 +53,7 @@ // ocl::oclMat d_src(src), d_templ(templ), d_dst; // ocl::matchTemplate(d_src, d_templ, d_dst, CV_TM_CCORR); //} -TEST(matchTemplate) +PERFTEST(matchTemplate) { //InitMatchTemplate(); @@ -89,9 +90,10 @@ TEST(matchTemplate) ocl::matchTemplate(d_src, d_templ, d_dst, CV_TM_CCORR); WARMUP_OFF; + TestSystem::instance().setAccurate(ExpectedMatNear(dst, cv::Mat(d_dst), templ.rows * templ.cols * 1e-1)); + GPU_ON; ocl::matchTemplate(d_src, d_templ, d_dst, CV_TM_CCORR); - ; GPU_OFF; GPU_FULL_ON; @@ -129,9 +131,10 @@ TEST(matchTemplate) ocl::matchTemplate(d_src, d_templ, d_dst, CV_TM_CCORR_NORMED); WARMUP_OFF; + TestSystem::instance().setAccurate(ExpectedMatNear(dst, cv::Mat(d_dst), templ.rows * templ.cols * 1e-1)); + GPU_ON; ocl::matchTemplate(d_src, d_templ, d_dst, CV_TM_CCORR_NORMED); - ; GPU_OFF; GPU_FULL_ON; diff --git a/modules/ocl/perf/perf_matrix_operation.cpp b/modules/ocl/perf/perf_matrix_operation.cpp index 495b2b82cf..4b364b01b6 100644 --- a/modules/ocl/perf/perf_matrix_operation.cpp +++ b/modules/ocl/perf/perf_matrix_operation.cpp @@ -16,6 +16,7 @@ // // @Authors // Fangfang Bai, fangfang@multicorewareinc.com +// Jin Ma, jin@multicorewareinc.com // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: @@ -45,7 +46,7 @@ #include "precomp.hpp" ///////////// ConvertTo//////////////////////// -TEST(ConvertTo) +PERFTEST(ConvertTo) { Mat src, dst; ocl::oclMat d_src, d_dst; @@ -76,9 +77,11 @@ TEST(ConvertTo) d_src.convertTo(d_dst, CV_32FC1); WARMUP_OFF; + TestSystem::instance().setAccurate(ExpectedMatNear(dst, cv::Mat(d_dst), 0.0)); + + GPU_ON; d_src.convertTo(d_dst, CV_32FC1); - ; GPU_OFF; GPU_FULL_ON; @@ -91,7 +94,7 @@ TEST(ConvertTo) } } ///////////// copyTo//////////////////////// -TEST(copyTo) +PERFTEST(copyTo) { Mat src, dst; ocl::oclMat d_src, d_dst; @@ -122,9 +125,11 @@ TEST(copyTo) d_src.copyTo(d_dst); WARMUP_OFF; + TestSystem::instance().setAccurate(ExpectedMatNear(dst, cv::Mat(d_dst), 0.0)); + + GPU_ON; d_src.copyTo(d_dst); - ; GPU_OFF; GPU_FULL_ON; @@ -137,7 +142,7 @@ TEST(copyTo) } } ///////////// setTo//////////////////////// -TEST(setTo) +PERFTEST(setTo) { Mat src, dst; Scalar val(1, 2, 3, 4); @@ -166,9 +171,11 @@ TEST(setTo) d_src.setTo(val); WARMUP_OFF; + TestSystem::instance().setAccurate(ExpectedMatNear(src, cv::Mat(d_src), 1.0)); + + GPU_ON; d_src.setTo(val); - ; GPU_OFF; GPU_FULL_ON; diff --git a/modules/ocl/perf/perf_norm.cpp b/modules/ocl/perf/perf_norm.cpp index 8b7118a6ea..78ff001248 100644 --- a/modules/ocl/perf/perf_norm.cpp +++ b/modules/ocl/perf/perf_norm.cpp @@ -16,6 +16,7 @@ // // @Authors // Fangfang Bai, fangfang@multicorewareinc.com +// Jin Ma, jin@multicorewareinc.com // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: @@ -45,7 +46,7 @@ #include "precomp.hpp" ///////////// norm//////////////////////// -TEST(norm) +PERFTEST(norm) { Mat src, buf; ocl::oclMat d_src, d_buf; @@ -71,9 +72,10 @@ TEST(norm) ocl::norm(d_src, d_buf, NORM_INF); WARMUP_OFF; + TestSystem::instance().setAccurate(ExpectedMatNear(src, cv::Mat(d_buf), .5)); + GPU_ON; ocl::norm(d_src, d_buf, NORM_INF); - ; GPU_OFF; GPU_FULL_ON; diff --git a/modules/ocl/perf/perf_pyrdown.cpp b/modules/ocl/perf/perf_pyrdown.cpp index 1d1d2dec11..36d2e7ec70 100644 --- a/modules/ocl/perf/perf_pyrdown.cpp +++ b/modules/ocl/perf/perf_pyrdown.cpp @@ -16,6 +16,7 @@ // // @Authors // Fangfang Bai, fangfang@multicorewareinc.com +// Jin Ma, jin@multicorewareinc.com // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: @@ -45,7 +46,7 @@ #include "precomp.hpp" ///////////// pyrDown ////////////////////// -TEST(pyrDown) +PERFTEST(pyrDown) { Mat src, dst; int all_type[] = {CV_8UC1, CV_8UC4}; @@ -72,9 +73,11 @@ TEST(pyrDown) ocl::pyrDown(d_src, d_dst); WARMUP_OFF; + TestSystem::instance().setAccurate(ExpectedMatNear(dst, cv::Mat(d_dst), dst.depth() == CV_32F ? 1e-4f : 1.0f)); + + GPU_ON; ocl::pyrDown(d_src, d_dst); - ; GPU_OFF; GPU_FULL_ON; diff --git a/modules/ocl/perf/perf_pyrlk.cpp b/modules/ocl/perf/perf_pyrlk.cpp index f7fc22b9d0..32bf145b9f 100644 --- a/modules/ocl/perf/perf_pyrlk.cpp +++ b/modules/ocl/perf/perf_pyrlk.cpp @@ -16,6 +16,7 @@ // // @Authors // Fangfang Bai, fangfang@multicorewareinc.com +// Jin Ma, jin@multicorewareinc.com // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: @@ -45,7 +46,7 @@ #include "precomp.hpp" ///////////// PyrLKOpticalFlow //////////////////////// -TEST(PyrLKOpticalFlow) +PERFTEST(PyrLKOpticalFlow) { std::string images1[] = {"rubberwhale1.png", "aloeL.jpg"}; std::string images2[] = {"rubberwhale2.png", "aloeR.jpg"}; @@ -115,9 +116,14 @@ TEST(PyrLKOpticalFlow) d_pyrLK.sparse(d_frame0, d_frame1, d_pts, d_nextPts, d_status, &d_err); WARMUP_OFF; + std::vector ocl_nextPts(d_nextPts.cols); + std::vector ocl_status(d_status.cols); + TestSystem::instance().setAccurate(AssertEQ(nextPts.size(), ocl_nextPts.size())); + TestSystem::instance().setAccurate(AssertEQ(status.size(), ocl_status.size())); + + GPU_ON; d_pyrLK.sparse(d_frame0, d_frame1, d_pts, d_nextPts, d_status, &d_err); - ; GPU_OFF; GPU_FULL_ON; diff --git a/modules/ocl/perf/perf_pyrup.cpp b/modules/ocl/perf/perf_pyrup.cpp index d3b3003a2e..3b2022e096 100644 --- a/modules/ocl/perf/perf_pyrup.cpp +++ b/modules/ocl/perf/perf_pyrup.cpp @@ -16,6 +16,7 @@ // // @Authors // Fangfang Bai, fangfang@multicorewareinc.com +// Jin Ma, jin@multicorewareinc.com // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: @@ -45,7 +46,7 @@ #include "precomp.hpp" ///////////// pyrUp //////////////////////// -TEST(pyrUp) +PERFTEST(pyrUp) { Mat src, dst; int all_type[] = {CV_8UC1, CV_8UC4}; @@ -72,9 +73,10 @@ TEST(pyrUp) ocl::pyrUp(d_src, d_dst); WARMUP_OFF; + TestSystem::instance().setAccurate(ExpectedMatNear(dst, cv::Mat(d_dst), (src.depth() == CV_32F ? 1e-4f : 1.0))); + GPU_ON; ocl::pyrUp(d_src, d_dst); - ; GPU_OFF; GPU_FULL_ON; diff --git a/modules/ocl/perf/perf_split_merge.cpp b/modules/ocl/perf/perf_split_merge.cpp index 48ff1ff15a..629fbfcd92 100644 --- a/modules/ocl/perf/perf_split_merge.cpp +++ b/modules/ocl/perf/perf_split_merge.cpp @@ -16,6 +16,7 @@ // // @Authors // Fangfang Bai, fangfang@multicorewareinc.com +// Jin Ma, jin@multicorewareinc.com // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: @@ -45,7 +46,7 @@ #include "precomp.hpp" ///////////// Merge//////////////////////// -TEST(Merge) +PERFTEST(Merge) { Mat dst; ocl::oclMat d_dst; @@ -84,9 +85,10 @@ TEST(Merge) ocl::merge(d_src, d_dst); WARMUP_OFF; + TestSystem::instance().setAccurate(ExpectedMatNear(cv::Mat(dst), cv::Mat(d_dst), 0.0)); + GPU_ON; ocl::merge(d_src, d_dst); - ; GPU_OFF; GPU_FULL_ON; @@ -105,7 +107,7 @@ TEST(Merge) } ///////////// Split//////////////////////// -TEST(Split) +PERFTEST(Split) { //int channels = 4; int all_type[] = {CV_8UC1, CV_32FC1}; @@ -135,9 +137,23 @@ TEST(Split) ocl::split(d_src, d_dst); WARMUP_OFF; + if(d_dst.size() == dst.size()) + { + TestSystem::instance().setAccurate(1); + for(int i = 0; i < dst.size(); i++) + { + if(ExpectedMatNear(dst[i], cv::Mat(d_dst[i]), 0.0) == 0) + { + TestSystem::instance().setAccurate(0); + break; + } + } + }else + TestSystem::instance().setAccurate(0); + + GPU_ON; ocl::split(d_src, d_dst); - ; GPU_OFF; GPU_FULL_ON; diff --git a/modules/ocl/perf/precomp.cpp b/modules/ocl/perf/precomp.cpp index e35a071450..476c73eb18 100644 --- a/modules/ocl/perf/precomp.cpp +++ b/modules/ocl/perf/precomp.cpp @@ -41,6 +41,10 @@ //M*/ #include "precomp.hpp" +#if GTEST_OS_WINDOWS +#define NOMINMAX +# include +#endif // This program test most of the functions in ocl module and generate data metrix of x-factor in .csv files // All images needed in this test are in samples/gpu folder. @@ -110,6 +114,7 @@ void TestSystem::finishCurrentSubtest() return; } + int is_accurate = is_accurate_; double cpu_time = cpu_elapsed_ / getTickFrequency() * 1000.0; double gpu_time = gpu_elapsed_ / getTickFrequency() * 1000.0; double gpu_full_time = gpu_full_elapsed_ / getTickFrequency() * 1000.0; @@ -166,8 +171,8 @@ void TestSystem::finishCurrentSubtest() deviation = std::sqrt(sum / gpu_times_.size()); } - printMetrics(cpu_time, gpu_time, gpu_full_time, speedup, fullspeedup); - writeMetrics(cpu_time, gpu_time, gpu_full_time, speedup, fullspeedup, gpu_min, gpu_max, deviation); + printMetrics(is_accurate, cpu_time, gpu_time, gpu_full_time, speedup, fullspeedup); + writeMetrics(is_accurate, cpu_time, gpu_time, gpu_full_time, speedup, fullspeedup, gpu_min, gpu_max, deviation); num_subtests_called_++; resetCurrentSubtest(); @@ -184,10 +189,19 @@ double TestSystem::meanTime(const vector &samples) void TestSystem::printHeading() { cout << endl; - cout << setiosflags(ios_base::left); - cout << TAB << setw(10) << "CPU, ms" << setw(10) << "GPU, ms" - << setw(14) << "SPEEDUP" << setw(14) << "GPUTOTAL, ms" << setw(14) << "TOTALSPEEDUP" - << "DESCRIPTION\n"; + cout<< setiosflags(ios_base::left); + +#if 0 + cout< 0&&n <= cols * rows); + assert(type == CV_8UC1||type == CV_8UC3||type == CV_8UC4 + ||type == CV_32FC1||type == CV_32FC3||type == CV_32FC4); + RNG rng; + //generate random position without duplication + std::vector pos; + for(int i = 0; i < cols * rows; i++) + { + pos.push_back(i); + } + + for(int i = 0; i < cols * rows; i++) + { + int temp = i + rng.uniform(0, cols * rows - 1 - i); + int temp1 = pos[temp]; + pos[temp]= pos[i]; + pos[i] = temp1; + } + + std::vector selected_pos; + for(int i = 0; i < n; i++) + { + selected_pos.push_back(pos[i]); + } + + pos.clear(); + //end of generating random y without duplication + + if(type == CV_8UC1) + { + typedef struct coorStruct_ + { + int x; + int y; + uchar xy; + }coorStruct; + + coorStruct coor_struct; + + std::vector coor; + + for(int i = 0; i < n; i++) + { + coor_struct.x = -1; + coor_struct.y = -1; + coor_struct.xy = (uchar)rng.uniform(low, high); + coor.push_back(coor_struct); + } + + for(int i = 0; i < n; i++) + { + coor[i].y = selected_pos[i]/cols; + coor[i].x = selected_pos[i]%cols; + } + selected_pos.clear(); + + mat.create(rows, cols, type); + mat.setTo(0); + + for(int i = 0; i < n; i++) + { + mat.at(coor[i].y, coor[i].x) = coor[i].xy; + } + } + + if(type == CV_8UC4 || type == CV_8UC3) + { + mat.create(rows, cols, type); + mat.setTo(0); + + typedef struct Coor + { + int x; + int y; + + uchar r; + uchar g; + uchar b; + uchar alpha; + }coor; + + std::vector coor_vect; + + coor xy_coor; + + for(int i = 0; i < n; i++) + { + xy_coor.r = (uchar)rng.uniform(low, high); + xy_coor.g = (uchar)rng.uniform(low, high); + xy_coor.b = (uchar)rng.uniform(low, high); + if(type == CV_8UC4) + xy_coor.alpha = (uchar)rng.uniform(low, high); + + coor_vect.push_back(xy_coor); + } + + for(int i = 0; i < n; i++) + { + coor_vect[i].y = selected_pos[i]/((int)mat.step1()/mat.elemSize()); + coor_vect[i].x = selected_pos[i]%((int)mat.step1()/mat.elemSize()); + //printf("coor_vect[%d] = (%d, %d)\n", i, coor_vect[i].y, coor_vect[i].x); + } + + if(type == CV_8UC4) + { + for(int i = 0; i < n; i++) + { + mat.at(coor_vect[i].y, 4 * coor_vect[i].x) = coor_vect[i].r; + mat.at(coor_vect[i].y, 4 * coor_vect[i].x + 1) = coor_vect[i].g; + mat.at(coor_vect[i].y, 4 * coor_vect[i].x + 2) = coor_vect[i].b; + mat.at(coor_vect[i].y, 4 * coor_vect[i].x + 3) = coor_vect[i].alpha; + } + }else if(type == CV_8UC3) + { + for(int i = 0; i < n; i++) + { + mat.at(coor_vect[i].y, 3 * coor_vect[i].x) = coor_vect[i].r; + mat.at(coor_vect[i].y, 3 * coor_vect[i].x + 1) = coor_vect[i].g; + mat.at(coor_vect[i].y, 3 * coor_vect[i].x + 2) = coor_vect[i].b; + } + } + } +} +#endif string abspath(const string &relpath) { @@ -352,11 +605,57 @@ string abspath(const string &relpath) int CV_CDECL cvErrorCallback(int /*status*/, const char * /*func_name*/, - const char *err_msg, const char * /*file_name*/, - int /*line*/, void * /*userdata*/) + const char *err_msg, const char * /*file_name*/, + int /*line*/, void * /*userdata*/) { TestSystem::instance().printError(err_msg); return 0; } +double checkNorm(const Mat &m) +{ + return norm(m, NORM_INF); +} + +double checkNorm(const Mat &m1, const Mat &m2) +{ + return norm(m1, m2, NORM_INF); +} + +double checkSimilarity(const Mat &m1, const Mat &m2) +{ + Mat diff; + matchTemplate(m1, m2, diff, CV_TM_CCORR_NORMED); + return std::abs(diff.at(0, 0) - 1.f); +} + + +int ExpectedMatNear(cv::Mat dst, cv::Mat cpu_dst, double eps) +{ + assert(dst.type() == cpu_dst.type()); + assert(dst.size() == cpu_dst.size()); + if(checkNorm(cv::Mat(dst), cv::Mat(cpu_dst)) < eps ||checkNorm(cv::Mat(dst), cv::Mat(cpu_dst)) == eps) + return 1; + return 0; +} + +int ExceptDoubleNear(double val1, double val2, double abs_error) +{ + const double diff = fabs(val1 - val2); + if (diff <= abs_error) + return 1; + + return 0; +} + +int ExceptedMatSimilar(cv::Mat dst, cv::Mat cpu_dst, double eps) +{ + assert(dst.type() == cpu_dst.type()); + assert(dst.size() == cpu_dst.size()); + if(checkSimilarity(cv::Mat(cpu_dst), cv::Mat(dst)) <= eps) + return 1; + return 0; +} + + diff --git a/modules/ocl/perf/precomp.hpp b/modules/ocl/perf/precomp.hpp index c2cf1238ef..b025703cb2 100644 --- a/modules/ocl/perf/precomp.hpp +++ b/modules/ocl/perf/precomp.hpp @@ -50,10 +50,15 @@ #include "opencv2/core/core.hpp" #include "opencv2/imgproc/imgproc.hpp" #include "opencv2/highgui/highgui.hpp" +#include "opencv2/calib3d/calib3d.hpp" #include "opencv2/video/video.hpp" #include "opencv2/objdetect/objdetect.hpp" #include "opencv2/features2d/features2d.hpp" #include "opencv2/ocl/ocl.hpp" +#include "opencv2/ts/ts.hpp" +#include "opencv2/ts/ts_perf.hpp" +#include "opencv2/ts/ts_gtest.h" + #define Min_Size 1000 #define Max_Size 4000 @@ -64,6 +69,8 @@ using namespace std; using namespace cv; void gen(Mat &mat, int rows, int cols, int type, Scalar low, Scalar high); +void gen(Mat &mat, int rows, int cols, int type, int low, int high, int n); + string abspath(const string &relpath); int CV_CDECL cvErrorCallback(int, const char *, const char *, const char *, int, void *); typedef struct @@ -76,6 +83,50 @@ COOR do_meanShift(int x0, int y0, uchar *sptr, uchar *dptr, int sstep, void meanShiftProc_(const Mat &src_roi, Mat &dst_roi, Mat &dstCoor_roi, int sp, int sr, cv::TermCriteria crit); + +template +int ExpectedEQ(T1 expected, T2 actual) +{ + if(expected == actual) + return 1; + + return 0; +} + +template +int EeceptDoubleEQ(T1 expected, T1 actual) +{ + testing::internal::Double lhs(expected); + testing::internal::Double rhs(actual); + + if (lhs.AlmostEquals(rhs)) + { + return 1; + } + + return 0; +} + +template +int AssertEQ(T expected, T actual) +{ + if(expected == actual) + { + return 1; + } + return 0; +} + +int ExceptDoubleNear(double val1, double val2, double abs_error); +bool match_rect(cv::Rect r1, cv::Rect r2, int threshold); + +double checkNorm(const cv::Mat &m); +double checkNorm(const cv::Mat &m1, const cv::Mat &m2); +double checkSimilarity(const cv::Mat &m1, const cv::Mat &m2); + +int ExpectedMatNear(cv::Mat dst, cv::Mat cpu_dst, double eps); +int ExceptedMatSimilar(cv::Mat dst, cv::Mat cpu_dst, double eps); + class Runnable { public: @@ -171,6 +222,16 @@ public: return cur_iter_idx_ >= cpu_num_iters_; } + int get_cur_iter_idx() + { + return cur_iter_idx_; + } + + int get_cpu_num_iters() + { + return cpu_num_iters_; + } + bool warmupStop() { return cur_warmup_idx_++ >= gpu_warmup_iters_; @@ -252,6 +313,16 @@ public: itname_changed_ = true; } + void setAccurate(int is_accurate = -1) + { + is_accurate_ = is_accurate; + } + + std::stringstream &getCurSubtestDescription() + { + return cur_subtest_description_; + } + private: TestSystem(): cur_subtest_is_empty_(true), cpu_elapsed_(0), @@ -261,7 +332,8 @@ private: speedup_full_faster_count_(0), speedup_full_slower_count_(0), speedup_full_equal_count_(0), is_list_mode_(false), num_iters_(10), cpu_num_iters_(2), gpu_warmup_iters_(1), cur_iter_idx_(0), cur_warmup_idx_(0), - record_(0), recordname_("performance"), itname_changed_(true) + record_(0), recordname_("performance"), itname_changed_(true), + is_accurate_(-1) { cpu_times_.reserve(num_iters_); gpu_times_.reserve(num_iters_); @@ -277,20 +349,22 @@ private: cur_subtest_description_.str(""); cur_subtest_is_empty_ = true; cur_iter_idx_ = 0; + cur_warmup_idx_ = 0; cpu_times_.clear(); gpu_times_.clear(); gpu_full_times_.clear(); + is_accurate_ = -1; } double meanTime(const std::vector &samples); void printHeading(); void printSummary(); - void printMetrics(double cpu_time, double gpu_time = 0.0f, double gpu_full_time = 0.0f, double speedup = 0.0f, double fullspeedup = 0.0f); + void printMetrics(int is_accurate, double cpu_time, double gpu_time = 0.0f, double gpu_full_time = 0.0f, double speedup = 0.0f, double fullspeedup = 0.0f); void writeHeading(); void writeSummary(); - void writeMetrics(double cpu_time, double gpu_time = 0.0f, double gpu_full_time = 0.0f, + void writeMetrics(int is_accurate, double cpu_time, double gpu_time = 0.0f, double gpu_full_time = 0.0f, double speedup = 0.0f, double fullspeedup = 0.0f, double gpu_min = 0.0f, double gpu_max = 0.0f, double std_dev = 0.0f); @@ -340,6 +414,8 @@ private: std::string recordname_; std::string itname_; bool itname_changed_; + + int is_accurate_; }; @@ -353,7 +429,7 @@ struct name##_init: Runnable { \ void name##_init::run() -#define TEST(name) \ +#define PERFTEST(name) \ struct name##_test: Runnable { \ name##_test(): Runnable(#name) { \ TestSystem::instance().addTest(this); \ @@ -375,7 +451,7 @@ struct name##_test: Runnable { \ while (!TestSystem::instance().stop()) { \ TestSystem::instance().gpuOn() #define GPU_OFF \ - ocl::finish(); \ + ocl::finish();\ TestSystem::instance().gpuOff(); \ } TestSystem::instance().gpuComplete() @@ -389,5 +465,5 @@ struct name##_test: Runnable { \ #define WARMUP_ON \ while (!TestSystem::instance().warmupStop()) { #define WARMUP_OFF \ - ocl::finish(); \ + ocl::finish();\ } TestSystem::instance().warmupComplete() From 03c55db4fb1e58abfbc381f5f193d6153e33a275 Mon Sep 17 00:00:00 2001 From: yao Date: Fri, 17 May 2013 13:19:09 +0800 Subject: [PATCH 011/178] fix the waring in gemm test --- modules/ocl/test/test_gemm.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ocl/test/test_gemm.cpp b/modules/ocl/test/test_gemm.cpp index a5d90ff01c..5548456568 100644 --- a/modules/ocl/test/test_gemm.cpp +++ b/modules/ocl/test/test_gemm.cpp @@ -74,7 +74,7 @@ TEST_P(Gemm, Accuracy) cv::gemm(a, b, 1.0, c, 1.0, dst, flags); cv::ocl::gemm(cv::ocl::oclMat(a), cv::ocl::oclMat(b), 1.0, cv::ocl::oclMat(c), 1.0, ocl_dst, flags); - EXPECT_MAT_NEAR(dst, ocl_dst, mat_size.area() * 1e-4, ""); + EXPECT_MAT_NEAR(dst, ocl_dst, mat_size.area() * 1e-4); } INSTANTIATE_TEST_CASE_P(ocl_gemm, Gemm, testing::Combine( From 4162ebfad344891d809056026313462712a5f400 Mon Sep 17 00:00:00 2001 From: yao Date: Fri, 17 May 2013 15:34:22 +0800 Subject: [PATCH 012/178] add OpticalFlowDual_TVL1_OCL function --- modules/ocl/include/opencv2/ocl/ocl.hpp | 93 ++++ modules/ocl/src/arithm.cpp | 7 + modules/ocl/src/opencl/tvl1flow.cl | 407 +++++++++++++++ modules/ocl/src/tvl1flow.cpp | 475 ++++++++++++++++++ .../test/{test_pyrlk.cpp => test_optflow.cpp} | 65 ++- 5 files changed, 1037 insertions(+), 10 deletions(-) create mode 100644 modules/ocl/src/opencl/tvl1flow.cl create mode 100644 modules/ocl/src/tvl1flow.cpp rename modules/ocl/test/{test_pyrlk.cpp => test_optflow.cpp} (69%) diff --git a/modules/ocl/include/opencv2/ocl/ocl.hpp b/modules/ocl/include/opencv2/ocl/ocl.hpp index 2b01f94e3d..1cace848c3 100644 --- a/modules/ocl/include/opencv2/ocl/ocl.hpp +++ b/modules/ocl/include/opencv2/ocl/ocl.hpp @@ -407,6 +407,9 @@ namespace cv //! computes element-wise product of the two arrays (c = a * b) // supports all types except CV_8SC1,CV_8SC2,CV8SC3 and CV_8SC4 CV_EXPORTS void multiply(const oclMat &a, const oclMat &b, oclMat &c, double scale = 1); + //! multiplies matrix to a number (dst = scalar * src) + // supports CV_32FC1 only + CV_EXPORTS void multiply(double scalar, const oclMat &src, oclMat &dst); //! computes element-wise quotient of the two arrays (c = a / b) // supports all types except CV_8SC1,CV_8SC2,CV8SC3 and CV_8SC4 CV_EXPORTS void divide(const oclMat &a, const oclMat &b, oclMat &c, double scale = 1); @@ -1372,6 +1375,7 @@ namespace cv private: oclMat minSSD, leBuf, riBuf; }; + class CV_EXPORTS StereoBeliefPropagation { public: @@ -1402,6 +1406,7 @@ namespace cv std::vector datas; oclMat out; }; + class CV_EXPORTS StereoConstantSpaceBP { public: @@ -1440,6 +1445,94 @@ namespace cv oclMat temp; oclMat out; }; + + // Implementation of the Zach, Pock and Bischof Dual TV-L1 Optical Flow method + // + // see reference: + // [1] C. Zach, T. Pock and H. Bischof, "A Duality Based Approach for Realtime TV-L1 Optical Flow". + // [2] Javier Sanchez, Enric Meinhardt-Llopis and Gabriele Facciolo. "TV-L1 Optical Flow Estimation". + class CV_EXPORTS OpticalFlowDual_TVL1_OCL + { + public: + OpticalFlowDual_TVL1_OCL(); + + void operator ()(const oclMat& I0, const oclMat& I1, oclMat& flowx, oclMat& flowy); + + void collectGarbage(); + + /** + * Time step of the numerical scheme. + */ + double tau; + + /** + * Weight parameter for the data term, attachment parameter. + * This is the most relevant parameter, which determines the smoothness of the output. + * The smaller this parameter is, the smoother the solutions we obtain. + * It depends on the range of motions of the images, so its value should be adapted to each image sequence. + */ + double lambda; + + /** + * Weight parameter for (u - v)^2, tightness parameter. + * It serves as a link between the attachment and the regularization terms. + * In theory, it should have a small value in order to maintain both parts in correspondence. + * The method is stable for a large range of values of this parameter. + */ + double theta; + + /** + * Number of scales used to create the pyramid of images. + */ + int nscales; + + /** + * Number of warpings per scale. + * Represents the number of times that I1(x+u0) and grad( I1(x+u0) ) are computed per scale. + * This is a parameter that assures the stability of the method. + * It also affects the running time, so it is a compromise between speed and accuracy. + */ + int warps; + + /** + * Stopping criterion threshold used in the numerical scheme, which is a trade-off between precision and running time. + * A small value will yield more accurate solutions at the expense of a slower convergence. + */ + double epsilon; + + /** + * Stopping criterion iterations number used in the numerical scheme. + */ + int iterations; + + bool useInitialFlow; + + private: + void procOneScale(const oclMat& I0, const oclMat& I1, oclMat& u1, oclMat& u2); + + std::vector I0s; + std::vector I1s; + std::vector u1s; + std::vector u2s; + + oclMat I1x_buf; + oclMat I1y_buf; + + oclMat I1w_buf; + oclMat I1wx_buf; + oclMat I1wy_buf; + + oclMat grad_buf; + oclMat rho_c_buf; + + oclMat p11_buf; + oclMat p12_buf; + oclMat p21_buf; + oclMat p22_buf; + + oclMat diff_buf; + oclMat norm_buf; + }; } } #if defined _MSC_VER && _MSC_VER >= 1200 diff --git a/modules/ocl/src/arithm.cpp b/modules/ocl/src/arithm.cpp index d679a93480..ed2515dc6d 100644 --- a/modules/ocl/src/arithm.cpp +++ b/modules/ocl/src/arithm.cpp @@ -22,6 +22,7 @@ // Jiang Liyuan, jlyuan001.good@163.com // Rock Li, Rock.Li@amd.com // Zailong Wu, bullet@yeah.net +// Peng Xiao, pengxiao@outlook.com // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: @@ -286,6 +287,7 @@ void cv::ocl::multiply(const oclMat &src1, const oclMat &src2, oclMat &dst, doub else arithmetic_run(src1, src2, dst, "arithm_mul", &arithm_mul, (void *)(&scalar)); } + void cv::ocl::divide(const oclMat &src1, const oclMat &src2, oclMat &dst, double scalar) { @@ -468,6 +470,11 @@ void cv::ocl::subtract(const Scalar &src2, const oclMat &src1, oclMat &dst, cons const char **kernelString = mask.data ? &arithm_add_scalar_mask : &arithm_add_scalar; arithmetic_scalar( src1, src2, dst, mask, kernelName, kernelString, -1); } +void cv::ocl::multiply(double scalar, const oclMat &src, oclMat &dst) +{ + string kernelName = "arithm_muls"; + arithmetic_scalar_run( src, dst, kernelName, &arithm_mul, scalar); +} void cv::ocl::divide(double scalar, const oclMat &src, oclMat &dst) { if(!src.clCxt->supportsFeature(Context::CL_DOUBLE)) diff --git a/modules/ocl/src/opencl/tvl1flow.cl b/modules/ocl/src/opencl/tvl1flow.cl new file mode 100644 index 0000000000..e0ff7307b1 --- /dev/null +++ b/modules/ocl/src/opencl/tvl1flow.cl @@ -0,0 +1,407 @@ +/*M/////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// License Agreement +// For Open Source Computer Vision Library +// +// Copyright (C) 2010-2012, Multicoreware, Inc., all rights reserved. +// Copyright (C) 2010-2012, Advanced Micro Devices, Inc., all rights reserved. +// Third party copyrights are property of their respective owners. +// +// @Authors +// Jin Ma jin@multicorewareinc.com +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other oclMaterials provided with the distribution. +// +// * The name of the copyright holders may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors as is and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the Intel Corporation or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +//M*/ + +__kernel void centeredGradientKernel(__global const float* src, int src_col, int src_row, int src_step, +__global float* dx, __global float* dy, int dx_step) +{ + int x = get_global_id(0); + int y = get_global_id(1); + + if((x < src_col)&&(y < src_row)) + { + int src_x1 = (x + 1) < (src_col -1)? (x + 1) : (src_col - 1); + int src_x2 = (x - 1) > 0 ? (x -1) : 0; + + //if(src[y * src_step + src_x1] == src[y * src_step+ src_x2]) + //{ + // printf("y = %d\n", y); + // printf("src_x1 = %d\n", src_x1); + // printf("src_x2 = %d\n", src_x2); + //} + dx[y * dx_step+ x] = 0.5f * (src[y * src_step + src_x1] - src[y * src_step+ src_x2]); + + int src_y1 = (y+1) < (src_row - 1) ? (y + 1) : (src_row - 1); + int src_y2 = (y - 1) > 0 ? (y - 1) : 0; + dy[y * dx_step+ x] = 0.5f * (src[src_y1 * src_step + x] - src[src_y2 * src_step+ x]); + } + +} + +float bicubicCoeff(float x_) +{ + + float x = fabs(x_); + if (x <= 1.0f) + { + return x * x * (1.5f * x - 2.5f) + 1.0f; + } + else if (x < 2.0f) + { + return x * (x * (-0.5f * x + 2.5f) - 4.0f) + 2.0f; + } + else + { + return 0.0f; + } + +} + +__kernel void warpBackwardKernel(__global const float* I0, int I0_step, int I0_col, int I0_row, + image2d_t tex_I1, image2d_t tex_I1x, image2d_t tex_I1y, + __global const float* u1, int u1_step, + __global const float* u2, + __global float* I1w, + __global float* I1wx, /*int I1wx_step,*/ + __global float* I1wy, /*int I1wy_step,*/ + __global float* grad, /*int grad_step,*/ + __global float* rho, + int I1w_step, + int u2_step, + int u1_offset_x, + int u1_offset_y, + int u2_offset_x, + int u2_offset_y) +{ + const int x = get_global_id(0); + const int y = get_global_id(1); + + if(x < I0_col&&y < I0_row) + { + //const float u1Val = u1(y, x); + const float u1Val = u1[(y + u1_offset_y) * u1_step + x + u1_offset_x]; + //const float u2Val = u2(y, x); + const float u2Val = u2[(y + u2_offset_y) * u2_step + x + u2_offset_x]; + + const float wx = x + u1Val; + const float wy = y + u2Val; + + const int xmin = ceil(wx - 2.0f); + const int xmax = floor(wx + 2.0f); + + const int ymin = ceil(wy - 2.0f); + const int ymax = floor(wy + 2.0f); + + float sum = 0.0f; + float sumx = 0.0f; + float sumy = 0.0f; + float wsum = 0.0f; + sampler_t sampleri = CLK_NORMALIZED_COORDS_FALSE | CLK_ADDRESS_CLAMP_TO_EDGE | CLK_FILTER_NEAREST; + + for (int cy = ymin; cy <= ymax; ++cy) + { + for (int cx = xmin; cx <= xmax; ++cx) + { + const float w = bicubicCoeff(wx - cx) * bicubicCoeff(wy - cy); + + //sum += w * tex2D(tex_I1 , cx, cy); + int2 cood = (int2)(cx, cy); + sum += w * read_imagef(tex_I1, sampleri, cood).x; + //sumx += w * tex2D(tex_I1x, cx, cy); + sumx += w * read_imagef(tex_I1x, sampleri, cood).x; + //sumy += w * tex2D(tex_I1y, cx, cy); + sumy += w * read_imagef(tex_I1y, sampleri, cood).x; + + wsum += w; + } + } + + const float coeff = 1.0f / wsum; + + const float I1wVal = sum * coeff; + const float I1wxVal = sumx * coeff; + const float I1wyVal = sumy * coeff; + + I1w[y * I1w_step + x] = I1wVal; + I1wx[y * I1w_step + x] = I1wxVal; + I1wy[y * I1w_step + x] = I1wyVal; + + const float Ix2 = I1wxVal * I1wxVal; + const float Iy2 = I1wyVal * I1wyVal; + + // store the |Grad(I1)|^2 + grad[y * I1w_step + x] = Ix2 + Iy2; + + // compute the constant part of the rho function + const float I0Val = I0[y * I0_step + x]; + rho[y * I1w_step + x] = I1wVal - I1wxVal * u1Val - I1wyVal * u2Val - I0Val; + } + +} + +float readImage(__global const float *image, const int x, const int y, const int rows, const int cols, const int elemCntPerRow) +{ + int i0 = clamp(x, 0, cols - 1); + int j0 = clamp(y, 0, rows - 1); + int i1 = clamp(x + 1, 0, cols - 1); + int j1 = clamp(y + 1, 0, rows - 1); + + return image[j0 * elemCntPerRow + i0]; +} + +__kernel void warpBackwardKernelNoImage2d(__global const float* I0, int I0_step, int I0_col, int I0_row, + __global const float* tex_I1, __global const float* tex_I1x, __global const float* tex_I1y, + __global const float* u1, int u1_step, + __global const float* u2, + __global float* I1w, + __global float* I1wx, /*int I1wx_step,*/ + __global float* I1wy, /*int I1wy_step,*/ + __global float* grad, /*int grad_step,*/ + __global float* rho, + int I1w_step, + int u2_step, + int I1_step, + int I1x_step) +{ + const int x = get_global_id(0); + const int y = get_global_id(1); + + if(x < I0_col&&y < I0_row) + { + //const float u1Val = u1(y, x); + const float u1Val = u1[y * u1_step + x]; + //const float u2Val = u2(y, x); + const float u2Val = u2[y * u2_step + x]; + + const float wx = x + u1Val; + const float wy = y + u2Val; + + const int xmin = ceil(wx - 2.0f); + const int xmax = floor(wx + 2.0f); + + const int ymin = ceil(wy - 2.0f); + const int ymax = floor(wy + 2.0f); + + float sum = 0.0f; + float sumx = 0.0f; + float sumy = 0.0f; + float wsum = 0.0f; + + for (int cy = ymin; cy <= ymax; ++cy) + { + for (int cx = xmin; cx <= xmax; ++cx) + { + const float w = bicubicCoeff(wx - cx) * bicubicCoeff(wy - cy); + + int2 cood = (int2)(cx, cy); + sum += w * readImage(tex_I1, cood.x, cood.y, I0_col, I0_row, I1_step); + sumx += w * readImage(tex_I1x, cood.x, cood.y, I0_col, I0_row, I1x_step); + sumy += w * readImage(tex_I1y, cood.x, cood.y, I0_col, I0_row, I1x_step); + wsum += w; + } + } + + const float coeff = 1.0f / wsum; + + const float I1wVal = sum * coeff; + const float I1wxVal = sumx * coeff; + const float I1wyVal = sumy * coeff; + + I1w[y * I1w_step + x] = I1wVal; + I1wx[y * I1w_step + x] = I1wxVal; + I1wy[y * I1w_step + x] = I1wyVal; + + const float Ix2 = I1wxVal * I1wxVal; + const float Iy2 = I1wyVal * I1wyVal; + + // store the |Grad(I1)|^2 + grad[y * I1w_step + x] = Ix2 + Iy2; + + // compute the constant part of the rho function + const float I0Val = I0[y * I0_step + x]; + rho[y * I1w_step + x] = I1wVal - I1wxVal * u1Val - I1wyVal * u2Val - I0Val; + } + +} + + +__kernel void estimateDualVariablesKernel(__global const float* u1, int u1_col, int u1_row, int u1_step, + __global const float* u2, + __global float* p11, int p11_step, + __global float* p12, + __global float* p21, + __global float* p22, + const float taut, + int u2_step, + int u1_offset_x, + int u1_offset_y, + int u2_offset_x, + int u2_offset_y) +{ + + //const int x = blockIdx.x * blockDim.x + threadIdx.x; + //const int y = blockIdx.y * blockDim.y + threadIdx.y; + const int x = get_global_id(0); + const int y = get_global_id(1); + + if(x < u1_col && y < u1_row) + { + int src_x1 = (x + 1) < (u1_col - 1) ? (x + 1) : (u1_col - 1); + const float u1x = u1[(y + u1_offset_y) * u1_step + src_x1 + u1_offset_x] - u1[(y + u1_offset_y) * u1_step + x + u1_offset_x]; + + int src_y1 = (y + 1) < (u1_row - 1) ? (y + 1) : (u1_row - 1); + const float u1y = u1[(src_y1 + u1_offset_y) * u1_step + x + u1_offset_x] - u1[(y + u1_offset_y) * u1_step + x + u1_offset_x]; + + int src_x2 = (x + 1) < (u1_col - 1) ? (x + 1) : (u1_col - 1); + const float u2x = u2[(y + u2_offset_y) * u2_step + src_x2 + u2_offset_x] - u2[(y + u2_offset_y) * u2_step + x + u2_offset_x]; + + int src_y2 = (y + 1) < (u1_row - 1) ? (y + 1) : (u1_row - 1); + const float u2y = u2[(src_y2 + u2_offset_y) * u2_step + x + u2_offset_x] - u2[(y + u2_offset_y) * u2_step + x + u2_offset_x]; + + const float g1 = hypot(u1x, u1y); + const float g2 = hypot(u2x, u2y); + + const float ng1 = 1.0f + taut * g1; + const float ng2 = 1.0f + taut * g2; + + p11[y * p11_step + x] = (p11[y * p11_step + x] + taut * u1x) / ng1; + p12[y * p11_step + x] = (p12[y * p11_step + x] + taut * u1y) / ng1; + p21[y * p11_step + x] = (p21[y * p11_step + x] + taut * u2x) / ng2; + p22[y * p11_step + x] = (p22[y * p11_step + x] + taut * u2y) / ng2; + } + +} + +float divergence(__global const float* v1, __global const float* v2, int y, int x, int v1_step, int v2_step) +{ + + if (x > 0 && y > 0) + { + const float v1x = v1[y * v1_step + x] - v1[y * v1_step + x - 1]; + const float v2y = v2[y * v2_step + x] - v2[(y - 1) * v2_step + x]; + return v1x + v2y; + } + else + { + if (y > 0) + return v1[y * v1_step + 0] + v2[y * v2_step + 0] - v2[(y - 1) * v2_step + 0]; + else + { + if (x > 0) + return v1[0 * v1_step + x] - v1[0 * v1_step + x - 1] + v2[0 * v2_step + x]; + else + return v1[0 * v1_step + 0] + v2[0 * v2_step + 0]; + } + } + +} + +__kernel void estimateUKernel(__global const float* I1wx, int I1wx_col, int I1wx_row, int I1wx_step, + __global const float* I1wy, /*int I1wy_step,*/ + __global const float* grad, /*int grad_step,*/ + __global const float* rho_c, /*int rho_c_step,*/ + __global const float* p11, /*int p11_step,*/ + __global const float* p12, /*int p12_step,*/ + __global const float* p21, /*int p21_step,*/ + __global const float* p22, /*int p22_step,*/ + __global float* u1, int u1_step, + __global float* u2, + __global float* error, const float l_t, const float theta, int u2_step, + int u1_offset_x, + int u1_offset_y, + int u2_offset_x, + int u2_offset_y) +{ + + //const int x = blockIdx.x * blockDim.x + threadIdx.x; + //const int y = blockIdx.y * blockDim.y + threadIdx.y; + + int x = get_global_id(0); + int y = get_global_id(1); + + + if(x < I1wx_col && y < I1wx_row) + { + const float I1wxVal = I1wx[y * I1wx_step + x]; + const float I1wyVal = I1wy[y * I1wx_step + x]; + const float gradVal = grad[y * I1wx_step + x]; + const float u1OldVal = u1[(y + u1_offset_y) * u1_step + x + u1_offset_x]; + const float u2OldVal = u2[(y + u2_offset_y) * u2_step + x + u2_offset_x]; + + const float rho = rho_c[y * I1wx_step + x] + (I1wxVal * u1OldVal + I1wyVal * u2OldVal); + + // estimate the values of the variable (v1, v2) (thresholding operator TH) + + float d1 = 0.0f; + float d2 = 0.0f; + + if (rho < -l_t * gradVal) + { + d1 = l_t * I1wxVal; + d2 = l_t * I1wyVal; + } + else if (rho > l_t * gradVal) + { + d1 = -l_t * I1wxVal; + d2 = -l_t * I1wyVal; + } + else if (gradVal > 1.192092896e-07f) + { + const float fi = -rho / gradVal; + d1 = fi * I1wxVal; + d2 = fi * I1wyVal; + } + + const float v1 = u1OldVal + d1; + const float v2 = u2OldVal + d2; + + // compute the divergence of the dual variable (p1, p2) + + const float div_p1 = divergence(p11, p12, y, x, I1wx_step, I1wx_step); + const float div_p2 = divergence(p21, p22, y, x, I1wx_step, I1wx_step); + + // estimate the values of the optical flow (u1, u2) + + const float u1NewVal = v1 + theta * div_p1; + const float u2NewVal = v2 + theta * div_p2; + + u1[(y + u1_offset_y) * u1_step + x + u1_offset_x] = u1NewVal; + u2[(y + u2_offset_y) * u2_step + x + u2_offset_x] = u2NewVal; + + const float n1 = (u1OldVal - u1NewVal) * (u1OldVal - u1NewVal); + const float n2 = (u2OldVal - u2NewVal) * (u2OldVal - u2NewVal); + error[y * I1wx_step + x] = n1 + n2; + } + +} diff --git a/modules/ocl/src/tvl1flow.cpp b/modules/ocl/src/tvl1flow.cpp new file mode 100644 index 0000000000..8182f41a19 --- /dev/null +++ b/modules/ocl/src/tvl1flow.cpp @@ -0,0 +1,475 @@ +/*M/////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// License Agreement +// For Open Source Computer Vision Library +// +// Copyright (C) 2010-2012, Multicoreware, Inc., all rights reserved. +// Copyright (C) 2010-2012, Advanced Micro Devices, Inc., all rights reserved. +// Third party copyrights are property of their respective owners. +// +// @Authors +// Jin Ma, jin@multicorewareinc.com +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other oclMaterials provided with the distribution. +// +// * The name of the copyright holders may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors "as is" and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the Intel Corporation or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +//M*/ + + +#include "precomp.hpp" +using namespace std; +using namespace cv; +using namespace cv::ocl; + +namespace cv +{ + namespace ocl + { + ///////////////////////////OpenCL kernel strings/////////////////////////// + extern const char* tvl1flow; + } +} + +cv::ocl::OpticalFlowDual_TVL1_OCL::OpticalFlowDual_TVL1_OCL() +{ + tau = 0.25; + lambda = 0.15; + theta = 0.3; + nscales = 5; + warps = 5; + epsilon = 0.01; + iterations = 300; + useInitialFlow = false; +} + +void cv::ocl::OpticalFlowDual_TVL1_OCL::operator()(const oclMat& I0, const oclMat& I1, oclMat& flowx, oclMat& flowy) +{ + CV_Assert( I0.type() == CV_8UC1 || I0.type() == CV_32FC1 ); + CV_Assert( I0.size() == I1.size() ); + CV_Assert( I0.type() == I1.type() ); + CV_Assert( !useInitialFlow || (flowx.size() == I0.size() && flowx.type() == CV_32FC1 && flowy.size() == flowx.size() && flowy.type() == flowx.type()) ); + CV_Assert( nscales > 0 ); + + // allocate memory for the pyramid structure + I0s.resize(nscales); + I1s.resize(nscales); + u1s.resize(nscales); + u2s.resize(nscales); + //I0s_step == I1s_step + I0.convertTo(I0s[0], CV_32F, I0.depth() == CV_8U ? 1.0 : 255.0); + I1.convertTo(I1s[0], CV_32F, I1.depth() == CV_8U ? 1.0 : 255.0); + + + if (!useInitialFlow) + { + flowx.create(I0.size(), CV_32FC1); + flowy.create(I0.size(), CV_32FC1); + } + //u1s_step != u2s_step + u1s[0] = flowx; + u2s[0] = flowy; + + I1x_buf.create(I0.size(), CV_32FC1); + I1y_buf.create(I0.size(), CV_32FC1); + + I1w_buf.create(I0.size(), CV_32FC1); + I1wx_buf.create(I0.size(), CV_32FC1); + I1wy_buf.create(I0.size(), CV_32FC1); + + grad_buf.create(I0.size(), CV_32FC1); + rho_c_buf.create(I0.size(), CV_32FC1); + + p11_buf.create(I0.size(), CV_32FC1); + p12_buf.create(I0.size(), CV_32FC1); + p21_buf.create(I0.size(), CV_32FC1); + p22_buf.create(I0.size(), CV_32FC1); + + diff_buf.create(I0.size(), CV_32FC1); + + // create the scales + for (int s = 1; s < nscales; ++s) + { + ocl::pyrDown(I0s[s - 1], I0s[s]); + ocl::pyrDown(I1s[s - 1], I1s[s]); + + if (I0s[s].cols < 16 || I0s[s].rows < 16) + { + nscales = s; + break; + } + + if (useInitialFlow) + { + ocl::pyrDown(u1s[s - 1], u1s[s]); + ocl::pyrDown(u2s[s - 1], u2s[s]); + + //ocl::multiply(u1s[s], Scalar::all(0.5), u1s[s]); + multiply(0.5, u1s[s], u1s[s]); + //ocl::multiply(u2s[s], Scalar::all(0.5), u2s[s]); + multiply(0.5, u1s[s], u2s[s]); + } + } + + // pyramidal structure for computing the optical flow + for (int s = nscales - 1; s >= 0; --s) + { + // compute the optical flow at the current scale + procOneScale(I0s[s], I1s[s], u1s[s], u2s[s]); + + // if this was the last scale, finish now + if (s == 0) + break; + + // otherwise, upsample the optical flow + + // zoom the optical flow for the next finer scale + ocl::resize(u1s[s], u1s[s - 1], I0s[s - 1].size()); + ocl::resize(u2s[s], u2s[s - 1], I0s[s - 1].size()); + + // scale the optical flow with the appropriate zoom factor + multiply(2, u1s[s - 1], u1s[s - 1]); + multiply(2, u2s[s - 1], u2s[s - 1]); + + } + +} + +namespace ocl_tvl1flow +{ + void centeredGradient(const oclMat &src, oclMat &dx, oclMat &dy); + + void warpBackward(const oclMat &I0, const oclMat &I1, oclMat &I1x, oclMat &I1y, + oclMat &u1, oclMat &u2, oclMat &I1w, oclMat &I1wx, oclMat &I1wy, + oclMat &grad, oclMat &rho); + + void estimateU(oclMat &I1wx, oclMat &I1wy, oclMat &grad, + oclMat &rho_c, oclMat &p11, oclMat &p12, + oclMat &p21, oclMat &p22, oclMat &u1, + oclMat &u2, oclMat &error, float l_t, float theta); + + void estimateDualVariables(oclMat &u1, oclMat &u2, + oclMat &p11, oclMat &p12, oclMat &p21, oclMat &p22, float taut); +} + +void cv::ocl::OpticalFlowDual_TVL1_OCL::procOneScale(const oclMat &I0, const oclMat &I1, oclMat &u1, oclMat &u2) +{ + using namespace ocl_tvl1flow; + + const double scaledEpsilon = epsilon * epsilon * I0.size().area(); + + CV_DbgAssert( I1.size() == I0.size() ); + CV_DbgAssert( I1.type() == I0.type() ); + CV_DbgAssert( u1.empty() || u1.size() == I0.size() ); + CV_DbgAssert( u2.size() == u1.size() ); + + if (u1.empty()) + { + u1.create(I0.size(), CV_32FC1); + u1.setTo(Scalar::all(0)); + + u2.create(I0.size(), CV_32FC1); + u2.setTo(Scalar::all(0)); + } + + oclMat I1x = I1x_buf(Rect(0, 0, I0.cols, I0.rows)); + oclMat I1y = I1y_buf(Rect(0, 0, I0.cols, I0.rows)); + + centeredGradient(I1, I1x, I1y); + + oclMat I1w = I1w_buf(Rect(0, 0, I0.cols, I0.rows)); + oclMat I1wx = I1wx_buf(Rect(0, 0, I0.cols, I0.rows)); + oclMat I1wy = I1wy_buf(Rect(0, 0, I0.cols, I0.rows)); + + oclMat grad = grad_buf(Rect(0, 0, I0.cols, I0.rows)); + oclMat rho_c = rho_c_buf(Rect(0, 0, I0.cols, I0.rows)); + + oclMat p11 = p11_buf(Rect(0, 0, I0.cols, I0.rows)); + oclMat p12 = p12_buf(Rect(0, 0, I0.cols, I0.rows)); + oclMat p21 = p21_buf(Rect(0, 0, I0.cols, I0.rows)); + oclMat p22 = p22_buf(Rect(0, 0, I0.cols, I0.rows)); + p11.setTo(Scalar::all(0)); + p12.setTo(Scalar::all(0)); + p21.setTo(Scalar::all(0)); + p22.setTo(Scalar::all(0)); + + oclMat diff = diff_buf(Rect(0, 0, I0.cols, I0.rows)); + + const float l_t = static_cast(lambda * theta); + const float taut = static_cast(tau / theta); + + for (int warpings = 0; warpings < warps; ++warpings) + { + warpBackward(I0, I1, I1x, I1y, u1, u2, I1w, I1wx, I1wy, grad, rho_c); + + double error = numeric_limits::max(); + for (int n = 0; error > scaledEpsilon && n < iterations; ++n) + { + estimateU(I1wx, I1wy, grad, rho_c, p11, p12, p21, p22, + u1, u2, diff, l_t, static_cast(theta)); + + error = ocl::sum(diff)[0]; + + estimateDualVariables(u1, u2, p11, p12, p21, p22, taut); + + } + } + +} + +void cv::ocl::OpticalFlowDual_TVL1_OCL::collectGarbage() +{ + I0s.clear(); + I1s.clear(); + u1s.clear(); + u2s.clear(); + + I1x_buf.release(); + I1y_buf.release(); + + I1w_buf.release(); + I1wx_buf.release(); + I1wy_buf.release(); + + grad_buf.release(); + rho_c_buf.release(); + + p11_buf.release(); + p12_buf.release(); + p21_buf.release(); + p22_buf.release(); + + diff_buf.release(); + norm_buf.release(); +} + +void ocl_tvl1flow::centeredGradient(const oclMat &src, oclMat &dx, oclMat &dy) +{ + Context *clCxt = src.clCxt; + size_t localThreads[3] = {32, 8, 1}; + size_t globalThreads[3] = {src.cols, src.rows, 1}; + + int srcElementSize = src.elemSize(); + int src_step = src.step/srcElementSize; + + int dElememntSize = dx.elemSize(); + int dx_step = dx.step/dElememntSize; + + string kernelName = "centeredGradientKernel"; + vector< pair > args; + args.push_back( make_pair( sizeof(cl_mem), (void*)&src.data)); + args.push_back( make_pair( sizeof(cl_int), (void*)&src.cols)); + args.push_back( make_pair( sizeof(cl_int), (void*)&src.rows)); + args.push_back( make_pair( sizeof(cl_int), (void*)&src_step)); + args.push_back( make_pair( sizeof(cl_mem), (void*)&dx.data)); + args.push_back( make_pair( sizeof(cl_mem), (void*)&dy.data)); + args.push_back( make_pair( sizeof(cl_int), (void*)&dx_step)); + openCLExecuteKernel(clCxt, &tvl1flow, kernelName, globalThreads, localThreads, args, -1, -1); + +} + +void ocl_tvl1flow::estimateDualVariables(oclMat &u1, oclMat &u2, oclMat &p11, oclMat &p12, oclMat &p21, oclMat &p22, float taut) +{ + Context *clCxt = u1.clCxt; + + size_t localThread[] = {32, 8, 1}; + size_t globalThread[] = + { + u1.cols, + u1.rows, + 1 + }; + + int u1_element_size = u1.elemSize(); + int u1_step = u1.step/u1_element_size; + + int u2_element_size = u2.elemSize(); + int u2_step = u2.step/u2_element_size; + + int p11_element_size = p11.elemSize(); + int p11_step = p11.step/p11_element_size; + + int u1_offset_y = u1.offset/u1.step; + int u1_offset_x = u1.offset%u1.step; + u1_offset_x = u1_offset_x/u1.elemSize(); + + int u2_offset_y = u2.offset/u2.step; + int u2_offset_x = u2.offset%u2.step; + u2_offset_x = u2_offset_x/u2.elemSize(); + + string kernelName = "estimateDualVariablesKernel"; + vector< pair > args; + args.push_back( make_pair( sizeof(cl_mem), (void*)&u1.data)); + args.push_back( make_pair( sizeof(cl_int), (void*)&u1.cols)); + args.push_back( make_pair( sizeof(cl_int), (void*)&u1.rows)); + args.push_back( make_pair( sizeof(cl_int), (void*)&u1_step)); + args.push_back( make_pair( sizeof(cl_mem), (void*)&u2.data)); + args.push_back( make_pair( sizeof(cl_mem), (void*)&p11.data)); + args.push_back( make_pair( sizeof(cl_int), (void*)&p11_step)); + args.push_back( make_pair( sizeof(cl_mem), (void*)&p12.data)); + args.push_back( make_pair( sizeof(cl_mem), (void*)&p21.data)); + args.push_back( make_pair( sizeof(cl_mem), (void*)&p22.data)); + args.push_back( make_pair( sizeof(cl_float), (void*)&taut)); + args.push_back( make_pair( sizeof(cl_int), (void*)&u2_step)); + args.push_back( make_pair( sizeof(cl_int), (void*)&u1_offset_x)); + args.push_back( make_pair( sizeof(cl_int), (void*)&u1_offset_y)); + args.push_back( make_pair( sizeof(cl_int), (void*)&u2_offset_x)); + args.push_back( make_pair( sizeof(cl_int), (void*)&u2_offset_y)); + + openCLExecuteKernel(clCxt, &tvl1flow, kernelName, globalThread, localThread, args, -1, -1); +} + +void ocl_tvl1flow::estimateU(oclMat &I1wx, oclMat &I1wy, oclMat &grad, + oclMat &rho_c, oclMat &p11, oclMat &p12, + oclMat &p21, oclMat &p22, oclMat &u1, + oclMat &u2, oclMat &error, float l_t, float theta) +{ + Context* clCxt = I1wx.clCxt; + + size_t localThread[] = {32, 8, 1}; + size_t globalThread[] = + { + I1wx.cols, + I1wx.rows, + 1 + }; + + int I1wx_element_size = I1wx.elemSize(); + int I1wx_step = I1wx.step/I1wx_element_size; + + int u1_element_size = u1.elemSize(); + int u1_step = u1.step/u1_element_size; + + int u2_element_size = u2.elemSize(); + int u2_step = u2.step/u2_element_size; + + int u1_offset_y = u1.offset/u1.step; + int u1_offset_x = u1.offset%u1.step; + u1_offset_x = u1_offset_x/u1.elemSize(); + + int u2_offset_y = u2.offset/u2.step; + int u2_offset_x = u2.offset%u2.step; + u2_offset_x = u2_offset_x/u2.elemSize(); + + string kernelName = "estimateUKernel"; + vector< pair > args; + args.push_back( make_pair( sizeof(cl_mem), (void*)&I1wx.data)); + args.push_back( make_pair( sizeof(cl_int), (void*)&I1wx.cols)); + args.push_back( make_pair( sizeof(cl_int), (void*)&I1wx.rows)); + args.push_back( make_pair( sizeof(cl_int), (void*)&I1wx_step)); + args.push_back( make_pair( sizeof(cl_mem), (void*)&I1wy.data)); + args.push_back( make_pair( sizeof(cl_mem), (void*)&grad.data)); + args.push_back( make_pair( sizeof(cl_mem), (void*)&rho_c.data)); + args.push_back( make_pair( sizeof(cl_mem), (void*)&p11.data)); + args.push_back( make_pair( sizeof(cl_mem), (void*)&p12.data)); + args.push_back( make_pair( sizeof(cl_mem), (void*)&p21.data)); + args.push_back( make_pair( sizeof(cl_mem), (void*)&p22.data)); + args.push_back( make_pair( sizeof(cl_mem), (void*)&u1.data)); + args.push_back( make_pair( sizeof(cl_int), (void*)&u1_step)); + args.push_back( make_pair( sizeof(cl_mem), (void*)&u2.data)); + args.push_back( make_pair( sizeof(cl_mem), (void*)&error.data)); + args.push_back( make_pair( sizeof(cl_float), (void*)&l_t)); + args.push_back( make_pair( sizeof(cl_float), (void*)&theta)); + args.push_back( make_pair( sizeof(cl_int), (void*)&u2_step)); + args.push_back( make_pair( sizeof(cl_int), (void*)&u1_offset_x)); + args.push_back( make_pair( sizeof(cl_int), (void*)&u1_offset_y)); + args.push_back( make_pair( sizeof(cl_int), (void*)&u2_offset_x)); + args.push_back( make_pair( sizeof(cl_int), (void*)&u2_offset_y)); + + openCLExecuteKernel(clCxt, &tvl1flow, kernelName, globalThread, localThread, args, -1, -1); +} + +void ocl_tvl1flow::warpBackward(const oclMat &I0, const oclMat &I1, oclMat &I1x, oclMat &I1y, oclMat &u1, oclMat &u2, oclMat &I1w, oclMat &I1wx, oclMat &I1wy, oclMat &grad, oclMat &rho) +{ + Context* clCxt = I0.clCxt; + const bool isImgSupported = support_image2d(clCxt); + + CV_Assert(isImgSupported); + + int u1ElementSize = u1.elemSize(); + int u1Step = u1.step/u1ElementSize; + + int u2ElementSize = u2.elemSize(); + int u2Step = u2.step/u2ElementSize; + + int I0ElementSize = I0.elemSize(); + int I0Step = I0.step/I0ElementSize; + + int I1w_element_size = I1w.elemSize(); + int I1w_step = I1w.step/I1w_element_size; + + int u1_offset_y = u1.offset/u1.step; + int u1_offset_x = u1.offset%u1.step; + u1_offset_x = u1_offset_x/u1.elemSize(); + + int u2_offset_y = u2.offset/u2.step; + int u2_offset_x = u2.offset%u2.step; + u2_offset_x = u2_offset_x/u2.elemSize(); + + size_t localThread[] = {32, 8, 1}; + size_t globalThread[] = + { + I0.cols, + I0.rows, + 1 + }; + + cl_mem I1_tex; + cl_mem I1x_tex; + cl_mem I1y_tex; + I1_tex = bindTexture(I1); + I1x_tex = bindTexture(I1x); + I1y_tex = bindTexture(I1y); + + string kernelName = "warpBackwardKernel"; + vector< pair > args; + args.push_back( make_pair( sizeof(cl_mem), (void*)&I0.data)); + args.push_back( make_pair( sizeof(cl_int), (void*)&I0Step)); + args.push_back( make_pair( sizeof(cl_int), (void*)&I0.cols)); + args.push_back( make_pair( sizeof(cl_int), (void*)&I0.rows)); + args.push_back( make_pair( sizeof(cl_mem), (void*)&I1_tex)); + args.push_back( make_pair( sizeof(cl_mem), (void*)&I1x_tex)); + args.push_back( make_pair( sizeof(cl_mem), (void*)&I1y_tex)); + args.push_back( make_pair( sizeof(cl_mem), (void*)&u1.data)); + args.push_back( make_pair( sizeof(cl_int), (void*)&u1Step)); + args.push_back( make_pair( sizeof(cl_mem), (void*)&u2.data)); + args.push_back( make_pair( sizeof(cl_mem), (void*)&I1w.data)); + args.push_back( make_pair( sizeof(cl_mem), (void*)&I1wx.data)); + args.push_back( make_pair( sizeof(cl_mem), (void*)&I1wy.data)); + args.push_back( make_pair( sizeof(cl_mem), (void*)&grad.data)); + args.push_back( make_pair( sizeof(cl_mem), (void*)&rho.data)); + args.push_back( make_pair( sizeof(cl_int), (void*)&I1w_step)); + args.push_back( make_pair( sizeof(cl_int), (void*)&u2Step)); + args.push_back( make_pair( sizeof(cl_int), (void*)&u1_offset_x)); + args.push_back( make_pair( sizeof(cl_int), (void*)&u1_offset_y)); + args.push_back( make_pair( sizeof(cl_int), (void*)&u2_offset_x)); + args.push_back( make_pair( sizeof(cl_int), (void*)&u2_offset_y)); + + openCLExecuteKernel(clCxt, &tvl1flow, kernelName, globalThread, localThread, args, -1, -1); +} \ No newline at end of file diff --git a/modules/ocl/test/test_pyrlk.cpp b/modules/ocl/test/test_optflow.cpp similarity index 69% rename from modules/ocl/test/test_pyrlk.cpp rename to modules/ocl/test/test_optflow.cpp index 064cb30bd8..b08d33a08f 100644 --- a/modules/ocl/test/test_pyrlk.cpp +++ b/modules/ocl/test/test_optflow.cpp @@ -1,4 +1,4 @@ -/*M/////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////// // // IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. // @@ -7,12 +7,16 @@ // copy or use the software. // // -// Intel License Agreement +// License Agreement // For Open Source Computer Vision Library -// -// Copyright (C) 2000, Intel Corporation, all rights reserved. +// Copyright (C) 2010-2012, Multicoreware, Inc., all rights reserved. +// Copyright (C) 2010-2012, Institute Of Software Chinese Academy Of Science, all rights reserved. +// Copyright (C) 2010-2012, Advanced Micro Devices, Inc., all rights reserved. // Third party copyrights are property of their respective owners. // +// @Authors +// +// // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: // @@ -21,9 +25,9 @@ // // * Redistribution's in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. +// and/or other oclMaterials provided with the distribution. // -// * The name of Intel Corporation may not be used to endorse or promote products +// * The name of the copyright holders may not be used to endorse or promote products // derived from this software without specific prior written permission. // // This software is provided by the copyright holders and contributors "as is" and @@ -51,6 +55,47 @@ using namespace testing; using namespace std; extern string workdir; +////////////////////////////////////////////////////////////////////////// +PARAM_TEST_CASE(TVL1, bool) +{ + bool useRoi; + + virtual void SetUp() + { + useRoi = GET_PARAM(0); + } + +}; + +TEST_P(TVL1, Accuracy) +{ + cv::Mat frame0 = readImage(workdir + "../gpu/rubberwhale1.png", cv::IMREAD_GRAYSCALE); + ASSERT_FALSE(frame0.empty()); + + cv::Mat frame1 = readImage(workdir + "../gpu/rubberwhale2.png", cv::IMREAD_GRAYSCALE); + ASSERT_FALSE(frame1.empty()); + + cv::ocl::OpticalFlowDual_TVL1_OCL d_alg; + cv::RNG &rng = TS::ptr()->get_rng(); + cv::Mat flowx = randomMat(rng, frame0.size(), CV_32FC1, 0, 0, useRoi); + cv::Mat flowy = randomMat(rng, frame0.size(), CV_32FC1, 0, 0, useRoi); + cv::ocl::oclMat d_flowx(flowx), d_flowy(flowy); + d_alg(oclMat(frame0), oclMat(frame1), d_flowx, d_flowy); + + cv::Ptr alg = cv::createOptFlow_DualTVL1(); + cv::Mat flow; + alg->calc(frame0, frame1, flow); + cv::Mat gold[2]; + cv::split(flow, gold); + + EXPECT_MAT_SIMILAR(gold[0], d_flowx, 3e-3); + EXPECT_MAT_SIMILAR(gold[1], d_flowy, 3e-3); +} +INSTANTIATE_TEST_CASE_P(OCL_Video, TVL1, Values(true, false)); + + +///////////////////////////////////////////////////////////////////////////////////////////////// +// PyrLKOpticalFlow PARAM_TEST_CASE(Sparse, bool, bool) { @@ -60,7 +105,7 @@ PARAM_TEST_CASE(Sparse, bool, bool) virtual void SetUp() { UseSmart = GET_PARAM(0); - useGray = GET_PARAM(0); + useGray = GET_PARAM(1); } }; @@ -147,9 +192,9 @@ TEST_P(Sparse, Mat) } -INSTANTIATE_TEST_CASE_P(Video, Sparse, Combine( - Values(false, true), - Values(false))); +INSTANTIATE_TEST_CASE_P(OCL_Video, Sparse, Combine( + Values(false, true), + Values(false, true))); #endif // HAVE_OPENCL From 04399a27d01ba9bcaab7cdc740e891a3c28ce578 Mon Sep 17 00:00:00 2001 From: yao Date: Fri, 17 May 2013 15:44:22 +0800 Subject: [PATCH 013/178] fix a warning --- modules/ocl/perf/perf_split_merge.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ocl/perf/perf_split_merge.cpp b/modules/ocl/perf/perf_split_merge.cpp index 629fbfcd92..fc720c5b0d 100644 --- a/modules/ocl/perf/perf_split_merge.cpp +++ b/modules/ocl/perf/perf_split_merge.cpp @@ -140,7 +140,7 @@ PERFTEST(Split) if(d_dst.size() == dst.size()) { TestSystem::instance().setAccurate(1); - for(int i = 0; i < dst.size(); i++) + for(size_t i = 0; i < dst.size(); i++) { if(ExpectedMatNear(dst[i], cv::Mat(d_dst[i]), 0.0) == 0) { From 4af104aaae596158a4464276aad96a033412166e Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Sun, 19 May 2013 14:57:21 +0400 Subject: [PATCH 014/178] 'aapt' tool moved in SDK r22, adding support to run.py --- modules/ts/misc/run.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/modules/ts/misc/run.py b/modules/ts/misc/run.py index 4351713715..a64127f0d4 100755 --- a/modules/ts/misc/run.py +++ b/modules/ts/misc/run.py @@ -288,6 +288,16 @@ class TestSuite(object): if self.adb: # construct name for aapt tool self.aapt = [os.path.join(os.path.dirname(self.adb[0]), ("aapt","aapt.exe")[hostos == 'nt'])] + if not os.path.isfile(self.aapt[0]): + # it's moved in SDK r22 + sdk_dir = os.path.dirname( os.path.dirname(self.adb[0]) ) + aapt_fn = ("aapt", "aapt.exe")[hostos == 'nt'] + for r, ds, fs in os.walk( os.path.join(sdk_dir, 'build-tools') ): + if aapt_fn in fs: + self.aapt = [ os.path.join(r, aapt_fn) ] + break + else: + self.error = "Can't find '%s' tool!" % aapt_fn # fix has_perf_tests param self.has_perf_tests = self.has_perf_tests == "ON" From 44a2b109b77a71a5461d11e8fa430e371b5b7f34 Mon Sep 17 00:00:00 2001 From: alex77git Date: Mon, 20 May 2013 02:24:09 +0200 Subject: [PATCH 015/178] Bug #2967, basic_structures.rst, fix 2 typos --- modules/core/doc/basic_structures.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/core/doc/basic_structures.rst b/modules/core/doc/basic_structures.rst index ca9f5e21a2..91a61821ab 100644 --- a/modules/core/doc/basic_structures.rst +++ b/modules/core/doc/basic_structures.rst @@ -1682,7 +1682,7 @@ Returns the type of a matrix element. .. ocv:function:: int Mat::type() const -The method returns a matrix element type. This is an identifier compatible with the ``CvMat`` type system, like ``CV_16SC3`` or 16-bit signed 3-channel array, and so on. +The method returns a matrix element type. This is an identifier compatible with the ``CvMat`` type system, like ``CV_16SC3`` for 16-bit signed 3-channel array, and so on. Mat::depth @@ -1691,7 +1691,7 @@ Returns the depth of a matrix element. .. ocv:function:: int Mat::depth() const -The method returns the identifier of the matrix element depth (the type of each individual channel). For example, for a 16-bit signed 3-channel array, the method returns ``CV_16S`` . A complete list of matrix types contains the following values: +The method returns the identifier of the matrix element depth (the type of each individual channel). For example, for a 16-bit signed element array, the method returns ``CV_16S`` . A complete list of matrix types contains the following values: * ``CV_8U`` - 8-bit unsigned integers ( ``0..255`` ) From 6e7b1ef252bfede93e2805922f3d5d215645affc Mon Sep 17 00:00:00 2001 From: alex77git Date: Mon, 20 May 2013 02:26:58 +0200 Subject: [PATCH 016/178] Bug #2967, void DescriptorMatcher::radiusMatch() // description unclear, only file: common_interfaces_of_descriptor_matchers.rst --- .../features2d/doc/common_interfaces_of_descriptor_matchers.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/features2d/doc/common_interfaces_of_descriptor_matchers.rst b/modules/features2d/doc/common_interfaces_of_descriptor_matchers.rst index 8596ae43db..d7e5eb4c29 100644 --- a/modules/features2d/doc/common_interfaces_of_descriptor_matchers.rst +++ b/modules/features2d/doc/common_interfaces_of_descriptor_matchers.rst @@ -217,7 +217,7 @@ For each query descriptor, finds the training descriptors not farther than the s :param compactResult: Parameter used when the mask (or masks) is not empty. If ``compactResult`` is false, the ``matches`` vector has the same size as ``queryDescriptors`` rows. If ``compactResult`` is true, the ``matches`` vector does not contain matches for fully masked-out query descriptors. - :param maxDistance: Threshold for the distance between matched descriptors. + :param maxDistance: Threshold for the distance between matched descriptors. Distance means here metric distance (e.g. Hamming distance), not the distance between coordinates (which is measured in Pixels)! For each query descriptor, the methods find such training descriptors that the distance between the query descriptor and the training descriptor is equal or smaller than ``maxDistance``. Found matches are returned in the distance increasing order. From bc59428b3a363967d9097d1559a9a7601fd5a179 Mon Sep 17 00:00:00 2001 From: alex77git Date: Mon, 20 May 2013 02:28:40 +0200 Subject: [PATCH 017/178] Bug #2966, insert CV_Assert(size.width>0 && size.height>0); in imshow() --- modules/highgui/src/window.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/modules/highgui/src/window.cpp b/modules/highgui/src/window.cpp index 6d29534643..6801bd8a4b 100644 --- a/modules/highgui/src/window.cpp +++ b/modules/highgui/src/window.cpp @@ -256,12 +256,17 @@ namespace void cv::imshow( const string& winname, InputArray _img ) { + const Size size = _img.size(); #ifndef HAVE_OPENGL - Mat img = _img.getMat(); - CvMat c_img = img; - cvShowImage(winname.c_str(), &c_img); + CV_Assert(size.width>0 && size.height>0); + { + Mat img = _img.getMat(); + CvMat c_img = img; + cvShowImage(winname.c_str(), &c_img); + } #else const double useGl = getWindowProperty(winname, WND_PROP_OPENGL); + CV_Assert(size.width>0 && size.height>0); if (useGl <= 0) { @@ -275,7 +280,6 @@ void cv::imshow( const string& winname, InputArray _img ) if (autoSize > 0) { - Size size = _img.size(); resizeWindow(winname, size.width, size.height); } From 97e9368e758e6ce75a61a2e47572a15524f1a812 Mon Sep 17 00:00:00 2001 From: ograycode Date: Sun, 19 May 2013 22:04:56 -0400 Subject: [PATCH 018/178] Simple set of the camera index to allow the user to change it after the object has been initialized. --- .../generator/src/java/android+CameraBridgeViewBase.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/modules/java/generator/src/java/android+CameraBridgeViewBase.java b/modules/java/generator/src/java/android+CameraBridgeViewBase.java index 6c5c3294ff..b15ae2bd8f 100644 --- a/modules/java/generator/src/java/android+CameraBridgeViewBase.java +++ b/modules/java/generator/src/java/android+CameraBridgeViewBase.java @@ -80,6 +80,14 @@ public abstract class CameraBridgeViewBase extends SurfaceView implements Surfac mMaxHeight = MAX_UNSPECIFIED; styledAttrs.recycle(); } + + /** + * Sets the camera index + * @param camera index + */ + public void setCameraIndex(int cameraIndex) { + this.mCameraIndex = cameraIndex; + } public interface CvCameraViewListener { /** From d8b192c84d0b832174ece168356863674471ffb7 Mon Sep 17 00:00:00 2001 From: yao Date: Mon, 20 May 2013 14:46:17 +0800 Subject: [PATCH 019/178] Fix the mismatch on NV GPUs --- modules/ocl/src/opencl/arithm_mul.cl | 8 +++++++- modules/ocl/src/tvl1flow.cpp | 4 ++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/modules/ocl/src/opencl/arithm_mul.cl b/modules/ocl/src/opencl/arithm_mul.cl index e1cc9f6ab4..40988f5fed 100644 --- a/modules/ocl/src/opencl/arithm_mul.cl +++ b/modules/ocl/src/opencl/arithm_mul.cl @@ -277,9 +277,15 @@ __kernel void arithm_mul_D6 (__global double *src1, int src1_step, int src1_offs } #endif +#ifdef DOUBLE_SUPPORT +#define SCALAR_TYPE double +#else +#define SCALAR_TYPE float +#endif + __kernel void arithm_muls_D5 (__global float *src1, int src1_step, int src1_offset, __global float *dst, int dst_step, int dst_offset, - int rows, int cols, int dst_step1, float scalar) + int rows, int cols, int dst_step1, SCALAR_TYPE scalar) { int x = get_global_id(0); int y = get_global_id(1); diff --git a/modules/ocl/src/tvl1flow.cpp b/modules/ocl/src/tvl1flow.cpp index 8182f41a19..a322f62a4e 100644 --- a/modules/ocl/src/tvl1flow.cpp +++ b/modules/ocl/src/tvl1flow.cpp @@ -472,4 +472,8 @@ void ocl_tvl1flow::warpBackward(const oclMat &I0, const oclMat &I1, oclMat &I1x, args.push_back( make_pair( sizeof(cl_int), (void*)&u2_offset_y)); openCLExecuteKernel(clCxt, &tvl1flow, kernelName, globalThread, localThread, args, -1, -1); + + releaseTexture(I1_tex); + releaseTexture(I1x_tex); + releaseTexture(I1y_tex); } \ No newline at end of file From c8abaea368fbfb5a2437ed899f8088fb798f6ba6 Mon Sep 17 00:00:00 2001 From: alex77git Date: Mon, 20 May 2013 12:06:25 +0200 Subject: [PATCH 020/178] (tab to space) 2x --- modules/highgui/src/window.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/highgui/src/window.cpp b/modules/highgui/src/window.cpp index 6801bd8a4b..1e47bf6ee3 100644 --- a/modules/highgui/src/window.cpp +++ b/modules/highgui/src/window.cpp @@ -259,11 +259,11 @@ void cv::imshow( const string& winname, InputArray _img ) const Size size = _img.size(); #ifndef HAVE_OPENGL CV_Assert(size.width>0 && size.height>0); - { + { Mat img = _img.getMat(); CvMat c_img = img; cvShowImage(winname.c_str(), &c_img); - } + } #else const double useGl = getWindowProperty(winname, WND_PROP_OPENGL); CV_Assert(size.width>0 && size.height>0); From 445860d61902c5fa563beaf1eda25731fbecfadb Mon Sep 17 00:00:00 2001 From: alex77git Date: Mon, 20 May 2013 13:19:36 +0200 Subject: [PATCH 021/178] (typo) --- modules/core/doc/basic_structures.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/core/doc/basic_structures.rst b/modules/core/doc/basic_structures.rst index 91a61821ab..acfbb911d2 100644 --- a/modules/core/doc/basic_structures.rst +++ b/modules/core/doc/basic_structures.rst @@ -1682,7 +1682,7 @@ Returns the type of a matrix element. .. ocv:function:: int Mat::type() const -The method returns a matrix element type. This is an identifier compatible with the ``CvMat`` type system, like ``CV_16SC3`` for 16-bit signed 3-channel array, and so on. +The method returns a matrix element type. This is an identifier compatible with the ``CvMat`` type system, like ``CV_16SC3`` or 16-bit signed 3-channel array, and so on. Mat::depth From b4a4a05bdc7bcf220b132b5ab5fc9fb47b4e11ec Mon Sep 17 00:00:00 2001 From: peng xiao Date: Wed, 22 May 2013 13:46:42 +0800 Subject: [PATCH 022/178] Add ocl's good features to track implementation. Additional notes with this commit: 1. Add cornerHarris_dxdy and cornerMinEigenVal_dxdy to get the interim dx and dy output of Sobel operator; 2. Add minMax_buf to allow user to reuse buffers in minMax; 3. Fix an error when either min or max pointer fed into minMax is NULL; 4. Corner sorter temporarily uses C++ STL's quick sort. A parallel selection sort in OpneCL is contained in the implementation but disabled due to poor performance at the moment. 5. Accuracy test for ocl gfft. --- modules/ocl/include/opencv2/ocl/ocl.hpp | 54 ++- .../ocl/include/opencv2/ocl/private/util.hpp | 29 +- modules/ocl/src/arithm.cpp | 40 +- modules/ocl/src/gfft.cpp | 351 ++++++++++++++++++ modules/ocl/src/imgproc.cpp | 39 +- modules/ocl/src/mcwutil.cpp | 7 +- modules/ocl/src/opencl/imgproc_gfft.cl | 276 ++++++++++++++ modules/ocl/test/test_optflow.cpp | 77 ++++ 8 files changed, 841 insertions(+), 32 deletions(-) create mode 100644 modules/ocl/src/gfft.cpp create mode 100644 modules/ocl/src/opencl/imgproc_gfft.cl diff --git a/modules/ocl/include/opencv2/ocl/ocl.hpp b/modules/ocl/include/opencv2/ocl/ocl.hpp index 1cace848c3..f9fb4b44e5 100644 --- a/modules/ocl/include/opencv2/ocl/ocl.hpp +++ b/modules/ocl/include/opencv2/ocl/ocl.hpp @@ -122,8 +122,9 @@ namespace cv CV_EXPORTS void setBinpath(const char *path); //The two functions below enable other opencl program to use ocl module's cl_context and cl_command_queue + //returns cl_context * CV_EXPORTS void* getoclContext(); - + //returns cl_command_queue * CV_EXPORTS void* getoclCommandQueue(); //explicit call clFinish. The global command queue will be used. @@ -461,6 +462,7 @@ namespace cv // support all C1 types CV_EXPORTS void minMax(const oclMat &src, double *minVal, double *maxVal = 0, const oclMat &mask = oclMat()); + CV_EXPORTS void minMax_buf(const oclMat &src, double *minVal, double *maxVal, const oclMat &mask, oclMat& buf); //! finds global minimum and maximum array elements and returns their values with locations // support all C1 types @@ -789,7 +791,11 @@ namespace cv CV_EXPORTS void integral(const oclMat &src, oclMat &sum, oclMat &sqsum); CV_EXPORTS void integral(const oclMat &src, oclMat &sum); CV_EXPORTS void cornerHarris(const oclMat &src, oclMat &dst, int blockSize, int ksize, double k, int bordertype = cv::BORDER_DEFAULT); + CV_EXPORTS void cornerHarris_dxdy(const oclMat &src, oclMat &dst, oclMat &Dx, oclMat &Dy, + int blockSize, int ksize, double k, int bordertype = cv::BORDER_DEFAULT); CV_EXPORTS void cornerMinEigenVal(const oclMat &src, oclMat &dst, int blockSize, int ksize, int bordertype = cv::BORDER_DEFAULT); + CV_EXPORTS void cornerMinEigenVal_dxdy(const oclMat &src, oclMat &dst, oclMat &Dx, oclMat &Dy, + int blockSize, int ksize, int bordertype = cv::BORDER_DEFAULT); //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////CascadeClassifier////////////////////////////////////////////////////////////////// @@ -1253,6 +1259,52 @@ namespace cv public: explicit BFMatcher_OCL(int norm = NORM_L2) : BruteForceMatcher_OCL_base(norm == NORM_L1 ? L1Dist : norm == NORM_L2 ? L2Dist : HammingDist) {} }; + + class CV_EXPORTS GoodFeaturesToTrackDetector_OCL + { + public: + explicit GoodFeaturesToTrackDetector_OCL(int maxCorners = 1000, double qualityLevel = 0.01, double minDistance = 0.0, + int blockSize = 3, bool useHarrisDetector = false, double harrisK = 0.04); + + //! return 1 rows matrix with CV_32FC2 type + void operator ()(const oclMat& image, oclMat& corners, const oclMat& mask = oclMat()); + //! download points of type Point2f to a vector. the vector's content will be erased + void downloadPoints(const oclMat &points, vector &points_v); + + int maxCorners; + double qualityLevel; + double minDistance; + + int blockSize; + bool useHarrisDetector; + double harrisK; + void releaseMemory() + { + Dx_.release(); + Dy_.release(); + eig_.release(); + minMaxbuf_.release(); + tmpCorners_.release(); + } + private: + oclMat Dx_; + oclMat Dy_; + oclMat eig_; + oclMat minMaxbuf_; + oclMat tmpCorners_; + }; + + inline GoodFeaturesToTrackDetector_OCL::GoodFeaturesToTrackDetector_OCL(int maxCorners_, double qualityLevel_, double minDistance_, + int blockSize_, bool useHarrisDetector_, double harrisK_) + { + maxCorners = maxCorners_; + qualityLevel = qualityLevel_; + minDistance = minDistance_; + blockSize = blockSize_; + useHarrisDetector = useHarrisDetector_; + harrisK = harrisK_; + } + /////////////////////////////// PyrLKOpticalFlow ///////////////////////////////////// class CV_EXPORTS PyrLKOpticalFlow { diff --git a/modules/ocl/include/opencv2/ocl/private/util.hpp b/modules/ocl/include/opencv2/ocl/private/util.hpp index f3e582f483..23a3ad468e 100644 --- a/modules/ocl/include/opencv2/ocl/private/util.hpp +++ b/modules/ocl/include/opencv2/ocl/private/util.hpp @@ -120,6 +120,33 @@ namespace cv cl_mem CV_EXPORTS bindTexture(const oclMat &mat); void CV_EXPORTS releaseTexture(cl_mem& texture); + //Represents an image texture object + class CV_EXPORTS TextureCL + { + public: + TextureCL(cl_mem tex, int r, int c, int t) + : tex_(tex), rows(r), cols(c), type(t) {} + ~TextureCL() + { + openCLFree(tex_); + } + operator cl_mem() + { + return tex_; + } + cl_mem const tex_; + const int rows; + const int cols; + const int type; + private: + //disable assignment + void operator=(const TextureCL&); + }; + // bind oclMat to OpenCL image textures and retunrs an TextureCL object + // note: + // for faster clamping, there is no buffer padding for the constructed texture + Ptr CV_EXPORTS bindTexturePtr(const oclMat &mat); + // returns whether the current context supports image2d_t format or not bool CV_EXPORTS support_image2d(Context *clCxt = Context::getContext()); @@ -132,7 +159,7 @@ namespace cv }; template _ty queryDeviceInfo(cl_kernel kernel = NULL); - //info should have been pre-allocated + template<> int CV_EXPORTS queryDeviceInfo(cl_kernel kernel); template<> diff --git a/modules/ocl/src/arithm.cpp b/modules/ocl/src/arithm.cpp index ed2515dc6d..34569dc575 100644 --- a/modules/ocl/src/arithm.cpp +++ b/modules/ocl/src/arithm.cpp @@ -782,45 +782,55 @@ static void arithmetic_minMax_mask_run(const oclMat &src, const oclMat &mask, cl } } -template void arithmetic_minMax(const oclMat &src, double *minVal, double *maxVal, const oclMat &mask) +template void arithmetic_minMax(const oclMat &src, double *minVal, double *maxVal, + const oclMat &mask, oclMat &buf) { size_t groupnum = src.clCxt->computeUnits(); CV_Assert(groupnum != 0); groupnum = groupnum * 2; int vlen = 8; int dbsize = groupnum * 2 * vlen * sizeof(T) ; - Context *clCxt = src.clCxt; - cl_mem dstBuffer = openCLCreateBuffer(clCxt, CL_MEM_WRITE_ONLY, dbsize); - *minVal = std::numeric_limits::max() , *maxVal = -std::numeric_limits::max(); + + ensureSizeIsEnough(1, dbsize, CV_8UC1, buf); + + cl_mem buf_data = reinterpret_cast(buf.data); + if (mask.empty()) { - arithmetic_minMax_run(src, mask, dstBuffer, vlen, groupnum, "arithm_op_minMax"); + arithmetic_minMax_run(src, mask, buf_data, vlen, groupnum, "arithm_op_minMax"); } else { - arithmetic_minMax_mask_run(src, mask, dstBuffer, vlen, groupnum, "arithm_op_minMax_mask"); + arithmetic_minMax_mask_run(src, mask, buf_data, vlen, groupnum, "arithm_op_minMax_mask"); } - T *p = new T[groupnum * vlen * 2]; - memset(p, 0, dbsize); - openCLReadBuffer(clCxt, dstBuffer, (void *)p, dbsize); - if(minVal != NULL){ + + Mat matbuf = Mat(buf); + T *p = matbuf.ptr(); + if(minVal != NULL) + { + *minVal = std::numeric_limits::max(); for(int i = 0; i < vlen * (int)groupnum; i++) { *minVal = *minVal < p[i] ? *minVal : p[i]; } } - if(maxVal != NULL){ + if(maxVal != NULL) + { + *maxVal = -std::numeric_limits::max(); for(int i = vlen * (int)groupnum; i < 2 * vlen * (int)groupnum; i++) { *maxVal = *maxVal > p[i] ? *maxVal : p[i]; } } - delete[] p; - openCLFree(dstBuffer); } -typedef void (*minMaxFunc)(const oclMat &src, double *minVal, double *maxVal, const oclMat &mask); +typedef void (*minMaxFunc)(const oclMat &src, double *minVal, double *maxVal, const oclMat &mask, oclMat &buf); void cv::ocl::minMax(const oclMat &src, double *minVal, double *maxVal, const oclMat &mask) +{ + oclMat buf; + minMax_buf(src, minVal, maxVal, mask, buf); +} +void cv::ocl::minMax_buf(const oclMat &src, double *minVal, double *maxVal, const oclMat &mask, oclMat &buf) { CV_Assert(src.oclchannels() == 1); if(!src.clCxt->supportsFeature(Context::CL_DOUBLE) && src.depth() == CV_64F) @@ -840,7 +850,7 @@ void cv::ocl::minMax(const oclMat &src, double *minVal, double *maxVal, const oc }; minMaxFunc func; func = functab[src.depth()]; - func(src, minVal, maxVal, mask); + func(src, minVal, maxVal, mask, buf); } ////////////////////////////////////////////////////////////////////////////// diff --git a/modules/ocl/src/gfft.cpp b/modules/ocl/src/gfft.cpp new file mode 100644 index 0000000000..af7580bd43 --- /dev/null +++ b/modules/ocl/src/gfft.cpp @@ -0,0 +1,351 @@ +/*M/////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// License Agreement +// For Open Source Computer Vision Library +// +// Copyright (C) 2010-2012, Multicoreware, Inc., all rights reserved. +// Copyright (C) 2010-2012, Advanced Micro Devices, Inc., all rights reserved. +// Third party copyrights are property of their respective owners. +// +// @Authors +// Peng Xiao, pengxiao@outlook.com +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other oclMaterials provided with the distribution. +// +// * The name of the copyright holders may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors as is and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the Intel Corporation or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +//M*/ +#include +#include "precomp.hpp" + +using namespace cv; +using namespace cv::ocl; + +static bool use_cpu_sorter = true; + +namespace cv +{ + namespace ocl + { + ///////////////////////////OpenCL kernel strings/////////////////////////// + extern const char *imgproc_gfft; + } +} + +namespace +{ +enum SortMethod +{ + CPU_STL, + BITONIC, + SELECTION +}; + +const int GROUP_SIZE = 256; + +template +struct Sorter +{ + //typedef EigType; +}; + +//TODO(pengx): optimize GPU sorter's performance thus CPU sorter is removed. +template<> +struct Sorter +{ + typedef oclMat EigType; + static cv::Mutex cs; + static Mat mat_eig; + + //prototype + static int clfloat2Gt(cl_float2 pt1, cl_float2 pt2) + { + float v1 = mat_eig.at(cvRound(pt1.s[1]), cvRound(pt1.s[0])); + float v2 = mat_eig.at(cvRound(pt2.s[1]), cvRound(pt2.s[0])); + return v1 > v2; + } + static void sortCorners_caller(const EigType& eig_tex, oclMat& corners, const int count) + { + cv::AutoLock lock(cs); + //temporarily use STL's sort function + Mat mat_corners = corners; + mat_eig = eig_tex; + std::sort(mat_corners.begin(), mat_corners.begin() + count, clfloat2Gt); + corners = mat_corners; + } +}; +cv::Mutex Sorter::cs; +cv::Mat Sorter::mat_eig; + +template<> +struct Sorter +{ + typedef TextureCL EigType; + + static void sortCorners_caller(const EigType& eig_tex, oclMat& corners, const int count) + { + Context * cxt = Context::getContext(); + size_t globalThreads[3] = {count / 2, 1, 1}; + size_t localThreads[3] = {GROUP_SIZE, 1, 1}; + + // 2^numStages should be equal to count or the output is invalid + int numStages = 0; + for(int i = count; i > 1; i >>= 1) + { + ++numStages; + } + const int argc = 5; + std::vector< std::pair > args(argc); + std::string kernelname = "sortCorners_bitonicSort"; + args[0] = std::make_pair(sizeof(cl_mem), (void *)&eig_tex); + args[1] = std::make_pair(sizeof(cl_mem), (void *)&corners.data); + args[2] = std::make_pair(sizeof(cl_int), (void *)&count); + for(int stage = 0; stage < numStages; ++stage) + { + args[3] = std::make_pair(sizeof(cl_int), (void *)&stage); + for(int passOfStage = 0; passOfStage < stage + 1; ++passOfStage) + { + args[4] = std::make_pair(sizeof(cl_int), (void *)&passOfStage); + openCLExecuteKernel(cxt, &imgproc_gfft, kernelname, globalThreads, localThreads, args, -1, -1); + } + } + } +}; + +template<> +struct Sorter +{ + typedef TextureCL EigType; + + static void sortCorners_caller(const EigType& eig_tex, oclMat& corners, const int count) + { + Context * cxt = Context::getContext(); + + size_t globalThreads[3] = {count, 1, 1}; + size_t localThreads[3] = {GROUP_SIZE, 1, 1}; + + std::vector< std::pair > args; + //local + std::string kernelname = "sortCorners_selectionSortLocal"; + int lds_size = GROUP_SIZE * sizeof(cl_float2); + args.push_back( std::make_pair( sizeof(cl_mem), (void*)&eig_tex) ); + args.push_back( std::make_pair( sizeof(cl_mem), (void*)&corners.data) ); + args.push_back( std::make_pair( sizeof(cl_int), (void*)&count) ); + args.push_back( std::make_pair( lds_size, (void*)NULL) ); + + openCLExecuteKernel(cxt, &imgproc_gfft, kernelname, globalThreads, localThreads, args, -1, -1); + + //final + kernelname = "sortCorners_selectionSortFinal"; + args.pop_back(); + openCLExecuteKernel(cxt, &imgproc_gfft, kernelname, globalThreads, localThreads, args, -1, -1); + } +}; + +int findCorners_caller( + const TextureCL& eig, + const float threshold, + const oclMat& mask, + oclMat& corners, + const int max_count) +{ + std::vector k; + Context * cxt = Context::getContext(); + + std::vector< std::pair > args; + std::string kernelname = "findCorners"; + + const int mask_strip = mask.step / mask.elemSize1(); + + oclMat g_counter(1, 1, CV_32SC1); + g_counter.setTo(0); + + args.push_back(make_pair( sizeof(cl_mem), (void*)&eig )); + args.push_back(make_pair( sizeof(cl_mem), (void*)&mask.data )); + args.push_back(make_pair( sizeof(cl_mem), (void*)&corners.data )); + args.push_back(make_pair( sizeof(cl_int), (void*)&mask_strip)); + args.push_back(make_pair( sizeof(cl_float), (void*)&threshold )); + args.push_back(make_pair( sizeof(cl_int), (void*)&eig.rows )); + args.push_back(make_pair( sizeof(cl_int), (void*)&eig.cols )); + args.push_back(make_pair( sizeof(cl_int), (void*)&max_count )); + args.push_back(make_pair( sizeof(cl_mem), (void*)&g_counter.data )); + + size_t globalThreads[3] = {eig.cols, eig.rows, 1}; + size_t localThreads[3] = {16, 16, 1}; + + const char * opt = mask.empty() ? "" : "-D WITH_MASK"; + openCLExecuteKernel(cxt, &imgproc_gfft, kernelname, globalThreads, localThreads, args, -1, -1, opt); + return std::min(Mat(g_counter).at(0), max_count); +} +}//unnamed namespace + +void cv::ocl::GoodFeaturesToTrackDetector_OCL::operator ()(const oclMat& image, oclMat& corners, const oclMat& mask) +{ + CV_Assert(qualityLevel > 0 && minDistance >= 0 && maxCorners >= 0); + CV_Assert(mask.empty() || (mask.type() == CV_8UC1 && mask.size() == image.size())); + + CV_DbgAssert(support_image2d()); + + ensureSizeIsEnough(image.size(), CV_32F, eig_); + + if (useHarrisDetector) + cornerMinEigenVal_dxdy(image, eig_, Dx_, Dy_, blockSize, 3, harrisK); + else + cornerMinEigenVal_dxdy(image, eig_, Dx_, Dy_, blockSize, 3); + + double maxVal = 0; + minMax_buf(eig_, 0, &maxVal, oclMat(), minMaxbuf_); + + ensureSizeIsEnough(1, std::max(1000, static_cast(image.size().area() * 0.05)), CV_32FC2, tmpCorners_); + + Ptr eig_tex = bindTexturePtr(eig_); + int total = findCorners_caller( + *eig_tex, + static_cast(maxVal * qualityLevel), + mask, + tmpCorners_, + tmpCorners_.cols); + + if (total == 0) + { + corners.release(); + return; + } + if(use_cpu_sorter) + { + Sorter::sortCorners_caller(eig_, tmpCorners_, total); + } + else + { + //if total is power of 2 + if(((total - 1) & (total)) == 0) + { + Sorter::sortCorners_caller(*eig_tex, tmpCorners_, total); + } + else + { + Sorter::sortCorners_caller(*eig_tex, tmpCorners_, total); + } + } + + if (minDistance < 1) + { + corners = tmpCorners_(Rect(0, 0, maxCorners > 0 ? std::min(maxCorners, total) : total, 1)); + } + else + { + vector tmp(total); + downloadPoints(tmpCorners_, tmp); + + vector tmp2; + tmp2.reserve(total); + + const int cell_size = cvRound(minDistance); + const int grid_width = (image.cols + cell_size - 1) / cell_size; + const int grid_height = (image.rows + cell_size - 1) / cell_size; + + std::vector< std::vector > grid(grid_width * grid_height); + + for (int i = 0; i < total; ++i) + { + Point2f p = tmp[i]; + + bool good = true; + + int x_cell = static_cast(p.x / cell_size); + int y_cell = static_cast(p.y / cell_size); + + int x1 = x_cell - 1; + int y1 = y_cell - 1; + int x2 = x_cell + 1; + int y2 = y_cell + 1; + + // boundary check + x1 = std::max(0, x1); + y1 = std::max(0, y1); + x2 = std::min(grid_width - 1, x2); + y2 = std::min(grid_height - 1, y2); + + for (int yy = y1; yy <= y2; yy++) + { + for (int xx = x1; xx <= x2; xx++) + { + vector& m = grid[yy * grid_width + xx]; + + if (!m.empty()) + { + for(size_t j = 0; j < m.size(); j++) + { + float dx = p.x - m[j].x; + float dy = p.y - m[j].y; + + if (dx * dx + dy * dy < minDistance * minDistance) + { + good = false; + goto break_out; + } + } + } + } + } + + break_out: + + if(good) + { + grid[y_cell * grid_width + x_cell].push_back(p); + + tmp2.push_back(p); + + if (maxCorners > 0 && tmp2.size() == static_cast(maxCorners)) + break; + } + } + + corners.upload(Mat(1, static_cast(tmp2.size()), CV_32FC2, &tmp2[0])); + } +} +void cv::ocl::GoodFeaturesToTrackDetector_OCL::downloadPoints(const oclMat &points, vector &points_v) +{ + CV_DbgAssert(points.type() == CV_32FC2); + points_v.resize(points.cols); + openCLSafeCall(clEnqueueReadBuffer( + *reinterpret_cast(getoclCommandQueue()), + reinterpret_cast(points.data), + CL_TRUE, + 0, + points.cols * sizeof(Point2f), + &points_v[0], + 0, + NULL, + NULL)); +} + + diff --git a/modules/ocl/src/imgproc.cpp b/modules/ocl/src/imgproc.cpp index ee1e92a712..83643d2905 100644 --- a/modules/ocl/src/imgproc.cpp +++ b/modules/ocl/src/imgproc.cpp @@ -1207,30 +1207,41 @@ namespace cv void cornerHarris(const oclMat &src, oclMat &dst, int blockSize, int ksize, double k, int borderType) { - if(!src.clCxt->supportsFeature(Context::CL_DOUBLE) && src.depth() == CV_64F) - { - CV_Error(CV_GpuNotSupported, "select device don't support double"); - } - CV_Assert(src.cols >= blockSize / 2 && src.rows >= blockSize / 2); - oclMat Dx, Dy; - CV_Assert(borderType == cv::BORDER_CONSTANT || borderType == cv::BORDER_REFLECT101 || borderType == cv::BORDER_REPLICATE || borderType == cv::BORDER_REFLECT); - extractCovData(src, Dx, Dy, blockSize, ksize, borderType); - dst.create(src.size(), CV_32F); - corner_ocl(imgproc_calcHarris, "calcHarris", blockSize, static_cast(k), Dx, Dy, dst, borderType); + oclMat dx, dy; + cornerHarris_dxdy(src, dst, dx, dy, blockSize, ksize, k, borderType); } - void cornerMinEigenVal(const oclMat &src, oclMat &dst, int blockSize, int ksize, int borderType) + void cornerHarris_dxdy(const oclMat &src, oclMat &dst, oclMat &dx, oclMat &dy, int blockSize, int ksize, + double k, int borderType) { if(!src.clCxt->supportsFeature(Context::CL_DOUBLE) && src.depth() == CV_64F) { CV_Error(CV_GpuNotSupported, "select device don't support double"); } CV_Assert(src.cols >= blockSize / 2 && src.rows >= blockSize / 2); - oclMat Dx, Dy; CV_Assert(borderType == cv::BORDER_CONSTANT || borderType == cv::BORDER_REFLECT101 || borderType == cv::BORDER_REPLICATE || borderType == cv::BORDER_REFLECT); - extractCovData(src, Dx, Dy, blockSize, ksize, borderType); + extractCovData(src, dx, dy, blockSize, ksize, borderType); dst.create(src.size(), CV_32F); - corner_ocl(imgproc_calcMinEigenVal, "calcMinEigenVal", blockSize, 0, Dx, Dy, dst, borderType); + corner_ocl(imgproc_calcHarris, "calcHarris", blockSize, static_cast(k), dx, dy, dst, borderType); + } + + void cornerMinEigenVal(const oclMat &src, oclMat &dst, int blockSize, int ksize, int borderType) + { + oclMat dx, dy; + cornerMinEigenVal_dxdy(src, dst, dx, dy, blockSize, ksize, borderType); + } + + void cornerMinEigenVal_dxdy(const oclMat &src, oclMat &dst, oclMat &dx, oclMat &dy, int blockSize, int ksize, int borderType) + { + if(!src.clCxt->supportsFeature(Context::CL_DOUBLE) && src.depth() == CV_64F) + { + CV_Error(CV_GpuNotSupported, "select device don't support double"); + } + CV_Assert(src.cols >= blockSize / 2 && src.rows >= blockSize / 2); + CV_Assert(borderType == cv::BORDER_CONSTANT || borderType == cv::BORDER_REFLECT101 || borderType == cv::BORDER_REPLICATE || borderType == cv::BORDER_REFLECT); + extractCovData(src, dx, dy, blockSize, ksize, borderType); + dst.create(src.size(), CV_32F); + corner_ocl(imgproc_calcMinEigenVal, "calcMinEigenVal", blockSize, 0, dx, dy, dst, borderType); } /////////////////////////////////// MeanShiftfiltering /////////////////////////////////////////////// static void meanShiftFiltering_gpu(const oclMat &src, oclMat dst, int sp, int sr, int maxIter, float eps) diff --git a/modules/ocl/src/mcwutil.cpp b/modules/ocl/src/mcwutil.cpp index 3bcb8700b7..b1f8eebf64 100644 --- a/modules/ocl/src/mcwutil.cpp +++ b/modules/ocl/src/mcwutil.cpp @@ -156,7 +156,7 @@ namespace cv format.image_channel_order = CL_RGBA; break; default: - CV_Error(-1, "Image forma is not supported"); + CV_Error(-1, "Image format is not supported"); break; } #ifdef CL_VERSION_1_2 @@ -225,6 +225,11 @@ namespace cv openCLSafeCall(err); return texture; } + Ptr bindTexturePtr(const oclMat &mat) + { + return Ptr(new TextureCL(bindTexture(mat), mat.rows, mat.cols, mat.type())); + } + void releaseTexture(cl_mem& texture) { openCLFree(texture); diff --git a/modules/ocl/src/opencl/imgproc_gfft.cl b/modules/ocl/src/opencl/imgproc_gfft.cl new file mode 100644 index 0000000000..5fa27ffc1b --- /dev/null +++ b/modules/ocl/src/opencl/imgproc_gfft.cl @@ -0,0 +1,276 @@ +/*M/////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// License Agreement +// For Open Source Computer Vision Library +// +// Copyright (C) 2010-2012, Multicoreware, Inc., all rights reserved. +// Copyright (C) 2010-2012, Advanced Micro Devices, Inc., all rights reserved. +// Third party copyrights are property of their respective owners. +// +// @Authors +// Peng Xiao, pengxiao@outlook.com +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other oclMaterials provided with the distribution. +// +// * The name of the copyright holders may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors as is and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the Intel Corporation or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +//M*/ + +#ifndef WITH_MASK +#define WITH_MASK 0 +#endif + +__constant sampler_t sampler = CLK_NORMALIZED_COORDS_FALSE | CLK_ADDRESS_CLAMP_TO_EDGE | CLK_FILTER_NEAREST; + +inline float ELEM_INT2(image2d_t _eig, int _x, int _y) +{ + return read_imagef(_eig, sampler, (int2)(_x, _y)).x; +} + +inline float ELEM_FLT2(image2d_t _eig, float2 pt) +{ + return read_imagef(_eig, sampler, pt).x; +} + +__kernel + void findCorners + ( + image2d_t eig, + __global const char * mask, + __global float2 * corners, + const int mask_strip,// in pixels + const float threshold, + const int rows, + const int cols, + const int max_count, + __global int * g_counter + ) +{ + const int j = get_global_id(0); + const int i = get_global_id(1); + + if (i > 0 && i < rows - 1 && j > 0 && j < cols - 1 +#if WITH_MASK + && mask[i * mask_strip + j] != 0 +#endif + ) + { + const float val = ELEM_INT2(eig, j, i); + + if (val > threshold) + { + float maxVal = val; + + maxVal = fmax(ELEM_INT2(eig, j - 1, i - 1), maxVal); + maxVal = fmax(ELEM_INT2(eig, j , i - 1), maxVal); + maxVal = fmax(ELEM_INT2(eig, j + 1, i - 1), maxVal); + + maxVal = fmax(ELEM_INT2(eig, j - 1, i), maxVal); + maxVal = fmax(ELEM_INT2(eig, j + 1, i), maxVal); + + maxVal = fmax(ELEM_INT2(eig, j - 1, i + 1), maxVal); + maxVal = fmax(ELEM_INT2(eig, j , i + 1), maxVal); + maxVal = fmax(ELEM_INT2(eig, j + 1, i + 1), maxVal); + + if (val == maxVal) + { + const int ind = atomic_inc(g_counter); + + if (ind < max_count) + corners[ind] = (float2)(j, i); + } + } + } +} + +//bitonic sort +__kernel + void sortCorners_bitonicSort + ( + image2d_t eig, + __global float2 * corners, + const int count, + const int stage, + const int passOfStage + ) +{ + const int threadId = get_global_id(0); + if(threadId >= count / 2) + { + return; + } + + const int sortOrder = (((threadId/(1 << stage)) % 2)) == 1 ? 1 : 0; // 0 is descent + + const int pairDistance = 1 << (stage - passOfStage); + const int blockWidth = 2 * pairDistance; + + const int leftId = min( (threadId % pairDistance) + + (threadId / pairDistance) * blockWidth, count ); + + const int rightId = min( leftId + pairDistance, count ); + + const float2 leftPt = corners[leftId]; + const float2 rightPt = corners[rightId]; + + const float leftVal = ELEM_FLT2(eig, leftPt); + const float rightVal = ELEM_FLT2(eig, rightPt); + + const bool compareResult = leftVal > rightVal; + + float2 greater = compareResult ? leftPt:rightPt; + float2 lesser = compareResult ? rightPt:leftPt; + + corners[leftId] = sortOrder ? lesser : greater; + corners[rightId] = sortOrder ? greater : lesser; +} + +//selection sort for gfft +//kernel is ported from Bolt library: +//https://github.com/HSA-Libraries/Bolt/blob/master/include/bolt/cl/sort_kernels.cl +// Local sort will firstly sort elements of each workgroup using selection sort +// its performance is O(n) +__kernel + void sortCorners_selectionSortLocal + ( + image2d_t eig, + __global float2 * corners, + const int count, + __local float2 * scratch + ) +{ + int i = get_local_id(0); // index in workgroup + int numOfGroups = get_num_groups(0); // index in workgroup + int groupID = get_group_id(0); + int wg = get_local_size(0); // workgroup size = block size + int n; // number of elements to be processed for this work group + + int offset = groupID * wg; + int same = 0; + corners += offset; + n = (groupID == (numOfGroups-1))? (count - wg*(numOfGroups-1)) : wg; + float2 pt1, pt2; + + pt1 = corners[min(i, n)]; + scratch[i] = pt1; + barrier(CLK_LOCAL_MEM_FENCE); + + if(i >= n) + { + return; + } + + float val1 = ELEM_FLT2(eig, pt1); + float val2; + + int pos = 0; + for (int j=0;j val1) + pos++;//calculate the rank of this element in this work group + else + { + if(val1 > val2) + continue; + else + { + // val1 and val2 are same + same++; + } + } + } + for (int j=0; j< same; j++) + corners[pos + j] = pt1; +} +__kernel + void sortCorners_selectionSortFinal + ( + image2d_t eig, + __global float2 * corners, + const int count + ) +{ + const int i = get_local_id(0); // index in workgroup + const int numOfGroups = get_num_groups(0); // index in workgroup + const int groupID = get_group_id(0); + const int wg = get_local_size(0); // workgroup size = block size + int pos = 0, same = 0; + const int offset = get_group_id(0) * wg; + const int remainder = count - wg*(numOfGroups-1); + + if((offset + i ) >= count) + return; + float2 pt1, pt2; + pt1 = corners[groupID*wg + i]; + + float val1 = ELEM_FLT2(eig, pt1); + float val2; + + for(int j=0; j val2) + break; + else + { + //Increment only if the value is not the same. + if( val2 > val1 ) + pos++; + else + same++; + } + } + } + + for(int k=0; k val2) + break; + else + { + //Don't increment if the value is the same. + //Two elements are same if (*userComp)(jData, iData) and (*userComp)(iData, jData) are both false + if(val2 > val1) + pos++; + else + same++; + } + } + for (int j=0; j< same; j++) + corners[pos + j] = pt1; +} + diff --git a/modules/ocl/test/test_optflow.cpp b/modules/ocl/test/test_optflow.cpp index b08d33a08f..0121be8f9e 100644 --- a/modules/ocl/test/test_optflow.cpp +++ b/modules/ocl/test/test_optflow.cpp @@ -55,6 +55,83 @@ using namespace testing; using namespace std; extern string workdir; + + +////////////////////////////////////////////////////// +// GoodFeaturesToTrack +namespace +{ + IMPLEMENT_PARAM_CLASS(MinDistance, double) +} +PARAM_TEST_CASE(GoodFeaturesToTrack, MinDistance) +{ + double minDistance; + + virtual void SetUp() + { + minDistance = GET_PARAM(0); + } +}; + +TEST_P(GoodFeaturesToTrack, Accuracy) +{ + cv::Mat frame = readImage(workdir + "../gpu/rubberwhale1.png", cv::IMREAD_GRAYSCALE); + ASSERT_FALSE(frame.empty()); + + int maxCorners = 1000; + double qualityLevel = 0.01; + + cv::ocl::GoodFeaturesToTrackDetector_OCL detector(maxCorners, qualityLevel, minDistance); + + cv::ocl::oclMat d_pts; + detector(oclMat(frame), d_pts); + + ASSERT_FALSE(d_pts.empty()); + + std::vector pts(d_pts.cols); + + detector.downloadPoints(d_pts, pts); + + std::vector pts_gold; + cv::goodFeaturesToTrack(frame, pts_gold, maxCorners, qualityLevel, minDistance); + + ASSERT_EQ(pts_gold.size(), pts.size()); + + size_t mistmatch = 0; + for (size_t i = 0; i < pts.size(); ++i) + { + cv::Point2i a = pts_gold[i]; + cv::Point2i b = pts[i]; + + bool eq = std::abs(a.x - b.x) < 1 && std::abs(a.y - b.y) < 1; + + if (!eq) + ++mistmatch; + } + + double bad_ratio = static_cast(mistmatch) / pts.size(); + + ASSERT_LE(bad_ratio, 0.01); +} + +TEST_P(GoodFeaturesToTrack, EmptyCorners) +{ + int maxCorners = 1000; + double qualityLevel = 0.01; + + cv::ocl::GoodFeaturesToTrackDetector_OCL detector(maxCorners, qualityLevel, minDistance); + + cv::ocl::oclMat src(100, 100, CV_8UC1, cv::Scalar::all(0)); + cv::ocl::oclMat corners(1, maxCorners, CV_32FC2); + + detector(src, corners); + + ASSERT_TRUE(corners.empty()); +} + +INSTANTIATE_TEST_CASE_P(OCL_Video, GoodFeaturesToTrack, + testing::Values(MinDistance(0.0), MinDistance(3.0))); + ////////////////////////////////////////////////////////////////////////// PARAM_TEST_CASE(TVL1, bool) { From 46b770f2559df847eea375dd584b2cd14b11e58d Mon Sep 17 00:00:00 2001 From: Jan Machacek Date: Wed, 22 May 2013 13:22:16 +0100 Subject: [PATCH 023/178] Fixed include name in OpenCL on OS X --- modules/ocl/include/opencv2/ocl/private/util.hpp | 2 +- modules/ocl/src/safe_call.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ocl/include/opencv2/ocl/private/util.hpp b/modules/ocl/include/opencv2/ocl/private/util.hpp index 081d2343dc..f1333fcae2 100644 --- a/modules/ocl/include/opencv2/ocl/private/util.hpp +++ b/modules/ocl/include/opencv2/ocl/private/util.hpp @@ -49,7 +49,7 @@ #include "opencv2/ocl/ocl.hpp" #if defined __APPLE__ -#include +#include #else #include #endif diff --git a/modules/ocl/src/safe_call.hpp b/modules/ocl/src/safe_call.hpp index 441495f860..ba36cabd32 100644 --- a/modules/ocl/src/safe_call.hpp +++ b/modules/ocl/src/safe_call.hpp @@ -47,7 +47,7 @@ #define __OPENCV_OPENCL_SAFE_CALL_HPP__ #if defined __APPLE__ -#include +#include #else #include #endif From a223b5624f1e535fbcf9754c7a4e2ad8ce4f1432 Mon Sep 17 00:00:00 2001 From: yao Date: Thu, 23 May 2013 10:55:08 +0800 Subject: [PATCH 024/178] fix pyrLK's mismatch on Intel GPUs --- modules/ocl/src/opencl/pyrlk.cl | 438 ++++++++------------------------ modules/ocl/src/pyrlk.cpp | 80 +----- 2 files changed, 115 insertions(+), 403 deletions(-) diff --git a/modules/ocl/src/opencl/pyrlk.cl b/modules/ocl/src/opencl/pyrlk.cl index 1043b8410b..40a1993952 100644 --- a/modules/ocl/src/opencl/pyrlk.cl +++ b/modules/ocl/src/opencl/pyrlk.cl @@ -46,145 +46,10 @@ //#pragma OPENCL EXTENSION cl_amd_printf : enable -__kernel void calcSharrDeriv_vertical_C1_D0(__global const uchar* src, int srcStep, int rows, int cols, int cn, __global short* dx_buf, int dx_bufStep, __global short* dy_buf, int dy_bufStep) -{ - const int x = get_global_id(0); - const int y = get_global_id(1); - - if (y < rows && x < cols * cn) - { - const uchar src_val0 = (src + (y > 0 ? y-1 : rows > 1 ? 1 : 0) * srcStep)[x]; - const uchar src_val1 = (src + y * srcStep)[x]; - const uchar src_val2 = (src + (y < rows-1 ? y+1 : rows > 1 ? rows-2 : 0) * srcStep)[x]; - - ((__global short*)((__global char*)dx_buf + y * dx_bufStep / 2))[x] = (src_val0 + src_val2) * 3 + src_val1 * 10; - ((__global short*)((__global char*)dy_buf + y * dy_bufStep / 2))[x] = src_val2 - src_val0; - } -} - -__kernel void calcSharrDeriv_vertical_C4_D0(__global const uchar* src, int srcStep, int rows, int cols, int cn, __global short* dx_buf, int dx_bufStep, __global short* dy_buf, int dy_bufStep) -{ - const int x = get_global_id(0); - const int y = get_global_id(1); - - if (y < rows && x < cols * cn) - { - const uchar src_val0 = (src + (y > 0 ? y - 1 : 1) * srcStep)[x]; - const uchar src_val1 = (src + y * srcStep)[x]; - const uchar src_val2 = (src + (y < rows - 1 ? y + 1 : rows - 2) * srcStep)[x]; - - ((__global short*)((__global char*)dx_buf + y * dx_bufStep / 2))[x] = (src_val0 + src_val2) * 3 + src_val1 * 10; - ((__global short*)((__global char*)dy_buf + y * dy_bufStep / 2))[x] = src_val2 - src_val0; - } -} - -__kernel void calcSharrDeriv_horizontal_C1_D0(int rows, int cols, int cn, __global const short* dx_buf, int dx_bufStep, __global const short* dy_buf, int dy_bufStep, __global short* dIdx, int dIdxStep, __global short* dIdy, int dIdyStep) -{ - const int x = get_global_id(0); - const int y = get_global_id(1); - - const int colsn = cols * cn; - - if (y < rows && x < colsn) - { - __global const short* dx_buf_row = dx_buf + y * dx_bufStep; - __global const short* dy_buf_row = dy_buf + y * dy_bufStep; - - const int xr = x + cn < colsn ? x + cn : (cols - 2) * cn + x + cn - colsn; - const int xl = x - cn >= 0 ? x - cn : cn + x; - - ((__global short*)((__global char*)dIdx + y * dIdxStep / 2))[x] = dx_buf_row[xr] - dx_buf_row[xl]; - ((__global short*)((__global char*)dIdy + y * dIdyStep / 2))[x] = (dy_buf_row[xr] + dy_buf_row[xl]) * 3 + dy_buf_row[x] * 10; - } -} - -__kernel void calcSharrDeriv_horizontal_C4_D0(int rows, int cols, int cn, __global const short* dx_buf, int dx_bufStep, __global const short* dy_buf, int dy_bufStep, __global short* dIdx, int dIdxStep, __global short* dIdy, int dIdyStep) -{ - const int x = get_global_id(0); - const int y = get_global_id(1); - - const int colsn = cols * cn; - - if (y < rows && x < colsn) - { - __global const short* dx_buf_row = dx_buf + y * dx_bufStep; - __global const short* dy_buf_row = dy_buf + y * dy_bufStep; - - const int xr = x + cn < colsn ? x + cn : (cols - 2) * cn + x + cn - colsn; - const int xl = x - cn >= 0 ? x - cn : cn + x; - - ((__global short*)((__global char*)dIdx + y * dIdxStep / 2))[x] = dx_buf_row[xr] - dx_buf_row[xl]; - ((__global short*)((__global char*)dIdy + y * dIdyStep / 2))[x] = (dy_buf_row[xr] + dy_buf_row[xl]) * 3 + dy_buf_row[x] * 10; - } -} - -#define W_BITS 14 -#define W_BITS1 14 - -#define CV_DESCALE(x, n) (((x) + (1 << ((n)-1))) >> (n)) - -int linearFilter_uchar(__global const uchar* src, int srcStep, int cn, float2 pt, int x, int y) -{ - int2 ipt; - ipt.x = convert_int_sat_rtn(pt.x); - ipt.y = convert_int_sat_rtn(pt.y); - - float a = pt.x - ipt.x; - float b = pt.y - ipt.y; - - int iw00 = convert_int_sat_rte((1.0f - a) * (1.0f - b) * (1 << W_BITS)); - int iw01 = convert_int_sat_rte(a * (1.0f - b) * (1 << W_BITS)); - int iw10 = convert_int_sat_rte((1.0f - a) * b * (1 << W_BITS)); - int iw11 = (1 << W_BITS) - iw00 - iw01 - iw10; - - __global const uchar* src_row = src + (ipt.y + y) * srcStep + ipt.x * cn; - __global const uchar* src_row1 = src + (ipt.y + y + 1) * srcStep + ipt.x * cn; - - return CV_DESCALE(src_row[x] * iw00 + src_row[x + cn] * iw01 + src_row1[x] * iw10 + src_row1[x + cn] * iw11, W_BITS1 - 5); -} - -int linearFilter_short(__global const short* src, int srcStep, int cn, float2 pt, int x, int y) -{ - int2 ipt; - ipt.x = convert_int_sat_rtn(pt.x); - ipt.y = convert_int_sat_rtn(pt.y); - - float a = pt.x - ipt.x; - float b = pt.y - ipt.y; - - int iw00 = convert_int_sat_rte((1.0f - a) * (1.0f - b) * (1 << W_BITS)); - int iw01 = convert_int_sat_rte(a * (1.0f - b) * (1 << W_BITS)); - int iw10 = convert_int_sat_rte((1.0f - a) * b * (1 << W_BITS)); - int iw11 = (1 << W_BITS) - iw00 - iw01 - iw10; - - __global const short* src_row = src + (ipt.y + y) * srcStep + ipt.x * cn; - __global const short* src_row1 = src + (ipt.y + y + 1) * srcStep + ipt.x * cn; - - return CV_DESCALE(src_row[x] * iw00 + src_row[x + cn] * iw01 + src_row1[x] * iw10 + src_row1[x + cn] * iw11, W_BITS1); -} - -float linearFilter_float(__global const float* src, int srcStep, int cn, float2 pt, float x, float y) -{ - int2 ipt; - ipt.x = convert_int_sat_rtn(pt.x); - ipt.y = convert_int_sat_rtn(pt.y); - - float a = pt.x - ipt.x; - float b = pt.y - ipt.y; - - float iw00 = ((1.0f - a) * (1.0f - b) * (1 << W_BITS)); - float iw01 = (a * (1.0f - b) * (1 << W_BITS)); - float iw10 = ((1.0f - a) * b * (1 << W_BITS)); - float iw11 = (1 << W_BITS) - iw00 - iw01 - iw10; - - __global const float* src_row = src + (int)(ipt.y + y) * srcStep / 4 + ipt.x * cn; - __global const float* src_row1 = src + (int)(ipt.y + y + 1) * srcStep / 4 + ipt.x * cn; - - return src_row[(int)x] * iw00 + src_row[(int)x + cn] * iw01 + src_row1[(int)x] * iw10 + src_row1[(int)x + cn] * iw11, W_BITS1 - 5; -} - #define BUFFER 64 - +#ifndef WAVE_SIZE +#define WAVE_SIZE 1 +#endif #ifdef CPU void reduce3(float val1, float val2, float val3, __local float* smem1, __local float* smem2, __local float* smem3, int tid) { @@ -193,71 +58,51 @@ void reduce3(float val1, float val2, float val3, __local float* smem1, __local smem3[tid] = val3; barrier(CLK_LOCAL_MEM_FENCE); -#if BUFFER > 128 - if (tid < 128) - { - smem1[tid] = val1 += smem1[tid + 128]; - smem2[tid] = val2 += smem2[tid + 128]; - smem3[tid] = val3 += smem3[tid + 128]; - } - barrier(CLK_LOCAL_MEM_FENCE); -#endif - -#if BUFFER > 64 - if (tid < 64) - { - smem1[tid] = val1 += smem1[tid + 64]; - smem2[tid] = val2 += smem2[tid + 64]; - smem3[tid] = val3 += smem3[tid + 64]; - } - barrier(CLK_LOCAL_MEM_FENCE); -#endif - if (tid < 32) { - smem1[tid] = val1 += smem1[tid + 32]; - smem2[tid] = val2 += smem2[tid + 32]; - smem3[tid] = val3 += smem3[tid + 32]; + smem1[tid] += smem1[tid + 32]; + smem2[tid] += smem2[tid + 32]; + smem3[tid] += smem3[tid + 32]; } barrier(CLK_LOCAL_MEM_FENCE); if (tid < 16) { - smem1[tid] = val1 += smem1[tid + 16]; - smem2[tid] = val2 += smem2[tid + 16]; - smem3[tid] = val3 += smem3[tid + 16]; + smem1[tid] += smem1[tid + 16]; + smem2[tid] += smem2[tid + 16]; + smem3[tid] += smem3[tid + 16]; } barrier(CLK_LOCAL_MEM_FENCE); if (tid < 8) { - smem1[tid] = val1 += smem1[tid + 8]; - smem2[tid] = val2 += smem2[tid + 8]; - smem3[tid] = val3 += smem3[tid + 8]; + smem1[tid] += smem1[tid + 8]; + smem2[tid] += smem2[tid + 8]; + smem3[tid] += smem3[tid + 8]; } barrier(CLK_LOCAL_MEM_FENCE); if (tid < 4) { - smem1[tid] = val1 += smem1[tid + 4]; - smem2[tid] = val2 += smem2[tid + 4]; - smem3[tid] = val3 += smem3[tid + 4]; + smem1[tid] += smem1[tid + 4]; + smem2[tid] += smem2[tid + 4]; + smem3[tid] += smem3[tid + 4]; } barrier(CLK_LOCAL_MEM_FENCE); if (tid < 2) { - smem1[tid] = val1 += smem1[tid + 2]; - smem2[tid] = val2 += smem2[tid + 2]; - smem3[tid] = val3 += smem3[tid + 2]; + smem1[tid] += smem1[tid + 2]; + smem2[tid] += smem2[tid + 2]; + smem3[tid] += smem3[tid + 2]; } barrier(CLK_LOCAL_MEM_FENCE); if (tid < 1) { - smem1[BUFFER] = val1 += smem1[tid + 1]; - smem2[BUFFER] = val2 += smem2[tid + 1]; - smem3[BUFFER] = val3 += smem3[tid + 1]; + smem1[BUFFER] = smem1[tid] + smem1[tid + 1]; + smem2[BUFFER] = smem2[tid] + smem2[tid + 1]; + smem3[BUFFER] = smem3[tid] + smem3[tid + 1]; } barrier(CLK_LOCAL_MEM_FENCE); } @@ -268,63 +113,45 @@ void reduce2(float val1, float val2, volatile __local float* smem1, volatile __l smem2[tid] = val2; barrier(CLK_LOCAL_MEM_FENCE); -#if BUFFER > 128 - if (tid < 128) - { - smem1[tid] = (val1 += smem1[tid + 128]); - smem2[tid] = (val2 += smem2[tid + 128]); - } - barrier(CLK_LOCAL_MEM_FENCE); -#endif - -#if BUFFER > 64 - if (tid < 64) - { - smem1[tid] = (val1 += smem1[tid + 64]); - smem2[tid] = (val2 += smem2[tid + 64]); - } - barrier(CLK_LOCAL_MEM_FENCE); -#endif - if (tid < 32) { - smem1[tid] = (val1 += smem1[tid + 32]); - smem2[tid] = (val2 += smem2[tid + 32]); + smem1[tid] += smem1[tid + 32]; + smem2[tid] += smem2[tid + 32]; } barrier(CLK_LOCAL_MEM_FENCE); if (tid < 16) { - smem1[tid] = (val1 += smem1[tid + 16]); - smem2[tid] = (val2 += smem2[tid + 16]); + smem1[tid] += smem1[tid + 16]; + smem2[tid] += smem2[tid + 16]; } barrier(CLK_LOCAL_MEM_FENCE); if (tid < 8) { - smem1[tid] = (val1 += smem1[tid + 8]); - smem2[tid] = (val2 += smem2[tid + 8]); + smem1[tid] += smem1[tid + 8]; + smem2[tid] += smem2[tid + 8]; } barrier(CLK_LOCAL_MEM_FENCE); if (tid < 4) { - smem1[tid] = (val1 += smem1[tid + 4]); - smem2[tid] = (val2 += smem2[tid + 4]); + smem1[tid] += smem1[tid + 4]; + smem2[tid] += smem2[tid + 4]; } barrier(CLK_LOCAL_MEM_FENCE); if (tid < 2) { - smem1[tid] = (val1 += smem1[tid + 2]); - smem2[tid] = (val2 += smem2[tid + 2]); + smem1[tid] += smem1[tid + 2]; + smem2[tid] += smem2[tid + 2]; } barrier(CLK_LOCAL_MEM_FENCE); if (tid < 1) { - smem1[BUFFER] = (val1 += smem1[tid + 1]); - smem2[BUFFER] = (val2 += smem2[tid + 1]); + smem1[BUFFER] = smem1[tid] + smem1[tid + 1]; + smem2[BUFFER] = smem2[tid] + smem2[tid + 1]; } barrier(CLK_LOCAL_MEM_FENCE); } @@ -334,205 +161,146 @@ void reduce1(float val1, volatile __local float* smem1, int tid) smem1[tid] = val1; barrier(CLK_LOCAL_MEM_FENCE); -#if BUFFER > 128 - if (tid < 128) - { - smem1[tid] = (val1 += smem1[tid + 128]); - } - barrier(CLK_LOCAL_MEM_FENCE); -#endif - -#if BUFFER > 64 - if (tid < 64) - { - smem1[tid] = (val1 += smem1[tid + 64]); - } - barrier(CLK_LOCAL_MEM_FENCE); -#endif - if (tid < 32) { - smem1[tid] = (val1 += smem1[tid + 32]); + smem1[tid] += smem1[tid + 32]; } barrier(CLK_LOCAL_MEM_FENCE); if (tid < 16) { - smem1[tid] = (val1 += smem1[tid + 16]); + smem1[tid] += smem1[tid + 16]; } barrier(CLK_LOCAL_MEM_FENCE); if (tid < 8) { - smem1[tid] = (val1 += smem1[tid + 8]); + smem1[tid] += smem1[tid + 8]; } barrier(CLK_LOCAL_MEM_FENCE); if (tid < 4) { - smem1[tid] = (val1 += smem1[tid + 4]); + smem1[tid] += smem1[tid + 4]; } barrier(CLK_LOCAL_MEM_FENCE); if (tid < 2) { - smem1[tid] = (val1 += smem1[tid + 2]); + smem1[tid] += smem1[tid + 2]; } barrier(CLK_LOCAL_MEM_FENCE); if (tid < 1) { - smem1[BUFFER] = (val1 += smem1[tid + 1]); + smem1[BUFFER] = smem1[tid] + smem1[tid + 1]; } barrier(CLK_LOCAL_MEM_FENCE); } #else -void reduce3(float val1, float val2, float val3, __local float* smem1, __local float* smem2, __local float* smem3, int tid) +void reduce3(float val1, float val2, float val3, +__local volatile float* smem1, __local volatile float* smem2, __local volatile float* smem3, int tid) { smem1[tid] = val1; smem2[tid] = val2; smem3[tid] = val3; barrier(CLK_LOCAL_MEM_FENCE); -#if BUFFER > 128 - if (tid < 128) - { - smem1[tid] = val1 += smem1[tid + 128]; - smem2[tid] = val2 += smem2[tid + 128]; - smem3[tid] = val3 += smem3[tid + 128]; - } - barrier(CLK_LOCAL_MEM_FENCE); -#endif - -#if BUFFER > 64 - if (tid < 64) - { - smem1[tid] = val1 += smem1[tid + 64]; - smem2[tid] = val2 += smem2[tid + 64]; - smem3[tid] = val3 += smem3[tid + 64]; - } - barrier(CLK_LOCAL_MEM_FENCE); -#endif - if (tid < 32) { - volatile __local float* vmem1 = smem1; - volatile __local float* vmem2 = smem2; - volatile __local float* vmem3 = smem3; + smem1[tid] += smem1[tid + 32]; + smem2[tid] += smem2[tid + 32]; + smem3[tid] += smem3[tid + 32]; +#if WAVE_SIZE < 32 + } barrier(CLK_LOCAL_MEM_FENCE); + if (tid < 16) { +#endif + smem1[tid] += smem1[tid + 16]; + smem2[tid] += smem2[tid + 16]; + smem3[tid] += smem3[tid + 16]; +#if WAVE_SIZE <16 + } barrier(CLK_LOCAL_MEM_FENCE); + if (tid < 8) { +#endif + smem1[tid] += smem1[tid + 8]; + smem2[tid] += smem2[tid + 8]; + smem3[tid] += smem3[tid + 8]; - vmem1[tid] = val1 += vmem1[tid + 32]; - vmem2[tid] = val2 += vmem2[tid + 32]; - vmem3[tid] = val3 += vmem3[tid + 32]; + smem1[tid] += smem1[tid + 4]; + smem2[tid] += smem2[tid + 4]; + smem3[tid] += smem3[tid + 4]; - vmem1[tid] = val1 += vmem1[tid + 16]; - vmem2[tid] = val2 += vmem2[tid + 16]; - vmem3[tid] = val3 += vmem3[tid + 16]; + smem1[tid] += smem1[tid + 2]; + smem2[tid] += smem2[tid + 2]; + smem3[tid] += smem3[tid + 2]; - vmem1[tid] = val1 += vmem1[tid + 8]; - vmem2[tid] = val2 += vmem2[tid + 8]; - vmem3[tid] = val3 += vmem3[tid + 8]; - - vmem1[tid] = val1 += vmem1[tid + 4]; - vmem2[tid] = val2 += vmem2[tid + 4]; - vmem3[tid] = val3 += vmem3[tid + 4]; - - vmem1[tid] = val1 += vmem1[tid + 2]; - vmem2[tid] = val2 += vmem2[tid + 2]; - vmem3[tid] = val3 += vmem3[tid + 2]; - - vmem1[tid] = val1 += vmem1[tid + 1]; - vmem2[tid] = val2 += vmem2[tid + 1]; - vmem3[tid] = val3 += vmem3[tid + 1]; + smem1[tid] += smem1[tid + 1]; + smem2[tid] += smem2[tid + 1]; + smem3[tid] += smem3[tid + 1]; } } -void reduce2(float val1, float val2, __local float* smem1, __local float* smem2, int tid) +void reduce2(float val1, float val2, __local volatile float* smem1, __local volatile float* smem2, int tid) { smem1[tid] = val1; smem2[tid] = val2; barrier(CLK_LOCAL_MEM_FENCE); -#if BUFFER > 128 - if (tid < 128) - { - smem1[tid] = val1 += smem1[tid + 128]; - smem2[tid] = val2 += smem2[tid + 128]; - } - barrier(CLK_LOCAL_MEM_FENCE); -#endif - -#if BUFFER > 64 - if (tid < 64) - { - smem1[tid] = val1 += smem1[tid + 64]; - smem2[tid] = val2 += smem2[tid + 64]; - } - barrier(CLK_LOCAL_MEM_FENCE); -#endif - if (tid < 32) { - volatile __local float* vmem1 = smem1; - volatile __local float* vmem2 = smem2; + smem1[tid] += smem1[tid + 32]; + smem2[tid] += smem2[tid + 32]; +#if WAVE_SIZE < 32 + } barrier(CLK_LOCAL_MEM_FENCE); + if (tid < 16) { +#endif + smem1[tid] += smem1[tid + 16]; + smem2[tid] += smem2[tid + 16]; +#if WAVE_SIZE <16 + } barrier(CLK_LOCAL_MEM_FENCE); + if (tid < 8) { +#endif + smem1[tid] += smem1[tid + 8]; + smem2[tid] += smem2[tid + 8]; - vmem1[tid] = val1 += vmem1[tid + 32]; - vmem2[tid] = val2 += vmem2[tid + 32]; + smem1[tid] += smem1[tid + 4]; + smem2[tid] += smem2[tid + 4]; - vmem1[tid] = val1 += vmem1[tid + 16]; - vmem2[tid] = val2 += vmem2[tid + 16]; + smem1[tid] += smem1[tid + 2]; + smem2[tid] += smem2[tid + 2]; - vmem1[tid] = val1 += vmem1[tid + 8]; - vmem2[tid] = val2 += vmem2[tid + 8]; - - vmem1[tid] = val1 += vmem1[tid + 4]; - vmem2[tid] = val2 += vmem2[tid + 4]; - - vmem1[tid] = val1 += vmem1[tid + 2]; - vmem2[tid] = val2 += vmem2[tid + 2]; - - vmem1[tid] = val1 += vmem1[tid + 1]; - vmem2[tid] = val2 += vmem2[tid + 1]; + smem1[tid] += smem1[tid + 1]; + smem2[tid] += smem2[tid + 1]; } } -void reduce1(float val1, __local float* smem1, int tid) +void reduce1(float val1, __local volatile float* smem1, int tid) { smem1[tid] = val1; barrier(CLK_LOCAL_MEM_FENCE); -#if BUFFER > 128 - if (tid < 128) - { - smem1[tid] = val1 += smem1[tid + 128]; - } - barrier(CLK_LOCAL_MEM_FENCE); -#endif - -#if BUFFER > 64 - if (tid < 64) - { - smem1[tid] = val1 += smem1[tid + 64]; - } - barrier(CLK_LOCAL_MEM_FENCE); -#endif - if (tid < 32) { - volatile __local float* vmem1 = smem1; - - vmem1[tid] = val1 += vmem1[tid + 32]; - vmem1[tid] = val1 += vmem1[tid + 16]; - vmem1[tid] = val1 += vmem1[tid + 8]; - vmem1[tid] = val1 += vmem1[tid + 4]; - vmem1[tid] = val1 += vmem1[tid + 2]; - vmem1[tid] = val1 += vmem1[tid + 1]; + smem1[tid] += smem1[tid + 32]; +#if WAVE_SIZE < 32 + } barrier(CLK_LOCAL_MEM_FENCE); + if (tid < 16) { +#endif + smem1[tid] += smem1[tid + 16]; +#if WAVE_SIZE <16 + } barrier(CLK_LOCAL_MEM_FENCE); + if (tid < 8) { +#endif + smem1[tid] += smem1[tid + 8]; + smem1[tid] += smem1[tid + 4]; + smem1[tid] += smem1[tid + 2]; + smem1[tid] += smem1[tid + 1]; } } #endif #define SCALE (1.0f / (1 << 20)) #define THRESHOLD 0.01f -#define DIMENSION 21 // Image read mode __constant sampler_t sampler = CLK_NORMALIZED_COORDS_FALSE | CLK_ADDRESS_CLAMP_TO_EDGE | CLK_FILTER_LINEAR; diff --git a/modules/ocl/src/pyrlk.cpp b/modules/ocl/src/pyrlk.cpp index 6de4f9786a..b2e7d6a896 100644 --- a/modules/ocl/src/pyrlk.cpp +++ b/modules/ocl/src/pyrlk.cpp @@ -56,30 +56,15 @@ namespace cv { namespace ocl { -///////////////////////////OpenCL kernel strings/////////////////////////// extern const char *pyrlk; extern const char *pyrlk_no_image; -extern const char *arithm_mul; } } - struct dim3 { unsigned int x, y, z; }; -struct float2 -{ - float x, y; -}; - -struct int2 -{ - int x, y; -}; - -namespace -{ void calcPatchSize(cv::Size winSize, int cn, dim3 &block, dim3 &patch, bool isDeviceArch11) { winSize.width *= cn; @@ -100,45 +85,6 @@ void calcPatchSize(cv::Size winSize, int cn, dim3 &block, dim3 &patch, bool isDe block.z = patch.z = 1; } -} - -static void multiply_cus(const oclMat &src1, oclMat &dst, float scalar) -{ - if(!src1.clCxt->supportsFeature(Context::CL_DOUBLE) && src1.type() == CV_64F) - { - CV_Error(CV_GpuNotSupported, "Selected device don't support double\r\n"); - return; - } - - CV_Assert(src1.cols == dst.cols && - src1.rows == dst.rows); - - CV_Assert(src1.type() == dst.type()); - CV_Assert(src1.depth() != CV_8S); - - Context *clCxt = src1.clCxt; - - size_t localThreads[3] = { 16, 16, 1 }; - size_t globalThreads[3] = { src1.cols, - src1.rows, - 1 - }; - - int dst_step1 = dst.cols * dst.elemSize(); - vector > args; - args.push_back( make_pair( sizeof(cl_mem), (void *)&src1.data )); - args.push_back( make_pair( sizeof(cl_int), (void *)&src1.step )); - args.push_back( make_pair( sizeof(cl_int), (void *)&src1.offset )); - args.push_back( make_pair( sizeof(cl_mem), (void *)&dst.data )); - args.push_back( make_pair( sizeof(cl_int), (void *)&dst.step )); - args.push_back( make_pair( sizeof(cl_int), (void *)&dst.offset )); - args.push_back( make_pair( sizeof(cl_int), (void *)&src1.rows )); - args.push_back( make_pair( sizeof(cl_int), (void *)&src1.cols )); - args.push_back( make_pair( sizeof(cl_int), (void *)&dst_step1 )); - args.push_back( make_pair( sizeof(float), (float *)&scalar )); - - openCLExecuteKernel(clCxt, &arithm_mul, "arithm_muls", globalThreads, localThreads, args, -1, src1.depth()); -} static void lkSparse_run(oclMat &I, oclMat &J, const oclMat &prevPts, oclMat &nextPts, oclMat &status, oclMat& err, bool /*GET_MIN_EIGENVALS*/, int ptcount, @@ -151,15 +97,7 @@ static void lkSparse_run(oclMat &I, oclMat &J, size_t localThreads[3] = { 8, isImageSupported ? 8 : 32, 1 }; size_t globalThreads[3] = { 8 * ptcount, isImageSupported ? 8 : 32, 1}; int cn = I.oclchannels(); - char calcErr; - if (level == 0) - { - calcErr = 1; - } - else - { - calcErr = 0; - } + char calcErr = level==0?1:0; vector > args; @@ -198,7 +136,16 @@ static void lkSparse_run(oclMat &I, oclMat &J, { if(isImageSupported) { - openCLExecuteKernel(clCxt, &pyrlk, kernelName, globalThreads, localThreads, args, I.oclchannels(), I.depth()); + stringstream idxStr; + idxStr << kernelName << "_C" << I.oclchannels() << "_D" << I.depth(); + cl_kernel kernel = openCLGetKernelFromSource(clCxt, &pyrlk, idxStr.str()); + + size_t wave_size = queryDeviceInfo(kernel); + static char opt[16] = {0}; + sprintf(opt, " -D WAVE_SIZE=%d", wave_size); + + openCLExecuteKernel(clCxt, &pyrlk, kernelName, globalThreads, localThreads, + args, I.oclchannels(), I.depth(), opt); releaseTexture(ITex); releaseTexture(JTex); } @@ -241,8 +188,7 @@ void cv::ocl::PyrLKOpticalFlow::sparse(const oclMat &prevImg, const oclMat &next oclMat temp1 = (useInitialFlow ? nextPts : prevPts).reshape(1); oclMat temp2 = nextPts.reshape(1); - multiply_cus(temp1, temp2, 1.0f / (1 << maxLevel) / 2.0f); - //::multiply(temp1, 1.0f / (1 << maxLevel) / 2.0f, temp2); + multiply(1.0f/(1<= 0; level--) { lkSparse_run(prevPyr_[level], nextPyr_[level], From d45f9ef866d165220c7e2498f390ee3fa5f2115e Mon Sep 17 00:00:00 2001 From: yao Date: Thu, 23 May 2013 17:58:50 +0800 Subject: [PATCH 025/178] fix Linux build errors --- modules/ocl/src/pyrlk.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/ocl/src/pyrlk.cpp b/modules/ocl/src/pyrlk.cpp index b2e7d6a896..3b6edf79d6 100644 --- a/modules/ocl/src/pyrlk.cpp +++ b/modules/ocl/src/pyrlk.cpp @@ -15,8 +15,8 @@ // Third party copyrights are property of their respective owners. // // @Authors -// Dachuan Zhao, dachuan@multicorewareinc.com -// Yao Wang, bitwangyaoyao@gmail.com +// Dachuan Zhao, dachuan@multicorewareinc.com +// Yao Wang, bitwangyaoyao@gmail.com // Nathan, liujun@multicorewareinc.com // // Redistribution and use in source and binary forms, with or without modification, @@ -65,7 +65,7 @@ struct dim3 unsigned int x, y, z; }; -void calcPatchSize(cv::Size winSize, int cn, dim3 &block, dim3 &patch, bool isDeviceArch11) +static void calcPatchSize(cv::Size winSize, int cn, dim3 &block, dim3 &patch, bool isDeviceArch11) { winSize.width *= cn; @@ -140,7 +140,7 @@ static void lkSparse_run(oclMat &I, oclMat &J, idxStr << kernelName << "_C" << I.oclchannels() << "_D" << I.depth(); cl_kernel kernel = openCLGetKernelFromSource(clCxt, &pyrlk, idxStr.str()); - size_t wave_size = queryDeviceInfo(kernel); + int wave_size = queryDeviceInfo(kernel); static char opt[16] = {0}; sprintf(opt, " -D WAVE_SIZE=%d", wave_size); From 33a3a192078ef27fe0eb2a045fcc5dfc612419af Mon Sep 17 00:00:00 2001 From: yao Date: Thu, 23 May 2013 18:10:38 +0800 Subject: [PATCH 026/178] add two samples --- samples/ocl/pyrlk_optical_flow.cpp | 290 ++++++++++++++++++++ samples/ocl/stereo_match.cpp | 419 +++++++++++++++++++++++++++++ 2 files changed, 709 insertions(+) create mode 100644 samples/ocl/pyrlk_optical_flow.cpp create mode 100644 samples/ocl/stereo_match.cpp diff --git a/samples/ocl/pyrlk_optical_flow.cpp b/samples/ocl/pyrlk_optical_flow.cpp new file mode 100644 index 0000000000..1b2b1d3798 --- /dev/null +++ b/samples/ocl/pyrlk_optical_flow.cpp @@ -0,0 +1,290 @@ +#include +#include +#include + +#include "opencv2/highgui/highgui.hpp" +#include "opencv2/ocl/ocl.hpp" +#include "opencv2/video/video.hpp" + +using namespace std; +using namespace cv; +using namespace cv::ocl; + +typedef unsigned char uchar; +#define LOOP_NUM 10 +int64 work_begin = 0; +int64 work_end = 0; + +static void workBegin() +{ + work_begin = getTickCount(); +} +static void workEnd() +{ + work_end += (getTickCount() - work_begin); +} +static double getTime(){ + return work_end * 1000. / getTickFrequency(); +} + +static void download(const oclMat& d_mat, vector& vec) +{ + vec.resize(d_mat.cols); + Mat mat(1, d_mat.cols, CV_32FC2, (void*)&vec[0]); + d_mat.download(mat); +} + +static void download(const oclMat& d_mat, vector& vec) +{ + vec.resize(d_mat.cols); + Mat mat(1, d_mat.cols, CV_8UC1, (void*)&vec[0]); + d_mat.download(mat); +} + +static void drawArrows(Mat& frame, const vector& prevPts, const vector& nextPts, const vector& status, Scalar line_color = Scalar(0, 0, 255)) +{ + for (size_t i = 0; i < prevPts.size(); ++i) + { + if (status[i]) + { + int line_thickness = 1; + + Point p = prevPts[i]; + Point q = nextPts[i]; + + double angle = atan2((double) p.y - q.y, (double) p.x - q.x); + + double hypotenuse = sqrt( (double)(p.y - q.y)*(p.y - q.y) + (double)(p.x - q.x)*(p.x - q.x) ); + + if (hypotenuse < 1.0) + continue; + + // Here we lengthen the arrow by a factor of three. + q.x = (int) (p.x - 3 * hypotenuse * cos(angle)); + q.y = (int) (p.y - 3 * hypotenuse * sin(angle)); + + // Now we draw the main line of the arrow. + line(frame, p, q, line_color, line_thickness); + + // Now draw the tips of the arrow. I do some scaling so that the + // tips look proportional to the main line of the arrow. + + p.x = (int) (q.x + 9 * cos(angle + CV_PI / 4)); + p.y = (int) (q.y + 9 * sin(angle + CV_PI / 4)); + line(frame, p, q, line_color, line_thickness); + + p.x = (int) (q.x + 9 * cos(angle - CV_PI / 4)); + p.y = (int) (q.y + 9 * sin(angle - CV_PI / 4)); + line(frame, p, q, line_color, line_thickness); + } + } +} + + +int main(int argc, const char* argv[]) +{ + static std::vector ocl_info; + ocl::getDevice(ocl_info); + //if you want to use undefault device, set it here + setDevice(ocl_info[0]); + + //set this to save kernel compile time from second time you run + ocl::setBinpath("./"); + const char* keys = + "{ h | help | false | print help message }" + "{ l | left | | specify left image }" + "{ r | right | | specify right image }" + "{ c | camera | 0 | enable camera capturing }" + "{ s | use_cpu | false | use cpu or gpu to process the image }" + "{ v | video | | use video as input }" + "{ points | points | 1000 | specify points count [GoodFeatureToTrack] }" + "{ min_dist | min_dist | 0 | specify minimal distance between points [GoodFeatureToTrack] }"; + + CommandLineParser cmd(argc, argv, keys); + + if (cmd.get("help")) + { + cout << "Usage: pyrlk_optical_flow [options]" << endl; + cout << "Avaible options:" << endl; + cmd.printParams(); + return 0; + } + + bool defaultPicturesFail = false; + string fname0 = cmd.get("left"); + string fname1 = cmd.get("right"); + string vdofile = cmd.get("video"); + int points = cmd.get("points"); + double minDist = cmd.get("min_dist"); + bool useCPU = cmd.get("s"); + bool useCamera = cmd.get("c"); + int inputName = cmd.get("c"); + oclMat d_nextPts, d_status; + + Mat frame0 = imread(fname0, cv::IMREAD_GRAYSCALE); + Mat frame1 = imread(fname1, cv::IMREAD_GRAYSCALE); + PyrLKOpticalFlow d_pyrLK; + vector pts; + vector nextPts; + vector status; + vector err; + + if (frame0.empty() || frame1.empty()) + { + useCamera = true; + defaultPicturesFail = true; + CvCapture* capture = 0; + capture = cvCaptureFromCAM( inputName ); + if (!capture) + { + cout << "Can't load input images" << endl; + return -1; + } + } + + cout << "Points count : " << points << endl << endl; + + if (useCamera) + { + CvCapture* capture = 0; + Mat frame, frameCopy; + Mat frame0Gray, frame1Gray; + Mat ptr0, ptr1; + + if(vdofile == "") + capture = cvCaptureFromCAM( inputName ); + else + capture = cvCreateFileCapture(vdofile.c_str()); + + int c = inputName ; + if(!capture) + { + if(vdofile == "") + cout << "Capture from CAM " << c << " didn't work" << endl; + else + cout << "Capture from file " << vdofile << " failed" <= 0 ) + goto _cleanup_; + } + + waitKey(0); + +_cleanup_: + cvReleaseCapture( &capture ); + } + else + { +nocamera: + for(int i = 0; i <= LOOP_NUM;i ++) + { + cout << "loop" << i << endl; + if (i > 0) workBegin(); + + cv::goodFeaturesToTrack(frame0, pts, points, 0.01, minDist); + + if (useCPU) + { + cv::calcOpticalFlowPyrLK(frame0, frame1, pts, nextPts, status, err); + } + else + { + oclMat d_prevPts(1, points, CV_32FC2, (void*)&pts[0]); + + d_pyrLK.sparse(oclMat(frame0), oclMat(frame1), d_prevPts, d_nextPts, d_status); + + download(d_prevPts, pts); + download(d_nextPts, nextPts); + download(d_status, status); + } + + if (i > 0 && i <= LOOP_NUM) + workEnd(); + + if (i == LOOP_NUM) + { + if (useCPU) + cout << "average CPU time (noCamera) : "; + else + cout << "average GPU time (noCamera) : "; + + cout << getTime() / LOOP_NUM << " ms" << endl; + + drawArrows(frame0, pts, nextPts, status, Scalar(255, 0, 0)); + + imshow("PyrLK [Sparse]", frame0); + } + } + } + + waitKey(); + + return 0; +} diff --git a/samples/ocl/stereo_match.cpp b/samples/ocl/stereo_match.cpp new file mode 100644 index 0000000000..7ac2c9a6f3 --- /dev/null +++ b/samples/ocl/stereo_match.cpp @@ -0,0 +1,419 @@ +#include +#include +#include +#include +#include +#include "opencv2/ocl/ocl.hpp" +#include "opencv2/highgui/highgui.hpp" + +using namespace cv; +using namespace std; +using namespace ocl; + +bool help_showed = false; + +struct Params +{ + Params(); + static Params read(int argc, char** argv); + + string left; + string right; + + string method_str() const + { + switch (method) + { + case BM: return "BM"; + case BP: return "BP"; + case CSBP: return "CSBP"; + } + return ""; + } + enum {BM, BP, CSBP} method; + int ndisp; // Max disparity + 1 + enum {GPU, CPU} type; +}; + + +struct App +{ + App(const Params& p); + void run(); + void handleKey(char key); + void printParams() const; + + void workBegin() { work_begin = getTickCount(); } + void workEnd() + { + int64 d = getTickCount() - work_begin; + double f = getTickFrequency(); + work_fps = f / d; + } + + string text() const + { + stringstream ss; + ss << "(" << p.method_str() << ") FPS: " << setiosflags(ios::left) + << setprecision(4) << work_fps; + return ss.str(); + } +private: + Params p; + bool running; + + Mat left_src, right_src; + Mat left, right; + oclMat d_left, d_right; + + StereoBM_OCL bm; + StereoBeliefPropagation bp; + StereoConstantSpaceBP csbp; + + int64 work_begin; + double work_fps; +}; + +static void printHelp() +{ + cout << "Usage: stereo_match_gpu\n" + << "\t--left --right # must be rectified\n" + << "\t--method # BM | BP | CSBP\n" + << "\t--ndisp # number of disparity levels\n" + << "\t--type # cpu | CPU | gpu | GPU\n"; + help_showed = true; +} + +int main(int argc, char** argv) +{ + try + { + if (argc < 2) + { + printHelp(); + return 1; + } + + Params args = Params::read(argc, argv); + if (help_showed) + return -1; + + int flags[2] = { CVCL_DEVICE_TYPE_GPU, CVCL_DEVICE_TYPE_CPU }; + vector info; + + if(getDevice(info, flags[args.type]) == 0) + { + throw runtime_error("Error: Did not find a valid OpenCL device!"); + } + cout << "Device name:" << info[0].DeviceName[0] << endl; + + App app(args); + app.run(); + } + catch (const exception& e) + { + cout << "error: " << e.what() << endl; + } + return 0; +} + + +Params::Params() +{ + method = BM; + ndisp = 64; + type = GPU; +} + + +Params Params::read(int argc, char** argv) +{ + Params p; + + for (int i = 1; i < argc; i++) + { + if (string(argv[i]) == "--left") p.left = argv[++i]; + else if (string(argv[i]) == "--right") p.right = argv[++i]; + else if (string(argv[i]) == "--method") + { + if (string(argv[i + 1]) == "BM") p.method = BM; + else if (string(argv[i + 1]) == "BP") p.method = BP; + else if (string(argv[i + 1]) == "CSBP") p.method = CSBP; + else throw runtime_error("unknown stereo match method: " + string(argv[i + 1])); + i++; + } + else if (string(argv[i]) == "--ndisp") p.ndisp = atoi(argv[++i]); + else if (string(argv[i]) == "--type") + { + string t(argv[++i]); + if (t == "cpu" || t == "CPU") + { + p.type = CPU; + } + else if (t == "gpu" || t == "GPU") + { + p.type = GPU; + } + else throw runtime_error("unknown device type: " + t); + } + else if (string(argv[i]) == "--help") printHelp(); + else throw runtime_error("unknown key: " + string(argv[i])); + } + + return p; +} + + +App::App(const Params& params) + : p(params), running(false) +{ + cout << "stereo_match_ocl sample\n"; + cout << "\nControls:\n" + << "\tesc - exit\n" + << "\tp - print current parameters\n" + << "\tg - convert source images into gray\n" + << "\tm - change stereo match method\n" + << "\ts - change Sobel prefiltering flag (for BM only)\n" + << "\t1/q - increase/decrease maximum disparity\n" + << "\t2/w - increase/decrease window size (for BM only)\n" + << "\t3/e - increase/decrease iteration count (for BP and CSBP only)\n" + << "\t4/r - increase/decrease level count (for BP and CSBP only)\n"; +} + + +void App::run() +{ + // Load images + left_src = imread(p.left); + right_src = imread(p.right); + if (left_src.empty()) throw runtime_error("can't open file \"" + p.left + "\""); + if (right_src.empty()) throw runtime_error("can't open file \"" + p.right + "\""); + + cvtColor(left_src, left, CV_BGR2GRAY); + cvtColor(right_src, right, CV_BGR2GRAY); + + d_left.upload(left); + d_right.upload(right); + + imshow("left", left); + imshow("right", right); + + // Set common parameters + bm.ndisp = p.ndisp; + bp.ndisp = p.ndisp; + csbp.ndisp = p.ndisp; + + cout << endl; + printParams(); + + running = true; + while (running) + { + + // Prepare disparity map of specified type + Mat disp; + oclMat d_disp; + workBegin(); + switch (p.method) + { + case Params::BM: + if (d_left.channels() > 1 || d_right.channels() > 1) + { + cout << "BM doesn't support color images\n"; + cvtColor(left_src, left, CV_BGR2GRAY); + cvtColor(right_src, right, CV_BGR2GRAY); + cout << "image_channels: " << left.channels() << endl; + d_left.upload(left); + d_right.upload(right); + imshow("left", left); + imshow("right", right); + } + bm(d_left, d_right, d_disp); + break; + case Params::BP: + bp(d_left, d_right, d_disp); + break; + case Params::CSBP: + csbp(d_left, d_right, d_disp); + break; + } + ocl::finish(); + workEnd(); + + // Show results + d_disp.download(disp); + if (p.method != Params::BM) + { + disp.convertTo(disp, 0); + } + putText(disp, text(), Point(5, 25), FONT_HERSHEY_SIMPLEX, 1.0, Scalar::all(255)); + imshow("disparity", disp); + + handleKey((char)waitKey(3)); + } +} + + +void App::printParams() const +{ + cout << "--- Parameters ---\n"; + cout << "image_size: (" << left.cols << ", " << left.rows << ")\n"; + cout << "image_channels: " << left.channels() << endl; + cout << "method: " << p.method_str() << endl + << "ndisp: " << p.ndisp << endl; + switch (p.method) + { + case Params::BM: + cout << "win_size: " << bm.winSize << endl; + cout << "prefilter_sobel: " << bm.preset << endl; + break; + case Params::BP: + cout << "iter_count: " << bp.iters << endl; + cout << "level_count: " << bp.levels << endl; + break; + case Params::CSBP: + cout << "iter_count: " << csbp.iters << endl; + cout << "level_count: " << csbp.levels << endl; + break; + } + cout << endl; +} + + +void App::handleKey(char key) +{ + switch (key) + { + case 27: + running = false; + break; + case 'p': case 'P': + printParams(); + break; + case 'g': case 'G': + if (left.channels() == 1 && p.method != Params::BM) + { + left = left_src; + right = right_src; + } + else + { + cvtColor(left_src, left, CV_BGR2GRAY); + cvtColor(right_src, right, CV_BGR2GRAY); + } + d_left.upload(left); + d_right.upload(right); + cout << "image_channels: " << left.channels() << endl; + imshow("left", left); + imshow("right", right); + break; + case 'm': case 'M': + switch (p.method) + { + case Params::BM: + p.method = Params::BP; + break; + case Params::BP: + p.method = Params::CSBP; + break; + case Params::CSBP: + p.method = Params::BM; + break; + } + cout << "method: " << p.method_str() << endl; + break; + case 's': case 'S': + if (p.method == Params::BM) + { + switch (bm.preset) + { + case StereoBM_OCL::BASIC_PRESET: + bm.preset = StereoBM_OCL::PREFILTER_XSOBEL; + break; + case StereoBM_OCL::PREFILTER_XSOBEL: + bm.preset = StereoBM_OCL::BASIC_PRESET; + break; + } + cout << "prefilter_sobel: " << bm.preset << endl; + } + break; + case '1': + p.ndisp = p.ndisp == 1 ? 8 : p.ndisp + 8; + cout << "ndisp: " << p.ndisp << endl; + bm.ndisp = p.ndisp; + bp.ndisp = p.ndisp; + csbp.ndisp = p.ndisp; + break; + case 'q': case 'Q': + p.ndisp = max(p.ndisp - 8, 1); + cout << "ndisp: " << p.ndisp << endl; + bm.ndisp = p.ndisp; + bp.ndisp = p.ndisp; + csbp.ndisp = p.ndisp; + break; + case '2': + if (p.method == Params::BM) + { + bm.winSize = min(bm.winSize + 1, 51); + cout << "win_size: " << bm.winSize << endl; + } + break; + case 'w': case 'W': + if (p.method == Params::BM) + { + bm.winSize = max(bm.winSize - 1, 2); + cout << "win_size: " << bm.winSize << endl; + } + break; + case '3': + if (p.method == Params::BP) + { + bp.iters += 1; + cout << "iter_count: " << bp.iters << endl; + } + else if (p.method == Params::CSBP) + { + csbp.iters += 1; + cout << "iter_count: " << csbp.iters << endl; + } + break; + case 'e': case 'E': + if (p.method == Params::BP) + { + bp.iters = max(bp.iters - 1, 1); + cout << "iter_count: " << bp.iters << endl; + } + else if (p.method == Params::CSBP) + { + csbp.iters = max(csbp.iters - 1, 1); + cout << "iter_count: " << csbp.iters << endl; + } + break; + case '4': + if (p.method == Params::BP) + { + bp.levels += 1; + cout << "level_count: " << bp.levels << endl; + } + else if (p.method == Params::CSBP) + { + csbp.levels += 1; + cout << "level_count: " << csbp.levels << endl; + } + break; + case 'r': case 'R': + if (p.method == Params::BP) + { + bp.levels = max(bp.levels - 1, 1); + cout << "level_count: " << bp.levels << endl; + } + else if (p.method == Params::CSBP) + { + csbp.levels = max(csbp.levels - 1, 1); + cout << "level_count: " << csbp.levels << endl; + } + break; + } +} + + From 036b0579f130ac60ea8537df491ba3a258528af5 Mon Sep 17 00:00:00 2001 From: yao Date: Thu, 23 May 2013 18:12:09 +0800 Subject: [PATCH 027/178] remove the images in ocl sample folder --- samples/ocl/aloe-L.png | Bin 737508 -> 0 bytes samples/ocl/aloe-R.png | Bin 739497 -> 0 bytes samples/ocl/aloe-disp.png | Bin 60396 -> 0 bytes 3 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 samples/ocl/aloe-L.png delete mode 100644 samples/ocl/aloe-R.png delete mode 100644 samples/ocl/aloe-disp.png diff --git a/samples/ocl/aloe-L.png b/samples/ocl/aloe-L.png deleted file mode 100644 index 47587668e25f51fd9117a98d1b38ea1ee834fdc5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 737508 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­{!a7hRVykq_oi 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%0Avd#E$d(cVE%}_#gfU39!zyV>1}?3qJR- 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

7Z4HaX;#4@8e6FruW;z3z(2 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 |_j=GcCclE_*tIsG1VceJm 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

QLF&Mo_pFA!cyo3z0jFOfzMf9mU&s{ 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 z�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

R_A)AZ=}B{GrjcwU${s2aJG9JKncd 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%0D~!~FLK^FFIhsz3`E0y6nw*|3YFjU# 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

)%&&3LM?G@9vd}p>10pv20e9%*F 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 yNH7ZBm0W~{#iZWfcn`KpFs2 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�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 rBm*Zx@%Safn9xXTtp|&hHlo^YyTcIx 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

#TLiS%N8}td(*AXr)Sge-=lB?7Lx*T1%~sRn{3} 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

dK?p=0O(7;-!*c!UY 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[d=69 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

9^L(-1KCM}68U>Kr&2IBFA0JPO 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 Date: Fri, 24 May 2013 13:46:21 +0800 Subject: [PATCH 028/178] fix hog --- modules/ocl/src/hog.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/ocl/src/hog.cpp b/modules/ocl/src/hog.cpp index 9c8f315ec5..ae74ed59d1 100644 --- a/modules/ocl/src/hog.cpp +++ b/modules/ocl/src/hog.cpp @@ -1578,7 +1578,8 @@ static void openCLExecuteKernel_hog(Context *clCxt , const char **source, string size_t globalThreads[3], size_t localThreads[3], vector< pair > &args) { - size_t wave_size = queryDeviceInfo(); + cl_kernel kernel = openCLGetKernelFromSource(clCxt, source, kernelName); + size_t wave_size = queryDeviceInfo(kernel); if (wave_size <= 16) { char build_options[64]; From fad96b95adafc7139c0e279992e9367293e1b330 Mon Sep 17 00:00:00 2001 From: yao Date: Fri, 24 May 2013 15:52:33 +0800 Subject: [PATCH 029/178] add results verification to facedetect and hog samples --- samples/ocl/facedetect.cpp | 290 +++++++++++++++++++++---------------- samples/ocl/hog.cpp | 64 +++++++- 2 files changed, 229 insertions(+), 125 deletions(-) diff --git a/samples/ocl/facedetect.cpp b/samples/ocl/facedetect.cpp index ec79339518..684c2d923b 100644 --- a/samples/ocl/facedetect.cpp +++ b/samples/ocl/facedetect.cpp @@ -1,5 +1,3 @@ -//This sample is inherited from facedetect.cpp in smaple/c - #include "opencv2/objdetect/objdetect.hpp" #include "opencv2/highgui/highgui.hpp" #include "opencv2/imgproc/imgproc.hpp" @@ -9,78 +7,84 @@ using namespace std; using namespace cv; +#define LOOP_NUM 10 -static void help() -{ - cout << "\nThis program demonstrates the cascade recognizer.\n" - "This classifier can recognize many ~rigid objects, it's most known use is for faces.\n" - "Usage:\n" - "./facedetect [--cascade= this is the primary trained classifier such as frontal face]\n" - " [--scale=\n" - " [filename|camera_index]\n\n" - "see facedetect.cmd for one call:\n" - "./facedetect --cascade=\"../../data/haarcascades/haarcascade_frontalface_alt.xml\" --scale=1.3 \n" - "Hit any key to quit.\n" - "Using OpenCV version " << CV_VERSION << "\n" << endl; +const static Scalar colors[] = { CV_RGB(0,0,255), + CV_RGB(0,128,255), + CV_RGB(0,255,255), + CV_RGB(0,255,0), + CV_RGB(255,128,0), + CV_RGB(255,255,0), + CV_RGB(255,0,0), + CV_RGB(255,0,255)} ; + +int64 work_begin = 0; +int64 work_end = 0; + +static void workBegin() +{ + work_begin = getTickCount(); +} +static void workEnd() +{ + work_end += (getTickCount() - work_begin); +} +static double getTime(){ + return work_end /((double)cvGetTickFrequency() * 1000.); } -struct getRect { Rect operator ()(const CvAvgComp& e) const { return e.rect; } }; -void detectAndDraw( Mat& img, - cv::ocl::OclCascadeClassifier& cascade, CascadeClassifier& nestedCascade, - double scale); -String cascadeName = "../../../data/haarcascades/haarcascade_frontalface_alt.xml"; +void detect( Mat& img, vector& faces, + cv::ocl::OclCascadeClassifierBuf& cascade, + double scale, bool calTime); + +void detectCPU( Mat& img, vector& faces, + CascadeClassifier& cascade, + double scale, bool calTime); + +void Draw(Mat& img, vector& faces, double scale); + +// This function test if gpu_rst matches cpu_rst. +// If the two vectors are not equal, it will return the difference in vector size +// Else if will return (total diff of each cpu and gpu rects covered pixels)/(total cpu rects covered pixels) +double checkRectSimilarity(Size sz, std::vector& cpu_rst, std::vector& gpu_rst); int main( int argc, const char** argv ) { + const char* keys = + "{ h | help | false | print help message }" + "{ i | input | | specify input image }" + "{ t | template | ../../../data/haarcascades/haarcascade_frontalface_alt.xml | specify template file }" + "{ c | scale | 1.0 | scale image }" + "{ s | use_cpu | false | use cpu or gpu to process the image }"; + + CommandLineParser cmd(argc, argv, keys); + if (cmd.get("help")) + { + cout << "Avaible options:" << endl; + cmd.printParams(); + return 0; + } CvCapture* capture = 0; Mat frame, frameCopy, image; - const String scaleOpt = "--scale="; - size_t scaleOptLen = scaleOpt.length(); - const String cascadeOpt = "--cascade="; - size_t cascadeOptLen = cascadeOpt.length(); - String inputName; - help(); - cv::ocl::OclCascadeClassifier cascade; - CascadeClassifier nestedCascade; - double scale = 1; + bool useCPU = cmd.get("s"); + string inputName = cmd.get("i"); + string cascadeName = cmd.get("t"); + double scale = cmd.get("c"); + cv::ocl::OclCascadeClassifierBuf cascade; + CascadeClassifier cpu_cascade; - for( int i = 1; i < argc; i++ ) - { - cout << "Processing " << i << " " << argv[i] << endl; - if( cascadeOpt.compare( 0, cascadeOptLen, argv[i], cascadeOptLen ) == 0 ) - { - cascadeName.assign( argv[i] + cascadeOptLen ); - cout << " from which we have cascadeName= " << cascadeName << endl; - } - else if( scaleOpt.compare( 0, scaleOptLen, argv[i], scaleOptLen ) == 0 ) - { - if( !sscanf( argv[i] + scaleOpt.length(), "%lf", &scale ) || scale < 1 ) - scale = 1; - cout << " from which we read scale = " << scale << endl; - } - else if( argv[i][0] == '-' ) - { - cerr << "WARNING: Unknown option %s" << argv[i] << endl; - } - else - inputName.assign( argv[i] ); - } - - if( !cascade.load( cascadeName ) ) + if( !cascade.load( cascadeName ) || !cpu_cascade.load(cascadeName) ) { cerr << "ERROR: Could not load classifier cascade" << endl; - cerr << "Usage: facedetect [--cascade=]\n" - " [--scale[=\n" - " [filename|camera_index]\n" << endl ; return -1; } - if( inputName.empty() || (isdigit(inputName.c_str()[0]) && inputName.c_str()[1] == '\0') ) + if( inputName.empty() ) { - capture = cvCaptureFromCAM( inputName.empty() ? 0 : inputName.c_str()[0] - '0' ); - int c = inputName.empty() ? 0 : inputName.c_str()[0] - '0' ; - if(!capture) cout << "Capture from CAM " << c << " didn't work" << endl; + capture = cvCaptureFromCAM(0); + if(!capture) + cout << "Capture from CAM 0 didn't work" << endl; } else if( inputName.size() ) { @@ -88,26 +92,30 @@ int main( int argc, const char** argv ) if( image.empty() ) { capture = cvCaptureFromAVI( inputName.c_str() ); - if(!capture) cout << "Capture from AVI didn't work" << endl; + if(!capture) + cout << "Capture from AVI didn't work" << endl; + return -1; } } else { image = imread( "lena.jpg", 1 ); - if(image.empty()) cout << "Couldn't read lena.jpg" << endl; + if(image.empty()) + cout << "Couldn't read lena.jpg" << endl; + return -1; } cvNamedWindow( "result", 1 ); std::vector oclinfo; int devnums = cv::ocl::getDevice(oclinfo); - if(devnums<1) + if( devnums < 1 ) { std::cout << "no device found\n"; return -1; } //if you want to use undefault device, set it here //setDevice(oclinfo[0]); - //setBinpath(CLBINPATH); + ocl::setBinpath("./"); if( capture ) { cout << "In capture ..." << endl; @@ -115,15 +123,20 @@ int main( int argc, const char** argv ) { IplImage* iplImg = cvQueryFrame( capture ); frame = iplImg; + vector faces; if( frame.empty() ) break; if( iplImg->origin == IPL_ORIGIN_TL ) frame.copyTo( frameCopy ); else flip( frame, frameCopy, 0 ); - - detectAndDraw( frameCopy, cascade, nestedCascade, scale ); - + if(useCPU){ + detectCPU(frameCopy, faces, cpu_cascade, scale, false); + } + else{ + detect(frameCopy, faces, cascade, scale, false); + } + Draw(frameCopy, faces, scale); if( waitKey( 10 ) >= 0 ) goto _cleanup_; } @@ -136,42 +149,34 @@ _cleanup_: else { cout << "In image read" << endl; - if( !image.empty() ) + vector faces; + vector ref_rst; + double accuracy = 0.; + for(int i = 0; i <= LOOP_NUM;i ++) { - detectAndDraw( image, cascade, nestedCascade, scale ); - waitKey(0); - } - else if( !inputName.empty() ) - { - /* assume it is a text file containing the - list of the image filenames to be processed - one per line */ - FILE* f = fopen( inputName.c_str(), "rt" ); - if( f ) + cout << "loop" << i << endl; + if(useCPU){ + detectCPU(image, faces, cpu_cascade, scale, i==0?false:true); + } + else{ + detect(image, faces, cascade, scale, i==0?false:true); + if(i == 0){ + detectCPU(image, ref_rst, cpu_cascade, scale, false); + accuracy = checkRectSimilarity(image.size(), ref_rst, faces); + } + } + if (i == LOOP_NUM) { - char buf[1000+1]; - while( fgets( buf, 1000, f ) ) - { - int len = (int)strlen(buf), c; - while( len > 0 && isspace(buf[len-1]) ) - len--; - buf[len] = '\0'; - cout << "file " << buf << endl; - image = imread( buf, 1 ); - if( !image.empty() ) - { - detectAndDraw( image, cascade, nestedCascade, scale ); - c = waitKey(0); - if( c == 27 || c == 'q' || c == 'Q' ) - break; - } - else - { - cerr << "Aw snap, couldn't read image " << buf << endl; - } - } - fclose(f); + if (useCPU) + cout << "average CPU time (noCamera) : "; + else + cout << "average GPU time (noCamera) : "; + cout << getTime() / LOOP_NUM << " ms" << endl; + cout << "accuracy value: " << accuracy <& faces, + cv::ocl::OclCascadeClassifierBuf& cascade, + double scale, bool calTime) { - int i = 0; - double t = 0; - vector faces; - const static Scalar colors[] = { CV_RGB(0,0,255), - CV_RGB(0,128,255), - CV_RGB(0,255,255), - CV_RGB(0,255,0), - CV_RGB(255,128,0), - CV_RGB(255,255,0), - CV_RGB(255,0,0), - CV_RGB(255,0,255)} ; cv::ocl::oclMat image(img); cv::ocl::oclMat gray, smallImg( cvRound (img.rows/scale), cvRound(img.cols/scale), CV_8UC1 ); - + if(calTime) workBegin(); cv::ocl::cvtColor( image, gray, CV_BGR2GRAY ); cv::ocl::resize( gray, smallImg, smallImg.size(), 0, 0, INTER_LINEAR ); cv::ocl::equalizeHist( smallImg, smallImg ); - CvSeq* _objects; - MemStorage storage(cvCreateMemStorage(0)); - t = (double)cvGetTickCount(); - _objects = cascade.oclHaarDetectObjects( smallImg, storage, 1.1, + cascade.detectMultiScale( smallImg, faces, 1.1, 3, 0 |CV_HAAR_SCALE_IMAGE , Size(30,30), Size(0, 0) ); - vector vecAvgComp; - Seq(_objects).copyTo(vecAvgComp); - faces.resize(vecAvgComp.size()); - std::transform(vecAvgComp.begin(), vecAvgComp.end(), faces.begin(), getRect()); - t = (double)cvGetTickCount() - t; - printf( "detection time = %g ms\n", t/((double)cvGetTickFrequency()*1000.) ); + if(calTime) workEnd(); +} + +void detectCPU( Mat& img, vector& faces, + CascadeClassifier& cascade, + double scale, bool calTime) +{ + if(calTime) workBegin(); + Mat cpu_gray, cpu_smallImg( cvRound (img.rows/scale), cvRound(img.cols/scale), CV_8UC1 ); + cvtColor(img, cpu_gray, CV_BGR2GRAY); + resize(cpu_gray, cpu_smallImg, cpu_smallImg.size(), 0, 0, INTER_LINEAR); + equalizeHist(cpu_smallImg, cpu_smallImg); + cascade.detectMultiScale(cpu_smallImg, faces, 1.1, + 3, 0 | CV_HAAR_SCALE_IMAGE, + Size(30, 30), Size(0, 0)); + if(calTime) workEnd(); +} + +void Draw(Mat& img, vector& faces, double scale) +{ + int i = 0; for( vector::const_iterator r = faces.begin(); r != faces.end(); r++, i++ ) { - Mat smallImgROI; Point center; Scalar color = colors[i%8]; int radius; @@ -227,3 +232,42 @@ void detectAndDraw( Mat& img, } cv::imshow( "result", img ); } + +double checkRectSimilarity(Size sz, std::vector& ob1, std::vector& ob2) +{ + double final_test_result = 0.0; + size_t sz1 = ob1.size(); + size_t sz2 = ob2.size(); + + if(sz1 != sz2) + return sz1 > sz2 ? (double)(sz1 - sz2) : (double)(sz2 - sz1); + else + { + cv::Mat cpu_result(sz, CV_8UC1); + cpu_result.setTo(0); + + for(vector::const_iterator r = ob1.begin(); r != ob1.end(); r++) + { + cv::Mat cpu_result_roi(cpu_result, *r); + cpu_result_roi.setTo(1); + cpu_result.copyTo(cpu_result); + } + int cpu_area = cv::countNonZero(cpu_result > 0); + + cv::Mat gpu_result(sz, CV_8UC1); + gpu_result.setTo(0); + for(vector::const_iterator r2 = ob2.begin(); r2 != ob2.end(); r2++) + { + cv::Mat gpu_result_roi(gpu_result, *r2); + gpu_result_roi.setTo(1); + gpu_result.copyTo(gpu_result); + } + + cv::Mat result_; + multiply(cpu_result, gpu_result, result_); + int result = cv::countNonZero(result_ > 0); + + final_test_result = 1.0 - (double)result/(double)cpu_area; + } + return final_test_result; +} diff --git a/samples/ocl/hog.cpp b/samples/ocl/hog.cpp index 76b6d2830e..28be6fa9af 100644 --- a/samples/ocl/hog.cpp +++ b/samples/ocl/hog.cpp @@ -45,7 +45,6 @@ public: bool gamma_corr; }; - class App { public: @@ -64,6 +63,13 @@ public: string message() const; +// This function test if gpu_rst matches cpu_rst. +// If the two vectors are not equal, it will return the difference in vector size +// Else if will return +// (total diff of each cpu and gpu rects covered pixels)/(total cpu rects covered pixels) + double checkRectSimilarity(Size sz, + std::vector& cpu_rst, + std::vector& gpu_rst); private: App operator=(App&); @@ -290,6 +296,7 @@ void App::run() ocl::oclMat gpu_img; // Iterate over all frames + bool verify = false; while (running && !frame.empty()) { workBegin(); @@ -316,7 +323,18 @@ void App::run() gpu_img.upload(img); gpu_hog.detectMultiScale(gpu_img, found, hit_threshold, win_stride, Size(0, 0), scale, gr_threshold); - } + if (!verify) + { + // verify if GPU output same objects with CPU at 1st run + verify = true; + vector ref_rst; + cvtColor(img, img, CV_BGRA2BGR); + cpu_hog.detectMultiScale(img, ref_rst, hit_threshold, win_stride, + Size(0, 0), scale, gr_threshold-2); + double accuracy = checkRectSimilarity(img.size(), ref_rst, found); + cout << "\naccuracy value: " << accuracy << endl; + } + } else cpu_hog.detectMultiScale(img, found, hit_threshold, win_stride, Size(0, 0), scale, gr_threshold); hogWorkEnd(); @@ -457,3 +475,45 @@ inline string App::workFps() const return ss.str(); } +double App::checkRectSimilarity(Size sz, + std::vector& ob1, + std::vector& ob2) +{ + double final_test_result = 0.0; + size_t sz1 = ob1.size(); + size_t sz2 = ob2.size(); + + if(sz1 != sz2) + return sz1 > sz2 ? (double)(sz1 - sz2) : (double)(sz2 - sz1); + else + { + cv::Mat cpu_result(sz, CV_8UC1); + cpu_result.setTo(0); + + for(vector::const_iterator r = ob1.begin(); r != ob1.end(); r++) + { + cv::Mat cpu_result_roi(cpu_result, *r); + cpu_result_roi.setTo(1); + cpu_result.copyTo(cpu_result); + } + int cpu_area = cv::countNonZero(cpu_result > 0); + + cv::Mat gpu_result(sz, CV_8UC1); + gpu_result.setTo(0); + for(vector::const_iterator r2 = ob2.begin(); r2 != ob2.end(); r2++) + { + cv::Mat gpu_result_roi(gpu_result, *r2); + gpu_result_roi.setTo(1); + gpu_result.copyTo(gpu_result); + } + + cv::Mat result_; + multiply(cpu_result, gpu_result, result_); + int result = cv::countNonZero(result_ > 0); + + final_test_result = 1.0 - (double)result/(double)cpu_area; + } + return final_test_result; + +} + From 98960bf201f6ec51f28dfda067d670bb9f85c31b Mon Sep 17 00:00:00 2001 From: abidrahmank Date: Sat, 25 May 2013 00:55:31 +0530 Subject: [PATCH 030/178] A new python sample on grabcut --- .../doc/miscellaneous_transformations.rst | 2 +- samples/python2/grabcut.py | 174 ++++++++++++++++++ 2 files changed, 175 insertions(+), 1 deletion(-) create mode 100644 samples/python2/grabcut.py diff --git a/modules/imgproc/doc/miscellaneous_transformations.rst b/modules/imgproc/doc/miscellaneous_transformations.rst index 4ebf6d5ee5..84f94818ae 100644 --- a/modules/imgproc/doc/miscellaneous_transformations.rst +++ b/modules/imgproc/doc/miscellaneous_transformations.rst @@ -765,7 +765,7 @@ Runs the GrabCut algorithm. * **GC_PR_BGD** defines a possible background pixel. - * **GC_PR_BGD** defines a possible foreground pixel. + * **GC_PR_FGD** defines a possible foreground pixel. :param rect: ROI containing a segmented object. The pixels outside of the ROI are marked as "obvious background". The parameter is only used when ``mode==GC_INIT_WITH_RECT`` . diff --git a/samples/python2/grabcut.py b/samples/python2/grabcut.py new file mode 100644 index 0000000000..4de4f393b4 --- /dev/null +++ b/samples/python2/grabcut.py @@ -0,0 +1,174 @@ +#!/usr/bin/env python +''' +=============================================================================== +Interactive Image Segmentation using GrabCut algorithm. + +This sample shows interactive image segmentation using grabcut algorithm. + +USAGE : + python grabcut.py + +README FIRST: + Two windows will show up, one for input and one for output. + + At first, in input window, draw a rectangle around the object using +mouse right button. Then press 'n' to segment the object (once or a few times) +For any finer touch-ups, you can press any of the keys below and draw lines on +the areas you want. Then again press 'n' for updating the output. + +Key '0' - To select areas of sure background +Key '1' - To select areas of sure foreground +Key '2' - To select areas of probable background +Key '3' - To select areas of probable foreground + +Key 'n' - To update the segmentation +Key 'r' - To reset the setup +Key 's' - To save the results +=============================================================================== +''' + +import numpy as np +import cv2 +import sys + +BLUE = [255,0,0] # rectangle color +RED = [0,0,255] # PR BG +GREEN = [0,255,0] # PR FG +BLACK = [0,0,0] # sure BG +WHITE = [255,255,255] # sure FG + +DRAW_BG = {'color' : BLACK, 'val' : 0} +DRAW_FG = {'color' : WHITE, 'val' : 1} +DRAW_PR_FG = {'color' : GREEN, 'val' : 3} +DRAW_PR_BG = {'color' : RED, 'val' : 2} + +# setting up flags +rect = (0,0,1,1) +drawing = False # flag for drawing curves +rectangle = False # flag for drawing rect +rect_over = False # flag to check if rect drawn +rect_or_mask = 100 # flag for selecting rect or mask mode +value = DRAW_FG # drawing initialized to FG +thickness = 3 # brush thickness + +def onmouse(event,x,y,flags,param): + global img,img2,drawing,value,mask,rectangle,rect,rect_or_mask,ix,iy,rect_over + + # Draw Rectangle + if event == cv2.EVENT_RBUTTONDOWN: + rectangle = True + ix,iy = x,y + + elif event == cv2.EVENT_MOUSEMOVE: + if rectangle == True: + img = img2.copy() + cv2.rectangle(img,(ix,iy),(x,y),BLUE,2) + rect = (ix,iy,abs(ix-x),abs(iy-y)) + rect_or_mask = 0 + + elif event == cv2.EVENT_RBUTTONUP: + rectangle = False + rect_over = True + cv2.rectangle(img,(ix,iy),(x,y),BLUE,2) + rect = (ix,iy,abs(ix-x),abs(iy-y)) + rect_or_mask = 0 + print " Now press the key 'n' a few times until no further change \n" + + # draw touchup curves + + if event == cv2.EVENT_LBUTTONDOWN: + if rect_over == False: + print "first draw rectangle \n" + else: + drawing = True + cv2.circle(img,(x,y),thickness,value['color'],-1) + cv2.circle(mask,(x,y),thickness,value['val'],-1) + + elif event == cv2.EVENT_MOUSEMOVE: + if drawing == True: + cv2.circle(img,(x,y),thickness,value['color'],-1) + cv2.circle(mask,(x,y),thickness,value['val'],-1) + + elif event == cv2.EVENT_LBUTTONUP: + if drawing == True: + drawing = False + cv2.circle(img,(x,y),thickness,value['color'],-1) + cv2.circle(mask,(x,y),thickness,value['val'],-1) + +# print documentation +print __doc__ + +# Loading images +if len(sys.argv) == 2: + filename = sys.argv[1] # for drawing purposes +else: + print "No input image given, so loading default image, lena.jpg \n" + print "Correct Usage : python grabcut.py \n" + filename = '../cpp/lena.jpg' + +img = cv2.imread(filename) +img2 = img.copy() # a copy of original image +mask = np.zeros(img.shape[:2],dtype = np.uint8) # mask initialized to PR_BG +output = np.zeros(img.shape,np.uint8) # output image to be shown + +# input and output windows +cv2.namedWindow('output') +cv2.namedWindow('input') +cv2.setMouseCallback('input',onmouse) +cv2.moveWindow('input',img.shape[1]+10,90) + +print " Instructions : \n" +print " Draw a rectangle around the object using right mouse button \n" + +while(1): + + cv2.imshow('output',output) + cv2.imshow('input',img) + k = cv2.waitKey(1) + + # key bindings + if k == 27: # esc to exit + break + elif k == ord('0'): # BG drawing + print " mark background regions with left mouse button \n" + value = DRAW_BG + elif k == ord('1'): # FG drawing + print " mark foreground regions with left mouse button \n" + value = DRAW_FG + elif k == ord('2'): # PR_BG drawing + value = DRAW_PR_BG + elif k == ord('3'): # PR_FG drawing + value = DRAW_PR_FG + elif k == ord('s'): # save image + bar = np.zeros((img.shape[0],5,3),np.uint8) + res = np.hstack((img2,bar,img,bar,output)) + cv2.imwrite('grabcut_output.png',res) + print " Result saved as image \n" + elif k == ord('r'): # reset everything + print "resetting \n" + rect = (0,0,1,1) + drawing = False + rectangle = False + rect_or_mask = 100 + rect_over = False + value = DRAW_FG + img = img2.copy() + mask = np.zeros(img.shape[:2],dtype = np.uint8) # mask initialized to PR_BG + output = np.zeros(img.shape,np.uint8) # output image to be shown + elif k == ord('n'): # segment the image + print """ For finer touchups, mark foreground and background after pressing keys 0-3 + and again press 'n' \n""" + if (rect_or_mask == 0): # grabcut with rect + bgdmodel = np.zeros((1,65),np.float64) + fgdmodel = np.zeros((1,65),np.float64) + cv2.grabCut(img2,mask,rect,bgdmodel,fgdmodel,1,cv2.GC_INIT_WITH_RECT) + rect_or_mask = 1 + elif rect_or_mask == 1: # grabcut with mask + bgdmodel = np.zeros((1,65),np.float64) + fgdmodel = np.zeros((1,65),np.float64) + cv2.grabCut(img2,mask,rect,bgdmodel,fgdmodel,1,cv2.GC_INIT_WITH_MASK) + + mask2 = np.where((mask==1) + (mask==3),255,0).astype('uint8') + output = cv2.bitwise_and(img2,img2,mask=mask2) + +cv2.destroyAllWindows() From ca09ba685239c3f48e14b66897ac4e3cd4b950ab Mon Sep 17 00:00:00 2001 From: abidrahmank Date: Sat, 25 May 2013 01:50:41 +0530 Subject: [PATCH 031/178] Bug #2960 : docs about CV_BGR2GRAY --- modules/imgproc/doc/miscellaneous_transformations.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/imgproc/doc/miscellaneous_transformations.rst b/modules/imgproc/doc/miscellaneous_transformations.rst index 84f94818ae..9fd8df517a 100644 --- a/modules/imgproc/doc/miscellaneous_transformations.rst +++ b/modules/imgproc/doc/miscellaneous_transformations.rst @@ -116,6 +116,7 @@ If you use ``cvtColor`` with 8-bit images, the conversion will have some informa The function can do the following transformations: * + RGB :math:`\leftrightarrow` GRAY ( ``CV_BGR2GRAY, CV_RGB2GRAY, CV_GRAY2BGR, CV_GRAY2RGB`` ) Transformations within RGB space like adding/removing the alpha channel, reversing the channel order, conversion to/from 16-bit RGB color (R5:G6:B5 or R5:G5:B5), as well as conversion to/from grayscale using: .. math:: From 2dd3bf116e7b5441d6ae5c7d9e79af2fe5babf07 Mon Sep 17 00:00:00 2001 From: abidrahmank Date: Mon, 27 May 2013 10:30:30 +0530 Subject: [PATCH 032/178] ANDed waitkey result with 0xFF for x64 systems --- samples/python2/grabcut.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/python2/grabcut.py b/samples/python2/grabcut.py index 4de4f393b4..9fc1280acf 100644 --- a/samples/python2/grabcut.py +++ b/samples/python2/grabcut.py @@ -124,7 +124,7 @@ while(1): cv2.imshow('output',output) cv2.imshow('input',img) - k = cv2.waitKey(1) + k = 0xFF & cv2.waitKey(1) # key bindings if k == 27: # esc to exit From 20fef00a778e17aa5fdaf8ff71645db6df429ea4 Mon Sep 17 00:00:00 2001 From: Alexander Smorkalov Date: Fri, 17 May 2013 17:15:20 +0400 Subject: [PATCH 033/178] android -> plarforms/android --- CMakeLists.txt | 6 +++--- android/refman.rst | 9 --------- cmake/OpenCVGenConfig.cmake | 2 +- doc/CMakeLists.txt | 4 ++-- doc/conf.py | 2 +- index.rst | 2 +- modules/androidcamera/CMakeLists.txt | 2 +- {android => platforms/android}/README.android | 0 .../android}/android.toolchain.cmake | 0 {android => platforms/android}/java.rst | 0 .../android}/libinfo/CMakeLists.txt | 0 {android => platforms/android}/libinfo/info.c | 0 .../android}/package/AndroidManifest.xml | 0 .../android}/package/CMakeLists.txt | 0 .../android}/package/res/drawable/icon.png | Bin .../android}/package/res/values/strings.xml | 0 .../android}/scripts/ABI_compat_generator.py | 0 {android => platforms/android}/scripts/build.cmd | 0 .../android}/scripts/camera_build.conf | 0 .../android}/scripts/cmake_android.cmd | 0 .../android}/scripts/cmake_android.sh | 2 +- .../android}/scripts/cmake_android_all_cameras.py | 4 ++-- .../android}/scripts/cmake_android_armeabi.sh | 2 +- platforms/android/scripts/cmake_android_debug.sh | 8 ++++++++ .../android}/scripts/cmake_android_mips.sh | 2 +- .../android}/scripts/cmake_android_neon.sh | 2 +- .../android}/scripts/cmake_android_service.sh | 2 +- .../android/scripts/cmake_android_service_x86.sh | 8 ++++++++ .../android}/scripts/cmake_android_x86.sh | 2 +- .../android}/scripts/wincfg.cmd.tmpl | 0 .../android}/service/CMakeLists.txt | 0 {android => platforms/android}/service/all.py | 0 {android => platforms/android}/service/device.conf | 0 .../android}/service/doc/AndroidAppUsageModel.dia | Bin .../android}/service/doc/BaseLoaderCallback.rst | 0 .../service/doc/InstallCallbackInterface.rst | 0 .../android}/service/doc/Intro.rst | 0 .../android}/service/doc/JavaHelper.rst | 0 .../android}/service/doc/LibInstallAproved.dia | Bin .../android}/service/doc/LibInstallCanceled.dia | Bin .../android}/service/doc/LibInstalled.dia | Bin .../service/doc/LoaderCallbackInterface.rst | 0 {android => platforms/android}/service/doc/Makefile | 0 .../android}/service/doc/NoService.dia | Bin .../android}/service/doc/Structure.dia | Bin .../android}/service/doc/UseCases.rst | 0 .../android}/service/doc/build_uml.py | 0 .../service/doc/img/AndroidAppUsageModel.png | Bin .../android}/service/doc/img/LibInstallAproved.png | Bin .../android}/service/doc/img/LibInstallCanceled.png | Bin .../android}/service/doc/img/LibInstalled.png | Bin .../android}/service/doc/img/NoService.png | Bin .../android}/service/doc/img/Structure.png | Bin .../android}/service/doc/index.rst | 0 .../android}/service/engine/.classpath | 0 .../android}/service/engine/.project | 0 .../android}/service/engine/AndroidManifest.xml | 0 .../android}/service/engine/CMakeLists.txt | 2 +- .../android}/service/engine/build.xml | 0 .../android}/service/engine/jni/Android.mk | 0 .../android}/service/engine/jni/Application.mk | 0 .../engine/jni/BinderComponent/BnOpenCVEngine.cpp | 0 .../engine/jni/BinderComponent/BnOpenCVEngine.h | 0 .../engine/jni/BinderComponent/BpOpenCVEngine.cpp | 0 .../engine/jni/BinderComponent/BpOpenCVEngine.h | 0 .../engine/jni/BinderComponent/HardwareDetector.cpp | 0 .../engine/jni/BinderComponent/HardwareDetector.h | 0 .../engine/jni/BinderComponent/OpenCVEngine.cpp | 0 .../engine/jni/BinderComponent/OpenCVEngine.h | 0 .../engine/jni/BinderComponent/ProcReader.cpp | 0 .../service/engine/jni/BinderComponent/ProcReader.h | 0 .../engine/jni/BinderComponent/StringUtils.cpp | 0 .../engine/jni/BinderComponent/StringUtils.h | 0 .../engine/jni/BinderComponent/TegraDetector.cpp | 0 .../engine/jni/BinderComponent/TegraDetector.h | 0 .../engine/jni/JNIWrapper/HardwareDetector_jni.cpp | 0 .../engine/jni/JNIWrapper/HardwareDetector_jni.h | 0 .../jni/JNIWrapper/JavaBasedPackageManager.cpp | 0 .../engine/jni/JNIWrapper/JavaBasedPackageManager.h | 0 .../engine/jni/JNIWrapper/OpenCVEngine_jni.cpp | 0 .../engine/jni/JNIWrapper/OpenCVEngine_jni.h | 0 .../engine/jni/JNIWrapper/OpenCVLibraryInfo.cpp | 0 .../engine/jni/JNIWrapper/OpenCVLibraryInfo.h | 0 .../service/engine/jni/NativeClient/ClientMain.cpp | 0 .../jni/NativeService/CommonPackageManager.cpp | 0 .../engine/jni/NativeService/CommonPackageManager.h | 0 .../jni/NativeService/NativePackageManager.cpp | 0 .../engine/jni/NativeService/NativePackageManager.h | 0 .../engine/jni/NativeService/PackageInfo.cpp | 0 .../service/engine/jni/NativeService/PackageInfo.h | 0 .../engine/jni/NativeService/ServiceMain.cpp | 0 .../engine/jni/Tests/HardwareDetectionTest.cpp | 0 .../service/engine/jni/Tests/OpenCVEngineTest.cpp | 0 .../service/engine/jni/Tests/PackageInfoTest.cpp | 0 .../service/engine/jni/Tests/PackageManagerStub.cpp | 0 .../service/engine/jni/Tests/PackageManagerStub.h | 0 .../engine/jni/Tests/PackageManagmentTest.cpp | 0 .../android}/service/engine/jni/Tests/TestMain.cpp | 0 .../android}/service/engine/jni/Tests/Tests.mk | 0 .../service/engine/jni/Tests/gtest/gtest-all.cpp | 0 .../android}/service/engine/jni/Tests/gtest/gtest.h | 0 .../service/engine/jni/include/EngineCommon.h | 0 .../service/engine/jni/include/IOpenCVEngine.h | 0 .../service/engine/jni/include/IPackageManager.h | 0 .../service/engine/jni/include/OpenCVEngineHelper.h | 0 .../android}/service/engine/project.properties | 0 .../android}/service/engine/res/drawable/icon.png | Bin .../service/engine/res/layout-small/info.xml | 0 .../service/engine/res/layout-small/main.xml | 0 .../android}/service/engine/res/layout/info.xml | 0 .../android}/service/engine/res/layout/main.xml | 0 .../android}/service/engine/res/values/strings.xml | 0 .../src/org/opencv/engine/BinderConnector.java | 0 .../src/org/opencv/engine/HardwareDetector.java | 0 .../src/org/opencv/engine/MarketConnector.java | 0 .../org/opencv/engine/OpenCVEngineInterface.aidl | 0 .../src/org/opencv/engine/OpenCVEngineService.java | 0 .../src/org/opencv/engine/OpenCVLibraryInfo.java | 0 .../org/opencv/engine/manager/ManagerActivity.java | 0 .../opencv/engine/manager/PackageListAdapter.java | 0 .../android}/service/engine_test/.classpath | 0 .../android}/service/engine_test/.project | 0 .../service/engine_test/AndroidManifest.xml | 0 .../android}/service/engine_test/build.xml | 0 .../android}/service/engine_test/project.properties | 0 .../engine_test/res/drawable-hdpi/ic_launcher.png | Bin .../engine_test/res/drawable-ldpi/ic_launcher.png | Bin .../engine_test/res/drawable-mdpi/ic_launcher.png | Bin .../service/engine_test/res/layout/main.xml | 0 .../service/engine_test/res/values/strings.xml | 0 .../org/opencv/engine/test/EngineInterfaceTest.java | 0 .../android}/service/push_native.py | 0 {android => platforms/android}/service/readme.txt | 0 .../android}/service/test_native.py | 0 134 files changed, 34 insertions(+), 27 deletions(-) delete mode 100644 android/refman.rst rename {android => platforms/android}/README.android (100%) rename {android => platforms/android}/android.toolchain.cmake (100%) rename {android => platforms/android}/java.rst (100%) rename {android => platforms/android}/libinfo/CMakeLists.txt (100%) rename {android => platforms/android}/libinfo/info.c (100%) rename {android => platforms/android}/package/AndroidManifest.xml (100%) rename {android => platforms/android}/package/CMakeLists.txt (100%) rename {android => platforms/android}/package/res/drawable/icon.png (100%) rename {android => platforms/android}/package/res/values/strings.xml (100%) rename {android => platforms/android}/scripts/ABI_compat_generator.py (100%) rename {android => platforms/android}/scripts/build.cmd (100%) rename {android => platforms/android}/scripts/camera_build.conf (100%) rename {android => platforms/android}/scripts/cmake_android.cmd (100%) rename {android => platforms/android}/scripts/cmake_android.sh (75%) rename {android => platforms/android}/scripts/cmake_android_all_cameras.py (92%) rename {android => platforms/android}/scripts/cmake_android_armeabi.sh (83%) create mode 100755 platforms/android/scripts/cmake_android_debug.sh rename {android => platforms/android}/scripts/cmake_android_mips.sh (84%) rename {android => platforms/android}/scripts/cmake_android_neon.sh (75%) rename {android => platforms/android}/scripts/cmake_android_service.sh (95%) create mode 100755 platforms/android/scripts/cmake_android_service_x86.sh rename {android => platforms/android}/scripts/cmake_android_x86.sh (85%) rename {android => platforms/android}/scripts/wincfg.cmd.tmpl (100%) rename {android => platforms/android}/service/CMakeLists.txt (100%) rename {android => platforms/android}/service/all.py (100%) rename {android => platforms/android}/service/device.conf (100%) rename {android => platforms/android}/service/doc/AndroidAppUsageModel.dia (100%) rename {android => platforms/android}/service/doc/BaseLoaderCallback.rst (100%) rename {android => platforms/android}/service/doc/InstallCallbackInterface.rst (100%) rename {android => platforms/android}/service/doc/Intro.rst (100%) rename {android => platforms/android}/service/doc/JavaHelper.rst (100%) rename {android => platforms/android}/service/doc/LibInstallAproved.dia (100%) rename {android => platforms/android}/service/doc/LibInstallCanceled.dia (100%) rename {android => platforms/android}/service/doc/LibInstalled.dia (100%) rename {android => platforms/android}/service/doc/LoaderCallbackInterface.rst (100%) rename {android => platforms/android}/service/doc/Makefile (100%) rename {android => platforms/android}/service/doc/NoService.dia (100%) rename {android => platforms/android}/service/doc/Structure.dia (100%) rename {android => platforms/android}/service/doc/UseCases.rst (100%) rename {android => platforms/android}/service/doc/build_uml.py (100%) rename {android => platforms/android}/service/doc/img/AndroidAppUsageModel.png (100%) rename {android => platforms/android}/service/doc/img/LibInstallAproved.png (100%) rename {android => platforms/android}/service/doc/img/LibInstallCanceled.png (100%) rename {android => platforms/android}/service/doc/img/LibInstalled.png (100%) rename {android => platforms/android}/service/doc/img/NoService.png (100%) rename {android => platforms/android}/service/doc/img/Structure.png (100%) rename {android => platforms/android}/service/doc/index.rst (100%) rename {android => platforms/android}/service/engine/.classpath (100%) rename {android => platforms/android}/service/engine/.project (100%) rename {android => platforms/android}/service/engine/AndroidManifest.xml (100%) rename {android => platforms/android}/service/engine/CMakeLists.txt (97%) rename {android => platforms/android}/service/engine/build.xml (100%) rename {android => platforms/android}/service/engine/jni/Android.mk (100%) rename {android => platforms/android}/service/engine/jni/Application.mk (100%) rename {android => platforms/android}/service/engine/jni/BinderComponent/BnOpenCVEngine.cpp (100%) rename {android => platforms/android}/service/engine/jni/BinderComponent/BnOpenCVEngine.h (100%) rename {android => platforms/android}/service/engine/jni/BinderComponent/BpOpenCVEngine.cpp (100%) rename {android => platforms/android}/service/engine/jni/BinderComponent/BpOpenCVEngine.h (100%) rename {android => platforms/android}/service/engine/jni/BinderComponent/HardwareDetector.cpp (100%) rename {android => platforms/android}/service/engine/jni/BinderComponent/HardwareDetector.h (100%) rename {android => platforms/android}/service/engine/jni/BinderComponent/OpenCVEngine.cpp (100%) rename {android => platforms/android}/service/engine/jni/BinderComponent/OpenCVEngine.h (100%) rename {android => platforms/android}/service/engine/jni/BinderComponent/ProcReader.cpp (100%) rename {android => platforms/android}/service/engine/jni/BinderComponent/ProcReader.h (100%) rename {android => platforms/android}/service/engine/jni/BinderComponent/StringUtils.cpp (100%) rename {android => platforms/android}/service/engine/jni/BinderComponent/StringUtils.h (100%) rename {android => platforms/android}/service/engine/jni/BinderComponent/TegraDetector.cpp (100%) rename {android => platforms/android}/service/engine/jni/BinderComponent/TegraDetector.h (100%) rename {android => platforms/android}/service/engine/jni/JNIWrapper/HardwareDetector_jni.cpp (100%) rename {android => platforms/android}/service/engine/jni/JNIWrapper/HardwareDetector_jni.h (100%) rename {android => platforms/android}/service/engine/jni/JNIWrapper/JavaBasedPackageManager.cpp (100%) rename {android => platforms/android}/service/engine/jni/JNIWrapper/JavaBasedPackageManager.h (100%) rename {android => platforms/android}/service/engine/jni/JNIWrapper/OpenCVEngine_jni.cpp (100%) rename {android => platforms/android}/service/engine/jni/JNIWrapper/OpenCVEngine_jni.h (100%) rename {android => platforms/android}/service/engine/jni/JNIWrapper/OpenCVLibraryInfo.cpp (100%) rename {android => platforms/android}/service/engine/jni/JNIWrapper/OpenCVLibraryInfo.h (100%) rename {android => platforms/android}/service/engine/jni/NativeClient/ClientMain.cpp (100%) rename {android => platforms/android}/service/engine/jni/NativeService/CommonPackageManager.cpp (100%) rename {android => platforms/android}/service/engine/jni/NativeService/CommonPackageManager.h (100%) rename {android => platforms/android}/service/engine/jni/NativeService/NativePackageManager.cpp (100%) rename {android => platforms/android}/service/engine/jni/NativeService/NativePackageManager.h (100%) rename {android => platforms/android}/service/engine/jni/NativeService/PackageInfo.cpp (100%) rename {android => platforms/android}/service/engine/jni/NativeService/PackageInfo.h (100%) rename {android => platforms/android}/service/engine/jni/NativeService/ServiceMain.cpp (100%) rename {android => platforms/android}/service/engine/jni/Tests/HardwareDetectionTest.cpp (100%) rename {android => platforms/android}/service/engine/jni/Tests/OpenCVEngineTest.cpp (100%) rename {android => platforms/android}/service/engine/jni/Tests/PackageInfoTest.cpp (100%) rename {android => platforms/android}/service/engine/jni/Tests/PackageManagerStub.cpp (100%) rename {android => platforms/android}/service/engine/jni/Tests/PackageManagerStub.h (100%) rename {android => platforms/android}/service/engine/jni/Tests/PackageManagmentTest.cpp (100%) rename {android => platforms/android}/service/engine/jni/Tests/TestMain.cpp (100%) rename {android => platforms/android}/service/engine/jni/Tests/Tests.mk (100%) rename {android => platforms/android}/service/engine/jni/Tests/gtest/gtest-all.cpp (100%) rename {android => platforms/android}/service/engine/jni/Tests/gtest/gtest.h (100%) rename {android => platforms/android}/service/engine/jni/include/EngineCommon.h (100%) rename {android => platforms/android}/service/engine/jni/include/IOpenCVEngine.h (100%) rename {android => platforms/android}/service/engine/jni/include/IPackageManager.h (100%) rename {android => platforms/android}/service/engine/jni/include/OpenCVEngineHelper.h (100%) rename {android => platforms/android}/service/engine/project.properties (100%) rename {android => platforms/android}/service/engine/res/drawable/icon.png (100%) rename {android => platforms/android}/service/engine/res/layout-small/info.xml (100%) rename {android => platforms/android}/service/engine/res/layout-small/main.xml (100%) rename {android => platforms/android}/service/engine/res/layout/info.xml (100%) rename {android => platforms/android}/service/engine/res/layout/main.xml (100%) rename {android => platforms/android}/service/engine/res/values/strings.xml (100%) rename {android => platforms/android}/service/engine/src/org/opencv/engine/BinderConnector.java (100%) rename {android => platforms/android}/service/engine/src/org/opencv/engine/HardwareDetector.java (100%) rename {android => platforms/android}/service/engine/src/org/opencv/engine/MarketConnector.java (100%) rename {android => platforms/android}/service/engine/src/org/opencv/engine/OpenCVEngineInterface.aidl (100%) rename {android => platforms/android}/service/engine/src/org/opencv/engine/OpenCVEngineService.java (100%) rename {android => platforms/android}/service/engine/src/org/opencv/engine/OpenCVLibraryInfo.java (100%) rename {android => platforms/android}/service/engine/src/org/opencv/engine/manager/ManagerActivity.java (100%) rename {android => platforms/android}/service/engine/src/org/opencv/engine/manager/PackageListAdapter.java (100%) rename {android => platforms/android}/service/engine_test/.classpath (100%) rename {android => platforms/android}/service/engine_test/.project (100%) rename {android => platforms/android}/service/engine_test/AndroidManifest.xml (100%) rename {android => platforms/android}/service/engine_test/build.xml (100%) rename {android => platforms/android}/service/engine_test/project.properties (100%) rename {android => platforms/android}/service/engine_test/res/drawable-hdpi/ic_launcher.png (100%) rename {android => platforms/android}/service/engine_test/res/drawable-ldpi/ic_launcher.png (100%) rename {android => platforms/android}/service/engine_test/res/drawable-mdpi/ic_launcher.png (100%) rename {android => platforms/android}/service/engine_test/res/layout/main.xml (100%) rename {android => platforms/android}/service/engine_test/res/values/strings.xml (100%) rename {android => platforms/android}/service/engine_test/src/org/opencv/engine/test/EngineInterfaceTest.java (100%) rename {android => platforms/android}/service/push_native.py (100%) rename {android => platforms/android}/service/readme.txt (100%) rename {android => platforms/android}/service/test_native.py (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 93549c9430..e9aa14fc7c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -456,15 +456,15 @@ if(BUILD_EXAMPLES OR BUILD_ANDROID_EXAMPLES OR INSTALL_PYTHON_EXAMPLES) endif() if(ANDROID) - add_subdirectory(android/service) + add_subdirectory(platforms/android/service) endif() if(BUILD_ANDROID_PACKAGE) - add_subdirectory(android/package) + add_subdirectory(platforms/android/package) endif() if (ANDROID) - add_subdirectory(android/libinfo) + add_subdirectory(platforms/android/libinfo) endif() # ---------------------------------------------------------------------------- diff --git a/android/refman.rst b/android/refman.rst deleted file mode 100644 index 12d7ea6ec0..0000000000 --- a/android/refman.rst +++ /dev/null @@ -1,9 +0,0 @@ -############################ -OpenCV4Android Reference -############################ - -.. toctree:: - :maxdepth: 2 - - service/doc/index.rst - java.rst \ No newline at end of file diff --git a/cmake/OpenCVGenConfig.cmake b/cmake/OpenCVGenConfig.cmake index 705ccc8df1..c99cae7883 100644 --- a/cmake/OpenCVGenConfig.cmake +++ b/cmake/OpenCVGenConfig.cmake @@ -162,7 +162,7 @@ if(UNIX) endif() if(ANDROID) - install(FILES "${OpenCV_SOURCE_DIR}/android/android.toolchain.cmake" DESTINATION ${OPENCV_CONFIG_INSTALL_PATH}/) + install(FILES "${OpenCV_SOURCE_DIR}/platforms/android/android.toolchain.cmake" DESTINATION ${OPENCV_CONFIG_INSTALL_PATH}/) endif() # -------------------------------------------------------------------------------------------- diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index 0f2695fc9a..70f4809d22 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -53,8 +53,8 @@ if(BUILD_DOCS AND HAVE_SPHINX) endif() endforeach() - file(GLOB_RECURSE _OPENCV_FILES_REF "${OpenCV_SOURCE_DIR}/android/service/doc/*.rst") - file(GLOB_RECURSE _OPENCV_FILES_REF_PICT "${OpenCV_SOURCE_DIR}/android/service/doc/*.png" "${OpenCV_SOURCE_DIR}/android/service/doc/*.jpg") + file(GLOB_RECURSE _OPENCV_FILES_REF "${OpenCV_SOURCE_DIR}/platforms/android/service/doc/*.rst") + file(GLOB_RECURSE _OPENCV_FILES_REF_PICT "${OpenCV_SOURCE_DIR}/platforms/android/service/doc/*.png" "${OpenCV_SOURCE_DIR}/platforms/android/service/doc/*.jpg") list(APPEND OPENCV_FILES_REF ${_OPENCV_FILES_REF}) list(APPEND OPENCV_FILES_REF_PICT ${_OPENCV_FILES_REF_PICT}) diff --git a/doc/conf.py b/doc/conf.py index 4c7a15c891..f3f7aec58a 100755 --- a/doc/conf.py +++ b/doc/conf.py @@ -239,7 +239,7 @@ latex_documents = [ u'', 'manual'), ('doc/tutorials/tutorials', 'opencv_tutorials.tex', u'The OpenCV Tutorials', u'', 'manual'), - ('android/refman', 'opencv2manager.tex', u'The OpenCV Manager Manual', + ('platforms/android/refman', 'opencv2manager.tex', u'The OpenCV Manager Manual', u'', 'manual'), ] diff --git a/index.rst b/index.rst index 909bf908b8..5f50b66d0f 100644 --- a/index.rst +++ b/index.rst @@ -10,7 +10,7 @@ Welcome to opencv documentation! :maxdepth: 2 modules/refman.rst - android/refman.rst + platforms/android/refman.rst doc/user_guide/user_guide.rst doc/tutorials/tutorials.rst diff --git a/modules/androidcamera/CMakeLists.txt b/modules/androidcamera/CMakeLists.txt index d54dd5d208..8ac8ced88e 100644 --- a/modules/androidcamera/CMakeLists.txt +++ b/modules/androidcamera/CMakeLists.txt @@ -6,7 +6,7 @@ set(the_description "Auxiliary module for Android native camera support") set(OPENCV_MODULE_TYPE STATIC) ocv_define_module(androidcamera INTERNAL opencv_core log dl) -ocv_include_directories("${CMAKE_CURRENT_SOURCE_DIR}/camera_wrapper" "${OpenCV_SOURCE_DIR}/android/service/engine/jni/include") +ocv_include_directories("${CMAKE_CURRENT_SOURCE_DIR}/camera_wrapper" "${OpenCV_SOURCE_DIR}/platforms/android/service/engine/jni/include") # Android source tree for native camera SET (ANDROID_SOURCE_TREE "ANDROID_SOURCE_TREE-NOTFOUND" CACHE PATH diff --git a/android/README.android b/platforms/android/README.android similarity index 100% rename from android/README.android rename to platforms/android/README.android diff --git a/android/android.toolchain.cmake b/platforms/android/android.toolchain.cmake similarity index 100% rename from android/android.toolchain.cmake rename to platforms/android/android.toolchain.cmake diff --git a/android/java.rst b/platforms/android/java.rst similarity index 100% rename from android/java.rst rename to platforms/android/java.rst diff --git a/android/libinfo/CMakeLists.txt b/platforms/android/libinfo/CMakeLists.txt similarity index 100% rename from android/libinfo/CMakeLists.txt rename to platforms/android/libinfo/CMakeLists.txt diff --git a/android/libinfo/info.c b/platforms/android/libinfo/info.c similarity index 100% rename from android/libinfo/info.c rename to platforms/android/libinfo/info.c diff --git a/android/package/AndroidManifest.xml b/platforms/android/package/AndroidManifest.xml similarity index 100% rename from android/package/AndroidManifest.xml rename to platforms/android/package/AndroidManifest.xml diff --git a/android/package/CMakeLists.txt b/platforms/android/package/CMakeLists.txt similarity index 100% rename from android/package/CMakeLists.txt rename to platforms/android/package/CMakeLists.txt diff --git a/android/package/res/drawable/icon.png b/platforms/android/package/res/drawable/icon.png similarity index 100% rename from android/package/res/drawable/icon.png rename to platforms/android/package/res/drawable/icon.png diff --git a/android/package/res/values/strings.xml b/platforms/android/package/res/values/strings.xml similarity index 100% rename from android/package/res/values/strings.xml rename to platforms/android/package/res/values/strings.xml diff --git a/android/scripts/ABI_compat_generator.py b/platforms/android/scripts/ABI_compat_generator.py similarity index 100% rename from android/scripts/ABI_compat_generator.py rename to platforms/android/scripts/ABI_compat_generator.py diff --git a/android/scripts/build.cmd b/platforms/android/scripts/build.cmd similarity index 100% rename from android/scripts/build.cmd rename to platforms/android/scripts/build.cmd diff --git a/android/scripts/camera_build.conf b/platforms/android/scripts/camera_build.conf similarity index 100% rename from android/scripts/camera_build.conf rename to platforms/android/scripts/camera_build.conf diff --git a/android/scripts/cmake_android.cmd b/platforms/android/scripts/cmake_android.cmd similarity index 100% rename from android/scripts/cmake_android.cmd rename to platforms/android/scripts/cmake_android.cmd diff --git a/android/scripts/cmake_android.sh b/platforms/android/scripts/cmake_android.sh similarity index 75% rename from android/scripts/cmake_android.sh rename to platforms/android/scripts/cmake_android.sh index 101ba3cee8..941a665b80 100755 --- a/android/scripts/cmake_android.sh +++ b/platforms/android/scripts/cmake_android.sh @@ -4,5 +4,5 @@ cd `dirname $0`/.. mkdir -p build cd build -cmake -DCMAKE_BUILD_WITH_INSTALL_RPATH=ON -DCMAKE_TOOLCHAIN_FILE=../android.toolchain.cmake $@ ../.. +cmake -DCMAKE_BUILD_WITH_INSTALL_RPATH=ON -DCMAKE_TOOLCHAIN_FILE=../android.toolchain.cmake $@ ../../.. diff --git a/android/scripts/cmake_android_all_cameras.py b/platforms/android/scripts/cmake_android_all_cameras.py similarity index 92% rename from android/scripts/cmake_android_all_cameras.py rename to platforms/android/scripts/cmake_android_all_cameras.py index afcab63a75..59418944ce 100755 --- a/android/scripts/cmake_android_all_cameras.py +++ b/platforms/android/scripts/cmake_android_all_cameras.py @@ -49,7 +49,7 @@ for s in ConfFile.readlines(): os.chdir(BuildDir) BuildLog = os.path.join(BuildDir, "build.log") - CmakeCmdLine = "cmake -DCMAKE_TOOLCHAIN_FILE=../android.toolchain.cmake -DANDROID_SOURCE_TREE=\"%s\" -DANDROID_NATIVE_API_LEVEL=\"%s\" -DANDROID_ABI=\"%s\" -DANDROID_STL=stlport_static ../../ > \"%s\" 2>&1" % (AndroidTreeRoot, NativeApiLevel, Arch, BuildLog) + CmakeCmdLine = "cmake -DCMAKE_TOOLCHAIN_FILE=../android.toolchain.cmake -DANDROID_SOURCE_TREE=\"%s\" -DANDROID_NATIVE_API_LEVEL=\"%s\" -DANDROID_ABI=\"%s\" -DANDROID_STL=stlport_static ../../../ > \"%s\" 2>&1" % (AndroidTreeRoot, NativeApiLevel, Arch, BuildLog) MakeCmdLine = "make %s >> \"%s\" 2>&1" % (MakeTarget, BuildLog); #print(CmakeCmdLine) os.system(CmakeCmdLine) @@ -59,7 +59,7 @@ for s in ConfFile.readlines(): CameraLib = os.path.join(BuildDir, "lib", Arch, "lib" + MakeTarget + ".so") if (os.path.exists(CameraLib)): try: - shutil.copyfile(CameraLib, os.path.join("..", "3rdparty", "lib", Arch, "lib" + MakeTarget + ".so")) + shutil.copyfile(CameraLib, os.path.join("..", "..", "3rdparty", "lib", Arch, "lib" + MakeTarget + ".so")) print("Building %s for %s\t[\033[92mOK\033[0m]" % (MakeTarget, Arch)); except: print("Building %s for %s\t[\033[91mFAILED\033[0m]" % (MakeTarget, Arch)); diff --git a/android/scripts/cmake_android_armeabi.sh b/platforms/android/scripts/cmake_android_armeabi.sh similarity index 83% rename from android/scripts/cmake_android_armeabi.sh rename to platforms/android/scripts/cmake_android_armeabi.sh index 9c711d8855..dec0ce3429 100755 --- a/android/scripts/cmake_android_armeabi.sh +++ b/platforms/android/scripts/cmake_android_armeabi.sh @@ -4,5 +4,5 @@ cd `dirname $0`/.. mkdir -p build_armeabi cd build_armeabi -cmake -DANDROID_ABI=armeabi -DCMAKE_TOOLCHAIN_FILE=../android.toolchain.cmake $@ ../.. +cmake -DANDROID_ABI=armeabi -DCMAKE_TOOLCHAIN_FILE=../android.toolchain.cmake $@ ../../.. diff --git a/platforms/android/scripts/cmake_android_debug.sh b/platforms/android/scripts/cmake_android_debug.sh new file mode 100755 index 0000000000..dc5a3a17f6 --- /dev/null +++ b/platforms/android/scripts/cmake_android_debug.sh @@ -0,0 +1,8 @@ +#!/bin/sh +cd `dirname $0`/.. + +mkdir -p build_debug +cd build_debug + +cmake -DCMAKE_BUILD_WITH_INSTALL_RPATH=ON -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE=../android.toolchain.cmake $@ ../../.. + diff --git a/android/scripts/cmake_android_mips.sh b/platforms/android/scripts/cmake_android_mips.sh similarity index 84% rename from android/scripts/cmake_android_mips.sh rename to platforms/android/scripts/cmake_android_mips.sh index 17d2ff937e..5c4195de59 100755 --- a/android/scripts/cmake_android_mips.sh +++ b/platforms/android/scripts/cmake_android_mips.sh @@ -4,5 +4,5 @@ cd `dirname $0`/.. mkdir -p build_mips cd build_mips -cmake -DANDROID_ABI=mips -DCMAKE_TOOLCHAIN_FILE=../android.toolchain.cmake $@ ../.. +cmake -DANDROID_ABI=mips -DCMAKE_TOOLCHAIN_FILE=../android.toolchain.cmake $@ ../../.. diff --git a/android/scripts/cmake_android_neon.sh b/platforms/android/scripts/cmake_android_neon.sh similarity index 75% rename from android/scripts/cmake_android_neon.sh rename to platforms/android/scripts/cmake_android_neon.sh index 5e85605b56..716809a0b3 100755 --- a/android/scripts/cmake_android_neon.sh +++ b/platforms/android/scripts/cmake_android_neon.sh @@ -4,5 +4,5 @@ cd `dirname $0`/.. mkdir -p build_neon cd build_neon -cmake -DANDROID_ABI="armeabi-v7a with NEON" -DCMAKE_TOOLCHAIN_FILE=../android.toolchain.cmake $@ ../.. +cmake -DANDROID_ABI="armeabi-v7a with NEON" -DCMAKE_TOOLCHAIN_FILE=../android.toolchain.cmake $@ ../../.. diff --git a/android/scripts/cmake_android_service.sh b/platforms/android/scripts/cmake_android_service.sh similarity index 95% rename from android/scripts/cmake_android_service.sh rename to platforms/android/scripts/cmake_android_service.sh index 0dbd482520..c702e65dfd 100755 --- a/android/scripts/cmake_android_service.sh +++ b/platforms/android/scripts/cmake_android_service.sh @@ -4,4 +4,4 @@ cd `dirname $0`/.. mkdir -p build_service cd build_service -cmake -DCMAKE_TOOLCHAIN_FILE=../android.toolchain.cmake -DANDROID_TOOLCHAIN_NAME="arm-linux-androideabi-4.4.3" -DANDROID_STL=stlport_static -DANDROID_STL_FORCE_FEATURES=OFF -DBUILD_ANDROID_SERVICE=ON -DANDROID_SOURCE_TREE=~/Projects/AndroidSource/ServiceStub/ $@ ../.. +cmake -DCMAKE_TOOLCHAIN_FILE=../android.toolchain.cmake -DANDROID_TOOLCHAIN_NAME="arm-linux-androideabi-4.4.3" -DANDROID_STL=stlport_static -DANDROID_STL_FORCE_FEATURES=OFF -DBUILD_ANDROID_SERVICE=ON -DANDROID_SOURCE_TREE=~/Projects/AndroidSource/ServiceStub/ $@ ../../.. diff --git a/platforms/android/scripts/cmake_android_service_x86.sh b/platforms/android/scripts/cmake_android_service_x86.sh new file mode 100755 index 0000000000..89b1f7eccb --- /dev/null +++ b/platforms/android/scripts/cmake_android_service_x86.sh @@ -0,0 +1,8 @@ +#!/bin/sh +cd `dirname $0`/.. + +mkdir -p build_service_x86 +cd build_service_x86 + +cmake -DANDROID_ABI=x86 -DCMAKE_TOOLCHAIN_FILE=../android.toolchain.cmake -DANDROID_TOOLCHAIN_NAME="x86-4.4.3" -DANDROID_STL=stlport_static -DANDROID_STL_FORCE_FEATURES=OFF -DBUILD_ANDROID_SERVICE=ON -DANDROID_SOURCE_TREE=~/Projects/AndroidSource/ServiceStub/ $@ ../../.. + diff --git a/android/scripts/cmake_android_x86.sh b/platforms/android/scripts/cmake_android_x86.sh similarity index 85% rename from android/scripts/cmake_android_x86.sh rename to platforms/android/scripts/cmake_android_x86.sh index a01df2e668..539060083d 100755 --- a/android/scripts/cmake_android_x86.sh +++ b/platforms/android/scripts/cmake_android_x86.sh @@ -5,5 +5,5 @@ cd `dirname $0`/.. mkdir -p build_x86 cd build_x86 -cmake -DANDROID_ABI=x86 -DCMAKE_TOOLCHAIN_FILE=../android.toolchain.cmake $@ ../.. +cmake -DANDROID_ABI=x86 -DCMAKE_TOOLCHAIN_FILE=../android.toolchain.cmake $@ ../../.. diff --git a/android/scripts/wincfg.cmd.tmpl b/platforms/android/scripts/wincfg.cmd.tmpl similarity index 100% rename from android/scripts/wincfg.cmd.tmpl rename to platforms/android/scripts/wincfg.cmd.tmpl diff --git a/android/service/CMakeLists.txt b/platforms/android/service/CMakeLists.txt similarity index 100% rename from android/service/CMakeLists.txt rename to platforms/android/service/CMakeLists.txt diff --git a/android/service/all.py b/platforms/android/service/all.py similarity index 100% rename from android/service/all.py rename to platforms/android/service/all.py diff --git a/android/service/device.conf b/platforms/android/service/device.conf similarity index 100% rename from android/service/device.conf rename to platforms/android/service/device.conf diff --git a/android/service/doc/AndroidAppUsageModel.dia b/platforms/android/service/doc/AndroidAppUsageModel.dia similarity index 100% rename from android/service/doc/AndroidAppUsageModel.dia rename to platforms/android/service/doc/AndroidAppUsageModel.dia diff --git a/android/service/doc/BaseLoaderCallback.rst b/platforms/android/service/doc/BaseLoaderCallback.rst similarity index 100% rename from android/service/doc/BaseLoaderCallback.rst rename to platforms/android/service/doc/BaseLoaderCallback.rst diff --git a/android/service/doc/InstallCallbackInterface.rst b/platforms/android/service/doc/InstallCallbackInterface.rst similarity index 100% rename from android/service/doc/InstallCallbackInterface.rst rename to platforms/android/service/doc/InstallCallbackInterface.rst diff --git a/android/service/doc/Intro.rst b/platforms/android/service/doc/Intro.rst similarity index 100% rename from android/service/doc/Intro.rst rename to platforms/android/service/doc/Intro.rst diff --git a/android/service/doc/JavaHelper.rst b/platforms/android/service/doc/JavaHelper.rst similarity index 100% rename from android/service/doc/JavaHelper.rst rename to platforms/android/service/doc/JavaHelper.rst diff --git a/android/service/doc/LibInstallAproved.dia b/platforms/android/service/doc/LibInstallAproved.dia similarity index 100% rename from android/service/doc/LibInstallAproved.dia rename to platforms/android/service/doc/LibInstallAproved.dia diff --git a/android/service/doc/LibInstallCanceled.dia b/platforms/android/service/doc/LibInstallCanceled.dia similarity index 100% rename from android/service/doc/LibInstallCanceled.dia rename to platforms/android/service/doc/LibInstallCanceled.dia diff --git a/android/service/doc/LibInstalled.dia b/platforms/android/service/doc/LibInstalled.dia similarity index 100% rename from android/service/doc/LibInstalled.dia rename to platforms/android/service/doc/LibInstalled.dia diff --git a/android/service/doc/LoaderCallbackInterface.rst b/platforms/android/service/doc/LoaderCallbackInterface.rst similarity index 100% rename from android/service/doc/LoaderCallbackInterface.rst rename to platforms/android/service/doc/LoaderCallbackInterface.rst diff --git a/android/service/doc/Makefile b/platforms/android/service/doc/Makefile similarity index 100% rename from android/service/doc/Makefile rename to platforms/android/service/doc/Makefile diff --git a/android/service/doc/NoService.dia b/platforms/android/service/doc/NoService.dia similarity index 100% rename from android/service/doc/NoService.dia rename to platforms/android/service/doc/NoService.dia diff --git a/android/service/doc/Structure.dia b/platforms/android/service/doc/Structure.dia similarity index 100% rename from android/service/doc/Structure.dia rename to platforms/android/service/doc/Structure.dia diff --git a/android/service/doc/UseCases.rst b/platforms/android/service/doc/UseCases.rst similarity index 100% rename from android/service/doc/UseCases.rst rename to platforms/android/service/doc/UseCases.rst diff --git a/android/service/doc/build_uml.py b/platforms/android/service/doc/build_uml.py similarity index 100% rename from android/service/doc/build_uml.py rename to platforms/android/service/doc/build_uml.py diff --git a/android/service/doc/img/AndroidAppUsageModel.png b/platforms/android/service/doc/img/AndroidAppUsageModel.png similarity index 100% rename from android/service/doc/img/AndroidAppUsageModel.png rename to platforms/android/service/doc/img/AndroidAppUsageModel.png diff --git a/android/service/doc/img/LibInstallAproved.png b/platforms/android/service/doc/img/LibInstallAproved.png similarity index 100% rename from android/service/doc/img/LibInstallAproved.png rename to platforms/android/service/doc/img/LibInstallAproved.png diff --git a/android/service/doc/img/LibInstallCanceled.png b/platforms/android/service/doc/img/LibInstallCanceled.png similarity index 100% rename from android/service/doc/img/LibInstallCanceled.png rename to platforms/android/service/doc/img/LibInstallCanceled.png diff --git a/android/service/doc/img/LibInstalled.png b/platforms/android/service/doc/img/LibInstalled.png similarity index 100% rename from android/service/doc/img/LibInstalled.png rename to platforms/android/service/doc/img/LibInstalled.png diff --git a/android/service/doc/img/NoService.png b/platforms/android/service/doc/img/NoService.png similarity index 100% rename from android/service/doc/img/NoService.png rename to platforms/android/service/doc/img/NoService.png diff --git a/android/service/doc/img/Structure.png b/platforms/android/service/doc/img/Structure.png similarity index 100% rename from android/service/doc/img/Structure.png rename to platforms/android/service/doc/img/Structure.png diff --git a/android/service/doc/index.rst b/platforms/android/service/doc/index.rst similarity index 100% rename from android/service/doc/index.rst rename to platforms/android/service/doc/index.rst diff --git a/android/service/engine/.classpath b/platforms/android/service/engine/.classpath similarity index 100% rename from android/service/engine/.classpath rename to platforms/android/service/engine/.classpath diff --git a/android/service/engine/.project b/platforms/android/service/engine/.project similarity index 100% rename from android/service/engine/.project rename to platforms/android/service/engine/.project diff --git a/android/service/engine/AndroidManifest.xml b/platforms/android/service/engine/AndroidManifest.xml similarity index 100% rename from android/service/engine/AndroidManifest.xml rename to platforms/android/service/engine/AndroidManifest.xml diff --git a/android/service/engine/CMakeLists.txt b/platforms/android/service/engine/CMakeLists.txt similarity index 97% rename from android/service/engine/CMakeLists.txt rename to platforms/android/service/engine/CMakeLists.txt index 8b88393942..793c433480 100644 --- a/android/service/engine/CMakeLists.txt +++ b/platforms/android/service/engine/CMakeLists.txt @@ -24,7 +24,7 @@ else() message(WARNING "Can not automatically determine the value for ANDROID_PLATFORM_VERSION_CODE") endif() -configure_file("${CMAKE_CURRENT_SOURCE_DIR}/${ANDROID_MANIFEST_FILE}" "${OpenCV_BINARY_DIR}/android/service/engine/.build/${ANDROID_MANIFEST_FILE}" @ONLY) +configure_file("${CMAKE_CURRENT_SOURCE_DIR}/${ANDROID_MANIFEST_FILE}" "${OpenCV_BINARY_DIR}/platforms/android/service/engine/.build/${ANDROID_MANIFEST_FILE}" @ONLY) link_directories("${ANDROID_SOURCE_TREE}/out/target/product/generic/system/lib" "${ANDROID_SOURCE_TREE}/out/target/product/${ANDROID_PRODUCT}/system/lib" "${ANDROID_SOURCE_TREE}/bin/${ANDROID_ARCH_NAME}") diff --git a/android/service/engine/build.xml b/platforms/android/service/engine/build.xml similarity index 100% rename from android/service/engine/build.xml rename to platforms/android/service/engine/build.xml diff --git a/android/service/engine/jni/Android.mk b/platforms/android/service/engine/jni/Android.mk similarity index 100% rename from android/service/engine/jni/Android.mk rename to platforms/android/service/engine/jni/Android.mk diff --git a/android/service/engine/jni/Application.mk b/platforms/android/service/engine/jni/Application.mk similarity index 100% rename from android/service/engine/jni/Application.mk rename to platforms/android/service/engine/jni/Application.mk diff --git a/android/service/engine/jni/BinderComponent/BnOpenCVEngine.cpp b/platforms/android/service/engine/jni/BinderComponent/BnOpenCVEngine.cpp similarity index 100% rename from android/service/engine/jni/BinderComponent/BnOpenCVEngine.cpp rename to platforms/android/service/engine/jni/BinderComponent/BnOpenCVEngine.cpp diff --git a/android/service/engine/jni/BinderComponent/BnOpenCVEngine.h b/platforms/android/service/engine/jni/BinderComponent/BnOpenCVEngine.h similarity index 100% rename from android/service/engine/jni/BinderComponent/BnOpenCVEngine.h rename to platforms/android/service/engine/jni/BinderComponent/BnOpenCVEngine.h diff --git a/android/service/engine/jni/BinderComponent/BpOpenCVEngine.cpp b/platforms/android/service/engine/jni/BinderComponent/BpOpenCVEngine.cpp similarity index 100% rename from android/service/engine/jni/BinderComponent/BpOpenCVEngine.cpp rename to platforms/android/service/engine/jni/BinderComponent/BpOpenCVEngine.cpp diff --git a/android/service/engine/jni/BinderComponent/BpOpenCVEngine.h b/platforms/android/service/engine/jni/BinderComponent/BpOpenCVEngine.h similarity index 100% rename from android/service/engine/jni/BinderComponent/BpOpenCVEngine.h rename to platforms/android/service/engine/jni/BinderComponent/BpOpenCVEngine.h diff --git a/android/service/engine/jni/BinderComponent/HardwareDetector.cpp b/platforms/android/service/engine/jni/BinderComponent/HardwareDetector.cpp similarity index 100% rename from android/service/engine/jni/BinderComponent/HardwareDetector.cpp rename to platforms/android/service/engine/jni/BinderComponent/HardwareDetector.cpp diff --git a/android/service/engine/jni/BinderComponent/HardwareDetector.h b/platforms/android/service/engine/jni/BinderComponent/HardwareDetector.h similarity index 100% rename from android/service/engine/jni/BinderComponent/HardwareDetector.h rename to platforms/android/service/engine/jni/BinderComponent/HardwareDetector.h diff --git a/android/service/engine/jni/BinderComponent/OpenCVEngine.cpp b/platforms/android/service/engine/jni/BinderComponent/OpenCVEngine.cpp similarity index 100% rename from android/service/engine/jni/BinderComponent/OpenCVEngine.cpp rename to platforms/android/service/engine/jni/BinderComponent/OpenCVEngine.cpp diff --git a/android/service/engine/jni/BinderComponent/OpenCVEngine.h b/platforms/android/service/engine/jni/BinderComponent/OpenCVEngine.h similarity index 100% rename from android/service/engine/jni/BinderComponent/OpenCVEngine.h rename to platforms/android/service/engine/jni/BinderComponent/OpenCVEngine.h diff --git a/android/service/engine/jni/BinderComponent/ProcReader.cpp b/platforms/android/service/engine/jni/BinderComponent/ProcReader.cpp similarity index 100% rename from android/service/engine/jni/BinderComponent/ProcReader.cpp rename to platforms/android/service/engine/jni/BinderComponent/ProcReader.cpp diff --git a/android/service/engine/jni/BinderComponent/ProcReader.h b/platforms/android/service/engine/jni/BinderComponent/ProcReader.h similarity index 100% rename from android/service/engine/jni/BinderComponent/ProcReader.h rename to platforms/android/service/engine/jni/BinderComponent/ProcReader.h diff --git a/android/service/engine/jni/BinderComponent/StringUtils.cpp b/platforms/android/service/engine/jni/BinderComponent/StringUtils.cpp similarity index 100% rename from android/service/engine/jni/BinderComponent/StringUtils.cpp rename to platforms/android/service/engine/jni/BinderComponent/StringUtils.cpp diff --git a/android/service/engine/jni/BinderComponent/StringUtils.h b/platforms/android/service/engine/jni/BinderComponent/StringUtils.h similarity index 100% rename from android/service/engine/jni/BinderComponent/StringUtils.h rename to platforms/android/service/engine/jni/BinderComponent/StringUtils.h diff --git a/android/service/engine/jni/BinderComponent/TegraDetector.cpp b/platforms/android/service/engine/jni/BinderComponent/TegraDetector.cpp similarity index 100% rename from android/service/engine/jni/BinderComponent/TegraDetector.cpp rename to platforms/android/service/engine/jni/BinderComponent/TegraDetector.cpp diff --git a/android/service/engine/jni/BinderComponent/TegraDetector.h b/platforms/android/service/engine/jni/BinderComponent/TegraDetector.h similarity index 100% rename from android/service/engine/jni/BinderComponent/TegraDetector.h rename to platforms/android/service/engine/jni/BinderComponent/TegraDetector.h diff --git a/android/service/engine/jni/JNIWrapper/HardwareDetector_jni.cpp b/platforms/android/service/engine/jni/JNIWrapper/HardwareDetector_jni.cpp similarity index 100% rename from android/service/engine/jni/JNIWrapper/HardwareDetector_jni.cpp rename to platforms/android/service/engine/jni/JNIWrapper/HardwareDetector_jni.cpp diff --git a/android/service/engine/jni/JNIWrapper/HardwareDetector_jni.h b/platforms/android/service/engine/jni/JNIWrapper/HardwareDetector_jni.h similarity index 100% rename from android/service/engine/jni/JNIWrapper/HardwareDetector_jni.h rename to platforms/android/service/engine/jni/JNIWrapper/HardwareDetector_jni.h diff --git a/android/service/engine/jni/JNIWrapper/JavaBasedPackageManager.cpp b/platforms/android/service/engine/jni/JNIWrapper/JavaBasedPackageManager.cpp similarity index 100% rename from android/service/engine/jni/JNIWrapper/JavaBasedPackageManager.cpp rename to platforms/android/service/engine/jni/JNIWrapper/JavaBasedPackageManager.cpp diff --git a/android/service/engine/jni/JNIWrapper/JavaBasedPackageManager.h b/platforms/android/service/engine/jni/JNIWrapper/JavaBasedPackageManager.h similarity index 100% rename from android/service/engine/jni/JNIWrapper/JavaBasedPackageManager.h rename to platforms/android/service/engine/jni/JNIWrapper/JavaBasedPackageManager.h diff --git a/android/service/engine/jni/JNIWrapper/OpenCVEngine_jni.cpp b/platforms/android/service/engine/jni/JNIWrapper/OpenCVEngine_jni.cpp similarity index 100% rename from android/service/engine/jni/JNIWrapper/OpenCVEngine_jni.cpp rename to platforms/android/service/engine/jni/JNIWrapper/OpenCVEngine_jni.cpp diff --git a/android/service/engine/jni/JNIWrapper/OpenCVEngine_jni.h b/platforms/android/service/engine/jni/JNIWrapper/OpenCVEngine_jni.h similarity index 100% rename from android/service/engine/jni/JNIWrapper/OpenCVEngine_jni.h rename to platforms/android/service/engine/jni/JNIWrapper/OpenCVEngine_jni.h diff --git a/android/service/engine/jni/JNIWrapper/OpenCVLibraryInfo.cpp b/platforms/android/service/engine/jni/JNIWrapper/OpenCVLibraryInfo.cpp similarity index 100% rename from android/service/engine/jni/JNIWrapper/OpenCVLibraryInfo.cpp rename to platforms/android/service/engine/jni/JNIWrapper/OpenCVLibraryInfo.cpp diff --git a/android/service/engine/jni/JNIWrapper/OpenCVLibraryInfo.h b/platforms/android/service/engine/jni/JNIWrapper/OpenCVLibraryInfo.h similarity index 100% rename from android/service/engine/jni/JNIWrapper/OpenCVLibraryInfo.h rename to platforms/android/service/engine/jni/JNIWrapper/OpenCVLibraryInfo.h diff --git a/android/service/engine/jni/NativeClient/ClientMain.cpp b/platforms/android/service/engine/jni/NativeClient/ClientMain.cpp similarity index 100% rename from android/service/engine/jni/NativeClient/ClientMain.cpp rename to platforms/android/service/engine/jni/NativeClient/ClientMain.cpp diff --git a/android/service/engine/jni/NativeService/CommonPackageManager.cpp b/platforms/android/service/engine/jni/NativeService/CommonPackageManager.cpp similarity index 100% rename from android/service/engine/jni/NativeService/CommonPackageManager.cpp rename to platforms/android/service/engine/jni/NativeService/CommonPackageManager.cpp diff --git a/android/service/engine/jni/NativeService/CommonPackageManager.h b/platforms/android/service/engine/jni/NativeService/CommonPackageManager.h similarity index 100% rename from android/service/engine/jni/NativeService/CommonPackageManager.h rename to platforms/android/service/engine/jni/NativeService/CommonPackageManager.h diff --git a/android/service/engine/jni/NativeService/NativePackageManager.cpp b/platforms/android/service/engine/jni/NativeService/NativePackageManager.cpp similarity index 100% rename from android/service/engine/jni/NativeService/NativePackageManager.cpp rename to platforms/android/service/engine/jni/NativeService/NativePackageManager.cpp diff --git a/android/service/engine/jni/NativeService/NativePackageManager.h b/platforms/android/service/engine/jni/NativeService/NativePackageManager.h similarity index 100% rename from android/service/engine/jni/NativeService/NativePackageManager.h rename to platforms/android/service/engine/jni/NativeService/NativePackageManager.h diff --git a/android/service/engine/jni/NativeService/PackageInfo.cpp b/platforms/android/service/engine/jni/NativeService/PackageInfo.cpp similarity index 100% rename from android/service/engine/jni/NativeService/PackageInfo.cpp rename to platforms/android/service/engine/jni/NativeService/PackageInfo.cpp diff --git a/android/service/engine/jni/NativeService/PackageInfo.h b/platforms/android/service/engine/jni/NativeService/PackageInfo.h similarity index 100% rename from android/service/engine/jni/NativeService/PackageInfo.h rename to platforms/android/service/engine/jni/NativeService/PackageInfo.h diff --git a/android/service/engine/jni/NativeService/ServiceMain.cpp b/platforms/android/service/engine/jni/NativeService/ServiceMain.cpp similarity index 100% rename from android/service/engine/jni/NativeService/ServiceMain.cpp rename to platforms/android/service/engine/jni/NativeService/ServiceMain.cpp diff --git a/android/service/engine/jni/Tests/HardwareDetectionTest.cpp b/platforms/android/service/engine/jni/Tests/HardwareDetectionTest.cpp similarity index 100% rename from android/service/engine/jni/Tests/HardwareDetectionTest.cpp rename to platforms/android/service/engine/jni/Tests/HardwareDetectionTest.cpp diff --git a/android/service/engine/jni/Tests/OpenCVEngineTest.cpp b/platforms/android/service/engine/jni/Tests/OpenCVEngineTest.cpp similarity index 100% rename from android/service/engine/jni/Tests/OpenCVEngineTest.cpp rename to platforms/android/service/engine/jni/Tests/OpenCVEngineTest.cpp diff --git a/android/service/engine/jni/Tests/PackageInfoTest.cpp b/platforms/android/service/engine/jni/Tests/PackageInfoTest.cpp similarity index 100% rename from android/service/engine/jni/Tests/PackageInfoTest.cpp rename to platforms/android/service/engine/jni/Tests/PackageInfoTest.cpp diff --git a/android/service/engine/jni/Tests/PackageManagerStub.cpp b/platforms/android/service/engine/jni/Tests/PackageManagerStub.cpp similarity index 100% rename from android/service/engine/jni/Tests/PackageManagerStub.cpp rename to platforms/android/service/engine/jni/Tests/PackageManagerStub.cpp diff --git a/android/service/engine/jni/Tests/PackageManagerStub.h b/platforms/android/service/engine/jni/Tests/PackageManagerStub.h similarity index 100% rename from android/service/engine/jni/Tests/PackageManagerStub.h rename to platforms/android/service/engine/jni/Tests/PackageManagerStub.h diff --git a/android/service/engine/jni/Tests/PackageManagmentTest.cpp b/platforms/android/service/engine/jni/Tests/PackageManagmentTest.cpp similarity index 100% rename from android/service/engine/jni/Tests/PackageManagmentTest.cpp rename to platforms/android/service/engine/jni/Tests/PackageManagmentTest.cpp diff --git a/android/service/engine/jni/Tests/TestMain.cpp b/platforms/android/service/engine/jni/Tests/TestMain.cpp similarity index 100% rename from android/service/engine/jni/Tests/TestMain.cpp rename to platforms/android/service/engine/jni/Tests/TestMain.cpp diff --git a/android/service/engine/jni/Tests/Tests.mk b/platforms/android/service/engine/jni/Tests/Tests.mk similarity index 100% rename from android/service/engine/jni/Tests/Tests.mk rename to platforms/android/service/engine/jni/Tests/Tests.mk diff --git a/android/service/engine/jni/Tests/gtest/gtest-all.cpp b/platforms/android/service/engine/jni/Tests/gtest/gtest-all.cpp similarity index 100% rename from android/service/engine/jni/Tests/gtest/gtest-all.cpp rename to platforms/android/service/engine/jni/Tests/gtest/gtest-all.cpp diff --git a/android/service/engine/jni/Tests/gtest/gtest.h b/platforms/android/service/engine/jni/Tests/gtest/gtest.h similarity index 100% rename from android/service/engine/jni/Tests/gtest/gtest.h rename to platforms/android/service/engine/jni/Tests/gtest/gtest.h diff --git a/android/service/engine/jni/include/EngineCommon.h b/platforms/android/service/engine/jni/include/EngineCommon.h similarity index 100% rename from android/service/engine/jni/include/EngineCommon.h rename to platforms/android/service/engine/jni/include/EngineCommon.h diff --git a/android/service/engine/jni/include/IOpenCVEngine.h b/platforms/android/service/engine/jni/include/IOpenCVEngine.h similarity index 100% rename from android/service/engine/jni/include/IOpenCVEngine.h rename to platforms/android/service/engine/jni/include/IOpenCVEngine.h diff --git a/android/service/engine/jni/include/IPackageManager.h b/platforms/android/service/engine/jni/include/IPackageManager.h similarity index 100% rename from android/service/engine/jni/include/IPackageManager.h rename to platforms/android/service/engine/jni/include/IPackageManager.h diff --git a/android/service/engine/jni/include/OpenCVEngineHelper.h b/platforms/android/service/engine/jni/include/OpenCVEngineHelper.h similarity index 100% rename from android/service/engine/jni/include/OpenCVEngineHelper.h rename to platforms/android/service/engine/jni/include/OpenCVEngineHelper.h diff --git a/android/service/engine/project.properties b/platforms/android/service/engine/project.properties similarity index 100% rename from android/service/engine/project.properties rename to platforms/android/service/engine/project.properties diff --git a/android/service/engine/res/drawable/icon.png b/platforms/android/service/engine/res/drawable/icon.png similarity index 100% rename from android/service/engine/res/drawable/icon.png rename to platforms/android/service/engine/res/drawable/icon.png diff --git a/android/service/engine/res/layout-small/info.xml b/platforms/android/service/engine/res/layout-small/info.xml similarity index 100% rename from android/service/engine/res/layout-small/info.xml rename to platforms/android/service/engine/res/layout-small/info.xml diff --git a/android/service/engine/res/layout-small/main.xml b/platforms/android/service/engine/res/layout-small/main.xml similarity index 100% rename from android/service/engine/res/layout-small/main.xml rename to platforms/android/service/engine/res/layout-small/main.xml diff --git a/android/service/engine/res/layout/info.xml b/platforms/android/service/engine/res/layout/info.xml similarity index 100% rename from android/service/engine/res/layout/info.xml rename to platforms/android/service/engine/res/layout/info.xml diff --git a/android/service/engine/res/layout/main.xml b/platforms/android/service/engine/res/layout/main.xml similarity index 100% rename from android/service/engine/res/layout/main.xml rename to platforms/android/service/engine/res/layout/main.xml diff --git a/android/service/engine/res/values/strings.xml b/platforms/android/service/engine/res/values/strings.xml similarity index 100% rename from android/service/engine/res/values/strings.xml rename to platforms/android/service/engine/res/values/strings.xml diff --git a/android/service/engine/src/org/opencv/engine/BinderConnector.java b/platforms/android/service/engine/src/org/opencv/engine/BinderConnector.java similarity index 100% rename from android/service/engine/src/org/opencv/engine/BinderConnector.java rename to platforms/android/service/engine/src/org/opencv/engine/BinderConnector.java diff --git a/android/service/engine/src/org/opencv/engine/HardwareDetector.java b/platforms/android/service/engine/src/org/opencv/engine/HardwareDetector.java similarity index 100% rename from android/service/engine/src/org/opencv/engine/HardwareDetector.java rename to platforms/android/service/engine/src/org/opencv/engine/HardwareDetector.java diff --git a/android/service/engine/src/org/opencv/engine/MarketConnector.java b/platforms/android/service/engine/src/org/opencv/engine/MarketConnector.java similarity index 100% rename from android/service/engine/src/org/opencv/engine/MarketConnector.java rename to platforms/android/service/engine/src/org/opencv/engine/MarketConnector.java diff --git a/android/service/engine/src/org/opencv/engine/OpenCVEngineInterface.aidl b/platforms/android/service/engine/src/org/opencv/engine/OpenCVEngineInterface.aidl similarity index 100% rename from android/service/engine/src/org/opencv/engine/OpenCVEngineInterface.aidl rename to platforms/android/service/engine/src/org/opencv/engine/OpenCVEngineInterface.aidl diff --git a/android/service/engine/src/org/opencv/engine/OpenCVEngineService.java b/platforms/android/service/engine/src/org/opencv/engine/OpenCVEngineService.java similarity index 100% rename from android/service/engine/src/org/opencv/engine/OpenCVEngineService.java rename to platforms/android/service/engine/src/org/opencv/engine/OpenCVEngineService.java diff --git a/android/service/engine/src/org/opencv/engine/OpenCVLibraryInfo.java b/platforms/android/service/engine/src/org/opencv/engine/OpenCVLibraryInfo.java similarity index 100% rename from android/service/engine/src/org/opencv/engine/OpenCVLibraryInfo.java rename to platforms/android/service/engine/src/org/opencv/engine/OpenCVLibraryInfo.java diff --git a/android/service/engine/src/org/opencv/engine/manager/ManagerActivity.java b/platforms/android/service/engine/src/org/opencv/engine/manager/ManagerActivity.java similarity index 100% rename from android/service/engine/src/org/opencv/engine/manager/ManagerActivity.java rename to platforms/android/service/engine/src/org/opencv/engine/manager/ManagerActivity.java diff --git a/android/service/engine/src/org/opencv/engine/manager/PackageListAdapter.java b/platforms/android/service/engine/src/org/opencv/engine/manager/PackageListAdapter.java similarity index 100% rename from android/service/engine/src/org/opencv/engine/manager/PackageListAdapter.java rename to platforms/android/service/engine/src/org/opencv/engine/manager/PackageListAdapter.java diff --git a/android/service/engine_test/.classpath b/platforms/android/service/engine_test/.classpath similarity index 100% rename from android/service/engine_test/.classpath rename to platforms/android/service/engine_test/.classpath diff --git a/android/service/engine_test/.project b/platforms/android/service/engine_test/.project similarity index 100% rename from android/service/engine_test/.project rename to platforms/android/service/engine_test/.project diff --git a/android/service/engine_test/AndroidManifest.xml b/platforms/android/service/engine_test/AndroidManifest.xml similarity index 100% rename from android/service/engine_test/AndroidManifest.xml rename to platforms/android/service/engine_test/AndroidManifest.xml diff --git a/android/service/engine_test/build.xml b/platforms/android/service/engine_test/build.xml similarity index 100% rename from android/service/engine_test/build.xml rename to platforms/android/service/engine_test/build.xml diff --git a/android/service/engine_test/project.properties b/platforms/android/service/engine_test/project.properties similarity index 100% rename from android/service/engine_test/project.properties rename to platforms/android/service/engine_test/project.properties diff --git a/android/service/engine_test/res/drawable-hdpi/ic_launcher.png b/platforms/android/service/engine_test/res/drawable-hdpi/ic_launcher.png similarity index 100% rename from android/service/engine_test/res/drawable-hdpi/ic_launcher.png rename to platforms/android/service/engine_test/res/drawable-hdpi/ic_launcher.png diff --git a/android/service/engine_test/res/drawable-ldpi/ic_launcher.png b/platforms/android/service/engine_test/res/drawable-ldpi/ic_launcher.png similarity index 100% rename from android/service/engine_test/res/drawable-ldpi/ic_launcher.png rename to platforms/android/service/engine_test/res/drawable-ldpi/ic_launcher.png diff --git a/android/service/engine_test/res/drawable-mdpi/ic_launcher.png b/platforms/android/service/engine_test/res/drawable-mdpi/ic_launcher.png similarity index 100% rename from android/service/engine_test/res/drawable-mdpi/ic_launcher.png rename to platforms/android/service/engine_test/res/drawable-mdpi/ic_launcher.png diff --git a/android/service/engine_test/res/layout/main.xml b/platforms/android/service/engine_test/res/layout/main.xml similarity index 100% rename from android/service/engine_test/res/layout/main.xml rename to platforms/android/service/engine_test/res/layout/main.xml diff --git a/android/service/engine_test/res/values/strings.xml b/platforms/android/service/engine_test/res/values/strings.xml similarity index 100% rename from android/service/engine_test/res/values/strings.xml rename to platforms/android/service/engine_test/res/values/strings.xml diff --git a/android/service/engine_test/src/org/opencv/engine/test/EngineInterfaceTest.java b/platforms/android/service/engine_test/src/org/opencv/engine/test/EngineInterfaceTest.java similarity index 100% rename from android/service/engine_test/src/org/opencv/engine/test/EngineInterfaceTest.java rename to platforms/android/service/engine_test/src/org/opencv/engine/test/EngineInterfaceTest.java diff --git a/android/service/push_native.py b/platforms/android/service/push_native.py similarity index 100% rename from android/service/push_native.py rename to platforms/android/service/push_native.py diff --git a/android/service/readme.txt b/platforms/android/service/readme.txt similarity index 100% rename from android/service/readme.txt rename to platforms/android/service/readme.txt diff --git a/android/service/test_native.py b/platforms/android/service/test_native.py similarity index 100% rename from android/service/test_native.py rename to platforms/android/service/test_native.py From 7561b1c6e5d28d2d6c152bccc9413eed3e90f159 Mon Sep 17 00:00:00 2001 From: Roman Donchenko Date: Wed, 22 May 2013 15:14:38 +0400 Subject: [PATCH 034/178] Replace the pull request guidelines with a link to the wiki. I've moved the contents of CONTRIBUTING.md to the wiki (and slightly expanded it), so the former is no longer required. I've put a link to the wiki page and a summary in the README. --- CONTRIBUTING.md | 11 ----------- README | 11 +++++++++++ 2 files changed, 11 insertions(+), 11 deletions(-) delete mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 8fc54b1b8e..0000000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,11 +0,0 @@ -We greatly appreciate your support and contributions and they are always welcomed! - -Github pull requests are the convenient way to contribute to OpenCV project. Good pull requests have all of these attributes: - -* Are scoped to one specific issue -* Include a test to demonstrate the correctness -* Update the docs if relevant -* Match the [coding style guidelines](http://code.opencv.org/projects/opencv/wiki/CodingStyleGuide) -* Don't messed by "oops" commits - -You can find more detailes about contributing process on http://opencv.org/contribute.html \ No newline at end of file diff --git a/README b/README index 9dd45a230b..0799dff89f 100644 --- a/README +++ b/README @@ -4,3 +4,14 @@ Homepage: http://opencv.org Online docs: http://docs.opencv.org Q&A forum: http://answers.opencv.org Dev zone: http://code.opencv.org + +Please read before starting work on a pull request: + http://code.opencv.org/projects/opencv/wiki/How_to_contribute + +Summary of guidelines: + +* One pull request per issue; +* Choose the right base branch; +* Include tests and documentation; +* Clean up "oops" commits before submitting; +* Follow the coding style guide. From 6fae02c05dc1dbeb3b799f51ab19a72a7ef5c37a Mon Sep 17 00:00:00 2001 From: peng xiao Date: Tue, 28 May 2013 11:12:05 +0800 Subject: [PATCH 035/178] Fix some OpenCL kernel file build errors on Mac. --- modules/ocl/src/filtering.cpp | 3 +- modules/ocl/src/mcwutil.cpp | 34 ++++++++++++++----- modules/ocl/src/opencl/arithm_add.cl | 10 +++--- .../ocl/src/opencl/arithm_add_scalar_mask.cl | 6 ++-- modules/ocl/src/opencl/filtering_morph.cl | 2 +- modules/ocl/src/opencl/imgproc_threshold.cl | 2 +- modules/ocl/src/precomp.hpp | 1 + 7 files changed, 38 insertions(+), 20 deletions(-) diff --git a/modules/ocl/src/filtering.cpp b/modules/ocl/src/filtering.cpp index cc07209b15..56a70ae539 100644 --- a/modules/ocl/src/filtering.cpp +++ b/modules/ocl/src/filtering.cpp @@ -356,8 +356,7 @@ static void GPUDilate(const oclMat &src, oclMat &dst, oclMat &mat_kernel, char compile_option[128]; sprintf(compile_option, "-D RADIUSX=%d -D RADIUSY=%d -D LSIZE0=%d -D LSIZE1=%d -D DILATE %s %s", anchor.x, anchor.y, (int)localThreads[0], (int)localThreads[1], - rectKernel?"-D RECTKERNEL":"", - s); + s, rectKernel?"-D RECTKERNEL":""); vector< pair > args; args.push_back(make_pair(sizeof(cl_mem), (void *)&src.data)); args.push_back(make_pair(sizeof(cl_mem), (void *)&dst.data)); diff --git a/modules/ocl/src/mcwutil.cpp b/modules/ocl/src/mcwutil.cpp index 3bcb8700b7..79800c5242 100644 --- a/modules/ocl/src/mcwutil.cpp +++ b/modules/ocl/src/mcwutil.cpp @@ -43,9 +43,28 @@ // //M*/ -#define CL_USE_DEPRECATED_OPENCL_1_1_APIS #include "precomp.hpp" +#ifdef __GNUC__ +#if ((__GNUC__ * 100) + __GNUC_MINOR__) >= 402 +#define GCC_DIAG_STR(s) #s +#define GCC_DIAG_JOINSTR(x,y) GCC_DIAG_STR(x ## y) +# define GCC_DIAG_DO_PRAGMA(x) _Pragma (#x) +# define GCC_DIAG_PRAGMA(x) GCC_DIAG_DO_PRAGMA(GCC diagnostic x) +# if ((__GNUC__ * 100) + __GNUC_MINOR__) >= 406 +# define GCC_DIAG_OFF(x) GCC_DIAG_PRAGMA(push) \ +GCC_DIAG_PRAGMA(ignored GCC_DIAG_JOINSTR(-W,x)) +# define GCC_DIAG_ON(x) GCC_DIAG_PRAGMA(pop) +# else +# define GCC_DIAG_OFF(x) GCC_DIAG_PRAGMA(ignored GCC_DIAG_JOINSTR(-W,x)) +# define GCC_DIAG_ON(x) GCC_DIAG_PRAGMA(warning GCC_DIAG_JOINSTR(-W,x)) +# endif +#else +# define GCC_DIAG_OFF(x) +# define GCC_DIAG_ON(x) +#endif +#endif /* __GNUC__ */ + using namespace std; namespace cv @@ -121,6 +140,9 @@ namespace cv build_options, finish_mode); } +#ifdef __GNUC__ + GCC_DIAG_OFF(deprecated-declarations) +#endif cl_mem bindTexture(const oclMat &mat) { cl_mem texture; @@ -180,10 +202,6 @@ namespace cv else #endif { -#ifdef __GNUC__ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" -#endif texture = clCreateImage2D( (cl_context)mat.clCxt->oclContext(), CL_MEM_READ_WRITE, @@ -193,9 +211,6 @@ namespace cv 0, NULL, &err); -#ifdef __GNUC__ -#pragma GCC diagnostic pop -#endif } size_t origin[] = { 0, 0, 0 }; size_t region[] = { mat.cols, mat.rows, 1 }; @@ -225,6 +240,9 @@ namespace cv openCLSafeCall(err); return texture; } +#ifdef __GNUC__ + GCC_DIAG_ON(deprecated-declarations) +#endif void releaseTexture(cl_mem& texture) { openCLFree(texture); diff --git a/modules/ocl/src/opencl/arithm_add.cl b/modules/ocl/src/opencl/arithm_add.cl index 7d4b0a7653..070ced4731 100644 --- a/modules/ocl/src/opencl/arithm_add.cl +++ b/modules/ocl/src/opencl/arithm_add.cl @@ -127,7 +127,7 @@ __kernel void arithm_add_D2 (__global ushort *src1, int src1_step, int src1_offs #ifdef dst_align #undef dst_align #endif -#define dst_align ((dst_offset >> 1) & 3) +#define dst_align ((dst_offset / 2) & 3) int src1_index = mad24(y, src1_step, (x << 1) + src1_offset - (dst_align << 1)); int src2_index = mad24(y, src2_step, (x << 1) + src2_offset - (dst_align << 1)); @@ -165,7 +165,7 @@ __kernel void arithm_add_D3 (__global short *src1, int src1_step, int src1_offse #ifdef dst_align #undef dst_align #endif -#define dst_align ((dst_offset >> 1) & 3) +#define dst_align ((dst_offset / 2) & 3) int src1_index = mad24(y, src1_step, (x << 1) + src1_offset - (dst_align << 1)); int src2_index = mad24(y, src2_step, (x << 1) + src2_offset - (dst_align << 1)); @@ -335,7 +335,7 @@ __kernel void arithm_add_with_mask_C1_D2 (__global ushort *src1, int src1_step, #ifdef dst_align #undef dst_align #endif -#define dst_align ((dst_offset >> 1) & 1) +#define dst_align ((dst_offset / 2) & 1) int src1_index = mad24(y, src1_step, (x << 1) + src1_offset - (dst_align << 1)); int src2_index = mad24(y, src2_step, (x << 1) + src2_offset - (dst_align << 1)); int mask_index = mad24(y, mask_step, x + mask_offset - dst_align); @@ -375,7 +375,7 @@ __kernel void arithm_add_with_mask_C1_D3 (__global short *src1, int src1_step, i #ifdef dst_align #undef dst_align #endif -#define dst_align ((dst_offset >> 1) & 1) +#define dst_align ((dst_offset / 2) & 1) int src1_index = mad24(y, src1_step, (x << 1) + src1_offset - (dst_align << 1)); int src2_index = mad24(y, src2_step, (x << 1) + src2_offset - (dst_align << 1)); int mask_index = mad24(y, mask_step, x + mask_offset - dst_align); @@ -507,7 +507,7 @@ __kernel void arithm_add_with_mask_C2_D0 (__global uchar *src1, int src1_step, i #ifdef dst_align #undef dst_align #endif -#define dst_align ((dst_offset >> 1) & 1) +#define dst_align ((dst_offset / 2) & 1) int src1_index = mad24(y, src1_step, (x << 1) + src1_offset - (dst_align << 1)); int src2_index = mad24(y, src2_step, (x << 1) + src2_offset - (dst_align << 1)); int mask_index = mad24(y, mask_step, x + mask_offset - dst_align); diff --git a/modules/ocl/src/opencl/arithm_add_scalar_mask.cl b/modules/ocl/src/opencl/arithm_add_scalar_mask.cl index fdf65923cd..3dbd376ecf 100644 --- a/modules/ocl/src/opencl/arithm_add_scalar_mask.cl +++ b/modules/ocl/src/opencl/arithm_add_scalar_mask.cl @@ -126,7 +126,7 @@ __kernel void arithm_s_add_with_mask_C1_D2 (__global ushort *src1, int src1_st #ifdef dst_align #undef dst_align #endif -#define dst_align ((dst_offset >> 1) & 1) +#define dst_align ((dst_offset / 2) & 1) int src1_index = mad24(y, src1_step, (x << 1) + src1_offset - (dst_align << 1)); int mask_index = mad24(y, mask_step, x + mask_offset - dst_align); @@ -164,7 +164,7 @@ __kernel void arithm_s_add_with_mask_C1_D3 (__global short *src1, int src1_ste #ifdef dst_align #undef dst_align #endif -#define dst_align ((dst_offset >> 1) & 1) +#define dst_align ((dst_offset / 2) & 1) int src1_index = mad24(y, src1_step, (x << 1) + src1_offset - (dst_align << 1)); int mask_index = mad24(y, mask_step, x + mask_offset - dst_align); @@ -288,7 +288,7 @@ __kernel void arithm_s_add_with_mask_C2_D0 (__global uchar *src1, int src1_ste #ifdef dst_align #undef dst_align #endif -#define dst_align ((dst_offset >> 1) & 1) +#define dst_align ((dst_offset / 2) & 1) int src1_index = mad24(y, src1_step, (x << 1) + src1_offset - (dst_align << 1)); int mask_index = mad24(y, mask_step, x + mask_offset - dst_align); diff --git a/modules/ocl/src/opencl/filtering_morph.cl b/modules/ocl/src/opencl/filtering_morph.cl index 49640008f4..e659a59f51 100644 --- a/modules/ocl/src/opencl/filtering_morph.cl +++ b/modules/ocl/src/opencl/filtering_morph.cl @@ -120,7 +120,7 @@ __kernel void morph_C1_D0(__global const uchar * restrict src, int gidy = get_global_id(1); int out_addr = mad24(gidy,dst_step_in_pixel,gidx+dst_offset_in_pixel); - if(gidx+3= 0 && dpos < dst_cols; - ddata = convert_float4(con) != 0 ? ddata : dVal; + ddata = convert_float4(con) != (float4)(0) ? ddata : dVal; if(dstart < dst_cols) { *(__global float4*)(dst+dst_offset+gy*dst_step+dstart) = ddata; diff --git a/modules/ocl/src/precomp.hpp b/modules/ocl/src/precomp.hpp index b2a3e41c6f..4f93eac420 100644 --- a/modules/ocl/src/precomp.hpp +++ b/modules/ocl/src/precomp.hpp @@ -78,6 +78,7 @@ #if defined (HAVE_OPENCL) +#define CL_USE_DEPRECATED_OPENCL_1_1_APIS #include "opencv2/ocl/private/util.hpp" #include "safe_call.hpp" From f85cf5bdd91cbf78a4afb0bc8947ac45922693e6 Mon Sep 17 00:00:00 2001 From: Alexander Smorkalov Date: Wed, 22 May 2013 18:09:24 +0400 Subject: [PATCH 036/178] Build fixes. Build scrips reorganized. --- android/android.toolchain.cmake | 1634 +++++++++++++++++ android/readme.txt | 1 + platforms/android/refman.rst | 9 + platforms/android/scripts/build.cmd | 90 - platforms/android/scripts/cmake_android.cmd | 5 - .../android/scripts/cmake_android_armeabi.sh | 8 - .../android/scripts/cmake_android_debug.sh | 8 - .../android/scripts/cmake_android_mips.sh | 8 - .../android/scripts/cmake_android_neon.sh | 8 - .../android/scripts/cmake_android_service.sh | 7 - .../scripts/cmake_android_service_x86.sh | 8 - .../android/scripts/cmake_android_x86.sh | 9 - platforms/android/scripts/wincfg.cmd.tmpl | 30 - platforms/android/service/doc/Makefile | 89 - .../android/service/engine/CMakeLists.txt | 1 - .../engine/jni/Tests/PackageInfoTest.cpp | 1 - .../engine/jni/Tests/PackageManagmentTest.cpp | 2 - platforms/android/service/test_native.py | 1 - .../linux/scripts/cmake_arm_gnueabi_hardfp.sh | 8 - .../linux/scripts/cmake_arm_gnueabi_softfp.sh | 8 - .../scripts/ABI_compat_generator.py | 6 +- .../{android => }/scripts/camera_build.conf | 0 .../scripts/cmake_android_all_cameras.py | 4 +- .../cmake_android_arm.sh} | 7 +- platforms/scripts/cmake_android_mips.sh | 7 + platforms/scripts/cmake_android_service.sh | 7 + platforms/scripts/cmake_android_x86.sh | 8 + platforms/scripts/cmake_arm_gnueabi_hardfp.sh | 7 + platforms/scripts/cmake_arm_gnueabi_softfp.sh | 7 + platforms/{linux => }/scripts/cmake_carma.sh | 0 platforms/{winrt => }/scripts/cmake_winrt.cmd | 0 31 files changed, 1687 insertions(+), 301 deletions(-) create mode 100644 android/android.toolchain.cmake create mode 100644 android/readme.txt create mode 100644 platforms/android/refman.rst delete mode 100644 platforms/android/scripts/build.cmd delete mode 100644 platforms/android/scripts/cmake_android.cmd delete mode 100755 platforms/android/scripts/cmake_android_armeabi.sh delete mode 100755 platforms/android/scripts/cmake_android_debug.sh delete mode 100755 platforms/android/scripts/cmake_android_mips.sh delete mode 100755 platforms/android/scripts/cmake_android_neon.sh delete mode 100755 platforms/android/scripts/cmake_android_service.sh delete mode 100755 platforms/android/scripts/cmake_android_service_x86.sh delete mode 100755 platforms/android/scripts/cmake_android_x86.sh delete mode 100644 platforms/android/scripts/wincfg.cmd.tmpl delete mode 100644 platforms/android/service/doc/Makefile delete mode 100755 platforms/linux/scripts/cmake_arm_gnueabi_hardfp.sh delete mode 100755 platforms/linux/scripts/cmake_arm_gnueabi_softfp.sh rename platforms/{android => }/scripts/ABI_compat_generator.py (98%) rename platforms/{android => }/scripts/camera_build.conf (100%) rename platforms/{android => }/scripts/cmake_android_all_cameras.py (85%) rename platforms/{android/scripts/cmake_android.sh => scripts/cmake_android_arm.sh} (50%) create mode 100755 platforms/scripts/cmake_android_mips.sh create mode 100755 platforms/scripts/cmake_android_service.sh create mode 100755 platforms/scripts/cmake_android_x86.sh create mode 100755 platforms/scripts/cmake_arm_gnueabi_hardfp.sh create mode 100755 platforms/scripts/cmake_arm_gnueabi_softfp.sh rename platforms/{linux => }/scripts/cmake_carma.sh (100%) rename platforms/{winrt => }/scripts/cmake_winrt.cmd (100%) diff --git a/android/android.toolchain.cmake b/android/android.toolchain.cmake new file mode 100644 index 0000000000..df365fc2c0 --- /dev/null +++ b/android/android.toolchain.cmake @@ -0,0 +1,1634 @@ +message(STATUS "Android toolchain was moved to platfroms/android!") +message(STATUS "This file is depricated and will be removed!") + +# Copyright (c) 2011-2013, Andrey Kamaev +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. The name of the copyright holders may be used to endorse or promote +# products derived from this software without specific prior written +# permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +# ------------------------------------------------------------------------------ +# Android CMake toolchain file, for use with the Android NDK r5-r8 +# Requires cmake 2.6.3 or newer (2.8.5 or newer is recommended). +# See home page: https://github.com/taka-no-me/android-cmake +# +# The file is mantained by the OpenCV project. The latest version can be get at +# http://code.opencv.org/projects/opencv/repository/revisions/master/changes/android/android.toolchain.cmake +# +# Usage Linux: +# $ export ANDROID_NDK=/absolute/path/to/the/android-ndk +# $ mkdir build && cd build +# $ cmake -DCMAKE_TOOLCHAIN_FILE=path/to/the/android.toolchain.cmake .. +# $ make -j8 +# +# Usage Linux (using standalone toolchain): +# $ export ANDROID_STANDALONE_TOOLCHAIN=/absolute/path/to/android-toolchain +# $ mkdir build && cd build +# $ cmake -DCMAKE_TOOLCHAIN_FILE=path/to/the/android.toolchain.cmake .. +# $ make -j8 +# +# Usage Windows: +# You need native port of make to build your project. +# Android NDK r7 (or newer) already has make.exe on board. +# For older NDK you have to install it separately. +# For example, this one: http://gnuwin32.sourceforge.net/packages/make.htm +# +# $ SET ANDROID_NDK=C:\absolute\path\to\the\android-ndk +# $ mkdir build && cd build +# $ cmake.exe -G"MinGW Makefiles" +# -DCMAKE_TOOLCHAIN_FILE=path\to\the\android.toolchain.cmake +# -DCMAKE_MAKE_PROGRAM="%ANDROID_NDK%\prebuilt\windows\bin\make.exe" .. +# $ cmake.exe --build . +# +# +# Options (can be set as cmake parameters: -D=): +# ANDROID_NDK=/opt/android-ndk - path to the NDK root. +# Can be set as environment variable. Can be set only at first cmake run. +# +# ANDROID_STANDALONE_TOOLCHAIN=/opt/android-toolchain - path to the +# standalone toolchain. This option is not used if full NDK is found +# (ignored if ANDROID_NDK is set). +# Can be set as environment variable. Can be set only at first cmake run. +# +# ANDROID_ABI=armeabi-v7a - specifies the target Application Binary +# Interface (ABI). This option nearly matches to the APP_ABI variable +# used by ndk-build tool from Android NDK. +# +# Possible targets are: +# "armeabi" - matches to the NDK ABI with the same name. +# See ${ANDROID_NDK}/docs/CPU-ARCH-ABIS.html for the documentation. +# "armeabi-v7a" - matches to the NDK ABI with the same name. +# See ${ANDROID_NDK}/docs/CPU-ARCH-ABIS.html for the documentation. +# "armeabi-v7a with NEON" - same as armeabi-v7a, but +# sets NEON as floating-point unit +# "armeabi-v7a with VFPV3" - same as armeabi-v7a, but +# sets VFPV3 as floating-point unit (has 32 registers instead of 16). +# "armeabi-v6 with VFP" - tuned for ARMv6 processors having VFP. +# "x86" - matches to the NDK ABI with the same name. +# See ${ANDROID_NDK}/docs/CPU-ARCH-ABIS.html for the documentation. +# "mips" - matches to the NDK ABI with the same name +# (It is not tested on real devices by the authos of this toolchain) +# See ${ANDROID_NDK}/docs/CPU-ARCH-ABIS.html for the documentation. +# +# ANDROID_NATIVE_API_LEVEL=android-8 - level of Android API compile for. +# Option is read-only when standalone toolchain is used. +# +# ANDROID_TOOLCHAIN_NAME=arm-linux-androideabi-4.6 - the name of compiler +# toolchain to be used. The list of possible values depends on the NDK +# version. For NDK r8c the possible values are: +# +# * arm-linux-androideabi-4.4.3 +# * arm-linux-androideabi-4.6 +# * arm-linux-androideabi-clang3.1 +# * mipsel-linux-android-4.4.3 +# * mipsel-linux-android-4.6 +# * mipsel-linux-android-clang3.1 +# * x86-4.4.3 +# * x86-4.6 +# * x86-clang3.1 +# +# ANDROID_FORCE_ARM_BUILD=OFF - set ON to generate 32-bit ARM instructions +# instead of Thumb. Is not available for "x86" (inapplicable) and +# "armeabi-v6 with VFP" (is forced to be ON) ABIs. +# +# ANDROID_NO_UNDEFINED=ON - set ON to show all undefined symbols as linker +# errors even if they are not used. +# +# ANDROID_SO_UNDEFINED=OFF - set ON to allow undefined symbols in shared +# libraries. Automatically turned for NDK r5x and r6x due to GLESv2 +# problems. +# +# LIBRARY_OUTPUT_PATH_ROOT=${CMAKE_SOURCE_DIR} - where to output binary +# files. See additional details below. +# +# ANDROID_SET_OBSOLETE_VARIABLES=ON - if set, then toolchain defines some +# obsolete variables which were used by previous versions of this file for +# backward compatibility. +# +# ANDROID_STL=gnustl_static - specify the runtime to use. +# +# Possible values are: +# none -> Do not configure the runtime. +# system -> Use the default minimal system C++ runtime library. +# Implies -fno-rtti -fno-exceptions. +# Is not available for standalone toolchain. +# system_re -> Use the default minimal system C++ runtime library. +# Implies -frtti -fexceptions. +# Is not available for standalone toolchain. +# gabi++_static -> Use the GAbi++ runtime as a static library. +# Implies -frtti -fno-exceptions. +# Available for NDK r7 and newer. +# Is not available for standalone toolchain. +# gabi++_shared -> Use the GAbi++ runtime as a shared library. +# Implies -frtti -fno-exceptions. +# Available for NDK r7 and newer. +# Is not available for standalone toolchain. +# stlport_static -> Use the STLport runtime as a static library. +# Implies -fno-rtti -fno-exceptions for NDK before r7. +# Implies -frtti -fno-exceptions for NDK r7 and newer. +# Is not available for standalone toolchain. +# stlport_shared -> Use the STLport runtime as a shared library. +# Implies -fno-rtti -fno-exceptions for NDK before r7. +# Implies -frtti -fno-exceptions for NDK r7 and newer. +# Is not available for standalone toolchain. +# gnustl_static -> Use the GNU STL as a static library. +# Implies -frtti -fexceptions. +# gnustl_shared -> Use the GNU STL as a shared library. +# Implies -frtti -fno-exceptions. +# Available for NDK r7b and newer. +# Silently degrades to gnustl_static if not available. +# +# ANDROID_STL_FORCE_FEATURES=ON - turn rtti and exceptions support based on +# chosen runtime. If disabled, then the user is responsible for settings +# these options. +# +# What?: +# android-cmake toolchain searches for NDK/toolchain in the following order: +# ANDROID_NDK - cmake parameter +# ANDROID_NDK - environment variable +# ANDROID_STANDALONE_TOOLCHAIN - cmake parameter +# ANDROID_STANDALONE_TOOLCHAIN - environment variable +# ANDROID_NDK - default locations +# ANDROID_STANDALONE_TOOLCHAIN - default locations +# +# Make sure to do the following in your scripts: +# SET( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${my_cxx_flags}" ) +# SET( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${my_cxx_flags}" ) +# The flags will be prepopulated with critical flags, so don't loose them. +# Also be aware that toolchain also sets configuration-specific compiler +# flags and linker flags. +# +# ANDROID and BUILD_ANDROID will be set to true, you may test any of these +# variables to make necessary Android-specific configuration changes. +# +# Also ARMEABI or ARMEABI_V7A or X86 or MIPS will be set true, mutually +# exclusive. NEON option will be set true if VFP is set to NEON. +# +# LIBRARY_OUTPUT_PATH_ROOT should be set in cache to determine where Android +# libraries will be installed. +# Default is ${CMAKE_SOURCE_DIR}, and the android libs will always be +# under the ${LIBRARY_OUTPUT_PATH_ROOT}/libs/${ANDROID_NDK_ABI_NAME} +# (depending on the target ABI). This is convenient for Android packaging. +# +# Change Log: +# - initial version December 2010 +# - April 2011 +# [+] added possibility to build with NDK (without standalone toolchain) +# [+] support cross-compilation on Windows (native, no cygwin support) +# [+] added compiler option to force "char" type to be signed +# [+] added toolchain option to compile to 32-bit ARM instructions +# [+] added toolchain option to disable SWIG search +# [+] added platform "armeabi-v7a with VFPV3" +# [~] ARM_TARGETS renamed to ARM_TARGET +# [+] EXECUTABLE_OUTPUT_PATH is set by toolchain (required on Windows) +# [~] Fixed bug with ANDROID_API_LEVEL variable +# [~] turn off SWIG search if it is not found first time +# - May 2011 +# [~] ANDROID_LEVEL is renamed to ANDROID_API_LEVEL +# [+] ANDROID_API_LEVEL is detected by toolchain if not specified +# [~] added guard to prevent changing of output directories on the first +# cmake pass +# [~] toolchain exits with error if ARM_TARGET is not recognized +# - June 2011 +# [~] default NDK path is updated for version r5c +# [+] variable CMAKE_SYSTEM_PROCESSOR is set based on ARM_TARGET +# [~] toolchain install directory is added to linker paths +# [-] removed SWIG-related stuff from toolchain +# [+] added macro find_host_package, find_host_program to search +# packages/programs on the host system +# [~] fixed path to STL library +# - July 2011 +# [~] fixed options caching +# [~] search for all supported NDK versions +# [~] allowed spaces in NDK path +# - September 2011 +# [~] updated for NDK r6b +# - November 2011 +# [*] rewritten for NDK r7 +# [+] x86 toolchain support (experimental) +# [+] added "armeabi-v6 with VFP" ABI for ARMv6 processors. +# [~] improved compiler and linker flags management +# [+] support different build flags for Release and Debug configurations +# [~] by default compiler flags the same as used by ndk-build (but only +# where reasonable) +# [~] ANDROID_NDK_TOOLCHAIN_ROOT is splitted to ANDROID_STANDALONE_TOOLCHAIN +# and ANDROID_TOOLCHAIN_ROOT +# [~] ARM_TARGET is renamed to ANDROID_ABI +# [~] ARMEABI_NDK_NAME is renamed to ANDROID_NDK_ABI_NAME +# [~] ANDROID_API_LEVEL is renamed to ANDROID_NATIVE_API_LEVEL +# - January 2012 +# [+] added stlport_static support (experimental) +# [+] added special check for cygwin +# [+] filtered out hidden files (starting with .) while globbing inside NDK +# [+] automatically applied GLESv2 linkage fix for NDK revisions 5-6 +# [+] added ANDROID_GET_ABI_RAWNAME to get NDK ABI names by CMake flags +# - February 2012 +# [+] updated for NDK r7b +# [~] fixed cmake try_compile() command +# [~] Fix for missing install_name_tool on OS X +# - March 2012 +# [~] fixed incorrect C compiler flags +# [~] fixed CMAKE_SYSTEM_PROCESSOR change on ANDROID_ABI change +# [+] improved toolchain loading speed +# [+] added assembler language support (.S) +# [+] allowed preset search paths and extra search suffixes +# - April 2012 +# [+] updated for NDK r7c +# [~] fixed most of problems with compiler/linker flags and caching +# [+] added option ANDROID_FUNCTION_LEVEL_LINKING +# - May 2012 +# [+] updated for NDK r8 +# [+] added mips architecture support +# - August 2012 +# [+] updated for NDK r8b +# [~] all intermediate files generated by toolchain are moved to CMakeFiles +# [~] libstdc++ and libsupc are removed from explicit link libraries +# [+] added CCache support (via NDK_CCACHE environment or cmake variable) +# [+] added gold linker support for NDK r8b +# [~] fixed mips linker flags for NDK r8b +# - September 2012 +# [+] added NDK release name detection (see ANDROID_NDK_RELEASE) +# [+] added support for all C++ runtimes from NDK +# (system, gabi++, stlport, gnustl) +# [+] improved warnings on known issues of NDKs +# [~] use gold linker as default if available (NDK r8b) +# [~] globally turned off rpath +# [~] compiler options are aligned with NDK r8b +# - October 2012 +# [~] fixed C++ linking: explicitly link with math library (OpenCV #2426) +# - November 2012 +# [+] updated for NDK r8c +# [+] added support for clang compiler +# - December 2012 +# [+] suppress warning about unused CMAKE_TOOLCHAIN_FILE variable +# [+] adjust API level to closest compatible as NDK does +# [~] fixed ccache full path search +# [+] updated for NDK r8d +# [~] compiler options are aligned with NDK r8d +# - March 2013 +# [+] updated for NDK r8e (x86 version) +# [+] support x86_64 version of NDK +# ------------------------------------------------------------------------------ + +cmake_minimum_required( VERSION 2.6.3 ) + +if( DEFINED CMAKE_CROSSCOMPILING ) + # subsequent toolchain loading is not really needed + return() +endif() + +if( CMAKE_TOOLCHAIN_FILE ) + # touch toolchain variable only to suppress "unused variable" warning +endif() + +get_property( _CMAKE_IN_TRY_COMPILE GLOBAL PROPERTY IN_TRY_COMPILE ) +if( _CMAKE_IN_TRY_COMPILE ) + include( "${CMAKE_CURRENT_SOURCE_DIR}/../android.toolchain.config.cmake" OPTIONAL ) +endif() + +# this one is important +set( CMAKE_SYSTEM_NAME Linux ) +# this one not so much +set( CMAKE_SYSTEM_VERSION 1 ) + +# rpath makes low sence for Android +set( CMAKE_SKIP_RPATH TRUE CACHE BOOL "If set, runtime paths are not added when using shared libraries." ) + +set( ANDROID_SUPPORTED_NDK_VERSIONS ${ANDROID_EXTRA_NDK_VERSIONS} -r8e -r8d -r8c -r8b -r8 -r7c -r7b -r7 -r6b -r6 -r5c -r5b -r5 "" ) +if(NOT DEFINED ANDROID_NDK_SEARCH_PATHS) + if( CMAKE_HOST_WIN32 ) + file( TO_CMAKE_PATH "$ENV{PROGRAMFILES}" ANDROID_NDK_SEARCH_PATHS ) + set( ANDROID_NDK_SEARCH_PATHS "${ANDROID_NDK_SEARCH_PATHS}/android-ndk" "$ENV{SystemDrive}/NVPACK/android-ndk" ) + else() + file( TO_CMAKE_PATH "$ENV{HOME}" ANDROID_NDK_SEARCH_PATHS ) + set( ANDROID_NDK_SEARCH_PATHS /opt/android-ndk "${ANDROID_NDK_SEARCH_PATHS}/NVPACK/android-ndk" ) + endif() +endif() +if(NOT DEFINED ANDROID_STANDALONE_TOOLCHAIN_SEARCH_PATH) + set( ANDROID_STANDALONE_TOOLCHAIN_SEARCH_PATH /opt/android-toolchain ) +endif() + +set( ANDROID_SUPPORTED_ABIS_arm "armeabi-v7a;armeabi;armeabi-v7a with NEON;armeabi-v7a with VFPV3;armeabi-v6 with VFP" ) +set( ANDROID_SUPPORTED_ABIS_x86 "x86" ) +set( ANDROID_SUPPORTED_ABIS_mipsel "mips" ) + +set( ANDROID_DEFAULT_NDK_API_LEVEL 8 ) +set( ANDROID_DEFAULT_NDK_API_LEVEL_x86 9 ) +set( ANDROID_DEFAULT_NDK_API_LEVEL_mips 9 ) + + +macro( __LIST_FILTER listvar regex ) + if( ${listvar} ) + foreach( __val ${${listvar}} ) + if( __val MATCHES "${regex}" ) + list( REMOVE_ITEM ${listvar} "${__val}" ) + endif() + endforeach() + endif() +endmacro() + +macro( __INIT_VARIABLE var_name ) + set( __test_path 0 ) + foreach( __var ${ARGN} ) + if( __var STREQUAL "PATH" ) + set( __test_path 1 ) + break() + endif() + endforeach() + if( __test_path AND NOT EXISTS "${${var_name}}" ) + unset( ${var_name} CACHE ) + endif() + if( "${${var_name}}" STREQUAL "" ) + set( __values 0 ) + foreach( __var ${ARGN} ) + if( __var STREQUAL "VALUES" ) + set( __values 1 ) + elseif( NOT __var STREQUAL "PATH" ) + set( __obsolete 0 ) + if( __var MATCHES "^OBSOLETE_.*$" ) + string( REPLACE "OBSOLETE_" "" __var "${__var}" ) + set( __obsolete 1 ) + endif() + if( __var MATCHES "^ENV_.*$" ) + string( REPLACE "ENV_" "" __var "${__var}" ) + set( __value "$ENV{${__var}}" ) + elseif( DEFINED ${__var} ) + set( __value "${${__var}}" ) + else() + if( __values ) + set( __value "${__var}" ) + else() + set( __value "" ) + endif() + endif() + if( NOT "${__value}" STREQUAL "" ) + if( __test_path ) + if( EXISTS "${__value}" ) + file( TO_CMAKE_PATH "${__value}" ${var_name} ) + if( __obsolete AND NOT _CMAKE_IN_TRY_COMPILE ) + message( WARNING "Using value of obsolete variable ${__var} as initial value for ${var_name}. Please note, that ${__var} can be completely removed in future versions of the toolchain." ) + endif() + break() + endif() + else() + set( ${var_name} "${__value}" ) + if( __obsolete AND NOT _CMAKE_IN_TRY_COMPILE ) + message( WARNING "Using value of obsolete variable ${__var} as initial value for ${var_name}. Please note, that ${__var} can be completely removed in future versions of the toolchain." ) + endif() + break() + endif() + endif() + endif() + endforeach() + unset( __value ) + unset( __values ) + unset( __obsolete ) + elseif( __test_path ) + file( TO_CMAKE_PATH "${${var_name}}" ${var_name} ) + endif() + unset( __test_path ) +endmacro() + +macro( __DETECT_NATIVE_API_LEVEL _var _path ) + SET( __ndkApiLevelRegex "^[\t ]*#define[\t ]+__ANDROID_API__[\t ]+([0-9]+)[\t ]*$" ) + FILE( STRINGS ${_path} __apiFileContent REGEX "${__ndkApiLevelRegex}" ) + if( NOT __apiFileContent ) + message( SEND_ERROR "Could not get Android native API level. Probably you have specified invalid level value, or your copy of NDK/toolchain is broken." ) + endif() + string( REGEX REPLACE "${__ndkApiLevelRegex}" "\\1" ${_var} "${__apiFileContent}" ) + unset( __apiFileContent ) + unset( __ndkApiLevelRegex ) +endmacro() + +macro( __DETECT_TOOLCHAIN_MACHINE_NAME _var _root ) + if( EXISTS "${_root}" ) + file( GLOB __gccExePath RELATIVE "${_root}/bin/" "${_root}/bin/*-gcc${TOOL_OS_SUFFIX}" ) + __LIST_FILTER( __gccExePath "^[.].*" ) + list( LENGTH __gccExePath __gccExePathsCount ) + if( NOT __gccExePathsCount EQUAL 1 AND NOT _CMAKE_IN_TRY_COMPILE ) + message( WARNING "Could not determine machine name for compiler from ${_root}" ) + set( ${_var} "" ) + else() + get_filename_component( __gccExeName "${__gccExePath}" NAME_WE ) + string( REPLACE "-gcc" "" ${_var} "${__gccExeName}" ) + endif() + unset( __gccExePath ) + unset( __gccExePathsCount ) + unset( __gccExeName ) + else() + set( ${_var} "" ) + endif() +endmacro() + + +# fight against cygwin +set( ANDROID_FORBID_SYGWIN TRUE CACHE BOOL "Prevent cmake from working under cygwin and using cygwin tools") +mark_as_advanced( ANDROID_FORBID_SYGWIN ) +if( ANDROID_FORBID_SYGWIN ) + if( CYGWIN ) + message( FATAL_ERROR "Android NDK and android-cmake toolchain are not welcome Cygwin. It is unlikely that this cmake toolchain will work under cygwin. But if you want to try then you can set cmake variable ANDROID_FORBID_SYGWIN to FALSE and rerun cmake." ) + endif() + + if( CMAKE_HOST_WIN32 ) + # remove cygwin from PATH + set( __new_path "$ENV{PATH}") + __LIST_FILTER( __new_path "cygwin" ) + set(ENV{PATH} "${__new_path}") + unset(__new_path) + endif() +endif() + + +# detect current host platform +if( NOT DEFINED ANDROID_NDK_HOST_X64 AND CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "amd64|x86_64|AMD64") + set( ANDROID_NDK_HOST_X64 1 CACHE BOOL "Try to use 64-bit compiler toolchain" ) + mark_as_advanced( ANDROID_NDK_HOST_X64 ) +endif() + +set( TOOL_OS_SUFFIX "" ) +if( CMAKE_HOST_APPLE ) + set( ANDROID_NDK_HOST_SYSTEM_NAME "darwin-x86_64" ) + set( ANDROID_NDK_HOST_SYSTEM_NAME2 "darwin-x86" ) +elseif( CMAKE_HOST_WIN32 ) + set( ANDROID_NDK_HOST_SYSTEM_NAME "windows-x86_64" ) + set( ANDROID_NDK_HOST_SYSTEM_NAME2 "windows" ) + set( TOOL_OS_SUFFIX ".exe" ) +elseif( CMAKE_HOST_UNIX ) + set( ANDROID_NDK_HOST_SYSTEM_NAME "linux-x86_64" ) + set( ANDROID_NDK_HOST_SYSTEM_NAME2 "linux-x86" ) +else() + message( FATAL_ERROR "Cross-compilation on your platform is not supported by this cmake toolchain" ) +endif() + +if( NOT ANDROID_NDK_HOST_X64 ) + set( ANDROID_NDK_HOST_SYSTEM_NAME ${ANDROID_NDK_HOST_SYSTEM_NAME2} ) +endif() + +# see if we have path to Android NDK +__INIT_VARIABLE( ANDROID_NDK PATH ENV_ANDROID_NDK ) +if( NOT ANDROID_NDK ) + # see if we have path to Android standalone toolchain + __INIT_VARIABLE( ANDROID_STANDALONE_TOOLCHAIN PATH ENV_ANDROID_STANDALONE_TOOLCHAIN OBSOLETE_ANDROID_NDK_TOOLCHAIN_ROOT OBSOLETE_ENV_ANDROID_NDK_TOOLCHAIN_ROOT ) + + if( NOT ANDROID_STANDALONE_TOOLCHAIN ) + #try to find Android NDK in one of the the default locations + set( __ndkSearchPaths ) + foreach( __ndkSearchPath ${ANDROID_NDK_SEARCH_PATHS} ) + foreach( suffix ${ANDROID_SUPPORTED_NDK_VERSIONS} ) + list( APPEND __ndkSearchPaths "${__ndkSearchPath}${suffix}" ) + endforeach() + endforeach() + __INIT_VARIABLE( ANDROID_NDK PATH VALUES ${__ndkSearchPaths} ) + unset( __ndkSearchPaths ) + + if( ANDROID_NDK ) + message( STATUS "Using default path for Android NDK: ${ANDROID_NDK}" ) + message( STATUS " If you prefer to use a different location, please define a cmake or environment variable: ANDROID_NDK" ) + else() + #try to find Android standalone toolchain in one of the the default locations + __INIT_VARIABLE( ANDROID_STANDALONE_TOOLCHAIN PATH ANDROID_STANDALONE_TOOLCHAIN_SEARCH_PATH ) + + if( ANDROID_STANDALONE_TOOLCHAIN ) + message( STATUS "Using default path for standalone toolchain ${ANDROID_STANDALONE_TOOLCHAIN}" ) + message( STATUS " If you prefer to use a different location, please define the variable: ANDROID_STANDALONE_TOOLCHAIN" ) + endif( ANDROID_STANDALONE_TOOLCHAIN ) + endif( ANDROID_NDK ) + endif( NOT ANDROID_STANDALONE_TOOLCHAIN ) +endif( NOT ANDROID_NDK ) +# remember found paths +if( ANDROID_NDK ) + get_filename_component( ANDROID_NDK "${ANDROID_NDK}" ABSOLUTE ) + # try to detect change + if( CMAKE_AR ) + string( LENGTH "${ANDROID_NDK}" __length ) + string( SUBSTRING "${CMAKE_AR}" 0 ${__length} __androidNdkPreviousPath ) + if( NOT __androidNdkPreviousPath STREQUAL ANDROID_NDK ) + message( FATAL_ERROR "It is not possible to change the path to the NDK on subsequent CMake run. You must remove all generated files from your build folder first. + " ) + endif() + unset( __androidNdkPreviousPath ) + unset( __length ) + endif() + set( ANDROID_NDK "${ANDROID_NDK}" CACHE INTERNAL "Path of the Android NDK" FORCE ) + set( BUILD_WITH_ANDROID_NDK True ) + file( STRINGS "${ANDROID_NDK}/RELEASE.TXT" ANDROID_NDK_RELEASE_FULL LIMIT_COUNT 1 REGEX r[0-9]+[a-z]? ) + string( REGEX MATCH r[0-9]+[a-z]? ANDROID_NDK_RELEASE "${ANDROID_NDK_RELEASE_FULL}" ) +elseif( ANDROID_STANDALONE_TOOLCHAIN ) + get_filename_component( ANDROID_STANDALONE_TOOLCHAIN "${ANDROID_STANDALONE_TOOLCHAIN}" ABSOLUTE ) + # try to detect change + if( CMAKE_AR ) + string( LENGTH "${ANDROID_STANDALONE_TOOLCHAIN}" __length ) + string( SUBSTRING "${CMAKE_AR}" 0 ${__length} __androidStandaloneToolchainPreviousPath ) + if( NOT __androidStandaloneToolchainPreviousPath STREQUAL ANDROID_STANDALONE_TOOLCHAIN ) + message( FATAL_ERROR "It is not possible to change path to the Android standalone toolchain on subsequent run." ) + endif() + unset( __androidStandaloneToolchainPreviousPath ) + unset( __length ) + endif() + set( ANDROID_STANDALONE_TOOLCHAIN "${ANDROID_STANDALONE_TOOLCHAIN}" CACHE INTERNAL "Path of the Android standalone toolchain" FORCE ) + set( BUILD_WITH_STANDALONE_TOOLCHAIN True ) +else() + list(GET ANDROID_NDK_SEARCH_PATHS 0 ANDROID_NDK_SEARCH_PATH) + message( FATAL_ERROR "Could not find neither Android NDK nor Android standalone toolchain. + You should either set an environment variable: + export ANDROID_NDK=~/my-android-ndk + or + export ANDROID_STANDALONE_TOOLCHAIN=~/my-android-toolchain + or put the toolchain or NDK in the default path: + sudo ln -s ~/my-android-ndk ${ANDROID_NDK_SEARCH_PATH} + sudo ln -s ~/my-android-toolchain ${ANDROID_STANDALONE_TOOLCHAIN_SEARCH_PATH}" ) +endif() + +# get all the details about standalone toolchain +if( BUILD_WITH_STANDALONE_TOOLCHAIN ) + __DETECT_NATIVE_API_LEVEL( ANDROID_SUPPORTED_NATIVE_API_LEVELS "${ANDROID_STANDALONE_TOOLCHAIN}/sysroot/usr/include/android/api-level.h" ) + set( ANDROID_STANDALONE_TOOLCHAIN_API_LEVEL ${ANDROID_SUPPORTED_NATIVE_API_LEVELS} ) + set( __availableToolchains "standalone" ) + __DETECT_TOOLCHAIN_MACHINE_NAME( __availableToolchainMachines "${ANDROID_STANDALONE_TOOLCHAIN}" ) + if( NOT __availableToolchainMachines ) + message( FATAL_ERROR "Could not determine machine name of your toolchain. Probably your Android standalone toolchain is broken." ) + endif() + if( __availableToolchainMachines MATCHES i686 ) + set( __availableToolchainArchs "x86" ) + elseif( __availableToolchainMachines MATCHES arm ) + set( __availableToolchainArchs "arm" ) + elseif( __availableToolchainMachines MATCHES mipsel ) + set( __availableToolchainArchs "mipsel" ) + endif() + execute_process( COMMAND "${ANDROID_STANDALONE_TOOLCHAIN}/bin/${__availableToolchainMachines}-gcc${TOOL_OS_SUFFIX}" -dumpversion + OUTPUT_VARIABLE __availableToolchainCompilerVersions OUTPUT_STRIP_TRAILING_WHITESPACE ) + string( REGEX MATCH "[0-9]+[.][0-9]+([.][0-9]+)?" __availableToolchainCompilerVersions "${__availableToolchainCompilerVersions}" ) + if( EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/bin/clang${TOOL_OS_SUFFIX}" ) + list( APPEND __availableToolchains "standalone-clang" ) + list( APPEND __availableToolchainMachines ${__availableToolchainMachines} ) + list( APPEND __availableToolchainArchs ${__availableToolchainArchs} ) + list( APPEND __availableToolchainCompilerVersions ${__availableToolchainCompilerVersions} ) + endif() +endif() + +macro( __GLOB_NDK_TOOLCHAINS __availableToolchainsVar __availableToolchainsLst __host_system_name ) + foreach( __toolchain ${${__availableToolchainsLst}} ) + if( "${__toolchain}" MATCHES "-clang3[.][0-9]$" AND NOT EXISTS "${ANDROID_NDK}/toolchains/${__toolchain}/prebuilt/" ) + string( REGEX REPLACE "-clang3[.][0-9]$" "-4.6" __gcc_toolchain "${__toolchain}" ) + else() + set( __gcc_toolchain "${__toolchain}" ) + endif() + __DETECT_TOOLCHAIN_MACHINE_NAME( __machine "${ANDROID_NDK}/toolchains/${__gcc_toolchain}/prebuilt/${__host_system_name}" ) + if( __machine ) + string( REGEX MATCH "[0-9]+[.][0-9]+([.][0-9]+)?$" __version "${__gcc_toolchain}" ) + string( REGEX MATCH "^[^-]+" __arch "${__gcc_toolchain}" ) + list( APPEND __availableToolchainMachines "${__machine}" ) + list( APPEND __availableToolchainArchs "${__arch}" ) + list( APPEND __availableToolchainCompilerVersions "${__version}" ) + list( APPEND ${__availableToolchainsVar} "${__toolchain}" ) + endif() + unset( __gcc_toolchain ) + endforeach() +endmacro() + +# get all the details about NDK +if( BUILD_WITH_ANDROID_NDK ) + file( GLOB ANDROID_SUPPORTED_NATIVE_API_LEVELS RELATIVE "${ANDROID_NDK}/platforms" "${ANDROID_NDK}/platforms/android-*" ) + string( REPLACE "android-" "" ANDROID_SUPPORTED_NATIVE_API_LEVELS "${ANDROID_SUPPORTED_NATIVE_API_LEVELS}" ) + set( __availableToolchains "" ) + set( __availableToolchainMachines "" ) + set( __availableToolchainArchs "" ) + set( __availableToolchainCompilerVersions "" ) + if( ANDROID_TOOLCHAIN_NAME AND EXISTS "${ANDROID_NDK}/toolchains/${ANDROID_TOOLCHAIN_NAME}/" ) + # do not go through all toolchains if we know the name + set( __availableToolchainsLst "${ANDROID_TOOLCHAIN_NAME}" ) + __GLOB_NDK_TOOLCHAINS( __availableToolchains __availableToolchainsLst ${ANDROID_NDK_HOST_SYSTEM_NAME} ) + if( NOT __availableToolchains AND NOT ANDROID_NDK_HOST_SYSTEM_NAME STREQUAL ANDROID_NDK_HOST_SYSTEM_NAME2 ) + __GLOB_NDK_TOOLCHAINS( __availableToolchains __availableToolchainsLst ${ANDROID_NDK_HOST_SYSTEM_NAME2} ) + if( __availableToolchains ) + set( ANDROID_NDK_HOST_SYSTEM_NAME ${ANDROID_NDK_HOST_SYSTEM_NAME2} ) + endif() + endif() + endif() + if( NOT __availableToolchains ) + file( GLOB __availableToolchainsLst RELATIVE "${ANDROID_NDK}/toolchains" "${ANDROID_NDK}/toolchains/*" ) + if( __availableToolchains ) + list(SORT __availableToolchainsLst) # we need clang to go after gcc + endif() + __LIST_FILTER( __availableToolchainsLst "^[.]" ) + __LIST_FILTER( __availableToolchainsLst "llvm" ) + __GLOB_NDK_TOOLCHAINS( __availableToolchains __availableToolchainsLst ${ANDROID_NDK_HOST_SYSTEM_NAME} ) + if( NOT __availableToolchains AND NOT ANDROID_NDK_HOST_SYSTEM_NAME STREQUAL ANDROID_NDK_HOST_SYSTEM_NAME2 ) + __GLOB_NDK_TOOLCHAINS( __availableToolchains __availableToolchainsLst ${ANDROID_NDK_HOST_SYSTEM_NAME2} ) + if( __availableToolchains ) + set( ANDROID_NDK_HOST_SYSTEM_NAME ${ANDROID_NDK_HOST_SYSTEM_NAME2} ) + endif() + endif() + endif() + if( NOT __availableToolchains ) + message( FATAL_ERROR "Could not find any working toolchain in the NDK. Probably your Android NDK is broken." ) + endif() +endif() + +# build list of available ABIs +set( ANDROID_SUPPORTED_ABIS "" ) +set( __uniqToolchainArchNames ${__availableToolchainArchs} ) +list( REMOVE_DUPLICATES __uniqToolchainArchNames ) +list( SORT __uniqToolchainArchNames ) +foreach( __arch ${__uniqToolchainArchNames} ) + list( APPEND ANDROID_SUPPORTED_ABIS ${ANDROID_SUPPORTED_ABIS_${__arch}} ) +endforeach() +unset( __uniqToolchainArchNames ) +if( NOT ANDROID_SUPPORTED_ABIS ) + message( FATAL_ERROR "No one of known Android ABIs is supported by this cmake toolchain." ) +endif() + +# choose target ABI +__INIT_VARIABLE( ANDROID_ABI OBSOLETE_ARM_TARGET OBSOLETE_ARM_TARGETS VALUES ${ANDROID_SUPPORTED_ABIS} ) +# verify that target ABI is supported +list( FIND ANDROID_SUPPORTED_ABIS "${ANDROID_ABI}" __androidAbiIdx ) +if( __androidAbiIdx EQUAL -1 ) + string( REPLACE ";" "\", \"", PRINTABLE_ANDROID_SUPPORTED_ABIS "${ANDROID_SUPPORTED_ABIS}" ) + message( FATAL_ERROR "Specified ANDROID_ABI = \"${ANDROID_ABI}\" is not supported by this cmake toolchain or your NDK/toolchain. + Supported values are: \"${PRINTABLE_ANDROID_SUPPORTED_ABIS}\" + " ) +endif() +unset( __androidAbiIdx ) + +# set target ABI options +if( ANDROID_ABI STREQUAL "x86" ) + set( X86 true ) + set( ANDROID_NDK_ABI_NAME "x86" ) + set( ANDROID_ARCH_NAME "x86" ) + set( ANDROID_ARCH_FULLNAME "x86" ) + set( ANDROID_LLVM_TRIPLE "i686-none-linux-android" ) + set( CMAKE_SYSTEM_PROCESSOR "i686" ) +elseif( ANDROID_ABI STREQUAL "mips" ) + set( MIPS true ) + set( ANDROID_NDK_ABI_NAME "mips" ) + set( ANDROID_ARCH_NAME "mips" ) + set( ANDROID_ARCH_FULLNAME "mipsel" ) + set( ANDROID_LLVM_TRIPLE "mipsel-none-linux-android" ) + set( CMAKE_SYSTEM_PROCESSOR "mips" ) +elseif( ANDROID_ABI STREQUAL "armeabi" ) + set( ARMEABI true ) + set( ANDROID_NDK_ABI_NAME "armeabi" ) + set( ANDROID_ARCH_NAME "arm" ) + set( ANDROID_ARCH_FULLNAME "arm" ) + set( ANDROID_LLVM_TRIPLE "armv5te-none-linux-androideabi" ) + set( CMAKE_SYSTEM_PROCESSOR "armv5te" ) +elseif( ANDROID_ABI STREQUAL "armeabi-v6 with VFP" ) + set( ARMEABI_V6 true ) + set( ANDROID_NDK_ABI_NAME "armeabi" ) + set( ANDROID_ARCH_NAME "arm" ) + set( ANDROID_ARCH_FULLNAME "arm" ) + set( ANDROID_LLVM_TRIPLE "armv5te-none-linux-androideabi" ) + set( CMAKE_SYSTEM_PROCESSOR "armv6" ) + # need always fallback to older platform + set( ARMEABI true ) +elseif( ANDROID_ABI STREQUAL "armeabi-v7a") + set( ARMEABI_V7A true ) + set( ANDROID_NDK_ABI_NAME "armeabi-v7a" ) + set( ANDROID_ARCH_NAME "arm" ) + set( ANDROID_ARCH_FULLNAME "arm" ) + set( ANDROID_LLVM_TRIPLE "armv7-none-linux-androideabi" ) + set( CMAKE_SYSTEM_PROCESSOR "armv7-a" ) +elseif( ANDROID_ABI STREQUAL "armeabi-v7a with VFPV3" ) + set( ARMEABI_V7A true ) + set( ANDROID_NDK_ABI_NAME "armeabi-v7a" ) + set( ANDROID_ARCH_NAME "arm" ) + set( ANDROID_ARCH_FULLNAME "arm" ) + set( ANDROID_LLVM_TRIPLE "armv7-none-linux-androideabi" ) + set( CMAKE_SYSTEM_PROCESSOR "armv7-a" ) + set( VFPV3 true ) +elseif( ANDROID_ABI STREQUAL "armeabi-v7a with NEON" ) + set( ARMEABI_V7A true ) + set( ANDROID_NDK_ABI_NAME "armeabi-v7a" ) + set( ANDROID_ARCH_NAME "arm" ) + set( ANDROID_ARCH_FULLNAME "arm" ) + set( ANDROID_LLVM_TRIPLE "armv7-none-linux-androideabi" ) + set( CMAKE_SYSTEM_PROCESSOR "armv7-a" ) + set( VFPV3 true ) + set( NEON true ) +else() + message( SEND_ERROR "Unknown ANDROID_ABI=\"${ANDROID_ABI}\" is specified." ) +endif() + +if( CMAKE_BINARY_DIR AND EXISTS "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeSystem.cmake" ) + # really dirty hack + # it is not possible to change CMAKE_SYSTEM_PROCESSOR after the first run... + file( APPEND "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeSystem.cmake" "SET(CMAKE_SYSTEM_PROCESSOR \"${CMAKE_SYSTEM_PROCESSOR}\")\n" ) +endif() + +if( ANDROID_ARCH_NAME STREQUAL "arm" AND NOT ARMEABI_V6 ) + __INIT_VARIABLE( ANDROID_FORCE_ARM_BUILD OBSOLETE_FORCE_ARM VALUES OFF ) + set( ANDROID_FORCE_ARM_BUILD ${ANDROID_FORCE_ARM_BUILD} CACHE BOOL "Use 32-bit ARM instructions instead of Thumb-1" FORCE ) + mark_as_advanced( ANDROID_FORCE_ARM_BUILD ) +else() + unset( ANDROID_FORCE_ARM_BUILD CACHE ) +endif() + +# choose toolchain +if( ANDROID_TOOLCHAIN_NAME ) + list( FIND __availableToolchains "${ANDROID_TOOLCHAIN_NAME}" __toolchainIdx ) + if( __toolchainIdx EQUAL -1 ) + list( SORT __availableToolchains ) + string( REPLACE ";" "\n * " toolchains_list "${__availableToolchains}" ) + set( toolchains_list " * ${toolchains_list}") + message( FATAL_ERROR "Specified toolchain \"${ANDROID_TOOLCHAIN_NAME}\" is missing in your NDK or broken. Please verify that your NDK is working or select another compiler toolchain. +To configure the toolchain set CMake variable ANDROID_TOOLCHAIN_NAME to one of the following values:\n${toolchains_list}\n" ) + endif() + list( GET __availableToolchainArchs ${__toolchainIdx} __toolchainArch ) + if( NOT __toolchainArch STREQUAL ANDROID_ARCH_FULLNAME ) + message( SEND_ERROR "Selected toolchain \"${ANDROID_TOOLCHAIN_NAME}\" is not able to compile binaries for the \"${ANDROID_ARCH_NAME}\" platform." ) + endif() +else() + set( __toolchainIdx -1 ) + set( __applicableToolchains "" ) + set( __toolchainMaxVersion "0.0.0" ) + list( LENGTH __availableToolchains __availableToolchainsCount ) + math( EXPR __availableToolchainsCount "${__availableToolchainsCount}-1" ) + foreach( __idx RANGE ${__availableToolchainsCount} ) + list( GET __availableToolchainArchs ${__idx} __toolchainArch ) + if( __toolchainArch STREQUAL ANDROID_ARCH_FULLNAME ) + list( GET __availableToolchainCompilerVersions ${__idx} __toolchainVersion ) + if( __toolchainVersion VERSION_GREATER __toolchainMaxVersion ) + set( __toolchainMaxVersion "${__toolchainVersion}" ) + set( __toolchainIdx ${__idx} ) + endif() + endif() + endforeach() + unset( __availableToolchainsCount ) + unset( __toolchainMaxVersion ) + unset( __toolchainVersion ) +endif() +unset( __toolchainArch ) +if( __toolchainIdx EQUAL -1 ) + message( FATAL_ERROR "No one of available compiler toolchains is able to compile for ${ANDROID_ARCH_NAME} platform." ) +endif() +list( GET __availableToolchains ${__toolchainIdx} ANDROID_TOOLCHAIN_NAME ) +list( GET __availableToolchainMachines ${__toolchainIdx} ANDROID_TOOLCHAIN_MACHINE_NAME ) +list( GET __availableToolchainCompilerVersions ${__toolchainIdx} ANDROID_COMPILER_VERSION ) + +unset( __toolchainIdx ) +unset( __availableToolchains ) +unset( __availableToolchainMachines ) +unset( __availableToolchainArchs ) +unset( __availableToolchainCompilerVersions ) + +# choose native API level +__INIT_VARIABLE( ANDROID_NATIVE_API_LEVEL ENV_ANDROID_NATIVE_API_LEVEL ANDROID_API_LEVEL ENV_ANDROID_API_LEVEL ANDROID_STANDALONE_TOOLCHAIN_API_LEVEL ANDROID_DEFAULT_NDK_API_LEVEL_${ANDROID_ARCH_NAME} ANDROID_DEFAULT_NDK_API_LEVEL ) +string( REGEX MATCH "[0-9]+" ANDROID_NATIVE_API_LEVEL "${ANDROID_NATIVE_API_LEVEL}" ) +# adjust API level +set( __real_api_level ${ANDROID_DEFAULT_NDK_API_LEVEL_${ANDROID_ARCH_NAME}} ) +foreach( __level ${ANDROID_SUPPORTED_NATIVE_API_LEVELS} ) + if( NOT __level GREATER ANDROID_NATIVE_API_LEVEL AND NOT __level LESS __real_api_level ) + set( __real_api_level ${__level} ) + endif() +endforeach() +if( __real_api_level AND NOT ANDROID_NATIVE_API_LEVEL EQUAL __real_api_level ) + message( STATUS "Adjusting Android API level 'android-${ANDROID_NATIVE_API_LEVEL}' to 'android-${__real_api_level}'") + set( ANDROID_NATIVE_API_LEVEL ${__real_api_level} ) +endif() +unset(__real_api_level) +# validate +list( FIND ANDROID_SUPPORTED_NATIVE_API_LEVELS "${ANDROID_NATIVE_API_LEVEL}" __levelIdx ) +if( __levelIdx EQUAL -1 ) + message( SEND_ERROR "Specified Android native API level 'android-${ANDROID_NATIVE_API_LEVEL}' is not supported by your NDK/toolchain." ) +else() + if( BUILD_WITH_ANDROID_NDK ) + __DETECT_NATIVE_API_LEVEL( __realApiLevel "${ANDROID_NDK}/platforms/android-${ANDROID_NATIVE_API_LEVEL}/arch-${ANDROID_ARCH_NAME}/usr/include/android/api-level.h" ) + if( NOT __realApiLevel EQUAL ANDROID_NATIVE_API_LEVEL ) + message( SEND_ERROR "Specified Android API level (${ANDROID_NATIVE_API_LEVEL}) does not match to the level found (${__realApiLevel}). Probably your copy of NDK is broken." ) + endif() + unset( __realApiLevel ) + endif() + set( ANDROID_NATIVE_API_LEVEL "${ANDROID_NATIVE_API_LEVEL}" CACHE STRING "Android API level for native code" FORCE ) + if( CMAKE_VERSION VERSION_GREATER "2.8" ) + list( SORT ANDROID_SUPPORTED_NATIVE_API_LEVELS ) + set_property( CACHE ANDROID_NATIVE_API_LEVEL PROPERTY STRINGS ${ANDROID_SUPPORTED_NATIVE_API_LEVELS} ) + endif() +endif() +unset( __levelIdx ) + + +# remember target ABI +set( ANDROID_ABI "${ANDROID_ABI}" CACHE STRING "The target ABI for Android. If arm, then armeabi-v7a is recommended for hardware floating point." FORCE ) +if( CMAKE_VERSION VERSION_GREATER "2.8" ) + list( SORT ANDROID_SUPPORTED_ABIS_${ANDROID_ARCH_FULLNAME} ) + set_property( CACHE ANDROID_ABI PROPERTY STRINGS ${ANDROID_SUPPORTED_ABIS_${ANDROID_ARCH_FULLNAME}} ) +endif() + + +# runtime choice (STL, rtti, exceptions) +if( NOT ANDROID_STL ) + # honor legacy ANDROID_USE_STLPORT + if( DEFINED ANDROID_USE_STLPORT ) + if( ANDROID_USE_STLPORT ) + set( ANDROID_STL stlport_static ) + endif() + message( WARNING "You are using an obsolete variable ANDROID_USE_STLPORT to select the STL variant. Use -DANDROID_STL=stlport_static instead." ) + endif() + if( NOT ANDROID_STL ) + set( ANDROID_STL gnustl_static ) + endif() +endif() +set( ANDROID_STL "${ANDROID_STL}" CACHE STRING "C++ runtime" ) +set( ANDROID_STL_FORCE_FEATURES ON CACHE BOOL "automatically configure rtti and exceptions support based on C++ runtime" ) +mark_as_advanced( ANDROID_STL ANDROID_STL_FORCE_FEATURES ) + +if( BUILD_WITH_ANDROID_NDK ) + if( NOT "${ANDROID_STL}" MATCHES "^(none|system|system_re|gabi\\+\\+_static|gabi\\+\\+_shared|stlport_static|stlport_shared|gnustl_static|gnustl_shared)$") + message( FATAL_ERROR "ANDROID_STL is set to invalid value \"${ANDROID_STL}\". +The possible values are: + none -> Do not configure the runtime. + system -> Use the default minimal system C++ runtime library. + system_re -> Same as system but with rtti and exceptions. + gabi++_static -> Use the GAbi++ runtime as a static library. + gabi++_shared -> Use the GAbi++ runtime as a shared library. + stlport_static -> Use the STLport runtime as a static library. + stlport_shared -> Use the STLport runtime as a shared library. + gnustl_static -> (default) Use the GNU STL as a static library. + gnustl_shared -> Use the GNU STL as a shared library. +" ) + endif() +elseif( BUILD_WITH_STANDALONE_TOOLCHAIN ) + if( NOT "${ANDROID_STL}" MATCHES "^(none|gnustl_static|gnustl_shared)$") + message( FATAL_ERROR "ANDROID_STL is set to invalid value \"${ANDROID_STL}\". +The possible values are: + none -> Do not configure the runtime. + gnustl_static -> (default) Use the GNU STL as a static library. + gnustl_shared -> Use the GNU STL as a shared library. +" ) + endif() +endif() + +unset( ANDROID_RTTI ) +unset( ANDROID_EXCEPTIONS ) +unset( ANDROID_STL_INCLUDE_DIRS ) +unset( __libstl ) +unset( __libsupcxx ) + +if( NOT _CMAKE_IN_TRY_COMPILE AND ANDROID_NDK_RELEASE STREQUAL "r7b" AND ARMEABI_V7A AND NOT VFPV3 AND ANDROID_STL MATCHES "gnustl" ) + message( WARNING "The GNU STL armeabi-v7a binaries from NDK r7b can crash non-NEON devices. The files provided with NDK r7b were not configured properly, resulting in crashes on Tegra2-based devices and others when trying to use certain floating-point functions (e.g., cosf, sinf, expf). +You are strongly recommended to switch to another NDK release. +" ) +endif() + +if( NOT _CMAKE_IN_TRY_COMPILE AND X86 AND ANDROID_STL MATCHES "gnustl" AND ANDROID_NDK_RELEASE STREQUAL "r6" ) + message( WARNING "The x86 system header file from NDK r6 has incorrect definition for ptrdiff_t. You are recommended to upgrade to a newer NDK release or manually patch the header: +See https://android.googlesource.com/platform/development.git f907f4f9d4e56ccc8093df6fee54454b8bcab6c2 + diff --git a/ndk/platforms/android-9/arch-x86/include/machine/_types.h b/ndk/platforms/android-9/arch-x86/include/machine/_types.h + index 5e28c64..65892a1 100644 + --- a/ndk/platforms/android-9/arch-x86/include/machine/_types.h + +++ b/ndk/platforms/android-9/arch-x86/include/machine/_types.h + @@ -51,7 +51,11 @@ typedef long int ssize_t; + #endif + #ifndef _PTRDIFF_T + #define _PTRDIFF_T + -typedef long ptrdiff_t; + +# ifdef __ANDROID__ + + typedef int ptrdiff_t; + +# else + + typedef long ptrdiff_t; + +# endif + #endif +" ) +endif() + + +# setup paths and STL for standalone toolchain +if( BUILD_WITH_STANDALONE_TOOLCHAIN ) + set( ANDROID_TOOLCHAIN_ROOT "${ANDROID_STANDALONE_TOOLCHAIN}" ) + set( ANDROID_CLANG_TOOLCHAIN_ROOT "${ANDROID_STANDALONE_TOOLCHAIN}" ) + set( ANDROID_SYSROOT "${ANDROID_STANDALONE_TOOLCHAIN}/sysroot" ) + + if( NOT ANDROID_STL STREQUAL "none" ) + set( ANDROID_STL_INCLUDE_DIRS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/include/c++/${ANDROID_COMPILER_VERSION}" ) + if( ARMEABI_V7A AND EXISTS "${ANDROID_STL_INCLUDE_DIRS}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/${CMAKE_SYSTEM_PROCESSOR}/bits" ) + list( APPEND ANDROID_STL_INCLUDE_DIRS "${ANDROID_STL_INCLUDE_DIRS}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/${CMAKE_SYSTEM_PROCESSOR}" ) + elseif( ARMEABI AND NOT ANDROID_FORCE_ARM_BUILD AND EXISTS "${ANDROID_STL_INCLUDE_DIRS}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/thumb/bits" ) + list( APPEND ANDROID_STL_INCLUDE_DIRS "${ANDROID_STL_INCLUDE_DIRS}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/thumb" ) + else() + list( APPEND ANDROID_STL_INCLUDE_DIRS "${ANDROID_STL_INCLUDE_DIRS}/${ANDROID_TOOLCHAIN_MACHINE_NAME}" ) + endif() + # always search static GNU STL to get the location of libsupc++.a + if( ARMEABI_V7A AND NOT ANDROID_FORCE_ARM_BUILD AND EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/thumb/libstdc++.a" ) + set( __libstl "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/thumb" ) + elseif( ARMEABI_V7A AND EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/libstdc++.a" ) + set( __libstl "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}" ) + elseif( ARMEABI AND NOT ANDROID_FORCE_ARM_BUILD AND EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/thumb/libstdc++.a" ) + set( __libstl "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/thumb" ) + elseif( EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/libstdc++.a" ) + set( __libstl "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib" ) + endif() + if( __libstl ) + set( __libsupcxx "${__libstl}/libsupc++.a" ) + set( __libstl "${__libstl}/libstdc++.a" ) + endif() + if( NOT EXISTS "${__libsupcxx}" ) + message( FATAL_ERROR "The required libstdsupc++.a is missing in your standalone toolchain. + Usually it happens because of bug in make-standalone-toolchain.sh script from NDK r7, r7b and r7c. + You need to either upgrade to newer NDK or manually copy + $ANDROID_NDK/sources/cxx-stl/gnu-libstdc++/libs/${ANDROID_NDK_ABI_NAME}/libsupc++.a + to + ${__libsupcxx} + " ) + endif() + if( ANDROID_STL STREQUAL "gnustl_shared" ) + if( ARMEABI_V7A AND EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/libgnustl_shared.so" ) + set( __libstl "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/libgnustl_shared.so" ) + elseif( ARMEABI AND NOT ANDROID_FORCE_ARM_BUILD AND EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/thumb/libgnustl_shared.so" ) + set( __libstl "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/thumb/libgnustl_shared.so" ) + elseif( EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/libgnustl_shared.so" ) + set( __libstl "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/libgnustl_shared.so" ) + endif() + endif() + endif() +endif() + +# clang +if( "${ANDROID_TOOLCHAIN_NAME}" STREQUAL "standalone-clang" ) + set( ANDROID_COMPILER_IS_CLANG 1 ) + execute_process( COMMAND "${ANDROID_CLANG_TOOLCHAIN_ROOT}/bin/clang${TOOL_OS_SUFFIX}" --version OUTPUT_VARIABLE ANDROID_CLANG_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE ) + string( REGEX MATCH "[0-9]+[.][0-9]+" ANDROID_CLANG_VERSION "${ANDROID_CLANG_VERSION}") +elseif( "${ANDROID_TOOLCHAIN_NAME}" MATCHES "-clang3[.][0-9]?$" ) + string( REGEX MATCH "3[.][0-9]$" ANDROID_CLANG_VERSION "${ANDROID_TOOLCHAIN_NAME}") + string( REGEX REPLACE "-clang${ANDROID_CLANG_VERSION}$" "-4.6" ANDROID_GCC_TOOLCHAIN_NAME "${ANDROID_TOOLCHAIN_NAME}" ) + if( NOT EXISTS "${ANDROID_NDK}/toolchains/llvm-${ANDROID_CLANG_VERSION}/prebuilt/${ANDROID_NDK_HOST_SYSTEM_NAME}/bin/clang${TOOL_OS_SUFFIX}" ) + message( FATAL_ERROR "Could not find the Clang compiler driver" ) + endif() + set( ANDROID_COMPILER_IS_CLANG 1 ) + set( ANDROID_CLANG_TOOLCHAIN_ROOT "${ANDROID_NDK}/toolchains/llvm-${ANDROID_CLANG_VERSION}/prebuilt/${ANDROID_NDK_HOST_SYSTEM_NAME}" ) +else() + set( ANDROID_GCC_TOOLCHAIN_NAME "${ANDROID_TOOLCHAIN_NAME}" ) + unset( ANDROID_COMPILER_IS_CLANG CACHE ) +endif() + +string( REPLACE "." "" _clang_name "clang${ANDROID_CLANG_VERSION}" ) +if( NOT EXISTS "${ANDROID_CLANG_TOOLCHAIN_ROOT}/bin/${_clang_name}${TOOL_OS_SUFFIX}" ) + set( _clang_name "clang" ) +endif() + + +# setup paths and STL for NDK +if( BUILD_WITH_ANDROID_NDK ) + set( ANDROID_TOOLCHAIN_ROOT "${ANDROID_NDK}/toolchains/${ANDROID_GCC_TOOLCHAIN_NAME}/prebuilt/${ANDROID_NDK_HOST_SYSTEM_NAME}" ) + set( ANDROID_SYSROOT "${ANDROID_NDK}/platforms/android-${ANDROID_NATIVE_API_LEVEL}/arch-${ANDROID_ARCH_NAME}" ) + + if( ANDROID_STL STREQUAL "none" ) + # do nothing + elseif( ANDROID_STL STREQUAL "system" ) + set( ANDROID_RTTI OFF ) + set( ANDROID_EXCEPTIONS OFF ) + set( ANDROID_STL_INCLUDE_DIRS "${ANDROID_NDK}/sources/cxx-stl/system/include" ) + elseif( ANDROID_STL STREQUAL "system_re" ) + set( ANDROID_RTTI ON ) + set( ANDROID_EXCEPTIONS ON ) + set( ANDROID_STL_INCLUDE_DIRS "${ANDROID_NDK}/sources/cxx-stl/system/include" ) + elseif( ANDROID_STL MATCHES "gabi" ) + if( ANDROID_NDK_RELEASE STRLESS "r7" ) + message( FATAL_ERROR "gabi++ is not awailable in your NDK. You have to upgrade to NDK r7 or newer to use gabi++.") + endif() + set( ANDROID_RTTI ON ) + set( ANDROID_EXCEPTIONS OFF ) + set( ANDROID_STL_INCLUDE_DIRS "${ANDROID_NDK}/sources/cxx-stl/gabi++/include" ) + set( __libstl "${ANDROID_NDK}/sources/cxx-stl/gabi++/libs/${ANDROID_NDK_ABI_NAME}/libgabi++_static.a" ) + elseif( ANDROID_STL MATCHES "stlport" ) + if( NOT ANDROID_NDK_RELEASE STRLESS "r8d" ) + set( ANDROID_EXCEPTIONS ON ) + else() + set( ANDROID_EXCEPTIONS OFF ) + endif() + if( ANDROID_NDK_RELEASE STRLESS "r7" ) + set( ANDROID_RTTI OFF ) + else() + set( ANDROID_RTTI ON ) + endif() + set( ANDROID_STL_INCLUDE_DIRS "${ANDROID_NDK}/sources/cxx-stl/stlport/stlport" ) + set( __libstl "${ANDROID_NDK}/sources/cxx-stl/stlport/libs/${ANDROID_NDK_ABI_NAME}/libstlport_static.a" ) + elseif( ANDROID_STL MATCHES "gnustl" ) + set( ANDROID_EXCEPTIONS ON ) + set( ANDROID_RTTI ON ) + if( EXISTS "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/${ANDROID_COMPILER_VERSION}" ) + if( ARMEABI_V7A AND ANDROID_COMPILER_VERSION VERSION_EQUAL "4.7" AND ANDROID_NDK_RELEASE STREQUAL "r8d" ) + # gnustl binary for 4.7 compiler is buggy :( + # TODO: look for right fix + set( __libstl "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/4.6" ) + else() + set( __libstl "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/${ANDROID_COMPILER_VERSION}" ) + endif() + else() + set( __libstl "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++" ) + endif() + set( ANDROID_STL_INCLUDE_DIRS "${__libstl}/include" "${__libstl}/libs/${ANDROID_NDK_ABI_NAME}/include" ) + if( EXISTS "${__libstl}/libs/${ANDROID_NDK_ABI_NAME}/libgnustl_static.a" ) + set( __libstl "${__libstl}/libs/${ANDROID_NDK_ABI_NAME}/libgnustl_static.a" ) + else() + set( __libstl "${__libstl}/libs/${ANDROID_NDK_ABI_NAME}/libstdc++.a" ) + endif() + else() + message( FATAL_ERROR "Unknown runtime: ${ANDROID_STL}" ) + endif() + # find libsupc++.a - rtti & exceptions + if( ANDROID_STL STREQUAL "system_re" OR ANDROID_STL MATCHES "gnustl" ) + if( ANDROID_NDK_RELEASE STRGREATER "r8" ) # r8b + set( __libsupcxx "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/${ANDROID_COMPILER_VERSION}/libs/${ANDROID_NDK_ABI_NAME}/libsupc++.a" ) + elseif( NOT ANDROID_NDK_RELEASE STRLESS "r7" AND ANDROID_NDK_RELEASE STRLESS "r8b") + set( __libsupcxx "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/libs/${ANDROID_NDK_ABI_NAME}/libsupc++.a" ) + else( ANDROID_NDK_RELEASE STRLESS "r7" ) + if( ARMEABI_V7A ) + if( ANDROID_FORCE_ARM_BUILD ) + set( __libsupcxx "${ANDROID_TOOLCHAIN_ROOT}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/libsupc++.a" ) + else() + set( __libsupcxx "${ANDROID_TOOLCHAIN_ROOT}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/thumb/libsupc++.a" ) + endif() + elseif( ARMEABI AND NOT ANDROID_FORCE_ARM_BUILD ) + set( __libsupcxx "${ANDROID_TOOLCHAIN_ROOT}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/thumb/libsupc++.a" ) + else() + set( __libsupcxx "${ANDROID_TOOLCHAIN_ROOT}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/libsupc++.a" ) + endif() + endif() + if( NOT EXISTS "${__libsupcxx}") + message( ERROR "Could not find libsupc++.a for a chosen platform. Either your NDK is not supported or is broken.") + endif() + endif() +endif() + + +# case of shared STL linkage +if( ANDROID_STL MATCHES "shared" AND DEFINED __libstl ) + string( REPLACE "_static.a" "_shared.so" __libstl "${__libstl}" ) + if( NOT _CMAKE_IN_TRY_COMPILE AND __libstl MATCHES "[.]so$" ) + get_filename_component( __libstlname "${__libstl}" NAME ) + execute_process( COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${__libstl}" "${LIBRARY_OUTPUT_PATH}/${__libstlname}" RESULT_VARIABLE __fileCopyProcess ) + if( NOT __fileCopyProcess EQUAL 0 OR NOT EXISTS "${LIBRARY_OUTPUT_PATH}/${__libstlname}") + message( SEND_ERROR "Failed copying of ${__libstl} to the ${LIBRARY_OUTPUT_PATH}/${__libstlname}" ) + endif() + unset( __fileCopyProcess ) + unset( __libstlname ) + endif() +endif() + + +# ccache support +__INIT_VARIABLE( _ndk_ccache NDK_CCACHE ENV_NDK_CCACHE ) +if( _ndk_ccache ) + if( DEFINED NDK_CCACHE AND NOT EXISTS NDK_CCACHE ) + unset( NDK_CCACHE CACHE ) + endif() + find_program( NDK_CCACHE "${_ndk_ccache}" DOC "The path to ccache binary") +else() + unset( NDK_CCACHE CACHE ) +endif() +unset( _ndk_ccache ) + + +# setup the cross-compiler +if( NOT CMAKE_C_COMPILER ) + if( NDK_CCACHE ) + set( CMAKE_C_COMPILER "${NDK_CCACHE}" CACHE PATH "ccache as C compiler" ) + set( CMAKE_CXX_COMPILER "${NDK_CCACHE}" CACHE PATH "ccache as C++ compiler" ) + if( ANDROID_COMPILER_IS_CLANG ) + set( CMAKE_C_COMPILER_ARG1 "${ANDROID_CLANG_TOOLCHAIN_ROOT}/bin/${_clang_name}${TOOL_OS_SUFFIX}" CACHE PATH "C compiler") + set( CMAKE_CXX_COMPILER_ARG1 "${ANDROID_CLANG_TOOLCHAIN_ROOT}/bin/${_clang_name}++${TOOL_OS_SUFFIX}" CACHE PATH "C++ compiler") + else() + set( CMAKE_C_COMPILER_ARG1 "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-gcc${TOOL_OS_SUFFIX}" CACHE PATH "C compiler") + set( CMAKE_CXX_COMPILER_ARG1 "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-g++${TOOL_OS_SUFFIX}" CACHE PATH "C++ compiler") + endif() + else() + if( ANDROID_COMPILER_IS_CLANG ) + set( CMAKE_C_COMPILER "${ANDROID_CLANG_TOOLCHAIN_ROOT}/bin/${_clang_name}${TOOL_OS_SUFFIX}" CACHE PATH "C compiler") + set( CMAKE_CXX_COMPILER "${ANDROID_CLANG_TOOLCHAIN_ROOT}/bin/${_clang_name}++${TOOL_OS_SUFFIX}" CACHE PATH "C++ compiler") + else() + set( CMAKE_C_COMPILER "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-gcc${TOOL_OS_SUFFIX}" CACHE PATH "C compiler" ) + set( CMAKE_CXX_COMPILER "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-g++${TOOL_OS_SUFFIX}" CACHE PATH "C++ compiler" ) + endif() + endif() + set( CMAKE_ASM_COMPILER "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-gcc${TOOL_OS_SUFFIX}" CACHE PATH "assembler" ) + set( CMAKE_STRIP "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-strip${TOOL_OS_SUFFIX}" CACHE PATH "strip" ) + set( CMAKE_AR "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-ar${TOOL_OS_SUFFIX}" CACHE PATH "archive" ) + set( CMAKE_LINKER "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-ld${TOOL_OS_SUFFIX}" CACHE PATH "linker" ) + set( CMAKE_NM "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-nm${TOOL_OS_SUFFIX}" CACHE PATH "nm" ) + set( CMAKE_OBJCOPY "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-objcopy${TOOL_OS_SUFFIX}" CACHE PATH "objcopy" ) + set( CMAKE_OBJDUMP "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-objdump${TOOL_OS_SUFFIX}" CACHE PATH "objdump" ) + set( CMAKE_RANLIB "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-ranlib${TOOL_OS_SUFFIX}" CACHE PATH "ranlib" ) +endif() + +set( _CMAKE_TOOLCHAIN_PREFIX "${ANDROID_TOOLCHAIN_MACHINE_NAME}-" ) +if( CMAKE_VERSION VERSION_LESS 2.8.5 ) + set( CMAKE_ASM_COMPILER_ARG1 "-c" ) +endif() +if( APPLE ) + find_program( CMAKE_INSTALL_NAME_TOOL NAMES install_name_tool ) + if( NOT CMAKE_INSTALL_NAME_TOOL ) + message( FATAL_ERROR "Could not find install_name_tool, please check your installation." ) + endif() + mark_as_advanced( CMAKE_INSTALL_NAME_TOOL ) +endif() + +# Force set compilers because standard identification works badly for us +include( CMakeForceCompiler ) +CMAKE_FORCE_C_COMPILER( "${CMAKE_C_COMPILER}" GNU ) +if( ANDROID_COMPILER_IS_CLANG ) + set( CMAKE_C_COMPILER_ID Clang) +endif() +set( CMAKE_C_PLATFORM_ID Linux ) +set( CMAKE_C_SIZEOF_DATA_PTR 4 ) +set( CMAKE_C_HAS_ISYSROOT 1 ) +set( CMAKE_C_COMPILER_ABI ELF ) +CMAKE_FORCE_CXX_COMPILER( "${CMAKE_CXX_COMPILER}" GNU ) +if( ANDROID_COMPILER_IS_CLANG ) + set( CMAKE_CXX_COMPILER_ID Clang) +endif() +set( CMAKE_CXX_PLATFORM_ID Linux ) +set( CMAKE_CXX_SIZEOF_DATA_PTR 4 ) +set( CMAKE_CXX_HAS_ISYSROOT 1 ) +set( CMAKE_CXX_COMPILER_ABI ELF ) +set( CMAKE_CXX_SOURCE_FILE_EXTENSIONS cc cp cxx cpp CPP c++ C ) +# force ASM compiler (required for CMake < 2.8.5) +set( CMAKE_ASM_COMPILER_ID_RUN TRUE ) +set( CMAKE_ASM_COMPILER_ID GNU ) +set( CMAKE_ASM_COMPILER_WORKS TRUE ) +set( CMAKE_ASM_COMPILER_FORCED TRUE ) +set( CMAKE_COMPILER_IS_GNUASM 1) +set( CMAKE_ASM_SOURCE_FILE_EXTENSIONS s S asm ) + +# flags and definitions +remove_definitions( -DANDROID ) +add_definitions( -DANDROID ) + +if(ANDROID_SYSROOT MATCHES "[ ;\"]") + set( ANDROID_CXX_FLAGS "--sysroot=\"${ANDROID_SYSROOT}\"" ) + if( NOT _CMAKE_IN_TRY_COMPILE ) + # quotes will break try_compile and compiler identification + message(WARNING "Your Android system root has non-alphanumeric symbols. It can break compiler features detection and the whole build.") + endif() +else() + set( ANDROID_CXX_FLAGS "--sysroot=${ANDROID_SYSROOT}" ) +endif() + +# NDK flags +if( ARMEABI OR ARMEABI_V7A ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -fpic -funwind-tables" ) + if( NOT ANDROID_FORCE_ARM_BUILD AND NOT ARMEABI_V6 ) + set( ANDROID_CXX_FLAGS_RELEASE "-mthumb -fomit-frame-pointer -fno-strict-aliasing" ) + set( ANDROID_CXX_FLAGS_DEBUG "-marm -fno-omit-frame-pointer -fno-strict-aliasing" ) + if( NOT ANDROID_COMPILER_IS_CLANG ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -finline-limit=64" ) + endif() + else() + # always compile ARMEABI_V6 in arm mode; otherwise there is no difference from ARMEABI + set( ANDROID_CXX_FLAGS_RELEASE "-marm -fomit-frame-pointer -fstrict-aliasing" ) + set( ANDROID_CXX_FLAGS_DEBUG "-marm -fno-omit-frame-pointer -fno-strict-aliasing" ) + if( NOT ANDROID_COMPILER_IS_CLANG ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -funswitch-loops -finline-limit=300" ) + endif() + endif() +elseif( X86 ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -funwind-tables" ) + if( NOT ANDROID_COMPILER_IS_CLANG ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -funswitch-loops -finline-limit=300" ) + else() + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -fPIC" ) + endif() + set( ANDROID_CXX_FLAGS_RELEASE "-fomit-frame-pointer -fstrict-aliasing" ) + set( ANDROID_CXX_FLAGS_DEBUG "-fno-omit-frame-pointer -fno-strict-aliasing" ) +elseif( MIPS ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -fpic -fno-strict-aliasing -finline-functions -ffunction-sections -funwind-tables -fmessage-length=0" ) + set( ANDROID_CXX_FLAGS_RELEASE "-fomit-frame-pointer" ) + set( ANDROID_CXX_FLAGS_DEBUG "-fno-omit-frame-pointer" ) + if( NOT ANDROID_COMPILER_IS_CLANG ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -fno-inline-functions-called-once -fgcse-after-reload -frerun-cse-after-loop -frename-registers" ) + set( ANDROID_CXX_FLAGS_RELEASE "${ANDROID_CXX_FLAGS_RELEASE} -funswitch-loops -finline-limit=300" ) + endif() +elseif() + set( ANDROID_CXX_FLAGS_RELEASE "" ) + set( ANDROID_CXX_FLAGS_DEBUG "" ) +endif() + +set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -fsigned-char" ) # good/necessary when porting desktop libraries + +if( NOT X86 AND NOT ANDROID_COMPILER_IS_CLANG ) + set( ANDROID_CXX_FLAGS "-Wno-psabi ${ANDROID_CXX_FLAGS}" ) +endif() + +if( NOT ANDROID_COMPILER_VERSION VERSION_LESS "4.6" ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -no-canonical-prefixes" ) # see https://android-review.googlesource.com/#/c/47564/ +endif() + +# ABI-specific flags +if( ARMEABI_V7A ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -march=armv7-a -mfloat-abi=softfp" ) + if( NEON ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -mfpu=neon" ) + elseif( VFPV3 ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -mfpu=vfpv3" ) + else() + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -mfpu=vfpv3-d16" ) + endif() +elseif( ARMEABI_V6 ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -march=armv6 -mfloat-abi=softfp -mfpu=vfp" ) # vfp == vfpv2 +elseif( ARMEABI ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -march=armv5te -mtune=xscale -msoft-float" ) +endif() + +# STL +if( EXISTS "${__libstl}" OR EXISTS "${__libsupcxx}" ) + if( ANDROID_STL MATCHES "gnustl" ) + set( CMAKE_CXX_CREATE_SHARED_LIBRARY " -o " ) + set( CMAKE_CXX_CREATE_SHARED_MODULE " -o " ) + set( CMAKE_CXX_LINK_EXECUTABLE " -o " ) + else() + set( CMAKE_CXX_CREATE_SHARED_LIBRARY " -o " ) + set( CMAKE_CXX_CREATE_SHARED_MODULE " -o " ) + set( CMAKE_CXX_LINK_EXECUTABLE " -o " ) + endif() + if ( X86 AND ANDROID_STL MATCHES "gnustl" AND ANDROID_NDK_RELEASE STREQUAL "r6" ) + # workaround "undefined reference to `__dso_handle'" problem + set( CMAKE_CXX_CREATE_SHARED_LIBRARY "${CMAKE_CXX_CREATE_SHARED_LIBRARY} \"${ANDROID_SYSROOT}/usr/lib/crtbegin_so.o\"" ) + set( CMAKE_CXX_CREATE_SHARED_MODULE "${CMAKE_CXX_CREATE_SHARED_MODULE} \"${ANDROID_SYSROOT}/usr/lib/crtbegin_so.o\"" ) + endif() + if( EXISTS "${__libstl}" ) + set( CMAKE_CXX_CREATE_SHARED_LIBRARY "${CMAKE_CXX_CREATE_SHARED_LIBRARY} \"${__libstl}\"" ) + set( CMAKE_CXX_CREATE_SHARED_MODULE "${CMAKE_CXX_CREATE_SHARED_MODULE} \"${__libstl}\"" ) + set( CMAKE_CXX_LINK_EXECUTABLE "${CMAKE_CXX_LINK_EXECUTABLE} \"${__libstl}\"" ) + endif() + if( EXISTS "${__libsupcxx}" ) + set( CMAKE_CXX_CREATE_SHARED_LIBRARY "${CMAKE_CXX_CREATE_SHARED_LIBRARY} \"${__libsupcxx}\"" ) + set( CMAKE_CXX_CREATE_SHARED_MODULE "${CMAKE_CXX_CREATE_SHARED_MODULE} \"${__libsupcxx}\"" ) + set( CMAKE_CXX_LINK_EXECUTABLE "${CMAKE_CXX_LINK_EXECUTABLE} \"${__libsupcxx}\"" ) + # C objects: + set( CMAKE_C_CREATE_SHARED_LIBRARY " -o " ) + set( CMAKE_C_CREATE_SHARED_MODULE " -o " ) + set( CMAKE_C_LINK_EXECUTABLE " -o " ) + set( CMAKE_C_CREATE_SHARED_LIBRARY "${CMAKE_C_CREATE_SHARED_LIBRARY} \"${__libsupcxx}\"" ) + set( CMAKE_C_CREATE_SHARED_MODULE "${CMAKE_C_CREATE_SHARED_MODULE} \"${__libsupcxx}\"" ) + set( CMAKE_C_LINK_EXECUTABLE "${CMAKE_C_LINK_EXECUTABLE} \"${__libsupcxx}\"" ) + endif() + if( ANDROID_STL MATCHES "gnustl" ) + set( CMAKE_CXX_CREATE_SHARED_LIBRARY "${CMAKE_CXX_CREATE_SHARED_LIBRARY} -lm" ) + set( CMAKE_CXX_CREATE_SHARED_MODULE "${CMAKE_CXX_CREATE_SHARED_MODULE} -lm" ) + set( CMAKE_CXX_LINK_EXECUTABLE "${CMAKE_CXX_LINK_EXECUTABLE} -lm" ) + endif() +endif() + +# variables controlling optional build flags +if (ANDROID_NDK_RELEASE STRLESS "r7") + # libGLESv2.so in NDK's prior to r7 refers to missing external symbols. + # So this flag option is required for all projects using OpenGL from native. + __INIT_VARIABLE( ANDROID_SO_UNDEFINED VALUES ON ) +else() + __INIT_VARIABLE( ANDROID_SO_UNDEFINED VALUES OFF ) +endif() +__INIT_VARIABLE( ANDROID_NO_UNDEFINED OBSOLETE_NO_UNDEFINED VALUES ON ) +__INIT_VARIABLE( ANDROID_FUNCTION_LEVEL_LINKING VALUES ON ) +__INIT_VARIABLE( ANDROID_GOLD_LINKER VALUES ON ) +__INIT_VARIABLE( ANDROID_NOEXECSTACK VALUES ON ) +__INIT_VARIABLE( ANDROID_RELRO VALUES ON ) + +set( ANDROID_NO_UNDEFINED ${ANDROID_NO_UNDEFINED} CACHE BOOL "Show all undefined symbols as linker errors" ) +set( ANDROID_SO_UNDEFINED ${ANDROID_SO_UNDEFINED} CACHE BOOL "Allows or disallows undefined symbols in shared libraries" ) +set( ANDROID_FUNCTION_LEVEL_LINKING ${ANDROID_FUNCTION_LEVEL_LINKING} CACHE BOOL "Allows or disallows undefined symbols in shared libraries" ) +set( ANDROID_GOLD_LINKER ${ANDROID_GOLD_LINKER} CACHE BOOL "Enables gold linker (only avaialble for NDK r8b for ARM and x86 architectures on linux-86 and darwin-x86 hosts)" ) +set( ANDROID_NOEXECSTACK ${ANDROID_NOEXECSTACK} CACHE BOOL "Allows or disallows undefined symbols in shared libraries" ) +set( ANDROID_RELRO ${ANDROID_RELRO} CACHE BOOL "Enables RELRO - a memory corruption mitigation technique" ) +mark_as_advanced( ANDROID_NO_UNDEFINED ANDROID_SO_UNDEFINED ANDROID_FUNCTION_LEVEL_LINKING ANDROID_GOLD_LINKER ANDROID_NOEXECSTACK ANDROID_RELRO ) + +# linker flags +set( ANDROID_LINKER_FLAGS "" ) + +if( ARMEABI_V7A ) + # this is *required* to use the following linker flags that routes around + # a CPU bug in some Cortex-A8 implementations: + set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,--fix-cortex-a8" ) +endif() + +if( ANDROID_NO_UNDEFINED ) + set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,--no-undefined" ) +endif() + +if( ANDROID_SO_UNDEFINED ) + set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,-allow-shlib-undefined" ) +endif() + +if( ANDROID_FUNCTION_LEVEL_LINKING ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -fdata-sections -ffunction-sections" ) + set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,--gc-sections" ) +endif() + +if( ANDROID_COMPILER_VERSION VERSION_EQUAL "4.6" ) + if( ANDROID_GOLD_LINKER AND (CMAKE_HOST_UNIX OR ANDROID_NDK_RELEASE STRGREATER "r8b") AND (ARMEABI OR ARMEABI_V7A OR X86) ) + set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -fuse-ld=gold" ) + elseif( ANDROID_NDK_RELEASE STRGREATER "r8b") + set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -fuse-ld=bfd" ) + elseif( ANDROID_NDK_RELEASE STREQUAL "r8b" AND ARMEABI AND NOT _CMAKE_IN_TRY_COMPILE ) + message( WARNING "The default bfd linker from arm GCC 4.6 toolchain can fail with 'unresolvable R_ARM_THM_CALL relocation' error message. See https://code.google.com/p/android/issues/detail?id=35342 + On Linux and OS X host platform you can workaround this problem using gold linker (default). + Rerun cmake with -DANDROID_GOLD_LINKER=ON option in case of problems. +" ) + endif() +endif() # version 4.6 + +if( ANDROID_NOEXECSTACK ) + if( ANDROID_COMPILER_IS_CLANG ) + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -Xclang -mnoexecstack" ) + else() + set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -Wa,--noexecstack" ) + endif() + set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,-z,noexecstack" ) +endif() + +if( ANDROID_RELRO ) + set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,-z,relro -Wl,-z,now" ) +endif() + +if( ANDROID_COMPILER_IS_CLANG ) + set( ANDROID_CXX_FLAGS "-Qunused-arguments ${ANDROID_CXX_FLAGS}" ) + if( ARMEABI_V7A AND NOT ANDROID_FORCE_ARM_BUILD ) + set( ANDROID_CXX_FLAGS_RELEASE "-target thumbv7-none-linux-androideabi ${ANDROID_CXX_FLAGS_RELEASE}" ) + set( ANDROID_CXX_FLAGS_DEBUG "-target ${ANDROID_LLVM_TRIPLE} ${ANDROID_CXX_FLAGS_DEBUG}" ) + else() + set( ANDROID_CXX_FLAGS "-target ${ANDROID_LLVM_TRIPLE} ${ANDROID_CXX_FLAGS}" ) + endif() + if( BUILD_WITH_ANDROID_NDK ) + set( ANDROID_CXX_FLAGS "-gcc-toolchain ${ANDROID_TOOLCHAIN_ROOT} ${ANDROID_CXX_FLAGS}" ) + endif() +endif() + +# cache flags +set( CMAKE_CXX_FLAGS "" CACHE STRING "c++ flags" ) +set( CMAKE_C_FLAGS "" CACHE STRING "c flags" ) +set( CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG" CACHE STRING "c++ Release flags" ) +set( CMAKE_C_FLAGS_RELEASE "-O3 -DNDEBUG" CACHE STRING "c Release flags" ) +set( CMAKE_CXX_FLAGS_DEBUG "-O0 -g -DDEBUG -D_DEBUG" CACHE STRING "c++ Debug flags" ) +set( CMAKE_C_FLAGS_DEBUG "-O0 -g -DDEBUG -D_DEBUG" CACHE STRING "c Debug flags" ) +set( CMAKE_SHARED_LINKER_FLAGS "" CACHE STRING "shared linker flags" ) +set( CMAKE_MODULE_LINKER_FLAGS "" CACHE STRING "module linker flags" ) +set( CMAKE_EXE_LINKER_FLAGS "-Wl,-z,nocopyreloc" CACHE STRING "executable linker flags" ) + +# put flags to cache (for debug purpose only) +set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS}" CACHE INTERNAL "Android specific c/c++ flags" ) +set( ANDROID_CXX_FLAGS_RELEASE "${ANDROID_CXX_FLAGS_RELEASE}" CACHE INTERNAL "Android specific c/c++ Release flags" ) +set( ANDROID_CXX_FLAGS_DEBUG "${ANDROID_CXX_FLAGS_DEBUG}" CACHE INTERNAL "Android specific c/c++ Debug flags" ) +set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS}" CACHE INTERNAL "Android specific c/c++ linker flags" ) + +# finish flags +set( CMAKE_CXX_FLAGS "${ANDROID_CXX_FLAGS} ${CMAKE_CXX_FLAGS}" ) +set( CMAKE_C_FLAGS "${ANDROID_CXX_FLAGS} ${CMAKE_C_FLAGS}" ) +set( CMAKE_CXX_FLAGS_RELEASE "${ANDROID_CXX_FLAGS_RELEASE} ${CMAKE_CXX_FLAGS_RELEASE}" ) +set( CMAKE_C_FLAGS_RELEASE "${ANDROID_CXX_FLAGS_RELEASE} ${CMAKE_C_FLAGS_RELEASE}" ) +set( CMAKE_CXX_FLAGS_DEBUG "${ANDROID_CXX_FLAGS_DEBUG} ${CMAKE_CXX_FLAGS_DEBUG}" ) +set( CMAKE_C_FLAGS_DEBUG "${ANDROID_CXX_FLAGS_DEBUG} ${CMAKE_C_FLAGS_DEBUG}" ) +set( CMAKE_SHARED_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} ${CMAKE_SHARED_LINKER_FLAGS}" ) +set( CMAKE_MODULE_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} ${CMAKE_MODULE_LINKER_FLAGS}" ) +set( CMAKE_EXE_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} ${CMAKE_EXE_LINKER_FLAGS}" ) + +if( MIPS AND BUILD_WITH_ANDROID_NDK AND ANDROID_NDK_RELEASE STREQUAL "r8" ) + set( CMAKE_SHARED_LINKER_FLAGS "-Wl,-T,${ANDROID_NDK}/toolchains/${ANDROID_GCC_TOOLCHAIN_NAME}/mipself.xsc ${CMAKE_SHARED_LINKER_FLAGS}" ) + set( CMAKE_MODULE_LINKER_FLAGS "-Wl,-T,${ANDROID_NDK}/toolchains/${ANDROID_GCC_TOOLCHAIN_NAME}/mipself.xsc ${CMAKE_MODULE_LINKER_FLAGS}" ) + set( CMAKE_EXE_LINKER_FLAGS "-Wl,-T,${ANDROID_NDK}/toolchains/${ANDROID_GCC_TOOLCHAIN_NAME}/mipself.x ${CMAKE_EXE_LINKER_FLAGS}" ) +endif() + +# configure rtti +if( DEFINED ANDROID_RTTI AND ANDROID_STL_FORCE_FEATURES ) + if( ANDROID_RTTI ) + set( CMAKE_CXX_FLAGS "-frtti ${CMAKE_CXX_FLAGS}" ) + else() + set( CMAKE_CXX_FLAGS "-fno-rtti ${CMAKE_CXX_FLAGS}" ) + endif() +endif() + +# configure exceptios +if( DEFINED ANDROID_EXCEPTIONS AND ANDROID_STL_FORCE_FEATURES ) + if( ANDROID_EXCEPTIONS ) + set( CMAKE_CXX_FLAGS "-fexceptions ${CMAKE_CXX_FLAGS}" ) + set( CMAKE_C_FLAGS "-fexceptions ${CMAKE_C_FLAGS}" ) + else() + set( CMAKE_CXX_FLAGS "-fno-exceptions ${CMAKE_CXX_FLAGS}" ) + set( CMAKE_C_FLAGS "-fno-exceptions ${CMAKE_C_FLAGS}" ) + endif() +endif() + +# global includes and link directories +include_directories( SYSTEM "${ANDROID_SYSROOT}/usr/include" ${ANDROID_STL_INCLUDE_DIRS} ) +link_directories( "${CMAKE_INSTALL_PREFIX}/libs/${ANDROID_NDK_ABI_NAME}" ) + +# setup output directories +set( LIBRARY_OUTPUT_PATH_ROOT ${CMAKE_SOURCE_DIR} CACHE PATH "root for library output, set this to change where android libs are installed to" ) +set( CMAKE_INSTALL_PREFIX "${ANDROID_TOOLCHAIN_ROOT}/user" CACHE STRING "path for installing" ) + +if(NOT _CMAKE_IN_TRY_COMPILE) + if( EXISTS "${CMAKE_SOURCE_DIR}/jni/CMakeLists.txt" ) + set( EXECUTABLE_OUTPUT_PATH "${LIBRARY_OUTPUT_PATH_ROOT}/bin/${ANDROID_NDK_ABI_NAME}" CACHE PATH "Output directory for applications" ) + else() + set( EXECUTABLE_OUTPUT_PATH "${LIBRARY_OUTPUT_PATH_ROOT}/bin" CACHE PATH "Output directory for applications" ) + endif() + set( LIBRARY_OUTPUT_PATH "${LIBRARY_OUTPUT_PATH_ROOT}/libs/${ANDROID_NDK_ABI_NAME}" CACHE PATH "path for android libs" ) +endif() + +# set these global flags for cmake client scripts to change behavior +set( ANDROID True ) +set( BUILD_ANDROID True ) + +# where is the target environment +set( CMAKE_FIND_ROOT_PATH "${ANDROID_TOOLCHAIN_ROOT}/bin" "${ANDROID_TOOLCHAIN_ROOT}/${ANDROID_TOOLCHAIN_MACHINE_NAME}" "${ANDROID_SYSROOT}" "${CMAKE_INSTALL_PREFIX}" "${CMAKE_INSTALL_PREFIX}/share" ) + +# only search for libraries and includes in the ndk toolchain +set( CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY ) +set( CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY ) +set( CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY ) + + +# macro to find packages on the host OS +macro( find_host_package ) + set( CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER ) + set( CMAKE_FIND_ROOT_PATH_MODE_LIBRARY NEVER ) + set( CMAKE_FIND_ROOT_PATH_MODE_INCLUDE NEVER ) + if( CMAKE_HOST_WIN32 ) + SET( WIN32 1 ) + SET( UNIX ) + elseif( CMAKE_HOST_APPLE ) + SET( APPLE 1 ) + SET( UNIX ) + endif() + find_package( ${ARGN} ) + SET( WIN32 ) + SET( APPLE ) + SET( UNIX 1 ) + set( CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY ) + set( CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY ) + set( CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY ) +endmacro() + + +# macro to find programs on the host OS +macro( find_host_program ) + set( CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER ) + set( CMAKE_FIND_ROOT_PATH_MODE_LIBRARY NEVER ) + set( CMAKE_FIND_ROOT_PATH_MODE_INCLUDE NEVER ) + if( CMAKE_HOST_WIN32 ) + SET( WIN32 1 ) + SET( UNIX ) + elseif( CMAKE_HOST_APPLE ) + SET( APPLE 1 ) + SET( UNIX ) + endif() + find_program( ${ARGN} ) + SET( WIN32 ) + SET( APPLE ) + SET( UNIX 1 ) + set( CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY ) + set( CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY ) + set( CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY ) +endmacro() + + +macro( ANDROID_GET_ABI_RAWNAME TOOLCHAIN_FLAG VAR ) + if( "${TOOLCHAIN_FLAG}" STREQUAL "ARMEABI" ) + set( ${VAR} "armeabi" ) + elseif( "${TOOLCHAIN_FLAG}" STREQUAL "ARMEABI_V7A" ) + set( ${VAR} "armeabi-v7a" ) + elseif( "${TOOLCHAIN_FLAG}" STREQUAL "X86" ) + set( ${VAR} "x86" ) + elseif( "${TOOLCHAIN_FLAG}" STREQUAL "MIPS" ) + set( ${VAR} "mips" ) + else() + set( ${VAR} "unknown" ) + endif() +endmacro() + + +# export toolchain settings for the try_compile() command +if( NOT PROJECT_NAME STREQUAL "CMAKE_TRY_COMPILE" ) + set( __toolchain_config "") + foreach( __var NDK_CCACHE LIBRARY_OUTPUT_PATH_ROOT ANDROID_FORBID_SYGWIN ANDROID_SET_OBSOLETE_VARIABLES + ANDROID_NDK_HOST_X64 + ANDROID_NDK + ANDROID_STANDALONE_TOOLCHAIN + ANDROID_TOOLCHAIN_NAME + ANDROID_ABI + ANDROID_NATIVE_API_LEVEL + ANDROID_STL + ANDROID_STL_FORCE_FEATURES + ANDROID_FORCE_ARM_BUILD + ANDROID_NO_UNDEFINED + ANDROID_SO_UNDEFINED + ANDROID_FUNCTION_LEVEL_LINKING + ANDROID_GOLD_LINKER + ANDROID_NOEXECSTACK + ANDROID_RELRO + ) + if( DEFINED ${__var} ) + if( "${__var}" MATCHES " ") + set( __toolchain_config "${__toolchain_config}set( ${__var} \"${${__var}}\" CACHE INTERNAL \"\" )\n" ) + else() + set( __toolchain_config "${__toolchain_config}set( ${__var} ${${__var}} CACHE INTERNAL \"\" )\n" ) + endif() + endif() + endforeach() + file( WRITE "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/android.toolchain.config.cmake" "${__toolchain_config}" ) + unset( __toolchain_config ) +endif() + + +# set some obsolete variables for backward compatibility +set( ANDROID_SET_OBSOLETE_VARIABLES ON CACHE BOOL "Define obsolete Andrid-specific cmake variables" ) +mark_as_advanced( ANDROID_SET_OBSOLETE_VARIABLES ) +if( ANDROID_SET_OBSOLETE_VARIABLES ) + set( ANDROID_API_LEVEL ${ANDROID_NATIVE_API_LEVEL} ) + set( ARM_TARGET "${ANDROID_ABI}" ) + set( ARMEABI_NDK_NAME "${ANDROID_NDK_ABI_NAME}" ) +endif() + + +# Variables controlling behavior or set by cmake toolchain: +# ANDROID_ABI : "armeabi-v7a" (default), "armeabi", "armeabi-v7a with NEON", "armeabi-v7a with VFPV3", "armeabi-v6 with VFP", "x86", "mips" +# ANDROID_NATIVE_API_LEVEL : 3,4,5,8,9,14 (depends on NDK version) +# ANDROID_STL : gnustl_static/gnustl_shared/stlport_static/stlport_shared/gabi++_static/gabi++_shared/system_re/system/none +# ANDROID_FORBID_SYGWIN : ON/OFF +# ANDROID_NO_UNDEFINED : ON/OFF +# ANDROID_SO_UNDEFINED : OFF/ON (default depends on NDK version) +# ANDROID_FUNCTION_LEVEL_LINKING : ON/OFF +# ANDROID_GOLD_LINKER : ON/OFF +# ANDROID_NOEXECSTACK : ON/OFF +# ANDROID_RELRO : ON/OFF +# ANDROID_FORCE_ARM_BUILD : ON/OFF +# ANDROID_STL_FORCE_FEATURES : ON/OFF +# ANDROID_SET_OBSOLETE_VARIABLES : ON/OFF +# Can be set only at the first run: +# ANDROID_NDK +# ANDROID_STANDALONE_TOOLCHAIN +# ANDROID_TOOLCHAIN_NAME : the NDK name of compiler toolchain +# ANDROID_NDK_HOST_X64 : try to use x86_64 toolchain (default for x64 host systems) +# LIBRARY_OUTPUT_PATH_ROOT : +# NDK_CCACHE : +# Obsolete: +# ANDROID_API_LEVEL : superseded by ANDROID_NATIVE_API_LEVEL +# ARM_TARGET : superseded by ANDROID_ABI +# ARM_TARGETS : superseded by ANDROID_ABI (can be set only) +# ANDROID_NDK_TOOLCHAIN_ROOT : superseded by ANDROID_STANDALONE_TOOLCHAIN (can be set only) +# ANDROID_USE_STLPORT : superseded by ANDROID_STL=stlport_static +# ANDROID_LEVEL : superseded by ANDROID_NATIVE_API_LEVEL (completely removed) +# +# Primary read-only variables: +# ANDROID : always TRUE +# ARMEABI : TRUE for arm v6 and older devices +# ARMEABI_V6 : TRUE for arm v6 +# ARMEABI_V7A : TRUE for arm v7a +# NEON : TRUE if NEON unit is enabled +# VFPV3 : TRUE if VFP version 3 is enabled +# X86 : TRUE if configured for x86 +# MIPS : TRUE if configured for mips +# BUILD_ANDROID : always TRUE +# BUILD_WITH_ANDROID_NDK : TRUE if NDK is used +# BUILD_WITH_STANDALONE_TOOLCHAIN : TRUE if standalone toolchain is used +# ANDROID_NDK_HOST_SYSTEM_NAME : "windows", "linux-x86" or "darwin-x86" depending on host platform +# ANDROID_NDK_ABI_NAME : "armeabi", "armeabi-v7a", "x86" or "mips" depending on ANDROID_ABI +# ANDROID_NDK_RELEASE : one of r5, r5b, r5c, r6, r6b, r7, r7b, r7c, r8, r8b, r8c, r8d, r8e; set only for NDK +# ANDROID_ARCH_NAME : "arm" or "x86" or "mips" depending on ANDROID_ABI +# ANDROID_SYSROOT : path to the compiler sysroot +# TOOL_OS_SUFFIX : "" or ".exe" depending on host platform +# ANDROID_COMPILER_IS_CLANG : TRUE if clang compiler is used +# Obsolete: +# ARMEABI_NDK_NAME : superseded by ANDROID_NDK_ABI_NAME +# +# Secondary (less stable) read-only variables: +# ANDROID_COMPILER_VERSION : GCC version used +# ANDROID_CXX_FLAGS : C/C++ compiler flags required by Android platform +# ANDROID_SUPPORTED_ABIS : list of currently allowed values for ANDROID_ABI +# ANDROID_TOOLCHAIN_MACHINE_NAME : "arm-linux-androideabi", "arm-eabi" or "i686-android-linux" +# ANDROID_TOOLCHAIN_ROOT : path to the top level of toolchain (standalone or placed inside NDK) +# ANDROID_CLANG_TOOLCHAIN_ROOT : path to clang tools +# ANDROID_SUPPORTED_NATIVE_API_LEVELS : list of native API levels found inside NDK +# ANDROID_STL_INCLUDE_DIRS : stl include paths +# ANDROID_RTTI : if rtti is enabled by the runtime +# ANDROID_EXCEPTIONS : if exceptions are enabled by the runtime +# ANDROID_GCC_TOOLCHAIN_NAME : read-only, differs from ANDROID_TOOLCHAIN_NAME only if clang is used +# ANDROID_CLANG_VERSION : version of clang compiler if clang is used +# +# Defaults: +# ANDROID_DEFAULT_NDK_API_LEVEL +# ANDROID_DEFAULT_NDK_API_LEVEL_${ARCH} +# ANDROID_NDK_SEARCH_PATHS +# ANDROID_STANDALONE_TOOLCHAIN_SEARCH_PATH +# ANDROID_SUPPORTED_ABIS_${ARCH} +# ANDROID_SUPPORTED_NDK_VERSIONS diff --git a/android/readme.txt b/android/readme.txt new file mode 100644 index 0000000000..2d5f3962fe --- /dev/null +++ b/android/readme.txt @@ -0,0 +1 @@ +All Android specific sources are moved to platforms/android. \ No newline at end of file diff --git a/platforms/android/refman.rst b/platforms/android/refman.rst new file mode 100644 index 0000000000..12d7ea6ec0 --- /dev/null +++ b/platforms/android/refman.rst @@ -0,0 +1,9 @@ +############################ +OpenCV4Android Reference +############################ + +.. toctree:: + :maxdepth: 2 + + service/doc/index.rst + java.rst \ No newline at end of file diff --git a/platforms/android/scripts/build.cmd b/platforms/android/scripts/build.cmd deleted file mode 100644 index 3e0f1666b6..0000000000 --- a/platforms/android/scripts/build.cmd +++ /dev/null @@ -1,90 +0,0 @@ -@ECHO OFF - -:: enable command extensions -VERIFY BADVALUE 2>NUL -SETLOCAL ENABLEEXTENSIONS || (ECHO Unable to enable command extensions. & EXIT \B) - -:: build environment -SET SOURCE_DIR=%cd% -IF EXIST .\android.toolchain.cmake (SET BUILD_OPENCV=1) ELSE (SET BUILD_OPENCV=0) -IF EXIST .\jni\nul (SET BUILD_JAVA_PART=1) ELSE (SET BUILD_JAVA_PART=0) - -:: load configuration -PUSHD %~dp0 -SET SCRIPTS_DIR=%cd% -IF EXIST .\wincfg.cmd CALL .\wincfg.cmd -POPD - -:: inherit old names -IF NOT DEFINED CMAKE SET CMAKE=%CMAKE_EXE% -IF NOT DEFINED MAKE SET MAKE=%MAKE_EXE% - -:: defaults -IF NOT DEFINED BUILD_DIR SET BUILD_DIR=build -IF NOT DEFINED ANDROID_ABI SET ANDROID_ABI=armeabi-v7a -SET OPENCV_BUILD_DIR=%SCRIPTS_DIR%\..\%BUILD_DIR% - -:: check that all required variables defined -PUSHD . -IF NOT DEFINED ANDROID_NDK (ECHO. & ECHO You should set an environment variable ANDROID_NDK to the full path to your copy of Android NDK & GOTO end) -(CD "%ANDROID_NDK%") || (ECHO. & ECHO Directory "%ANDROID_NDK%" specified by ANDROID_NDK variable does not exist & GOTO end) - -IF NOT EXIST "%CMAKE%" (ECHO. & ECHO You should set an environment variable CMAKE to the full path to cmake executable & GOTO end) -IF NOT EXIST "%MAKE%" (ECHO. & ECHO You should set an environment variable MAKE to the full path to native port of make executable & GOTO end) - -IF NOT %BUILD_JAVA_PART%==1 GOTO required_variables_checked - -IF NOT DEFINED ANDROID_SDK (ECHO. & ECHO You should set an environment variable ANDROID_SDK to the full path to your copy of Android SDK & GOTO end) -(CD "%ANDROID_SDK%" 2>NUL) || (ECHO. & ECHO Directory "%ANDROID_SDK%" specified by ANDROID_SDK variable does not exist & GOTO end) - -IF NOT DEFINED ANT_DIR (ECHO. & ECHO You should set an environment variable ANT_DIR to the full path to Apache Ant root & GOTO end) -(CD "%ANT_DIR%" 2>NUL) || (ECHO. & ECHO Directory "%ANT_DIR%" specified by ANT_DIR variable does not exist & GOTO end) - -IF NOT DEFINED JAVA_HOME (ECHO. & ECHO You should set an environment variable JAVA_HOME to the full path to JDK & GOTO end) -(CD "%JAVA_HOME%" 2>NUL) || (ECHO. & ECHO Directory "%JAVA_HOME%" specified by JAVA_HOME variable does not exist & GOTO end) - -:required_variables_checked -POPD - -:: check for ninja -echo "%MAKE%"|findstr /i ninja >nul: -IF %errorlevel%==1 (SET BUILD_WITH_NINJA=0) ELSE (SET BUILD_WITH_NINJA=1) -IF %BUILD_WITH_NINJA%==1 (SET CMAKE_GENERATOR=Ninja) ELSE (SET CMAKE_GENERATOR=MinGW Makefiles) - -:: create build dir -IF DEFINED REBUILD rmdir /S /Q "%BUILD_DIR%" 2>NUL -MKDIR "%BUILD_DIR%" 2>NUL -PUSHD "%BUILD_DIR%" || (ECHO. & ECHO Directory "%BUILD_DIR%" is not found & GOTO end) - -:: run cmake -ECHO. & ECHO Runnning cmake... -ECHO ANDROID_ABI=%ANDROID_ABI% -ECHO. -IF NOT %BUILD_OPENCV%==1 GOTO other-cmake -:opencv-cmake -("%CMAKE%" -G"%CMAKE_GENERATOR%" -DANDROID_ABI="%ANDROID_ABI%" -DCMAKE_TOOLCHAIN_FILE="%SOURCE_DIR%"\android.toolchain.cmake -DCMAKE_MAKE_PROGRAM="%MAKE%" %* "%SOURCE_DIR%\..") && GOTO cmakefin -ECHO. & ECHO cmake failed & GOTO end -:other-cmake -("%CMAKE%" -G"%CMAKE_GENERATOR%" -DANDROID_ABI="%ANDROID_ABI%" -DOpenCV_DIR="%OPENCV_BUILD_DIR%" -DCMAKE_TOOLCHAIN_FILE="%OPENCV_BUILD_DIR%\..\android.toolchain.cmake" -DCMAKE_MAKE_PROGRAM="%MAKE%" %* "%SOURCE_DIR%") && GOTO cmakefin -ECHO. & ECHO cmake failed & GOTO end -:cmakefin - -:: run make -ECHO. & ECHO Building native libs... -IF %BUILD_WITH_NINJA%==0 ("%MAKE%" -j %NUMBER_OF_PROCESSORS% VERBOSE=%VERBOSE%) || (ECHO. & ECHO make failed & GOTO end) -IF %BUILD_WITH_NINJA%==1 ("%MAKE%") || (ECHO. & ECHO ninja failed & GOTO end) - -IF NOT %BUILD_JAVA_PART%==1 GOTO end -POPD && PUSHD %SOURCE_DIR% - -:: configure java part -ECHO. & ECHO Updating Android project... -(CALL "%ANDROID_SDK%\tools\android" update project --name %PROJECT_NAME% --path .) || (ECHO. & ECHO failed to update android project & GOTO end) - -:: compile java part -ECHO. & ECHO Compiling Android project... -(CALL "%ANT_DIR%\bin\ant" debug) || (ECHO. & ECHO failed to compile android project & GOTO end) - -:end -POPD -ENDLOCAL diff --git a/platforms/android/scripts/cmake_android.cmd b/platforms/android/scripts/cmake_android.cmd deleted file mode 100644 index 212c04b47e..0000000000 --- a/platforms/android/scripts/cmake_android.cmd +++ /dev/null @@ -1,5 +0,0 @@ -@ECHO OFF - -PUSHD %~dp0.. -CALL .\scripts\build.cmd %* -DCMAKE_BUILD_WITH_INSTALL_RPATH=ON -POPD \ No newline at end of file diff --git a/platforms/android/scripts/cmake_android_armeabi.sh b/platforms/android/scripts/cmake_android_armeabi.sh deleted file mode 100755 index dec0ce3429..0000000000 --- a/platforms/android/scripts/cmake_android_armeabi.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh -cd `dirname $0`/.. - -mkdir -p build_armeabi -cd build_armeabi - -cmake -DANDROID_ABI=armeabi -DCMAKE_TOOLCHAIN_FILE=../android.toolchain.cmake $@ ../../.. - diff --git a/platforms/android/scripts/cmake_android_debug.sh b/platforms/android/scripts/cmake_android_debug.sh deleted file mode 100755 index dc5a3a17f6..0000000000 --- a/platforms/android/scripts/cmake_android_debug.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh -cd `dirname $0`/.. - -mkdir -p build_debug -cd build_debug - -cmake -DCMAKE_BUILD_WITH_INSTALL_RPATH=ON -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE=../android.toolchain.cmake $@ ../../.. - diff --git a/platforms/android/scripts/cmake_android_mips.sh b/platforms/android/scripts/cmake_android_mips.sh deleted file mode 100755 index 5c4195de59..0000000000 --- a/platforms/android/scripts/cmake_android_mips.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh -cd `dirname $0`/.. - -mkdir -p build_mips -cd build_mips - -cmake -DANDROID_ABI=mips -DCMAKE_TOOLCHAIN_FILE=../android.toolchain.cmake $@ ../../.. - diff --git a/platforms/android/scripts/cmake_android_neon.sh b/platforms/android/scripts/cmake_android_neon.sh deleted file mode 100755 index 716809a0b3..0000000000 --- a/platforms/android/scripts/cmake_android_neon.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh -cd `dirname $0`/.. - -mkdir -p build_neon -cd build_neon - -cmake -DANDROID_ABI="armeabi-v7a with NEON" -DCMAKE_TOOLCHAIN_FILE=../android.toolchain.cmake $@ ../../.. - diff --git a/platforms/android/scripts/cmake_android_service.sh b/platforms/android/scripts/cmake_android_service.sh deleted file mode 100755 index c702e65dfd..0000000000 --- a/platforms/android/scripts/cmake_android_service.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/sh -cd `dirname $0`/.. - -mkdir -p build_service -cd build_service - -cmake -DCMAKE_TOOLCHAIN_FILE=../android.toolchain.cmake -DANDROID_TOOLCHAIN_NAME="arm-linux-androideabi-4.4.3" -DANDROID_STL=stlport_static -DANDROID_STL_FORCE_FEATURES=OFF -DBUILD_ANDROID_SERVICE=ON -DANDROID_SOURCE_TREE=~/Projects/AndroidSource/ServiceStub/ $@ ../../.. diff --git a/platforms/android/scripts/cmake_android_service_x86.sh b/platforms/android/scripts/cmake_android_service_x86.sh deleted file mode 100755 index 89b1f7eccb..0000000000 --- a/platforms/android/scripts/cmake_android_service_x86.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh -cd `dirname $0`/.. - -mkdir -p build_service_x86 -cd build_service_x86 - -cmake -DANDROID_ABI=x86 -DCMAKE_TOOLCHAIN_FILE=../android.toolchain.cmake -DANDROID_TOOLCHAIN_NAME="x86-4.4.3" -DANDROID_STL=stlport_static -DANDROID_STL_FORCE_FEATURES=OFF -DBUILD_ANDROID_SERVICE=ON -DANDROID_SOURCE_TREE=~/Projects/AndroidSource/ServiceStub/ $@ ../../.. - diff --git a/platforms/android/scripts/cmake_android_x86.sh b/platforms/android/scripts/cmake_android_x86.sh deleted file mode 100755 index 539060083d..0000000000 --- a/platforms/android/scripts/cmake_android_x86.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/sh - -cd `dirname $0`/.. - -mkdir -p build_x86 -cd build_x86 - -cmake -DANDROID_ABI=x86 -DCMAKE_TOOLCHAIN_FILE=../android.toolchain.cmake $@ ../../.. - diff --git a/platforms/android/scripts/wincfg.cmd.tmpl b/platforms/android/scripts/wincfg.cmd.tmpl deleted file mode 100644 index 166a5e7b02..0000000000 --- a/platforms/android/scripts/wincfg.cmd.tmpl +++ /dev/null @@ -1,30 +0,0 @@ -:: variables required for OpenCV build :: -:: Note: all pathes should be specified without tailing slashes! -SET ANDROID_NDK=C:\full\path\to\your\copy\of\android\NDK\android-ndk-r7b -SET CMAKE_EXE=C:\full\path\to\cmake\utility\cmake.exe -SET MAKE_EXE=%ANDROID_NDK%\prebuilt\windows\bin\make.exe - -:: variables required for android-opencv build :: -SET ANDROID_SDK=C:\full\path\to\your\copy\of\android\SDK\android-sdk-windows -SET ANT_DIR=C:\full\path\to\ant\directory\apache-ant-1.8.2 -SET JAVA_HOME=C:\full\path\to\JDK\jdk1.6.0_25 - -:: configuration options :: -:::: general ARM-V7 settings -SET ANDROID_ABI=armeabi-v7a -SET BUILD_DIR=build - -:::: uncomment following lines to compile for old emulator or old device -::SET ANDROID_ABI=armeabi -::SET BUILD_DIR=build_armeabi - -:::: uncomment following lines to compile for ARM-V7 with NEON support -::SET ANDROID_ABI=armeabi-v7a with NEON -::SET BUILD_DIR=build_neon - -:::: uncomment following lines to compile for x86 -::SET ANDROID_ABI=x86 -::SET BUILD_DIR=build_x86 - -:::: other options -::SET ANDROID_NATIVE_API_LEVEL=8 &:: android-3 is enough for native part of OpenCV but android-8 is required for Java API diff --git a/platforms/android/service/doc/Makefile b/platforms/android/service/doc/Makefile deleted file mode 100644 index b8e7bba113..0000000000 --- a/platforms/android/service/doc/Makefile +++ /dev/null @@ -1,89 +0,0 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = -BUILDDIR = _build - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . - -.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest - -help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - -clean: - -rm -rf $(BUILDDIR)/* - -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/OpenCVEngine.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/OpenCVEngine.qhc" - -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ - "run these through (pdf)latex." - -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." diff --git a/platforms/android/service/engine/CMakeLists.txt b/platforms/android/service/engine/CMakeLists.txt index 793c433480..852a028cab 100644 --- a/platforms/android/service/engine/CMakeLists.txt +++ b/platforms/android/service/engine/CMakeLists.txt @@ -72,4 +72,3 @@ file(GLOB engine_test_files "jni/Tests/*.cpp") add_executable(opencv_test_engine ${engine_test_files} jni/Tests/gtest/gtest-all.cpp) target_link_libraries(opencv_test_engine z binder log utils android_runtime ${engine} ${engine}_jni) - diff --git a/platforms/android/service/engine/jni/Tests/PackageInfoTest.cpp b/platforms/android/service/engine/jni/Tests/PackageInfoTest.cpp index 6cbb069431..36fdae764f 100644 --- a/platforms/android/service/engine/jni/Tests/PackageInfoTest.cpp +++ b/platforms/android/service/engine/jni/Tests/PackageInfoTest.cpp @@ -222,4 +222,3 @@ TEST(PackageInfo, Comparator3) EXPECT_EQ(info1, info2); } #endif - diff --git a/platforms/android/service/engine/jni/Tests/PackageManagmentTest.cpp b/platforms/android/service/engine/jni/Tests/PackageManagmentTest.cpp index e21dcf7604..61d6e01c24 100644 --- a/platforms/android/service/engine/jni/Tests/PackageManagmentTest.cpp +++ b/platforms/android/service/engine/jni/Tests/PackageManagmentTest.cpp @@ -137,5 +137,3 @@ TEST(PackageManager, GetPackagePathForMips) // string path = pm.GetPackagePathByVersion("240", PLATFORM_TEGRA2, 0); // EXPECT_STREQ("/data/data/org.opencv.lib_v24_tegra2/lib", path.c_str()); // } - - diff --git a/platforms/android/service/test_native.py b/platforms/android/service/test_native.py index 9a39032b18..328b9a8a51 100755 --- a/platforms/android/service/test_native.py +++ b/platforms/android/service/test_native.py @@ -34,4 +34,3 @@ if (__name__ == "__main__"): os.system("adb %s shell mkdir -p \"%s\"" % (DEVICE_STR, DEVICE_LOG_PATH)) RunTestApp("OpenCVEngineTestApp") - diff --git a/platforms/linux/scripts/cmake_arm_gnueabi_hardfp.sh b/platforms/linux/scripts/cmake_arm_gnueabi_hardfp.sh deleted file mode 100755 index f8df7859c3..0000000000 --- a/platforms/linux/scripts/cmake_arm_gnueabi_hardfp.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh -cd `dirname $0`/.. - -mkdir -p build_hardfp -cd build_hardfp - -cmake -DCMAKE_TOOLCHAIN_FILE=../arm-gnueabi.toolchain.cmake $@ ../../.. - diff --git a/platforms/linux/scripts/cmake_arm_gnueabi_softfp.sh b/platforms/linux/scripts/cmake_arm_gnueabi_softfp.sh deleted file mode 100755 index f4210fa829..0000000000 --- a/platforms/linux/scripts/cmake_arm_gnueabi_softfp.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh -cd `dirname $0`/.. - -mkdir -p build_softfp -cd build_softfp - -cmake -DSOFTFP=ON -DCMAKE_TOOLCHAIN_FILE=../arm-gnueabi.toolchain.cmake $@ ../../.. - diff --git a/platforms/android/scripts/ABI_compat_generator.py b/platforms/scripts/ABI_compat_generator.py similarity index 98% rename from platforms/android/scripts/ABI_compat_generator.py rename to platforms/scripts/ABI_compat_generator.py index b492a70fe4..fdabf00611 100755 --- a/platforms/android/scripts/ABI_compat_generator.py +++ b/platforms/scripts/ABI_compat_generator.py @@ -6,9 +6,7 @@ import os architecture = 'armeabi' -excludedHeaders = set(['hdf5.h', 'cap_ios.h', - 'eigen.hpp', 'cxeigen.hpp' #TOREMOVE - ]) +excludedHeaders = set(['hdf5.h', 'cap_ios.h', 'eigen.hpp', 'cxeigen.hpp']) #TOREMOVE systemIncludes = ['sources/cxx-stl/gnu-libstdc++/4.6/include', \ '/opt/android-ndk-r8c/platforms/android-8/arch-arm', # TODO: check if this one could be passed as command line arg 'sources/cxx-stl/gnu-libstdc++/4.6/libs/armeabi-v7a/include'] @@ -113,7 +111,7 @@ def FindHeaders(): if f == m: moduleHeaders += GetHeaderFiles(os.path.join(cppHeadersFolder, f)) if m == 'flann': - flann = os.path.join(cppHeadersFolder, f, 'flann.hpp') + flann = os.path.join(cppHeadersFolder, f, 'flann.hpp') moduleHeaders.remove(flann) moduleHeaders.insert(0, flann) cppHeaders += moduleHeaders diff --git a/platforms/android/scripts/camera_build.conf b/platforms/scripts/camera_build.conf similarity index 100% rename from platforms/android/scripts/camera_build.conf rename to platforms/scripts/camera_build.conf diff --git a/platforms/android/scripts/cmake_android_all_cameras.py b/platforms/scripts/cmake_android_all_cameras.py similarity index 85% rename from platforms/android/scripts/cmake_android_all_cameras.py rename to platforms/scripts/cmake_android_all_cameras.py index 59418944ce..c160df0fa0 100755 --- a/platforms/android/scripts/cmake_android_all_cameras.py +++ b/platforms/scripts/cmake_android_all_cameras.py @@ -49,7 +49,7 @@ for s in ConfFile.readlines(): os.chdir(BuildDir) BuildLog = os.path.join(BuildDir, "build.log") - CmakeCmdLine = "cmake -DCMAKE_TOOLCHAIN_FILE=../android.toolchain.cmake -DANDROID_SOURCE_TREE=\"%s\" -DANDROID_NATIVE_API_LEVEL=\"%s\" -DANDROID_ABI=\"%s\" -DANDROID_STL=stlport_static ../../../ > \"%s\" 2>&1" % (AndroidTreeRoot, NativeApiLevel, Arch, BuildLog) + CmakeCmdLine = "cmake -DCMAKE_TOOLCHAIN_FILE=../android/android.toolchain.cmake -DANDROID_SOURCE_TREE=\"%s\" -DANDROID_NATIVE_API_LEVEL=\"%s\" -DANDROID_ABI=\"%s\" -DANDROID_STL=stlport_static ../.. > \"%s\" 2>&1" % (AndroidTreeRoot, NativeApiLevel, Arch, BuildLog) MakeCmdLine = "make %s >> \"%s\" 2>&1" % (MakeTarget, BuildLog); #print(CmakeCmdLine) os.system(CmakeCmdLine) @@ -59,7 +59,7 @@ for s in ConfFile.readlines(): CameraLib = os.path.join(BuildDir, "lib", Arch, "lib" + MakeTarget + ".so") if (os.path.exists(CameraLib)): try: - shutil.copyfile(CameraLib, os.path.join("..", "..", "3rdparty", "lib", Arch, "lib" + MakeTarget + ".so")) + shutil.copyfile(CameraLib, os.path.join("..", "3rdparty", "lib", Arch, "lib" + MakeTarget + ".so")) print("Building %s for %s\t[\033[92mOK\033[0m]" % (MakeTarget, Arch)); except: print("Building %s for %s\t[\033[91mFAILED\033[0m]" % (MakeTarget, Arch)); diff --git a/platforms/android/scripts/cmake_android.sh b/platforms/scripts/cmake_android_arm.sh similarity index 50% rename from platforms/android/scripts/cmake_android.sh rename to platforms/scripts/cmake_android_arm.sh index 941a665b80..84c88a8159 100755 --- a/platforms/android/scripts/cmake_android.sh +++ b/platforms/scripts/cmake_android_arm.sh @@ -1,8 +1,7 @@ #!/bin/sh cd `dirname $0`/.. -mkdir -p build -cd build - -cmake -DCMAKE_BUILD_WITH_INSTALL_RPATH=ON -DCMAKE_TOOLCHAIN_FILE=../android.toolchain.cmake $@ ../../.. +mkdir -p build_android_arm +cd build_android_arm +cmake -DCMAKE_BUILD_WITH_INSTALL_RPATH=ON -DCMAKE_TOOLCHAIN_FILE=../android/android.toolchain.cmake $@ ../.. diff --git a/platforms/scripts/cmake_android_mips.sh b/platforms/scripts/cmake_android_mips.sh new file mode 100755 index 0000000000..6bc7944b6d --- /dev/null +++ b/platforms/scripts/cmake_android_mips.sh @@ -0,0 +1,7 @@ +#!/bin/sh +cd `dirname $0`/.. + +mkdir -p build_android_mips +cd build_android_mips + +cmake -DANDROID_ABI=mips -DCMAKE_TOOLCHAIN_FILE=../android/android.toolchain.cmake $@ ../.. diff --git a/platforms/scripts/cmake_android_service.sh b/platforms/scripts/cmake_android_service.sh new file mode 100755 index 0000000000..7ba8865b2a --- /dev/null +++ b/platforms/scripts/cmake_android_service.sh @@ -0,0 +1,7 @@ +#!/bin/sh +cd `dirname $0`/.. + +mkdir -p build_android_service +cd build_android_service + +cmake -DCMAKE_TOOLCHAIN_FILE=../android/android.toolchain.cmake -DANDROID_TOOLCHAIN_NAME="arm-linux-androideabi-4.4.3" -DANDROID_STL=stlport_static -DANDROID_STL_FORCE_FEATURES=OFF -DBUILD_ANDROID_SERVICE=ON -DANDROID_SOURCE_TREE=~/Projects/AndroidSource/ServiceStub/ $@ ../.. diff --git a/platforms/scripts/cmake_android_x86.sh b/platforms/scripts/cmake_android_x86.sh new file mode 100755 index 0000000000..8fb8abda7e --- /dev/null +++ b/platforms/scripts/cmake_android_x86.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +cd `dirname $0`/.. + +mkdir -p build_android_x86 +cd build_android_x86 + +cmake -DANDROID_ABI=x86 -DCMAKE_TOOLCHAIN_FILE=../android/android.toolchain.cmake $@ ../.. diff --git a/platforms/scripts/cmake_arm_gnueabi_hardfp.sh b/platforms/scripts/cmake_arm_gnueabi_hardfp.sh new file mode 100755 index 0000000000..1fce4f9dc1 --- /dev/null +++ b/platforms/scripts/cmake_arm_gnueabi_hardfp.sh @@ -0,0 +1,7 @@ +#!/bin/sh +cd `dirname $0`/.. + +mkdir -p build_linux_arm_hardfp +cd build_linux_arm_hardfp + +cmake -DCMAKE_TOOLCHAIN_FILE=../linux/arm-gnueabi.toolchain.cmake $@ ../.. diff --git a/platforms/scripts/cmake_arm_gnueabi_softfp.sh b/platforms/scripts/cmake_arm_gnueabi_softfp.sh new file mode 100755 index 0000000000..734348907c --- /dev/null +++ b/platforms/scripts/cmake_arm_gnueabi_softfp.sh @@ -0,0 +1,7 @@ +#!/bin/sh +cd `dirname $0`/.. + +mkdir -p build_linux_arm_softfp +cd build_linux_arm_softfp + +cmake -DSOFTFP=ON -DCMAKE_TOOLCHAIN_FILE=../linux/arm-gnueabi.toolchain.cmake $@ ../.. diff --git a/platforms/linux/scripts/cmake_carma.sh b/platforms/scripts/cmake_carma.sh similarity index 100% rename from platforms/linux/scripts/cmake_carma.sh rename to platforms/scripts/cmake_carma.sh diff --git a/platforms/winrt/scripts/cmake_winrt.cmd b/platforms/scripts/cmake_winrt.cmd similarity index 100% rename from platforms/winrt/scripts/cmake_winrt.cmd rename to platforms/scripts/cmake_winrt.cmd From 1d0c283508df92939709eaa6f99f626f3bfefa4a Mon Sep 17 00:00:00 2001 From: peng xiao Date: Tue, 28 May 2013 17:27:55 +0800 Subject: [PATCH 037/178] Fix a bug when pushing pointers of arguments into std::vector. When argument pointers pushed into an vector and the pointers point to address on stack, we need to make sure they are valid until kernels are successfully flushed onto the queue. --- modules/ocl/src/arithm.cpp | 7 ++++--- modules/ocl/src/imgproc.cpp | 8 +++----- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/modules/ocl/src/arithm.cpp b/modules/ocl/src/arithm.cpp index ed2515dc6d..9e5b7130ad 100644 --- a/modules/ocl/src/arithm.cpp +++ b/modules/ocl/src/arithm.cpp @@ -413,11 +413,11 @@ static void arithmetic_scalar_run(const oclMat &src, oclMat &dst, string kernelN args.push_back( make_pair( sizeof(cl_int), (void *)&cols )); args.push_back( make_pair( sizeof(cl_int), (void *)&dst_step1 )); + float f_scalar = (float)scalar; if(src.clCxt->supportsFeature(Context::CL_DOUBLE)) args.push_back( make_pair( sizeof(cl_double), (void *)&scalar )); else { - float f_scalar = (float)scalar; args.push_back( make_pair( sizeof(cl_float), (void *)&f_scalar)); } @@ -1687,10 +1687,11 @@ void bitwise_run(const oclMat &src1, const oclMat &src2, oclMat &dst, string ker args.push_back( make_pair( sizeof(cl_int), (void *)&cols )); args.push_back( make_pair( sizeof(cl_int), (void *)&dst_step1 )); + T scalar; if(_scalar != NULL) { double scalar1 = *((double *)_scalar); - T scalar = (T)scalar1; + scalar = (T)scalar1; args.push_back( make_pair( sizeof(T), (void *)&scalar )); } @@ -2307,9 +2308,9 @@ static void arithmetic_pow_run(const oclMat &src1, double p, oclMat &dst, string args.push_back( make_pair( sizeof(cl_int), (void *)&dst.rows )); args.push_back( make_pair( sizeof(cl_int), (void *)&cols )); args.push_back( make_pair( sizeof(cl_int), (void *)&dst_step1 )); + float pf = p; if(!src1.clCxt->supportsFeature(Context::CL_DOUBLE)) { - float pf = p; args.push_back( make_pair( sizeof(cl_float), (void *)&pf )); } else diff --git a/modules/ocl/src/imgproc.cpp b/modules/ocl/src/imgproc.cpp index ee1e92a712..bbec07a5c0 100644 --- a/modules/ocl/src/imgproc.cpp +++ b/modules/ocl/src/imgproc.cpp @@ -269,7 +269,7 @@ namespace cv size_t globalThreads[3] = {glbSizeX, glbSizeY, 1}; size_t localThreads[3] = {blkSizeX, blkSizeY, 1}; - + float borderFloat[4] = {(float)borderValue[0], (float)borderValue[1], (float)borderValue[2], (float)borderValue[3]}; vector< pair > args; if(map1.channels() == 2) { @@ -289,9 +289,8 @@ namespace cv args.push_back( make_pair(sizeof(cl_int), (void *)&map1.cols)); args.push_back( make_pair(sizeof(cl_int), (void *)&map1.rows)); args.push_back( make_pair(sizeof(cl_int), (void *)&cols)); - float borderFloat[4] = {(float)borderValue[0], (float)borderValue[1], (float)borderValue[2], (float)borderValue[3]}; - - if(src.clCxt->supportsFeature(Context::CL_DOUBLE)) + + if(src.clCxt->supportsFeature(Context::CL_DOUBLE)) { args.push_back( make_pair(sizeof(cl_double4), (void *)&borderValue)); } @@ -325,7 +324,6 @@ namespace cv } else { - float borderFloat[4] = {(float)borderValue[0], (float)borderValue[1], (float)borderValue[2], (float)borderValue[3]}; args.push_back( make_pair(sizeof(cl_float4), (void *)&borderFloat)); } } From 14bd6402bebddfbea74bacdf5ac61f027abcbef3 Mon Sep 17 00:00:00 2001 From: yao Date: Tue, 28 May 2013 17:53:06 +0800 Subject: [PATCH 038/178] revise perf --- modules/ocl/perf/perf_arithm.cpp | 327 +++++++----------- modules/ocl/perf/perf_blend.cpp | 10 +- modules/ocl/perf/perf_brute_force_matcher.cpp | 21 +- modules/ocl/perf/perf_canny.cpp | 8 +- modules/ocl/perf/perf_color.cpp | 10 +- modules/ocl/perf/perf_columnsum.cpp | 17 +- modules/ocl/perf/perf_fft.cpp | 8 +- modules/ocl/perf/perf_filters.cpp | 64 ++-- modules/ocl/perf/perf_gemm.cpp | 7 +- modules/ocl/perf/perf_haar.cpp | 6 +- modules/ocl/perf/perf_hog.cpp | 6 +- modules/ocl/perf/perf_imgproc.cpp | 148 ++++---- modules/ocl/perf/perf_match_template.cpp | 20 +- modules/ocl/perf/perf_matrix_operation.cpp | 28 +- modules/ocl/perf/perf_norm.cpp | 29 +- modules/ocl/perf/perf_pyrdown.cpp | 9 +- modules/ocl/perf/perf_pyrlk.cpp | 39 ++- modules/ocl/perf/perf_pyrup.cpp | 8 +- modules/ocl/perf/perf_split_merge.cpp | 36 +- modules/ocl/perf/precomp.cpp | 31 +- modules/ocl/perf/precomp.hpp | 47 ++- 21 files changed, 410 insertions(+), 469 deletions(-) diff --git a/modules/ocl/perf/perf_arithm.cpp b/modules/ocl/perf/perf_arithm.cpp index e69fecd647..4f690e0912 100644 --- a/modules/ocl/perf/perf_arithm.cpp +++ b/modules/ocl/perf/perf_arithm.cpp @@ -48,7 +48,7 @@ ///////////// Lut //////////////////////// PERFTEST(lut) { - Mat src, lut, dst; + Mat src, lut, dst, ocl_dst; ocl::oclMat d_src, d_lut, d_dst; int all_type[] = {CV_8UC1, CV_8UC3}; @@ -77,11 +77,6 @@ PERFTEST(lut) ocl::LUT(d_src, d_lut, d_dst); WARMUP_OFF; - cv::Mat ocl_mat_dst; - d_dst.download(ocl_mat_dst); - - TestSystem::instance().setAccurate(ExpectedMatNear(ocl_mat_dst, dst, 0)); - GPU_ON; ocl::LUT(d_src, d_lut, d_dst); GPU_OFF; @@ -90,9 +85,10 @@ PERFTEST(lut) d_src.upload(src); d_lut.upload(lut); ocl::LUT(d_src, d_lut, d_dst); - d_dst.download(dst); + d_dst.download(ocl_dst); GPU_FULL_OFF; + TestSystem::instance().ExpectedMatNear(ocl_dst, dst, 0); } } @@ -101,7 +97,7 @@ PERFTEST(lut) ///////////// Exp //////////////////////// PERFTEST(Exp) { - Mat src, dst; + Mat src, dst, ocl_dst; ocl::oclMat d_src, d_dst; for (int size = Min_Size; size <= Max_Size; size *= Multiple) @@ -121,11 +117,6 @@ PERFTEST(Exp) ocl::exp(d_src, d_dst); WARMUP_OFF; - cv::Mat ocl_mat_dst; - d_dst.download(ocl_mat_dst); - - TestSystem::instance().setAccurate(ExpectedMatNear(ocl_mat_dst, dst, 2)); - GPU_ON; ocl::exp(d_src, d_dst); GPU_OFF; @@ -133,15 +124,17 @@ PERFTEST(Exp) GPU_FULL_ON; d_src.upload(src); ocl::exp(d_src, d_dst); - d_dst.download(dst); + d_dst.download(ocl_dst); GPU_FULL_OFF; + + TestSystem::instance().ExpectedMatNear(ocl_dst, dst, 2); } } ///////////// LOG //////////////////////// PERFTEST(Log) { - Mat src, dst; + Mat src, dst, ocl_dst; ocl::oclMat d_src, d_dst; for (int size = Min_Size; size <= Max_Size; size *= Multiple) @@ -161,11 +154,6 @@ PERFTEST(Log) ocl::log(d_src, d_dst); WARMUP_OFF; - cv::Mat ocl_mat_dst; - d_dst.download(ocl_mat_dst); - - TestSystem::instance().setAccurate(ExpectedMatNear(ocl_mat_dst, dst, 1)); - GPU_ON; ocl::log(d_src, d_dst); GPU_OFF; @@ -173,15 +161,17 @@ PERFTEST(Log) GPU_FULL_ON; d_src.upload(src); ocl::log(d_src, d_dst); - d_dst.download(dst); + d_dst.download(ocl_dst); GPU_FULL_OFF; + + TestSystem::instance().ExpectedMatNear(ocl_dst, dst, 1); } } ///////////// Add //////////////////////// PERFTEST(Add) { - Mat src1, src2, dst; + Mat src1, src2, dst, ocl_dst; ocl::oclMat d_src1, d_src2, d_dst; int all_type[] = {CV_8UC1, CV_32FC1}; @@ -201,6 +191,7 @@ PERFTEST(Add) CPU_ON; add(src1, src2, dst); CPU_OFF; + d_src1.upload(src1); d_src2.upload(src2); @@ -208,11 +199,6 @@ PERFTEST(Add) ocl::add(d_src1, d_src2, d_dst); WARMUP_OFF; - cv::Mat ocl_mat_dst; - d_dst.download(ocl_mat_dst); - - TestSystem::instance().setAccurate(ExpectedMatNear(ocl_mat_dst, dst, 0.0)); - GPU_ON; ocl::add(d_src1, d_src2, d_dst); GPU_OFF; @@ -221,8 +207,10 @@ PERFTEST(Add) d_src1.upload(src1); d_src2.upload(src2); ocl::add(d_src1, d_src2, d_dst); - d_dst.download(dst); + d_dst.download(ocl_dst); GPU_FULL_OFF; + + TestSystem::instance().ExpectedMatNear(ocl_dst, dst, 0.0); } } @@ -231,7 +219,7 @@ PERFTEST(Add) ///////////// Mul //////////////////////// PERFTEST(Mul) { - Mat src1, src2, dst; + Mat src1, src2, dst, ocl_dst; ocl::oclMat d_src1, d_src2, d_dst; int all_type[] = {CV_8UC1, CV_8UC4}; @@ -260,11 +248,6 @@ PERFTEST(Mul) ocl::multiply(d_src1, d_src2, d_dst); WARMUP_OFF; - cv::Mat ocl_mat_dst; - d_dst.download(ocl_mat_dst); - - TestSystem::instance().setAccurate(ExpectedMatNear(ocl_mat_dst, dst, 0.0)); - GPU_ON; ocl::multiply(d_src1, d_src2, d_dst); GPU_OFF; @@ -273,8 +256,10 @@ PERFTEST(Mul) d_src1.upload(src1); d_src2.upload(src2); ocl::multiply(d_src1, d_src2, d_dst); - d_dst.download(dst); + d_dst.download(ocl_dst); GPU_FULL_OFF; + + TestSystem::instance().ExpectedMatNear(ocl_dst, dst, 0.0); } } @@ -283,7 +268,7 @@ PERFTEST(Mul) ///////////// Div //////////////////////// PERFTEST(Div) { - Mat src1, src2, dst; + Mat src1, src2, dst, ocl_dst; ocl::oclMat d_src1, d_src2, d_dst; int all_type[] = {CV_8UC1, CV_8UC4}; std::string type_name[] = {"CV_8UC1", "CV_8UC4"}; @@ -304,6 +289,7 @@ PERFTEST(Div) CPU_ON; divide(src1, src2, dst); CPU_OFF; + d_src1.upload(src1); d_src2.upload(src2); @@ -311,11 +297,6 @@ PERFTEST(Div) ocl::divide(d_src1, d_src2, d_dst); WARMUP_OFF; - cv::Mat ocl_mat_dst; - d_dst.download(ocl_mat_dst); - - TestSystem::instance().setAccurate(ExpectedMatNear(ocl_mat_dst, dst, 1)); - GPU_ON; ocl::divide(d_src1, d_src2, d_dst); GPU_OFF; @@ -324,8 +305,10 @@ PERFTEST(Div) d_src1.upload(src1); d_src2.upload(src2); ocl::divide(d_src1, d_src2, d_dst); - d_dst.download(dst); + d_dst.download(ocl_dst); GPU_FULL_OFF; + + TestSystem::instance().ExpectedMatNear(ocl_dst, dst, 1); } } @@ -334,7 +317,7 @@ PERFTEST(Div) ///////////// Absdiff //////////////////////// PERFTEST(Absdiff) { - Mat src1, src2, dst; + Mat src1, src2, dst, ocl_dst; ocl::oclMat d_src1, d_src2, d_dst; int all_type[] = {CV_8UC1, CV_8UC4}; @@ -355,6 +338,7 @@ PERFTEST(Absdiff) CPU_ON; absdiff(src1, src2, dst); CPU_OFF; + d_src1.upload(src1); d_src2.upload(src2); @@ -362,11 +346,6 @@ PERFTEST(Absdiff) ocl::absdiff(d_src1, d_src2, d_dst); WARMUP_OFF; - cv::Mat ocl_mat_dst; - d_dst.download(ocl_mat_dst); - - TestSystem::instance().setAccurate(ExpectedMatNear(ocl_mat_dst, dst, 0.0)); - GPU_ON; ocl::absdiff(d_src1, d_src2, d_dst); GPU_OFF; @@ -375,8 +354,10 @@ PERFTEST(Absdiff) d_src1.upload(src1); d_src2.upload(src2); ocl::absdiff(d_src1, d_src2, d_dst); - d_dst.download(dst); + d_dst.download(ocl_dst); GPU_FULL_OFF; + + TestSystem::instance().ExpectedMatNear(ocl_dst, dst, 0.0); } } @@ -385,7 +366,7 @@ PERFTEST(Absdiff) ///////////// CartToPolar //////////////////////// PERFTEST(CartToPolar) { - Mat src1, src2, dst, dst1; + Mat src1, src2, dst, dst1, ocl_dst, ocl_dst1; ocl::oclMat d_src1, d_src2, d_dst, d_dst1; int all_type[] = {CV_32FC1}; @@ -408,6 +389,7 @@ PERFTEST(CartToPolar) CPU_ON; cartToPolar(src1, src2, dst, dst1, 1); CPU_OFF; + d_src1.upload(src1); d_src2.upload(src2); @@ -415,14 +397,6 @@ PERFTEST(CartToPolar) ocl::cartToPolar(d_src1, d_src2, d_dst, d_dst1, 1); WARMUP_OFF; - cv::Mat ocl_mat_dst; - d_dst.download(ocl_mat_dst); - - cv::Mat ocl_mat_dst1; - d_dst1.download(ocl_mat_dst1); - - TestSystem::instance().setAccurate(ExpectedMatNear(ocl_mat_dst1, dst1, 0.5)&&ExpectedMatNear(ocl_mat_dst, dst, 0.5)); - GPU_ON; ocl::cartToPolar(d_src1, d_src2, d_dst, d_dst1, 1); GPU_OFF; @@ -431,9 +405,15 @@ PERFTEST(CartToPolar) d_src1.upload(src1); d_src2.upload(src2); ocl::cartToPolar(d_src1, d_src2, d_dst, d_dst1, 1); - d_dst.download(dst); - d_dst1.download(dst1); + d_dst.download(ocl_dst); + d_dst1.download(ocl_dst1); GPU_FULL_OFF; + + double diff1 = checkNorm(ocl_dst1, dst1); + double diff2 = checkNorm(ocl_dst, dst); + double max_diff = max(diff1, diff2); + TestSystem::instance().setAccurate(max_diff<=.5?1:0, max_diff); + } } @@ -442,7 +422,7 @@ PERFTEST(CartToPolar) ///////////// PolarToCart //////////////////////// PERFTEST(PolarToCart) { - Mat src1, src2, dst, dst1; + Mat src1, src2, dst, dst1, ocl_dst, ocl_dst1; ocl::oclMat d_src1, d_src2, d_dst, d_dst1; int all_type[] = {CV_32FC1}; @@ -472,14 +452,6 @@ PERFTEST(PolarToCart) ocl::polarToCart(d_src1, d_src2, d_dst, d_dst1, 1); WARMUP_OFF; - cv::Mat ocl_mat_dst; - d_dst.download(ocl_mat_dst); - - cv::Mat ocl_mat_dst1; - d_dst1.download(ocl_mat_dst1); - - TestSystem::instance().setAccurate(ExpectedMatNear(ocl_mat_dst1, dst1, 0.5)&&ExpectedMatNear(ocl_mat_dst, dst, 0.5)); - GPU_ON; ocl::polarToCart(d_src1, d_src2, d_dst, d_dst1, 1); GPU_OFF; @@ -488,9 +460,15 @@ PERFTEST(PolarToCart) d_src1.upload(src1); d_src2.upload(src2); ocl::polarToCart(d_src1, d_src2, d_dst, d_dst1, 1); - d_dst.download(dst); - d_dst1.download(dst1); + d_dst.download(ocl_dst); + d_dst1.download(ocl_dst1); GPU_FULL_OFF; + + double diff1 = checkNorm(ocl_dst1, dst1); + double diff2 = checkNorm(ocl_dst, dst); + double max_diff = max(diff1, diff2); + TestSystem::instance().setAccurate(max_diff<=.5?1:0, max_diff); + } } @@ -499,7 +477,7 @@ PERFTEST(PolarToCart) ///////////// Magnitude //////////////////////// PERFTEST(magnitude) { - Mat x, y, mag; + Mat x, y, mag, ocl_mag; ocl::oclMat d_x, d_y, d_mag; int all_type[] = {CV_32FC1}; @@ -526,11 +504,6 @@ PERFTEST(magnitude) ocl::magnitude(d_x, d_y, d_mag); WARMUP_OFF; - cv::Mat ocl_mat_dst; - d_mag.download(ocl_mat_dst); - - TestSystem::instance().setAccurate(ExpectedMatNear(ocl_mat_dst, mag, 1e-5)); - GPU_ON; ocl::magnitude(d_x, d_y, d_mag); GPU_OFF; @@ -539,8 +512,10 @@ PERFTEST(magnitude) d_x.upload(x); d_y.upload(y); ocl::magnitude(d_x, d_y, d_mag); - d_mag.download(mag); + d_mag.download(ocl_mag); GPU_FULL_OFF; + + TestSystem::instance().ExpectedMatNear(ocl_mag, mag, 1e-5); } } @@ -549,7 +524,7 @@ PERFTEST(magnitude) ///////////// Transpose //////////////////////// PERFTEST(Transpose) { - Mat src, dst; + Mat src, dst, ocl_dst; ocl::oclMat d_src, d_dst; int all_type[] = {CV_8UC1, CV_8UC4}; @@ -575,11 +550,6 @@ PERFTEST(Transpose) ocl::transpose(d_src, d_dst); WARMUP_OFF; - cv::Mat ocl_mat_dst; - d_dst.download(ocl_mat_dst); - - TestSystem::instance().setAccurate(ExpectedMatNear(ocl_mat_dst, dst, 1e-5)); - GPU_ON; ocl::transpose(d_src, d_dst); GPU_OFF; @@ -587,8 +557,10 @@ PERFTEST(Transpose) GPU_FULL_ON; d_src.upload(src); ocl::transpose(d_src, d_dst); - d_dst.download(dst); + d_dst.download(ocl_dst); GPU_FULL_OFF; + + TestSystem::instance().ExpectedMatNear(ocl_dst, dst, 1e-5); } } @@ -597,7 +569,7 @@ PERFTEST(Transpose) ///////////// Flip //////////////////////// PERFTEST(Flip) { - Mat src, dst; + Mat src, dst, ocl_dst; ocl::oclMat d_src, d_dst; int all_type[] = {CV_8UC1, CV_8UC4}; @@ -623,11 +595,6 @@ PERFTEST(Flip) ocl::flip(d_src, d_dst, 0); WARMUP_OFF; - cv::Mat ocl_mat_dst; - d_dst.download(ocl_mat_dst); - - TestSystem::instance().setAccurate(ExpectedMatNear(ocl_mat_dst, dst, 1e-5)); - GPU_ON; ocl::flip(d_src, d_dst, 0); GPU_OFF; @@ -635,8 +602,10 @@ PERFTEST(Flip) GPU_FULL_ON; d_src.upload(src); ocl::flip(d_src, d_dst, 0); - d_dst.download(dst); + d_dst.download(ocl_dst); GPU_FULL_OFF; + + TestSystem::instance().ExpectedMatNear(ocl_dst, dst, 1e-5); } } @@ -671,7 +640,10 @@ PERFTEST(minMax) ocl::minMax(d_src, &min_val_, &max_val_); WARMUP_OFF; - TestSystem::instance().setAccurate(EeceptDoubleEQ(max_val_, max_val)&&EeceptDoubleEQ(min_val_, min_val)); + if(EeceptDoubleEQ(max_val_, max_val) && EeceptDoubleEQ(min_val_, min_val)) + TestSystem::instance().setAccurate(1, max(fabs(max_val_-max_val), fabs(min_val_-min_val))); + else + TestSystem::instance().setAccurate(0, max(fabs(max_val_-max_val), fabs(min_val_-min_val))); GPU_ON; ocl::minMax(d_src, &min_val, &max_val); @@ -724,8 +696,6 @@ PERFTEST(minMaxLoc) minlocVal_ = src.at(min_loc_); maxlocVal = src.at(max_loc); maxlocVal_ = src.at(max_loc_); - error0 = ::abs(src.at(min_loc_) - src.at(min_loc)); - error1 = ::abs(src.at(max_loc_) - src.at(max_loc)); } if(src.depth() == 1) { @@ -733,8 +703,6 @@ PERFTEST(minMaxLoc) minlocVal_ = src.at(min_loc_); maxlocVal = src.at(max_loc); maxlocVal_ = src.at(max_loc_); - error0 = ::abs(src.at(min_loc_) - src.at(min_loc)); - error1 = ::abs(src.at(max_loc_) - src.at(max_loc)); } if(src.depth() == 2) { @@ -742,8 +710,6 @@ PERFTEST(minMaxLoc) minlocVal_ = src.at(min_loc_); maxlocVal = src.at(max_loc); maxlocVal_ = src.at(max_loc_); - error0 = ::abs(src.at(min_loc_) - src.at(min_loc)); - error1 = ::abs(src.at(max_loc_) - src.at(max_loc)); } if(src.depth() == 3) { @@ -751,8 +717,6 @@ PERFTEST(minMaxLoc) minlocVal_ = src.at(min_loc_); maxlocVal = src.at(max_loc); maxlocVal_ = src.at(max_loc_); - error0 = ::abs(src.at(min_loc_) - src.at(min_loc)); - error1 = ::abs(src.at(max_loc_) - src.at(max_loc)); } if(src.depth() == 4) { @@ -760,8 +724,6 @@ PERFTEST(minMaxLoc) minlocVal_ = src.at(min_loc_); maxlocVal = src.at(max_loc); maxlocVal_ = src.at(max_loc_); - error0 = ::abs(src.at(min_loc_) - src.at(min_loc)); - error1 = ::abs(src.at(max_loc_) - src.at(max_loc)); } if(src.depth() == 5) { @@ -769,8 +731,6 @@ PERFTEST(minMaxLoc) minlocVal_ = src.at(min_loc_); maxlocVal = src.at(max_loc); maxlocVal_ = src.at(max_loc_); - error0 = ::abs(src.at(min_loc_) - src.at(min_loc)); - error1 = ::abs(src.at(max_loc_) - src.at(max_loc)); } if(src.depth() == 6) { @@ -778,16 +738,16 @@ PERFTEST(minMaxLoc) minlocVal_ = src.at(min_loc_); maxlocVal = src.at(max_loc); maxlocVal_ = src.at(max_loc_); - error0 = ::abs(src.at(min_loc_) - src.at(min_loc)); - error1 = ::abs(src.at(max_loc_) - src.at(max_loc)); } - - TestSystem::instance().setAccurate(EeceptDoubleEQ(error1, 0.0) - &&EeceptDoubleEQ(error0, 0.0) - &&EeceptDoubleEQ(maxlocVal_, maxlocVal) + error0 = ::abs(minlocVal_ - minlocVal); + error1 = ::abs(maxlocVal_ - maxlocVal); + if( EeceptDoubleEQ(maxlocVal_, maxlocVal) &&EeceptDoubleEQ(minlocVal_, minlocVal) &&EeceptDoubleEQ(max_val_, max_val) - &&EeceptDoubleEQ(min_val_, min_val)); + &&EeceptDoubleEQ(min_val_, min_val)) + TestSystem::instance().setAccurate(1, 0.); + else + TestSystem::instance().setAccurate(0, max(error0, error1)); GPU_ON; ocl::minMaxLoc(d_src, &min_val, &max_val, &min_loc, &max_loc); @@ -831,11 +791,13 @@ PERFTEST(Sum) gpures = ocl::sum(d_src); WARMUP_OFF; - TestSystem::instance().setAccurate(ExceptDoubleNear(cpures[3], gpures[3], 0.1) - &&ExceptDoubleNear(cpures[2], gpures[2], 0.1) - &&ExceptDoubleNear(cpures[1], gpures[1], 0.1) - &&ExceptDoubleNear(cpures[0], gpures[0], 0.1)); - + vector diffs(4); + diffs[3] = fabs(cpures[3] - gpures[3]); + diffs[2] = fabs(cpures[2] - gpures[2]); + diffs[1] = fabs(cpures[1] - gpures[1]); + diffs[0] = fabs(cpures[0] - gpures[0]); + double max_diff = *max_element(diffs.begin(), diffs.end()); + TestSystem::instance().setAccurate(max_diff<0.1?1:0, max_diff); GPU_ON; gpures = ocl::sum(d_src); @@ -879,7 +841,11 @@ PERFTEST(countNonZero) gpures = ocl::countNonZero(d_src); WARMUP_OFF; - TestSystem::instance().setAccurate((EeceptDoubleEQ((double)cpures, (double)gpures))); + int diff = abs(cpures - gpures); + if(diff == 0) + TestSystem::instance().setAccurate(1, 0); + else + TestSystem::instance().setAccurate(0, diff); GPU_ON; ocl::countNonZero(d_src); @@ -897,7 +863,7 @@ PERFTEST(countNonZero) ///////////// Phase //////////////////////// PERFTEST(Phase) { - Mat src1, src2, dst; + Mat src1, src2, dst, ocl_dst; ocl::oclMat d_src1, d_src2, d_dst; int all_type[] = {CV_32FC1}; @@ -913,12 +879,12 @@ PERFTEST(Phase) gen(src2, size, size, all_type[j], 0, 256); gen(dst, size, size, all_type[j], 0, 256); - phase(src1, src2, dst, 1); CPU_ON; phase(src1, src2, dst, 1); CPU_OFF; + d_src1.upload(src1); d_src2.upload(src2); @@ -926,11 +892,6 @@ PERFTEST(Phase) ocl::phase(d_src1, d_src2, d_dst, 1); WARMUP_OFF; - cv::Mat ocl_mat_dst; - d_dst.download(ocl_mat_dst); - - TestSystem::instance().setAccurate(ExpectedMatNear(ocl_mat_dst, dst, 1e-2)); - GPU_ON; ocl::phase(d_src1, d_src2, d_dst, 1); GPU_OFF; @@ -939,8 +900,10 @@ PERFTEST(Phase) d_src1.upload(src1); d_src2.upload(src2); ocl::phase(d_src1, d_src2, d_dst, 1); - d_dst.download(dst); + d_dst.download(ocl_dst); GPU_FULL_OFF; + + TestSystem::instance().ExpectedMatNear(ocl_dst, dst, 1e-2); } } @@ -949,7 +912,7 @@ PERFTEST(Phase) ///////////// bitwise_and//////////////////////// PERFTEST(bitwise_and) { - Mat src1, src2, dst; + Mat src1, src2, dst, ocl_dst; ocl::oclMat d_src1, d_src2, d_dst; int all_type[] = {CV_8UC1, CV_32SC1}; @@ -965,7 +928,6 @@ PERFTEST(bitwise_and) gen(src2, size, size, all_type[j], 0, 256); gen(dst, size, size, all_type[j], 0, 256); - bitwise_and(src1, src2, dst); CPU_ON; @@ -978,11 +940,6 @@ PERFTEST(bitwise_and) ocl::bitwise_and(d_src1, d_src2, d_dst); WARMUP_OFF; - cv::Mat ocl_mat_dst; - d_dst.download(ocl_mat_dst); - - TestSystem::instance().setAccurate(ExpectedMatNear(ocl_mat_dst, dst, 0.0)); - GPU_ON; ocl::bitwise_and(d_src1, d_src2, d_dst); GPU_OFF; @@ -991,8 +948,10 @@ PERFTEST(bitwise_and) d_src1.upload(src1); d_src2.upload(src2); ocl::bitwise_and(d_src1, d_src2, d_dst); - d_dst.download(dst); + d_dst.download(ocl_dst); GPU_FULL_OFF; + + TestSystem::instance().ExpectedMatNear(ocl_dst, dst, 0.0); } } @@ -1001,7 +960,7 @@ PERFTEST(bitwise_and) ///////////// bitwise_not//////////////////////// PERFTEST(bitwise_not) { - Mat src1, dst; + Mat src1, dst, ocl_dst; ocl::oclMat d_src1, d_dst; int all_type[] = {CV_8UC1, CV_32SC1}; @@ -1016,7 +975,6 @@ PERFTEST(bitwise_not) gen(src1, size, size, all_type[j], 0, 256); gen(dst, size, size, all_type[j], 0, 256); - bitwise_not(src1, dst); CPU_ON; @@ -1028,11 +986,6 @@ PERFTEST(bitwise_not) ocl::bitwise_not(d_src1, d_dst); WARMUP_OFF; - cv::Mat ocl_mat_dst; - d_dst.download(ocl_mat_dst); - - TestSystem::instance().setAccurate(ExpectedMatNear(ocl_mat_dst, dst, 0.0)); - GPU_ON; ocl::bitwise_not(d_src1, d_dst); GPU_OFF; @@ -1040,8 +993,10 @@ PERFTEST(bitwise_not) GPU_FULL_ON; d_src1.upload(src1); ocl::bitwise_not(d_src1, d_dst); - d_dst.download(dst); + d_dst.download(ocl_dst); GPU_FULL_OFF; + + TestSystem::instance().ExpectedMatNear(ocl_dst, dst, 0.0); } } @@ -1050,7 +1005,7 @@ PERFTEST(bitwise_not) ///////////// compare//////////////////////// PERFTEST(compare) { - Mat src1, src2, dst; + Mat src1, src2, dst, ocl_dst; ocl::oclMat d_src1, d_src2, d_dst; int CMP_EQ = 0; @@ -1067,12 +1022,12 @@ PERFTEST(compare) gen(src2, size, size, all_type[j], 0, 256); gen(dst, size, size, all_type[j], 0, 256); - compare(src1, src2, dst, CMP_EQ); CPU_ON; compare(src1, src2, dst, CMP_EQ); CPU_OFF; + d_src1.upload(src1); d_src2.upload(src2); @@ -1080,11 +1035,6 @@ PERFTEST(compare) ocl::compare(d_src1, d_src2, d_dst, CMP_EQ); WARMUP_OFF; - cv::Mat ocl_mat_dst; - d_dst.download(ocl_mat_dst); - - TestSystem::instance().setAccurate(ExpectedMatNear(ocl_mat_dst, dst, 0.0)); - GPU_ON; ocl::compare(d_src1, d_src2, d_dst, CMP_EQ); GPU_OFF; @@ -1093,8 +1043,10 @@ PERFTEST(compare) d_src1.upload(src1); d_src2.upload(src2); ocl::compare(d_src1, d_src2, d_dst, CMP_EQ); - d_dst.download(dst); + d_dst.download(ocl_dst); GPU_FULL_OFF; + + TestSystem::instance().ExpectedMatNear(ocl_dst, dst, 0.0); } } @@ -1103,7 +1055,7 @@ PERFTEST(compare) ///////////// pow //////////////////////// PERFTEST(pow) { - Mat src, dst; + Mat src, dst, ocl_dst; ocl::oclMat d_src, d_dst; int all_type[] = {CV_32FC1}; @@ -1129,11 +1081,6 @@ PERFTEST(pow) ocl::pow(d_src, -2.0, d_dst); WARMUP_OFF; - cv::Mat ocl_mat_dst; - d_dst.download(ocl_mat_dst); - - TestSystem::instance().setAccurate(ExpectedMatNear(ocl_mat_dst, dst, 1.0)); - GPU_ON; ocl::pow(d_src, -2.0, d_dst); GPU_OFF; @@ -1141,8 +1088,10 @@ PERFTEST(pow) GPU_FULL_ON; d_src.upload(src); ocl::pow(d_src, -2.0, d_dst); - d_dst.download(dst); + d_dst.download(ocl_dst); GPU_FULL_OFF; + + TestSystem::instance().ExpectedMatNear(ocl_dst, dst, 1.0); } } @@ -1151,7 +1100,7 @@ PERFTEST(pow) ///////////// MagnitudeSqr//////////////////////// PERFTEST(MagnitudeSqr) { - Mat src1, src2, dst; + Mat src1, src2, dst, ocl_dst; ocl::oclMat d_src1, d_src2, d_dst; int all_type[] = {CV_32FC1}; @@ -1167,53 +1116,36 @@ PERFTEST(MagnitudeSqr) gen(src2, size, size, all_type[t], 0, 256); gen(dst, size, size, all_type[t], 0, 256); - + CPU_ON; for (int i = 0; i < src1.rows; ++i) - for (int j = 0; j < src1.cols; ++j) { float val1 = src1.at(i, j); float val2 = src2.at(i, j); - ((float *)(dst.data))[i * dst.step / 4 + j] = val1 * val1 + val2 * val2; } + CPU_OFF; - CPU_ON; + d_src1.upload(src1); + d_src2.upload(src2); - for (int i = 0; i < src1.rows; ++i) - for (int j = 0; j < src1.cols; ++j) - { - float val1 = src1.at(i, j); - float val2 = src2.at(i, j); + WARMUP_ON; + ocl::magnitudeSqr(d_src1, d_src2, d_dst); + WARMUP_OFF; - ((float *)(dst.data))[i * dst.step / 4 + j] = val1 * val1 + val2 * val2; + GPU_ON; + ocl::magnitudeSqr(d_src1, d_src2, d_dst); + GPU_OFF; - } + GPU_FULL_ON; + d_src1.upload(src1); + d_src2.upload(src2); + ocl::magnitudeSqr(d_src1, d_src2, d_dst); + d_dst.download(ocl_dst); + GPU_FULL_OFF; - CPU_OFF; - d_src1.upload(src1); - d_src2.upload(src2); - - WARMUP_ON; - ocl::magnitudeSqr(d_src1, d_src2, d_dst); - WARMUP_OFF; - - cv::Mat ocl_mat_dst; - d_dst.download(ocl_mat_dst); - - TestSystem::instance().setAccurate(ExpectedMatNear(ocl_mat_dst, dst, 1.0)); - - GPU_ON; - ocl::magnitudeSqr(d_src1, d_src2, d_dst); - GPU_OFF; - - GPU_FULL_ON; - d_src1.upload(src1); - d_src2.upload(src2); - ocl::magnitudeSqr(d_src1, d_src2, d_dst); - d_dst.download(dst); - GPU_FULL_OFF; + TestSystem::instance().ExpectedMatNear(ocl_dst, dst, 1.0); } } @@ -1222,7 +1154,7 @@ PERFTEST(MagnitudeSqr) ///////////// AddWeighted//////////////////////// PERFTEST(AddWeighted) { - Mat src1, src2, dst; + Mat src1, src2, dst, ocl_dst; ocl::oclMat d_src1, d_src2, d_dst; double alpha = 2.0, beta = 1.0, gama = 3.0; @@ -1252,11 +1184,6 @@ PERFTEST(AddWeighted) ocl::addWeighted(d_src1, alpha, d_src2, beta, gama, d_dst); WARMUP_OFF; - cv::Mat ocl_mat_dst; - d_dst.download(ocl_mat_dst); - - TestSystem::instance().setAccurate(ExpectedMatNear(ocl_mat_dst, dst, 1e-5)); - GPU_ON; ocl::addWeighted(d_src1, alpha, d_src2, beta, gama, d_dst); GPU_OFF; @@ -1265,8 +1192,10 @@ PERFTEST(AddWeighted) d_src1.upload(src1); d_src2.upload(src2); ocl::addWeighted(d_src1, alpha, d_src2, beta, gama, d_dst); - d_dst.download(dst); + d_dst.download(ocl_dst); GPU_FULL_OFF; + + TestSystem::instance().ExpectedMatNear(ocl_dst, dst, 1e-5); } } diff --git a/modules/ocl/perf/perf_blend.cpp b/modules/ocl/perf/perf_blend.cpp index 6dda464bd7..8ebb6482ba 100644 --- a/modules/ocl/perf/perf_blend.cpp +++ b/modules/ocl/perf/perf_blend.cpp @@ -71,7 +71,7 @@ void blendLinearGold(const cv::Mat &img1, const cv::Mat &img2, const cv::Mat &we } PERFTEST(blend) { - Mat src1, src2, weights1, weights2, dst; + Mat src1, src2, weights1, weights2, dst, ocl_dst; ocl::oclMat d_src1, d_src2, d_weights1, d_weights2, d_dst; int all_type[] = {CV_8UC1, CV_8UC4}; @@ -103,10 +103,6 @@ PERFTEST(blend) ocl::blendLinear(d_src1, d_src2, d_weights1, d_weights2, d_dst); WARMUP_OFF; - cv::Mat ocl_mat; - d_dst.download(ocl_mat); - TestSystem::instance().setAccurate(ExpectedMatNear(dst, ocl_mat, 1.f)); - GPU_ON; ocl::blendLinear(d_src1, d_src2, d_weights1, d_weights2, d_dst); GPU_OFF; @@ -117,8 +113,10 @@ PERFTEST(blend) d_weights1.upload(weights1); d_weights2.upload(weights2); ocl::blendLinear(d_src1, d_src2, d_weights1, d_weights2, d_dst); - d_dst.download(dst); + d_dst.download(ocl_dst); GPU_FULL_OFF; + + TestSystem::instance().ExpectedMatNear(dst, ocl_dst, 1.f); } } } \ No newline at end of file diff --git a/modules/ocl/perf/perf_brute_force_matcher.cpp b/modules/ocl/perf/perf_brute_force_matcher.cpp index ba87bd8924..9b2ce89a11 100644 --- a/modules/ocl/perf/perf_brute_force_matcher.cpp +++ b/modules/ocl/perf/perf_brute_force_matcher.cpp @@ -88,9 +88,6 @@ PERFTEST(BruteForceMatcher) d_matcher.matchSingle(d_query, d_train, d_trainIdx, d_distance); WARMUP_OFF; - d_matcher.match(d_query, d_train, d_matches[0]); - TestSystem::instance().setAccurate(AssertEQ(d_matches[0].size(), matches[0].size())); - GPU_ON; d_matcher.matchSingle(d_query, d_train, d_trainIdx, d_distance); GPU_OFF; @@ -101,6 +98,12 @@ PERFTEST(BruteForceMatcher) d_matcher.match(d_query, d_train, matches[0]); GPU_FULL_OFF; + int diff = abs((int)d_matches[0].size() - (int)matches[0].size()); + if(diff == 0) + TestSystem::instance().setAccurate(1, 0); + else + TestSystem::instance().setAccurate(0, diff); + SUBTEST << size << "; knnMatch"; matcher.knnMatch(query, train, matches, 2); @@ -123,7 +126,11 @@ PERFTEST(BruteForceMatcher) d_matcher.knnMatch(d_query, d_train, d_matches, 2); GPU_FULL_OFF; - TestSystem::instance().setAccurate(AssertEQ(d_matches[0].size(), matches[0].size())); + diff = abs((int)d_matches[0].size() - (int)matches[0].size()); + if(diff == 0) + TestSystem::instance().setAccurate(1, 0); + else + TestSystem::instance().setAccurate(0, diff); SUBTEST << size << "; radiusMatch"; @@ -151,6 +158,10 @@ PERFTEST(BruteForceMatcher) d_matcher.radiusMatch(d_query, d_train, d_matches, max_distance); GPU_FULL_OFF; - TestSystem::instance().setAccurate(AssertEQ(d_matches[0].size(), matches[0].size())); + diff = abs((int)d_matches[0].size() - (int)matches[0].size()); + if(diff == 0) + TestSystem::instance().setAccurate(1, 0); + else + TestSystem::instance().setAccurate(0, diff); } } \ No newline at end of file diff --git a/modules/ocl/perf/perf_canny.cpp b/modules/ocl/perf/perf_canny.cpp index 2acb2f611c..cb23d7ad28 100644 --- a/modules/ocl/perf/perf_canny.cpp +++ b/modules/ocl/perf/perf_canny.cpp @@ -57,7 +57,7 @@ PERFTEST(Canny) SUBTEST << img.cols << 'x' << img.rows << "; aloeL.jpg" << "; edges" << "; CV_8UC1"; - Mat edges(img.size(), CV_8UC1); + Mat edges(img.size(), CV_8UC1), ocl_edges; CPU_ON; Canny(img, edges, 50.0, 100.0); @@ -71,8 +71,6 @@ PERFTEST(Canny) ocl::Canny(d_img, d_buf, d_edges, 50.0, 100.0); WARMUP_OFF; - TestSystem::instance().setAccurate(ExceptedMatSimilar(edges, d_edges, 2e-2)); - GPU_ON; ocl::Canny(d_img, d_buf, d_edges, 50.0, 100.0); GPU_OFF; @@ -80,6 +78,8 @@ PERFTEST(Canny) GPU_FULL_ON; d_img.upload(img); ocl::Canny(d_img, d_buf, d_edges, 50.0, 100.0); - d_edges.download(edges); + d_edges.download(ocl_edges); GPU_FULL_OFF; + + TestSystem::instance().ExceptedMatSimilar(edges, ocl_edges, 2e-2); } \ No newline at end of file diff --git a/modules/ocl/perf/perf_color.cpp b/modules/ocl/perf/perf_color.cpp index 3ebd32ee4a..daf1cfdc9c 100644 --- a/modules/ocl/perf/perf_color.cpp +++ b/modules/ocl/perf/perf_color.cpp @@ -48,7 +48,7 @@ ///////////// cvtColor//////////////////////// PERFTEST(cvtColor) { - Mat src, dst; + Mat src, dst, ocl_dst; ocl::oclMat d_src, d_dst; int all_type[] = {CV_8UC4}; @@ -73,10 +73,6 @@ PERFTEST(cvtColor) ocl::cvtColor(d_src, d_dst, CV_RGBA2GRAY, 4); WARMUP_OFF; - cv::Mat ocl_mat; - d_dst.download(ocl_mat); - TestSystem::instance().setAccurate(ExceptedMatSimilar(dst, ocl_mat, 1e-5)); - GPU_ON; ocl::cvtColor(d_src, d_dst, CV_RGBA2GRAY, 4); GPU_OFF; @@ -84,8 +80,10 @@ PERFTEST(cvtColor) GPU_FULL_ON; d_src.upload(src); ocl::cvtColor(d_src, d_dst, CV_RGBA2GRAY, 4); - d_dst.download(dst); + d_dst.download(ocl_dst); GPU_FULL_OFF; + + TestSystem::instance().ExceptedMatSimilar(dst, ocl_dst, 1e-5); } diff --git a/modules/ocl/perf/perf_columnsum.cpp b/modules/ocl/perf/perf_columnsum.cpp index a07af17793..ff7ebcd1de 100644 --- a/modules/ocl/perf/perf_columnsum.cpp +++ b/modules/ocl/perf/perf_columnsum.cpp @@ -48,7 +48,7 @@ ///////////// columnSum//////////////////////// PERFTEST(columnSum) { - Mat src, dst; + Mat src, dst, ocl_dst; ocl::oclMat d_src, d_dst; for (int size = Min_Size; size <= Max_Size; size *= Multiple) @@ -63,23 +63,16 @@ PERFTEST(columnSum) dst.at(0, j) = src.at(0, j); for (int i = 1; i < src.rows; ++i) - {for (int j = 0; j < src.cols; ++j) - { + for (int j = 0; j < src.cols; ++j) dst.at(i, j) = dst.at(i - 1 , j) + src.at(i , j); - } - } - CPU_OFF; d_src.upload(src); + WARMUP_ON; ocl::columnSum(d_src, d_dst); WARMUP_OFF; - cv::Mat ocl_mat; - d_dst.download(ocl_mat); - TestSystem::instance().setAccurate(ExpectedMatNear(dst, ocl_mat, 5e-1)); - GPU_ON; ocl::columnSum(d_src, d_dst); GPU_OFF; @@ -87,7 +80,9 @@ PERFTEST(columnSum) GPU_FULL_ON; d_src.upload(src); ocl::columnSum(d_src, d_dst); - d_dst.download(dst); + d_dst.download(ocl_dst); GPU_FULL_OFF; + + TestSystem::instance().ExpectedMatNear(dst, ocl_dst, 5e-1); } } \ No newline at end of file diff --git a/modules/ocl/perf/perf_fft.cpp b/modules/ocl/perf/perf_fft.cpp index 49c88821dd..6e0be3f19d 100644 --- a/modules/ocl/perf/perf_fft.cpp +++ b/modules/ocl/perf/perf_fft.cpp @@ -48,7 +48,7 @@ ///////////// dft //////////////////////// PERFTEST(dft) { - Mat src, dst; + Mat src, dst, ocl_dst; ocl::oclMat d_src, d_dst; int all_type[] = {CV_32FC2}; @@ -74,8 +74,6 @@ PERFTEST(dft) ocl::dft(d_src, d_dst, Size(size, size)); WARMUP_OFF; - TestSystem::instance().setAccurate(ExpectedMatNear(dst, cv::Mat(d_dst), src.size().area() * 1e-4)); - GPU_ON; ocl::dft(d_src, d_dst, Size(size, size)); GPU_OFF; @@ -83,8 +81,10 @@ PERFTEST(dft) GPU_FULL_ON; d_src.upload(src); ocl::dft(d_src, d_dst, Size(size, size)); - d_dst.download(dst); + d_dst.download(ocl_dst); GPU_FULL_OFF; + + TestSystem::instance().ExpectedMatNear(dst, ocl_dst, src.size().area() * 1e-4); } } diff --git a/modules/ocl/perf/perf_filters.cpp b/modules/ocl/perf/perf_filters.cpp index c1cf19eefc..c8c840d0e8 100644 --- a/modules/ocl/perf/perf_filters.cpp +++ b/modules/ocl/perf/perf_filters.cpp @@ -48,7 +48,7 @@ ///////////// Blur//////////////////////// PERFTEST(Blur) { - Mat src1, dst; + Mat src1, dst, ocl_dst; ocl::oclMat d_src1, d_dst; Size ksize = Size(3, 3); @@ -65,7 +65,6 @@ PERFTEST(Blur) gen(src1, size, size, all_type[j], 0, 256); gen(dst, size, size, all_type[j], 0, 256); - blur(src1, dst, ksize, Point(-1, -1), bordertype); CPU_ON; @@ -78,8 +77,6 @@ PERFTEST(Blur) ocl::blur(d_src1, d_dst, ksize, Point(-1, -1), bordertype); WARMUP_OFF; - TestSystem::instance().setAccurate(ExpectedMatNear(cv::Mat(d_dst), dst, 1.0)); - GPU_ON; ocl::blur(d_src1, d_dst, ksize, Point(-1, -1), bordertype); GPU_OFF; @@ -87,8 +84,10 @@ PERFTEST(Blur) GPU_FULL_ON; d_src1.upload(src1); ocl::blur(d_src1, d_dst, ksize, Point(-1, -1), bordertype); - d_dst.download(dst); + d_dst.download(ocl_dst); GPU_FULL_OFF; + + TestSystem::instance().ExpectedMatNear(ocl_dst, dst, 1.0); } } @@ -96,7 +95,7 @@ PERFTEST(Blur) ///////////// Laplacian//////////////////////// PERFTEST(Laplacian) { - Mat src1, dst; + Mat src1, dst, ocl_dst; ocl::oclMat d_src1, d_dst; int ksize = 3; @@ -112,7 +111,6 @@ PERFTEST(Laplacian) gen(src1, size, size, all_type[j], 0, 256); gen(dst, size, size, all_type[j], 0, 256); - Laplacian(src1, dst, -1, ksize, 1); CPU_ON; @@ -125,8 +123,6 @@ PERFTEST(Laplacian) ocl::Laplacian(d_src1, d_dst, -1, ksize, 1); WARMUP_OFF; - TestSystem::instance().setAccurate(ExpectedMatNear(cv::Mat(d_dst), dst, 1e-5)); - GPU_ON; ocl::Laplacian(d_src1, d_dst, -1, ksize, 1); GPU_OFF; @@ -134,8 +130,10 @@ PERFTEST(Laplacian) GPU_FULL_ON; d_src1.upload(src1); ocl::Laplacian(d_src1, d_dst, -1, ksize, 1); - d_dst.download(dst); + d_dst.download(ocl_dst); GPU_FULL_OFF; + + TestSystem::instance().ExpectedMatNear(ocl_dst, dst, 1e-5); } } @@ -144,7 +142,7 @@ PERFTEST(Laplacian) ///////////// Erode //////////////////// PERFTEST(Erode) { - Mat src, dst, ker; + Mat src, dst, ker, ocl_dst; ocl::oclMat d_src, d_dst; int all_type[] = {CV_8UC1, CV_8UC4, CV_32FC1, CV_32FC4}; @@ -171,8 +169,6 @@ PERFTEST(Erode) ocl::erode(d_src, d_dst, ker); WARMUP_OFF; - TestSystem::instance().setAccurate(ExpectedMatNear(cv::Mat(d_dst), dst, 1e-5)); - GPU_ON; ocl::erode(d_src, d_dst, ker); GPU_OFF; @@ -180,8 +176,10 @@ PERFTEST(Erode) GPU_FULL_ON; d_src.upload(src); ocl::erode(d_src, d_dst, ker); - d_dst.download(dst); + d_dst.download(ocl_dst); GPU_FULL_OFF; + + TestSystem::instance().ExpectedMatNear(ocl_dst, dst, 1e-5); } } @@ -190,7 +188,7 @@ PERFTEST(Erode) ///////////// Sobel //////////////////////// PERFTEST(Sobel) { - Mat src, dst; + Mat src, dst, ocl_dst; ocl::oclMat d_src, d_dst; int dx = 1; @@ -218,8 +216,6 @@ PERFTEST(Sobel) ocl::Sobel(d_src, d_dst, -1, dx, dy); WARMUP_OFF; - TestSystem::instance().setAccurate(ExpectedMatNear(cv::Mat(d_dst), dst, 1)); - GPU_ON; ocl::Sobel(d_src, d_dst, -1, dx, dy); GPU_OFF; @@ -227,8 +223,10 @@ PERFTEST(Sobel) GPU_FULL_ON; d_src.upload(src); ocl::Sobel(d_src, d_dst, -1, dx, dy); - d_dst.download(dst); + d_dst.download(ocl_dst); GPU_FULL_OFF; + + TestSystem::instance().ExpectedMatNear(ocl_dst, dst, 1); } } @@ -236,7 +234,7 @@ PERFTEST(Sobel) ///////////// Scharr //////////////////////// PERFTEST(Scharr) { - Mat src, dst; + Mat src, dst, ocl_dst; ocl::oclMat d_src, d_dst; int dx = 1; @@ -264,8 +262,6 @@ PERFTEST(Scharr) ocl::Scharr(d_src, d_dst, -1, dx, dy); WARMUP_OFF; - TestSystem::instance().setAccurate(ExpectedMatNear(cv::Mat(d_dst), dst, 1)); - GPU_ON; ocl::Scharr(d_src, d_dst, -1, dx, dy); GPU_OFF; @@ -273,8 +269,10 @@ PERFTEST(Scharr) GPU_FULL_ON; d_src.upload(src); ocl::Scharr(d_src, d_dst, -1, dx, dy); - d_dst.download(dst); + d_dst.download(ocl_dst); GPU_FULL_OFF; + + TestSystem::instance().ExpectedMatNear(ocl_dst, dst, 1); } } @@ -283,7 +281,7 @@ PERFTEST(Scharr) ///////////// GaussianBlur //////////////////////// PERFTEST(GaussianBlur) { - Mat src, dst; + Mat src, dst, ocl_dst; int all_type[] = {CV_8UC1, CV_8UC4, CV_32FC1, CV_32FC4}; std::string type_name[] = {"CV_8UC1", "CV_8UC4", "CV_32FC1", "CV_32FC4"}; @@ -311,9 +309,6 @@ PERFTEST(GaussianBlur) ocl::GaussianBlur(d_src, d_dst, Size(9, 9), 0); WARMUP_OFF; - TestSystem::instance().setAccurate(ExpectedMatNear(cv::Mat(d_dst), dst, 1.0)); - - GPU_ON; ocl::GaussianBlur(d_src, d_dst, Size(9, 9), 0); GPU_OFF; @@ -321,8 +316,10 @@ PERFTEST(GaussianBlur) GPU_FULL_ON; d_src.upload(src); ocl::GaussianBlur(d_src, d_dst, Size(9, 9), 0); - d_dst.download(dst); + d_dst.download(ocl_dst); GPU_FULL_OFF; + + TestSystem::instance().ExpectedMatNear(ocl_dst, dst, 1.0); } } @@ -349,7 +346,7 @@ PERFTEST(filter2D) Mat kernel; gen(kernel, ksize, ksize, CV_32FC1, 0.0, 1.0); - Mat dst(src); + Mat dst, ocl_dst; dst.setTo(0); cv::filter2D(src, dst, -1, kernel); @@ -357,17 +354,12 @@ PERFTEST(filter2D) cv::filter2D(src, dst, -1, kernel); CPU_OFF; - ocl::oclMat d_src(src); - ocl::oclMat d_dst(d_src); - d_dst.setTo(0); + ocl::oclMat d_src(src), d_dst; WARMUP_ON; ocl::filter2D(d_src, d_dst, -1, kernel); WARMUP_OFF; - TestSystem::instance().setAccurate(ExpectedMatNear(cv::Mat(d_dst), dst, 1e-5)); - - GPU_ON; ocl::filter2D(d_src, d_dst, -1, kernel); GPU_OFF; @@ -375,8 +367,10 @@ PERFTEST(filter2D) GPU_FULL_ON; d_src.upload(src); ocl::filter2D(d_src, d_dst, -1, kernel); - d_dst.download(dst); + d_dst.download(ocl_dst); GPU_FULL_OFF; + + TestSystem::instance().ExpectedMatNear(ocl_dst, dst, 1e-5); } } diff --git a/modules/ocl/perf/perf_gemm.cpp b/modules/ocl/perf/perf_gemm.cpp index 280a0394ce..f197c5f5a0 100644 --- a/modules/ocl/perf/perf_gemm.cpp +++ b/modules/ocl/perf/perf_gemm.cpp @@ -48,7 +48,7 @@ ///////////// gemm //////////////////////// PERFTEST(gemm) { - Mat src1, src2, src3, dst; + Mat src1, src2, src3, dst, ocl_dst; ocl::oclMat d_src1, d_src2, d_src3, d_dst; for (int size = Min_Size; size <= Max_Size; size *= Multiple) @@ -72,7 +72,6 @@ PERFTEST(gemm) WARMUP_ON; ocl::gemm(d_src1, d_src2, 1.0, d_src3, 1.0, d_dst); WARMUP_OFF; - TestSystem::instance().setAccurate(ExpectedMatNear(cv::Mat(d_dst), dst, src1.cols * src1.rows * 1e-4)); GPU_ON; ocl::gemm(d_src1, d_src2, 1.0, d_src3, 1.0, d_dst); @@ -83,7 +82,9 @@ PERFTEST(gemm) d_src2.upload(src2); d_src3.upload(src3); ocl::gemm(d_src1, d_src2, 1.0, d_src3, 1.0, d_dst); - d_dst.download(dst); + d_dst.download(ocl_dst); GPU_FULL_OFF; + + TestSystem::instance().ExpectedMatNear(ocl_dst, dst, src1.cols * src1.rows * 1e-4); } } \ No newline at end of file diff --git a/modules/ocl/perf/perf_haar.cpp b/modules/ocl/perf/perf_haar.cpp index 792ead1f09..72f01dc935 100644 --- a/modules/ocl/perf/perf_haar.cpp +++ b/modules/ocl/perf/perf_haar.cpp @@ -123,8 +123,10 @@ PERFTEST(Haar) 1.1, 2, 0 | CV_HAAR_SCALE_IMAGE, Size(30, 30)); WARMUP_OFF; - //Testing whether the expected is equal to the actual. - TestSystem::instance().setAccurate(ExpectedEQ::size_type, vector::size_type>(faces.size(), oclfaces.size())); + if(faces.size() == oclfaces.size()) + TestSystem::instance().setAccurate(1, 0); + else + TestSystem::instance().setAccurate(0, abs((int)faces.size() - (int)oclfaces.size())); faces.clear(); diff --git a/modules/ocl/perf/perf_hog.cpp b/modules/ocl/perf/perf_hog.cpp index c425ef4848..05093811fe 100644 --- a/modules/ocl/perf/perf_hog.cpp +++ b/modules/ocl/perf/perf_hog.cpp @@ -146,10 +146,8 @@ PERFTEST(HOG) } } - cv::Mat ocl_mat; - ocl_mat = cv::Mat(d_comp); - ocl_mat.convertTo(ocl_mat, cv::Mat(comp).type()); - TestSystem::instance().setAccurate(ExpectedMatNear(ocl_mat, cv::Mat(comp), 3)); + cv::Mat gpu_rst(d_comp), cpu_rst(comp); + TestSystem::instance().ExpectedMatNear(gpu_rst, cpu_rst, 3); GPU_ON; ocl_hog.detectMultiScale(d_src, found_locations); diff --git a/modules/ocl/perf/perf_imgproc.cpp b/modules/ocl/perf/perf_imgproc.cpp index 980d3be40b..18c7429e84 100644 --- a/modules/ocl/perf/perf_imgproc.cpp +++ b/modules/ocl/perf/perf_imgproc.cpp @@ -48,7 +48,7 @@ ///////////// equalizeHist //////////////////////// PERFTEST(equalizeHist) { - Mat src, dst; + Mat src, dst, ocl_dst; int all_type[] = {CV_8UC1}; std::string type_name[] = {"CV_8UC1"}; @@ -75,9 +75,6 @@ PERFTEST(equalizeHist) ocl::equalizeHist(d_src, d_dst); WARMUP_OFF; - TestSystem::instance().setAccurate(ExpectedMatNear(dst, cv::Mat(d_dst), 1.1)); - - GPU_ON; ocl::equalizeHist(d_src, d_dst); GPU_OFF; @@ -85,8 +82,10 @@ PERFTEST(equalizeHist) GPU_FULL_ON; d_src.upload(src); ocl::equalizeHist(d_src, d_dst); - d_dst.download(dst); + d_dst.download(ocl_dst); GPU_FULL_OFF; + + TestSystem::instance().ExpectedMatNear(dst, ocl_dst, 1.1); } } @@ -94,7 +93,7 @@ PERFTEST(equalizeHist) /////////// CopyMakeBorder ////////////////////// PERFTEST(CopyMakeBorder) { - Mat src, dst; + Mat src, dst, ocl_dst; ocl::oclMat d_dst; int bordertype = BORDER_CONSTANT; @@ -122,9 +121,6 @@ PERFTEST(CopyMakeBorder) ocl::copyMakeBorder(d_src, d_dst, 7, 5, 5, 7, bordertype, cv::Scalar(1.0)); WARMUP_OFF; - TestSystem::instance().setAccurate(ExpectedMatNear(dst, cv::Mat(d_dst), 0.0)); - - GPU_ON; ocl::copyMakeBorder(d_src, d_dst, 7, 5, 5, 7, bordertype, cv::Scalar(1.0)); GPU_OFF; @@ -132,8 +128,10 @@ PERFTEST(CopyMakeBorder) GPU_FULL_ON; d_src.upload(src); ocl::copyMakeBorder(d_src, d_dst, 7, 5, 5, 7, bordertype, cv::Scalar(1.0)); - d_dst.download(dst); + d_dst.download(ocl_dst); GPU_FULL_OFF; + + TestSystem::instance().ExpectedMatNear(dst, ocl_dst, 0.0); } } @@ -141,7 +139,7 @@ PERFTEST(CopyMakeBorder) ///////////// cornerMinEigenVal //////////////////////// PERFTEST(cornerMinEigenVal) { - Mat src, dst; + Mat src, dst, ocl_dst; ocl::oclMat d_dst; int blockSize = 7, apertureSize = 1 + 2 * (rand() % 4); @@ -155,7 +153,6 @@ PERFTEST(cornerMinEigenVal) { SUBTEST << size << 'x' << size << "; " << type_name[j] ; - gen(src, size, size, all_type[j], 0, 256); cornerMinEigenVal(src, dst, blockSize, apertureSize, borderType); @@ -170,9 +167,6 @@ PERFTEST(cornerMinEigenVal) ocl::cornerMinEigenVal(d_src, d_dst, blockSize, apertureSize, borderType); WARMUP_OFF; - TestSystem::instance().setAccurate(ExpectedMatNear(dst, cv::Mat(d_dst), 1.0)); - - GPU_ON; ocl::cornerMinEigenVal(d_src, d_dst, blockSize, apertureSize, borderType); GPU_OFF; @@ -180,8 +174,10 @@ PERFTEST(cornerMinEigenVal) GPU_FULL_ON; d_src.upload(src); ocl::cornerMinEigenVal(d_src, d_dst, blockSize, apertureSize, borderType); - d_dst.download(dst); + d_dst.download(ocl_dst); GPU_FULL_OFF; + + TestSystem::instance().ExpectedMatNear(dst, ocl_dst, 1.0); } } @@ -189,7 +185,7 @@ PERFTEST(cornerMinEigenVal) ///////////// cornerHarris //////////////////////// PERFTEST(cornerHarris) { - Mat src, dst; + Mat src, dst, ocl_dst; ocl::oclMat d_src, d_dst; int all_type[] = {CV_8UC1, CV_32FC1}; @@ -215,8 +211,6 @@ PERFTEST(cornerHarris) ocl::cornerHarris(d_src, d_dst, 5, 7, 0.1, BORDER_REFLECT); WARMUP_OFF; - TestSystem::instance().setAccurate(ExpectedMatNear(dst, cv::Mat(d_dst), 1.0)); - GPU_ON; ocl::cornerHarris(d_src, d_dst, 5, 7, 0.1, BORDER_REFLECT); GPU_OFF; @@ -224,8 +218,10 @@ PERFTEST(cornerHarris) GPU_FULL_ON; d_src.upload(src); ocl::cornerHarris(d_src, d_dst, 5, 7, 0.1, BORDER_REFLECT); - d_dst.download(dst); + d_dst.download(ocl_dst); GPU_FULL_OFF; + + TestSystem::instance().ExpectedMatNear(dst, ocl_dst, 1.0); } @@ -234,7 +230,7 @@ PERFTEST(cornerHarris) ///////////// integral //////////////////////// PERFTEST(integral) { - Mat src, sum; + Mat src, sum, ocl_sum; ocl::oclMat d_src, d_sum, d_buf; int all_type[] = {CV_8UC1}; @@ -260,12 +256,6 @@ PERFTEST(integral) ocl::integral(d_src, d_sum); WARMUP_OFF; - cv::Mat ocl_mat; - d_sum.download(ocl_mat); - if(sum.type() == ocl_mat.type()) //we won't test accuracy when cpu function overlow - TestSystem::instance().setAccurate(ExpectedMatNear(sum, ocl_mat, 0.0)); - - GPU_ON; ocl::integral(d_src, d_sum); GPU_OFF; @@ -273,8 +263,12 @@ PERFTEST(integral) GPU_FULL_ON; d_src.upload(src); ocl::integral(d_src, d_sum); - d_sum.download(sum); + d_sum.download(ocl_sum); GPU_FULL_OFF; + + if(sum.type() == ocl_sum.type()) //we won't test accuracy when cpu function overlow + TestSystem::instance().ExpectedMatNear(sum, ocl_sum, 0.0); + } } @@ -282,7 +276,7 @@ PERFTEST(integral) ///////////// WarpAffine //////////////////////// PERFTEST(WarpAffine) { - Mat src, dst; + Mat src, dst, ocl_dst; ocl::oclMat d_src, d_dst; static const double coeffs[2][3] = @@ -319,8 +313,6 @@ PERFTEST(WarpAffine) ocl::warpAffine(d_src, d_dst, M, size1, interpolation); WARMUP_OFF; - TestSystem::instance().setAccurate(ExpectedMatNear(dst, cv::Mat(d_dst), 1.0)); - GPU_ON; ocl::warpAffine(d_src, d_dst, M, size1, interpolation); GPU_OFF; @@ -328,8 +320,10 @@ PERFTEST(WarpAffine) GPU_FULL_ON; d_src.upload(src); ocl::warpAffine(d_src, d_dst, M, size1, interpolation); - d_dst.download(dst); + d_dst.download(ocl_dst); GPU_FULL_OFF; + + TestSystem::instance().ExpectedMatNear(dst, ocl_dst, 1.0); } } @@ -337,7 +331,7 @@ PERFTEST(WarpAffine) ///////////// WarpPerspective //////////////////////// PERFTEST(WarpPerspective) { - Mat src, dst; + Mat src, dst, ocl_dst; ocl::oclMat d_src, d_dst; static const double coeffs[3][3] = @@ -374,8 +368,6 @@ PERFTEST(WarpPerspective) ocl::warpPerspective(d_src, d_dst, M, size1, interpolation); WARMUP_OFF; - TestSystem::instance().setAccurate(ExpectedMatNear(dst, cv::Mat(d_dst), 1.0)); - GPU_ON; ocl::warpPerspective(d_src, d_dst, M, size1, interpolation); GPU_OFF; @@ -383,8 +375,10 @@ PERFTEST(WarpPerspective) GPU_FULL_ON; d_src.upload(src); ocl::warpPerspective(d_src, d_dst, M, size1, interpolation); - d_dst.download(dst); + d_dst.download(ocl_dst); GPU_FULL_OFF; + + TestSystem::instance().ExpectedMatNear(dst, ocl_dst, 1.0); } } @@ -393,7 +387,7 @@ PERFTEST(WarpPerspective) ///////////// resize //////////////////////// PERFTEST(resize) { - Mat src, dst; + Mat src, dst, ocl_dst; ocl::oclMat d_src, d_dst; @@ -420,9 +414,6 @@ PERFTEST(resize) ocl::resize(d_src, d_dst, Size(), 2.0, 2.0); WARMUP_OFF; - TestSystem::instance().setAccurate(ExpectedMatNear(dst, cv::Mat(d_dst), 1.0)); - - GPU_ON; ocl::resize(d_src, d_dst, Size(), 2.0, 2.0); GPU_OFF; @@ -430,8 +421,10 @@ PERFTEST(resize) GPU_FULL_ON; d_src.upload(src); ocl::resize(d_src, d_dst, Size(), 2.0, 2.0); - d_dst.download(dst); + d_dst.download(ocl_dst); GPU_FULL_OFF; + + TestSystem::instance().ExpectedMatNear(dst, ocl_dst, 1.0); } } @@ -456,8 +449,6 @@ PERFTEST(resize) ocl::resize(d_src, d_dst, Size(), 0.5, 0.5); WARMUP_OFF; - TestSystem::instance().setAccurate(ExpectedMatNear(dst, cv::Mat(d_dst), 1.0)); - GPU_ON; ocl::resize(d_src, d_dst, Size(), 0.5, 0.5); GPU_OFF; @@ -465,8 +456,10 @@ PERFTEST(resize) GPU_FULL_ON; d_src.upload(src); ocl::resize(d_src, d_dst, Size(), 0.5, 0.5); - d_dst.download(dst); + d_dst.download(ocl_dst); GPU_FULL_OFF; + + TestSystem::instance().ExpectedMatNear(dst, ocl_dst, 1.0); } } @@ -474,10 +467,9 @@ PERFTEST(resize) ///////////// threshold//////////////////////// PERFTEST(threshold) { - Mat src, dst; + Mat src, dst, ocl_dst; ocl::oclMat d_src, d_dst; - for (int size = Min_Size; size <= Max_Size; size *= Multiple) { SUBTEST << size << 'x' << size << "; 8UC1; THRESH_BINARY"; @@ -496,9 +488,6 @@ PERFTEST(threshold) ocl::threshold(d_src, d_dst, 50.0, 0.0, THRESH_BINARY); WARMUP_OFF; - TestSystem::instance().setAccurate(ExpectedMatNear(dst, cv::Mat(d_dst), 1.0)); - - GPU_ON; ocl::threshold(d_src, d_dst, 50.0, 0.0, THRESH_BINARY); GPU_OFF; @@ -506,9 +495,10 @@ PERFTEST(threshold) GPU_FULL_ON; d_src.upload(src); ocl::threshold(d_src, d_dst, 50.0, 0.0, THRESH_BINARY); - d_dst.download(dst); + d_dst.download(ocl_dst); GPU_FULL_OFF; + TestSystem::instance().ExpectedMatNear(dst, ocl_dst, 1.0); } for (int size = Min_Size; size <= Max_Size; size *= Multiple) @@ -529,8 +519,6 @@ PERFTEST(threshold) ocl::threshold(d_src, d_dst, 50.0, 0.0, THRESH_TRUNC); WARMUP_OFF; - TestSystem::instance().setAccurate(ExpectedMatNear(dst, cv::Mat(d_dst), 1.0)); - GPU_ON; ocl::threshold(d_src, d_dst, 50.0, 0.0, THRESH_TRUNC); GPU_OFF; @@ -538,8 +526,10 @@ PERFTEST(threshold) GPU_FULL_ON; d_src.upload(src); ocl::threshold(d_src, d_dst, 50.0, 0.0, THRESH_TRUNC); - d_dst.download(dst); + d_dst.download(ocl_dst); GPU_FULL_OFF; + + TestSystem::instance().ExpectedMatNear(dst, ocl_dst, 1.0); } } ///////////// meanShiftFiltering//////////////////////// @@ -726,7 +716,7 @@ void meanShiftFiltering_(const Mat &src_roi, Mat &dst_roi, int sp, int sr, cv::T PERFTEST(meanShiftFiltering) { int sp = 5, sr = 6; - Mat src, dst; + Mat src, dst, ocl_dst; ocl::oclMat d_src, d_dst; @@ -753,11 +743,6 @@ PERFTEST(meanShiftFiltering) ocl::meanShiftFiltering(d_src, d_dst, sp, sr, crit); WARMUP_OFF; - cv::Mat ocl_mat; - d_dst.download(ocl_mat); - - TestSystem::instance().setAccurate(ExpectedMatNear(dst, ocl_mat, 0.0)); - GPU_ON; ocl::meanShiftFiltering(d_src, d_dst, sp, sr); GPU_OFF; @@ -765,8 +750,10 @@ PERFTEST(meanShiftFiltering) GPU_FULL_ON; d_src.upload(src); ocl::meanShiftFiltering(d_src, d_dst, sp, sr); - d_dst.download(dst); + d_dst.download(ocl_dst); GPU_FULL_OFF; + + TestSystem::instance().ExpectedMatNear(dst, ocl_dst, 0.0); } } ///////////// meanShiftProc//////////////////////// @@ -1010,8 +997,9 @@ void meanShiftProc_(const Mat &src_roi, Mat &dst_roi, Mat &dstCoor_roi, int sp, } PERFTEST(meanShiftProc) { - Mat src, dst, dstCoor_roi; - ocl::oclMat d_src, d_dst, d_dstCoor_roi; + Mat src; + vector dst(2), ocl_dst(2); + ocl::oclMat d_src, d_dst, d_dstCoor; TermCriteria crit(TermCriteria::COUNT + TermCriteria::EPS, 5, 1); @@ -1020,42 +1008,41 @@ PERFTEST(meanShiftProc) SUBTEST << size << 'x' << size << "; 8UC4 and CV_16SC2 "; gen(src, size, size, CV_8UC4, Scalar::all(0), Scalar::all(256)); - gen(dst, size, size, CV_8UC4, Scalar::all(0), Scalar::all(256)); - gen(dstCoor_roi, size, size, CV_16SC2, Scalar::all(0), Scalar::all(256)); + gen(dst[0], size, size, CV_8UC4, Scalar::all(0), Scalar::all(256)); + gen(dst[1], size, size, CV_16SC2, Scalar::all(0), Scalar::all(256)); - meanShiftProc_(src, dst, dstCoor_roi, 5, 6, crit); + meanShiftProc_(src, dst[0], dst[1], 5, 6, crit); CPU_ON; - meanShiftProc_(src, dst, dstCoor_roi, 5, 6, crit); + meanShiftProc_(src, dst[0], dst[1], 5, 6, crit); CPU_OFF; d_src.upload(src); WARMUP_ON; - ocl::meanShiftProc(d_src, d_dst, d_dstCoor_roi, 5, 6, crit); + ocl::meanShiftProc(d_src, d_dst, d_dstCoor, 5, 6, crit); WARMUP_OFF; - TestSystem::instance().setAccurate(ExpectedMatNear(dstCoor_roi, cv::Mat(d_dstCoor_roi), 0.0) - &&ExpectedMatNear(dst, cv::Mat(d_dst), 0.0)); - GPU_ON; - ocl::meanShiftProc(d_src, d_dst, d_dstCoor_roi, 5, 6, crit); + ocl::meanShiftProc(d_src, d_dst, d_dstCoor, 5, 6, crit); GPU_OFF; GPU_FULL_ON; d_src.upload(src); - ocl::meanShiftProc(d_src, d_dst, d_dstCoor_roi, 5, 6, crit); - d_dst.download(dst); - d_dstCoor_roi.download(dstCoor_roi); + ocl::meanShiftProc(d_src, d_dst, d_dstCoor, 5, 6, crit); + d_dst.download(ocl_dst[0]); + d_dstCoor.download(ocl_dst[1]); GPU_FULL_OFF; + vector eps(2, 0.); + TestSystem::instance().ExpectMatsNear(dst, ocl_dst, eps); } } ///////////// remap//////////////////////// PERFTEST(remap) { - Mat src, dst, xmap, ymap; + Mat src, dst, xmap, ymap, ocl_dst; ocl::oclMat d_src, d_dst, d_xmap, d_ymap; int all_type[] = {CV_8UC1, CV_8UC4}; @@ -1088,7 +1075,6 @@ PERFTEST(remap) } } - remap(src, dst, xmap, ymap, interpolation, borderMode); CPU_ON; @@ -1104,12 +1090,6 @@ PERFTEST(remap) ocl::remap(d_src, d_dst, d_xmap, d_ymap, interpolation, borderMode); WARMUP_OFF; - if(interpolation == 0) - TestSystem::instance().setAccurate(ExpectedMatNear(dst, cv::Mat(d_dst), 1.0)); - else - TestSystem::instance().setAccurate(ExpectedMatNear(dst, cv::Mat(d_dst), 2.0)); - - GPU_ON; ocl::remap(d_src, d_dst, d_xmap, d_ymap, interpolation, borderMode); GPU_OFF; @@ -1117,8 +1097,10 @@ PERFTEST(remap) GPU_FULL_ON; d_src.upload(src); ocl::remap(d_src, d_dst, d_xmap, d_ymap, interpolation, borderMode); - d_dst.download(dst); + d_dst.download(ocl_dst); GPU_FULL_OFF; + + TestSystem::instance().ExpectedMatNear(dst, ocl_dst, 2.0); } } diff --git a/modules/ocl/perf/perf_match_template.cpp b/modules/ocl/perf/perf_match_template.cpp index f9f0f6a4d6..5da15aaf64 100644 --- a/modules/ocl/perf/perf_match_template.cpp +++ b/modules/ocl/perf/perf_match_template.cpp @@ -56,11 +56,9 @@ PERFTEST(matchTemplate) { //InitMatchTemplate(); - - Mat src, templ, dst; + Mat src, templ, dst, ocl_dst; int templ_size = 5; - for (int size = Min_Size; size <= Max_Size; size *= Multiple) { int all_type[] = {CV_32FC1, CV_32FC4}; @@ -82,16 +80,12 @@ PERFTEST(matchTemplate) matchTemplate(src, templ, dst, CV_TM_CCORR); CPU_OFF; - ocl::oclMat d_src(src), d_templ, d_dst; - - d_templ.upload(templ); + ocl::oclMat d_src(src), d_templ(templ), d_dst; WARMUP_ON; ocl::matchTemplate(d_src, d_templ, d_dst, CV_TM_CCORR); WARMUP_OFF; - TestSystem::instance().setAccurate(ExpectedMatNear(dst, cv::Mat(d_dst), templ.rows * templ.cols * 1e-1)); - GPU_ON; ocl::matchTemplate(d_src, d_templ, d_dst, CV_TM_CCORR); GPU_OFF; @@ -100,8 +94,10 @@ PERFTEST(matchTemplate) d_src.upload(src); d_templ.upload(templ); ocl::matchTemplate(d_src, d_templ, d_dst, CV_TM_CCORR); - d_dst.download(dst); + d_dst.download(ocl_dst); GPU_FULL_OFF; + + TestSystem::instance().ExpectedMatNear(dst, ocl_dst, templ.rows * templ.cols * 1e-1); } } @@ -131,8 +127,6 @@ PERFTEST(matchTemplate) ocl::matchTemplate(d_src, d_templ, d_dst, CV_TM_CCORR_NORMED); WARMUP_OFF; - TestSystem::instance().setAccurate(ExpectedMatNear(dst, cv::Mat(d_dst), templ.rows * templ.cols * 1e-1)); - GPU_ON; ocl::matchTemplate(d_src, d_templ, d_dst, CV_TM_CCORR_NORMED); GPU_OFF; @@ -141,8 +135,10 @@ PERFTEST(matchTemplate) d_src.upload(src); d_templ.upload(templ); ocl::matchTemplate(d_src, d_templ, d_dst, CV_TM_CCORR_NORMED); - d_dst.download(dst); + d_dst.download(ocl_dst); GPU_FULL_OFF; + + TestSystem::instance().ExpectedMatNear(dst, ocl_dst, templ.rows * templ.cols * 1e-1); } } } diff --git a/modules/ocl/perf/perf_matrix_operation.cpp b/modules/ocl/perf/perf_matrix_operation.cpp index 4b364b01b6..b724cdbe64 100644 --- a/modules/ocl/perf/perf_matrix_operation.cpp +++ b/modules/ocl/perf/perf_matrix_operation.cpp @@ -48,7 +48,7 @@ ///////////// ConvertTo//////////////////////// PERFTEST(ConvertTo) { - Mat src, dst; + Mat src, dst, ocl_dst; ocl::oclMat d_src, d_dst; int all_type[] = {CV_8UC1, CV_8UC4}; @@ -77,9 +77,6 @@ PERFTEST(ConvertTo) d_src.convertTo(d_dst, CV_32FC1); WARMUP_OFF; - TestSystem::instance().setAccurate(ExpectedMatNear(dst, cv::Mat(d_dst), 0.0)); - - GPU_ON; d_src.convertTo(d_dst, CV_32FC1); GPU_OFF; @@ -87,8 +84,10 @@ PERFTEST(ConvertTo) GPU_FULL_ON; d_src.upload(src); d_src.convertTo(d_dst, CV_32FC1); - d_dst.download(dst); + d_dst.download(ocl_dst); GPU_FULL_OFF; + + TestSystem::instance().ExpectedMatNear(dst, ocl_dst, 0.0); } } @@ -96,7 +95,7 @@ PERFTEST(ConvertTo) ///////////// copyTo//////////////////////// PERFTEST(copyTo) { - Mat src, dst; + Mat src, dst, ocl_dst; ocl::oclMat d_src, d_dst; int all_type[] = {CV_8UC1, CV_8UC4}; @@ -125,9 +124,6 @@ PERFTEST(copyTo) d_src.copyTo(d_dst); WARMUP_OFF; - TestSystem::instance().setAccurate(ExpectedMatNear(dst, cv::Mat(d_dst), 0.0)); - - GPU_ON; d_src.copyTo(d_dst); GPU_OFF; @@ -135,8 +131,10 @@ PERFTEST(copyTo) GPU_FULL_ON; d_src.upload(src); d_src.copyTo(d_dst); - d_dst.download(dst); + d_dst.download(ocl_dst); GPU_FULL_OFF; + + TestSystem::instance().ExpectedMatNear(dst, ocl_dst, 0.0); } } @@ -144,9 +142,9 @@ PERFTEST(copyTo) ///////////// setTo//////////////////////// PERFTEST(setTo) { - Mat src, dst; + Mat src, ocl_src; Scalar val(1, 2, 3, 4); - ocl::oclMat d_src, d_dst; + ocl::oclMat d_src; int all_type[] = {CV_8UC1, CV_8UC4}; std::string type_name[] = {"CV_8UC1", "CV_8UC4"}; @@ -171,10 +169,10 @@ PERFTEST(setTo) d_src.setTo(val); WARMUP_OFF; - TestSystem::instance().setAccurate(ExpectedMatNear(src, cv::Mat(d_src), 1.0)); + d_src.download(ocl_src); + TestSystem::instance().ExpectedMatNear(src, ocl_src, 1.0); - - GPU_ON; + GPU_ON;; d_src.setTo(val); GPU_OFF; diff --git a/modules/ocl/perf/perf_norm.cpp b/modules/ocl/perf/perf_norm.cpp index 78ff001248..1d986c8e49 100644 --- a/modules/ocl/perf/perf_norm.cpp +++ b/modules/ocl/perf/perf_norm.cpp @@ -48,39 +48,40 @@ ///////////// norm//////////////////////// PERFTEST(norm) { - Mat src, buf; - ocl::oclMat d_src, d_buf; - + Mat src1, src2, ocl_src1; + ocl::oclMat d_src1, d_src2; for (int size = Min_Size; size <= Max_Size; size *= Multiple) { SUBTEST << size << 'x' << size << "; CV_8UC1; NORM_INF"; - gen(src, size, size, CV_8UC1, Scalar::all(0), Scalar::all(1)); - gen(buf, size, size, CV_8UC1, Scalar::all(0), Scalar::all(1)); + gen(src1, size, size, CV_8UC1, Scalar::all(0), Scalar::all(1)); + gen(src2, size, size, CV_8UC1, Scalar::all(0), Scalar::all(1)); - norm(src, NORM_INF); + norm(src1, src2, NORM_INF); CPU_ON; - norm(src, NORM_INF); + norm(src1, src2, NORM_INF); CPU_OFF; - d_src.upload(src); - d_buf.upload(buf); + d_src1.upload(src1); + d_src2.upload(src2); WARMUP_ON; - ocl::norm(d_src, d_buf, NORM_INF); + ocl::norm(d_src1, d_src2, NORM_INF); WARMUP_OFF; - TestSystem::instance().setAccurate(ExpectedMatNear(src, cv::Mat(d_buf), .5)); + d_src1.download(ocl_src1); + TestSystem::instance().ExpectedMatNear(src1, ocl_src1, .5); GPU_ON; - ocl::norm(d_src, d_buf, NORM_INF); + ocl::norm(d_src1, d_src2, NORM_INF); GPU_OFF; GPU_FULL_ON; - d_src.upload(src); - ocl::norm(d_src, d_buf, NORM_INF); + d_src1.upload(src1); + d_src2.upload(src2); + ocl::norm(d_src1, d_src2, NORM_INF); GPU_FULL_OFF; } } \ No newline at end of file diff --git a/modules/ocl/perf/perf_pyrdown.cpp b/modules/ocl/perf/perf_pyrdown.cpp index 36d2e7ec70..b6eca45188 100644 --- a/modules/ocl/perf/perf_pyrdown.cpp +++ b/modules/ocl/perf/perf_pyrdown.cpp @@ -48,7 +48,7 @@ ///////////// pyrDown ////////////////////// PERFTEST(pyrDown) { - Mat src, dst; + Mat src, dst, ocl_dst; int all_type[] = {CV_8UC1, CV_8UC4}; std::string type_name[] = {"CV_8UC1", "CV_8UC4"}; @@ -73,9 +73,6 @@ PERFTEST(pyrDown) ocl::pyrDown(d_src, d_dst); WARMUP_OFF; - TestSystem::instance().setAccurate(ExpectedMatNear(dst, cv::Mat(d_dst), dst.depth() == CV_32F ? 1e-4f : 1.0f)); - - GPU_ON; ocl::pyrDown(d_src, d_dst); GPU_OFF; @@ -83,8 +80,10 @@ PERFTEST(pyrDown) GPU_FULL_ON; d_src.upload(src); ocl::pyrDown(d_src, d_dst); - d_dst.download(dst); + d_dst.download(ocl_dst); GPU_FULL_OFF; + + TestSystem::instance().ExpectedMatNear(dst, ocl_dst, dst.depth() == CV_32F ? 1e-4f : 1.0f); } } } \ No newline at end of file diff --git a/modules/ocl/perf/perf_pyrlk.cpp b/modules/ocl/perf/perf_pyrlk.cpp index 32bf145b9f..76442d950a 100644 --- a/modules/ocl/perf/perf_pyrlk.cpp +++ b/modules/ocl/perf/perf_pyrlk.cpp @@ -82,8 +82,8 @@ PERFTEST(PyrLKOpticalFlow) SUBTEST << frame0.cols << "x" << frame0.rows << "; color; " << points << " points"; else SUBTEST << frame0.cols << "x" << frame0.rows << "; gray; " << points << " points"; - Mat nextPts_cpu; - Mat status_cpu; + Mat ocl_nextPts; + Mat ocl_status; vector pts; goodFeaturesToTrack(i == 0 ? gray_frame : frame0, pts, points, 0.01, 0.0); @@ -116,12 +116,6 @@ PERFTEST(PyrLKOpticalFlow) d_pyrLK.sparse(d_frame0, d_frame1, d_pts, d_nextPts, d_status, &d_err); WARMUP_OFF; - std::vector ocl_nextPts(d_nextPts.cols); - std::vector ocl_status(d_status.cols); - TestSystem::instance().setAccurate(AssertEQ(nextPts.size(), ocl_nextPts.size())); - TestSystem::instance().setAccurate(AssertEQ(status.size(), ocl_status.size())); - - GPU_ON; d_pyrLK.sparse(d_frame0, d_frame1, d_pts, d_nextPts, d_status, &d_err); GPU_OFF; @@ -133,16 +127,31 @@ PERFTEST(PyrLKOpticalFlow) d_pyrLK.sparse(d_frame0, d_frame1, d_pts, d_nextPts, d_status, &d_err); if (!d_nextPts.empty()) - { - d_nextPts.download(nextPts_cpu); - } + d_nextPts.download(ocl_nextPts); if (!d_status.empty()) - { - d_status.download(status_cpu); - } - + d_status.download(ocl_status); GPU_FULL_OFF; + + size_t mismatch = 0; + for (int i = 0; i < (int)nextPts.size(); ++i) + { + if(status[i] != ocl_status.at(0, i)){ + mismatch++; + continue; + } + if(status[i]){ + Point2f gpu_rst = ocl_nextPts.at(0, i); + Point2f cpu_rst = nextPts[i]; + if(fabs(gpu_rst.x - cpu_rst.x) >= 1. || fabs(gpu_rst.y - cpu_rst.y) >= 1.) + mismatch++; + } + } + double ratio = (double)mismatch / (double)nextPts.size(); + if(ratio < .02) + TestSystem::instance().setAccurate(1, ratio); + else + TestSystem::instance().setAccurate(0, ratio); } } diff --git a/modules/ocl/perf/perf_pyrup.cpp b/modules/ocl/perf/perf_pyrup.cpp index 3b2022e096..bfefe5e0c2 100644 --- a/modules/ocl/perf/perf_pyrup.cpp +++ b/modules/ocl/perf/perf_pyrup.cpp @@ -48,7 +48,7 @@ ///////////// pyrUp //////////////////////// PERFTEST(pyrUp) { - Mat src, dst; + Mat src, dst, ocl_dst; int all_type[] = {CV_8UC1, CV_8UC4}; std::string type_name[] = {"CV_8UC1", "CV_8UC4"}; @@ -73,8 +73,6 @@ PERFTEST(pyrUp) ocl::pyrUp(d_src, d_dst); WARMUP_OFF; - TestSystem::instance().setAccurate(ExpectedMatNear(dst, cv::Mat(d_dst), (src.depth() == CV_32F ? 1e-4f : 1.0))); - GPU_ON; ocl::pyrUp(d_src, d_dst); GPU_OFF; @@ -82,8 +80,10 @@ PERFTEST(pyrUp) GPU_FULL_ON; d_src.upload(src); ocl::pyrUp(d_src, d_dst); - d_dst.download(dst); + d_dst.download(ocl_dst); GPU_FULL_OFF; + + TestSystem::instance().ExpectedMatNear(dst, ocl_dst, (src.depth() == CV_32F ? 1e-4f : 1.0)); } } } \ No newline at end of file diff --git a/modules/ocl/perf/perf_split_merge.cpp b/modules/ocl/perf/perf_split_merge.cpp index fc720c5b0d..0fafd14aba 100644 --- a/modules/ocl/perf/perf_split_merge.cpp +++ b/modules/ocl/perf/perf_split_merge.cpp @@ -48,7 +48,7 @@ ///////////// Merge//////////////////////// PERFTEST(Merge) { - Mat dst; + Mat dst, ocl_dst; ocl::oclMat d_dst; int channels = 4; @@ -85,22 +85,20 @@ PERFTEST(Merge) ocl::merge(d_src, d_dst); WARMUP_OFF; - TestSystem::instance().setAccurate(ExpectedMatNear(cv::Mat(dst), cv::Mat(d_dst), 0.0)); - GPU_ON; ocl::merge(d_src, d_dst); GPU_OFF; GPU_FULL_ON; - for (int i = 0; i < channels; ++i) { - d_src[i] = ocl::oclMat(size1, CV_8U, cv::Scalar::all(i)); + d_src[i] = ocl::oclMat(size1, all_type[j], cv::Scalar::all(i)); } - ocl::merge(d_src, d_dst); - d_dst.download(dst); + d_dst.download(ocl_dst); GPU_FULL_OFF; + + TestSystem::instance().ExpectedMatNear(dst, ocl_dst, 0.0); } } @@ -122,7 +120,7 @@ PERFTEST(Split) Mat src(size1, CV_MAKE_TYPE(all_type[j], 4), cv::Scalar(1, 2, 3, 4)); - std::vector dst; + std::vector dst, ocl_dst(4); split(src, dst); @@ -135,22 +133,7 @@ PERFTEST(Split) WARMUP_ON; ocl::split(d_src, d_dst); - WARMUP_OFF; - - if(d_dst.size() == dst.size()) - { - TestSystem::instance().setAccurate(1); - for(size_t i = 0; i < dst.size(); i++) - { - if(ExpectedMatNear(dst[i], cv::Mat(d_dst[i]), 0.0) == 0) - { - TestSystem::instance().setAccurate(0); - break; - } - } - }else - TestSystem::instance().setAccurate(0); - + WARMUP_OFF; GPU_ON; ocl::split(d_src, d_dst); @@ -159,7 +142,12 @@ PERFTEST(Split) GPU_FULL_ON; d_src.upload(src); ocl::split(d_src, d_dst); + for(size_t i = 0; i < dst.size(); i++) + d_dst[i].download(ocl_dst[i]); GPU_FULL_OFF; + + vector eps(4, 0.); + TestSystem::instance().ExpectMatsNear(dst, ocl_dst, eps); } } diff --git a/modules/ocl/perf/precomp.cpp b/modules/ocl/perf/precomp.cpp index 476c73eb18..616ef4dfd4 100644 --- a/modules/ocl/perf/precomp.cpp +++ b/modules/ocl/perf/precomp.cpp @@ -114,7 +114,6 @@ void TestSystem::finishCurrentSubtest() return; } - int is_accurate = is_accurate_; double cpu_time = cpu_elapsed_ / getTickFrequency() * 1000.0; double gpu_time = gpu_elapsed_ / getTickFrequency() * 1000.0; double gpu_full_time = gpu_full_elapsed_ / getTickFrequency() * 1000.0; @@ -171,8 +170,8 @@ void TestSystem::finishCurrentSubtest() deviation = std::sqrt(sum / gpu_times_.size()); } - printMetrics(is_accurate, cpu_time, gpu_time, gpu_full_time, speedup, fullspeedup); - writeMetrics(is_accurate, cpu_time, gpu_time, gpu_full_time, speedup, fullspeedup, gpu_min, gpu_max, deviation); + printMetrics(is_accurate_, cpu_time, gpu_time, gpu_full_time, speedup, fullspeedup); + writeMetrics(cpu_time, gpu_time, gpu_full_time, speedup, fullspeedup, gpu_min, gpu_max, deviation); num_subtests_called_++; resetCurrentSubtest(); @@ -219,7 +218,7 @@ void TestSystem::writeHeading() } } - fprintf(record_, "NAME,DESCRIPTION,ACCURACY,CPU (ms),GPU (ms),SPEEDUP,GPUTOTAL (ms),TOTALSPEEDUP,GPU Min (ms),GPU Max (ms), Standard deviation (ms)\n"); + fprintf(record_, "NAME,DESCRIPTION,ACCURACY,DIFFERENCE,CPU (ms),GPU (ms),SPEEDUP,GPUTOTAL (ms),TOTALSPEEDUP,GPU Min (ms),GPU Max (ms), Standard deviation (ms)\n"); fflush(record_); } @@ -392,7 +391,7 @@ void TestSystem::printMetrics(int is_accurate, double cpu_time, double gpu_time, #endif } -void TestSystem::writeMetrics(int is_accurate, double cpu_time, double gpu_time, double gpu_full_time, double speedup, double fullspeedup, double gpu_min, double gpu_max, double std_dev) +void TestSystem::writeMetrics(double cpu_time, double gpu_time, double gpu_full_time, double speedup, double fullspeedup, double gpu_min, double gpu_max, double std_dev) { if (!record_) { @@ -402,21 +401,24 @@ void TestSystem::writeMetrics(int is_accurate, double cpu_time, double gpu_time, string _is_accurate_; - if(is_accurate == 1) + if(is_accurate_ == 1) _is_accurate_ = "Pass"; - else if(is_accurate == 0) + else if(is_accurate_ == 0) _is_accurate_ = "Fail"; - else if(is_accurate == -1) + else if(is_accurate_ == -1) _is_accurate_ = " "; else { - std::cout<<"is_accurate errer: "<(0, 0) - 1.f); } - +/* int ExpectedMatNear(cv::Mat dst, cv::Mat cpu_dst, double eps) { assert(dst.type() == cpu_dst.type()); @@ -647,15 +649,16 @@ int ExceptDoubleNear(double val1, double val2, double abs_error) return 0; } - +/* int ExceptedMatSimilar(cv::Mat dst, cv::Mat cpu_dst, double eps) { assert(dst.type() == cpu_dst.type()); - assert(dst.size() == cpu_dst.size()); + assert(dst.size() == cpu_dst.size()); if(checkSimilarity(cv::Mat(cpu_dst), cv::Mat(dst)) <= eps) return 1; return 0; } +*/ diff --git a/modules/ocl/perf/precomp.hpp b/modules/ocl/perf/precomp.hpp index b025703cb2..97e3d7e5c6 100644 --- a/modules/ocl/perf/precomp.hpp +++ b/modules/ocl/perf/precomp.hpp @@ -313,9 +313,46 @@ public: itname_changed_ = true; } - void setAccurate(int is_accurate = -1) + void setAccurate(int accurate, double diff) { - is_accurate_ = is_accurate; + is_accurate_ = accurate; + accurate_diff_ = diff; + } + + void ExpectMatsNear(vector& dst, vector& cpu_dst, vector& eps) + { + assert(dst.size() == cpu_dst.size()); + assert(cpu_dst.size() == eps.size()); + is_accurate_ = 1; + for(size_t i=0; i eps[i]) + is_accurate_ = 0; + } + } + + void ExpectedMatNear(cv::Mat& dst, cv::Mat& cpu_dst, double eps) + { + assert(dst.type() == cpu_dst.type()); + assert(dst.size() == cpu_dst.size()); + accurate_diff_ = checkNorm(dst, cpu_dst); + if(accurate_diff_ <= eps) + is_accurate_ = 1; + else + is_accurate_ = 0; + } + + void ExceptedMatSimilar(cv::Mat& dst, cv::Mat& cpu_dst, double eps) + { + assert(dst.type() == cpu_dst.type()); + assert(dst.size() == cpu_dst.size()); + accurate_diff_ = checkSimilarity(cpu_dst, dst); + if(accurate_diff_ <= eps) + is_accurate_ = 1; + else + is_accurate_ = 0; } std::stringstream &getCurSubtestDescription() @@ -333,7 +370,7 @@ private: num_iters_(10), cpu_num_iters_(2), gpu_warmup_iters_(1), cur_iter_idx_(0), cur_warmup_idx_(0), record_(0), recordname_("performance"), itname_changed_(true), - is_accurate_(-1) + is_accurate_(-1), accurate_diff_(0.) { cpu_times_.reserve(num_iters_); gpu_times_.reserve(num_iters_); @@ -354,6 +391,7 @@ private: gpu_times_.clear(); gpu_full_times_.clear(); is_accurate_ = -1; + accurate_diff_ = 0.; } double meanTime(const std::vector &samples); @@ -364,7 +402,7 @@ private: void writeHeading(); void writeSummary(); - void writeMetrics(int is_accurate, double cpu_time, double gpu_time = 0.0f, double gpu_full_time = 0.0f, + void writeMetrics(double cpu_time, double gpu_time = 0.0f, double gpu_full_time = 0.0f, double speedup = 0.0f, double fullspeedup = 0.0f, double gpu_min = 0.0f, double gpu_max = 0.0f, double std_dev = 0.0f); @@ -416,6 +454,7 @@ private: bool itname_changed_; int is_accurate_; + double accurate_diff_; }; From d81c145fa966c4973810a4e21a207adfa3cf1b24 Mon Sep 17 00:00:00 2001 From: yao Date: Tue, 28 May 2013 18:07:41 +0800 Subject: [PATCH 039/178] fix memory leak --- modules/ocl/src/hog.cpp | 1 + modules/ocl/src/pyrlk.cpp | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/ocl/src/hog.cpp b/modules/ocl/src/hog.cpp index ae74ed59d1..a3514586fa 100644 --- a/modules/ocl/src/hog.cpp +++ b/modules/ocl/src/hog.cpp @@ -1580,6 +1580,7 @@ static void openCLExecuteKernel_hog(Context *clCxt , const char **source, string { cl_kernel kernel = openCLGetKernelFromSource(clCxt, source, kernelName); size_t wave_size = queryDeviceInfo(kernel); + openCLSafeCall(clReleaseKernel(kernel)); if (wave_size <= 16) { char build_options[64]; diff --git a/modules/ocl/src/pyrlk.cpp b/modules/ocl/src/pyrlk.cpp index 3b6edf79d6..a3e65dde3f 100644 --- a/modules/ocl/src/pyrlk.cpp +++ b/modules/ocl/src/pyrlk.cpp @@ -139,8 +139,9 @@ static void lkSparse_run(oclMat &I, oclMat &J, stringstream idxStr; idxStr << kernelName << "_C" << I.oclchannels() << "_D" << I.depth(); cl_kernel kernel = openCLGetKernelFromSource(clCxt, &pyrlk, idxStr.str()); - int wave_size = queryDeviceInfo(kernel); + openCLSafeCall(clReleaseKernel(kernel)); + static char opt[16] = {0}; sprintf(opt, " -D WAVE_SIZE=%d", wave_size); From d015fa76fab95483ec7cdf0b44796c4caf70c31c Mon Sep 17 00:00:00 2001 From: peng xiao Date: Wed, 29 May 2013 14:15:26 +0800 Subject: [PATCH 040/178] Fix 2.4 ocl Canny. This fix is a workaround for current 2.4 branch without introducing an additional oclMat buffer into CannyBuf object. Test case is cleaned up. Volatile keywords in kernels are removed for performance concern. --- modules/ocl/src/canny.cpp | 50 ++++++++++++------------- modules/ocl/src/opencl/imgproc_canny.cl | 28 +++++++++----- modules/ocl/test/test_canny.cpp | 20 +--------- 3 files changed, 45 insertions(+), 53 deletions(-) diff --git a/modules/ocl/src/canny.cpp b/modules/ocl/src/canny.cpp index cc7e60e0d9..82bb01bfdc 100644 --- a/modules/ocl/src/canny.cpp +++ b/modules/ocl/src/canny.cpp @@ -87,7 +87,7 @@ void cv::ocl::CannyBuf::create(const Size &image_size, int apperture_size) filterDY = createDerivFilter_GPU(CV_8U, CV_32S, 0, 1, apperture_size, BORDER_REPLICATE); } } - ensureSizeIsEnough(image_size.height + 2, image_size.width + 2, CV_32FC1, edgeBuf); + ensureSizeIsEnough(2 * (image_size.height + 2), image_size.width + 2, CV_32FC1, edgeBuf); ensureSizeIsEnough(1, image_size.width * image_size.height, CV_16UC2, trackBuf1); ensureSizeIsEnough(1, image_size.width * image_size.height, CV_16UC2, trackBuf2); @@ -141,13 +141,16 @@ namespace void CannyCaller(CannyBuf &buf, oclMat &dst, float low_thresh, float high_thresh) { using namespace ::cv::ocl::canny; - calcMap_gpu(buf.dx, buf.dy, buf.edgeBuf, buf.edgeBuf, dst.rows, dst.cols, low_thresh, high_thresh); + oclMat magBuf = buf.edgeBuf(Rect(0, 0, buf.edgeBuf.cols, buf.edgeBuf.rows / 2)); + oclMat mapBuf = buf.edgeBuf(Rect(0, buf.edgeBuf.rows / 2, buf.edgeBuf.cols, buf.edgeBuf.rows / 2)); - edgesHysteresisLocal_gpu(buf.edgeBuf, buf.trackBuf1, buf.counter, dst.rows, dst.cols); + calcMap_gpu(buf.dx, buf.dy, magBuf, mapBuf, dst.rows, dst.cols, low_thresh, high_thresh); - edgesHysteresisGlobal_gpu(buf.edgeBuf, buf.trackBuf1, buf.trackBuf2, buf.counter, dst.rows, dst.cols); + edgesHysteresisLocal_gpu(mapBuf, buf.trackBuf1, buf.counter, dst.rows, dst.cols); - getEdges_gpu(buf.edgeBuf, dst, dst.rows, dst.cols); + edgesHysteresisGlobal_gpu(mapBuf, buf.trackBuf1, buf.trackBuf2, buf.counter, dst.rows, dst.cols); + + getEdges_gpu(mapBuf, dst, dst.rows, dst.cols); } } @@ -172,18 +175,20 @@ void cv::ocl::Canny(const oclMat &src, CannyBuf &buf, oclMat &dst, double low_th buf.create(src.size(), apperture_size); buf.edgeBuf.setTo(Scalar::all(0)); + oclMat magBuf = buf.edgeBuf(Rect(0, 0, buf.edgeBuf.cols, buf.edgeBuf.rows / 2)); + if (apperture_size == 3) { calcSobelRowPass_gpu(src, buf.dx_buf, buf.dy_buf, src.rows, src.cols); - calcMagnitude_gpu(buf.dx_buf, buf.dy_buf, buf.dx, buf.dy, buf.edgeBuf, src.rows, src.cols, L2gradient); + calcMagnitude_gpu(buf.dx_buf, buf.dy_buf, buf.dx, buf.dy, magBuf, src.rows, src.cols, L2gradient); } else { buf.filterDX->apply(src, buf.dx); buf.filterDY->apply(src, buf.dy); - calcMagnitude_gpu(buf.dx, buf.dy, buf.edgeBuf, src.rows, src.cols, L2gradient); + calcMagnitude_gpu(buf.dx, buf.dy, magBuf, src.rows, src.cols, L2gradient); } CannyCaller(buf, dst, static_cast(low_thresh), static_cast(high_thresh)); } @@ -209,7 +214,10 @@ void cv::ocl::Canny(const oclMat &dx, const oclMat &dy, CannyBuf &buf, oclMat &d buf.dy = dy; buf.create(dx.size(), -1); buf.edgeBuf.setTo(Scalar::all(0)); - calcMagnitude_gpu(buf.dx, buf.dy, buf.edgeBuf, dx.rows, dx.cols, L2gradient); + + oclMat magBuf = buf.edgeBuf(Rect(0, 0, buf.edgeBuf.cols, buf.edgeBuf.rows / 2)); + + calcMagnitude_gpu(buf.dx, buf.dy, magBuf, dx.rows, dx.cols, L2gradient); CannyCaller(buf, dst, static_cast(low_thresh), static_cast(high_thresh)); } @@ -234,7 +242,7 @@ void canny::calcSobelRowPass_gpu(const oclMat &src, oclMat &dx_buf, oclMat &dy_b size_t globalThreads[3] = {cols, rows, 1}; size_t localThreads[3] = {16, 16, 1}; - openCLExecuteKernel2(clCxt, &imgproc_canny, kernelName, globalThreads, localThreads, args, -1, -1); + openCLExecuteKernel(clCxt, &imgproc_canny, kernelName, globalThreads, localThreads, args, -1, -1); } void canny::calcMagnitude_gpu(const oclMat &dx_buf, const oclMat &dy_buf, oclMat &dx, oclMat &dy, oclMat &mag, int rows, int cols, bool L2Grad) @@ -264,12 +272,8 @@ void canny::calcMagnitude_gpu(const oclMat &dx_buf, const oclMat &dy_buf, oclMat size_t globalThreads[3] = {cols, rows, 1}; size_t localThreads[3] = {16, 16, 1}; - char build_options [15] = ""; - if(L2Grad) - { - strcat(build_options, "-D L2GRAD"); - } - openCLExecuteKernel2(clCxt, &imgproc_canny, kernelName, globalThreads, localThreads, args, -1, -1, build_options); + const char * build_options = L2Grad ? "-D L2GRAD":""; + openCLExecuteKernel(clCxt, &imgproc_canny, kernelName, globalThreads, localThreads, args, -1, -1, build_options); } void canny::calcMagnitude_gpu(const oclMat &dx, const oclMat &dy, oclMat &mag, int rows, int cols, bool L2Grad) { @@ -292,12 +296,8 @@ void canny::calcMagnitude_gpu(const oclMat &dx, const oclMat &dy, oclMat &mag, i size_t globalThreads[3] = {cols, rows, 1}; size_t localThreads[3] = {16, 16, 1}; - char build_options [15] = ""; - if(L2Grad) - { - strcat(build_options, "-D L2GRAD"); - } - openCLExecuteKernel2(clCxt, &imgproc_canny, kernelName, globalThreads, localThreads, args, -1, -1, build_options); + const char * build_options = L2Grad ? "-D L2GRAD":""; + openCLExecuteKernel(clCxt, &imgproc_canny, kernelName, globalThreads, localThreads, args, -1, -1, build_options); } void canny::calcMap_gpu(oclMat &dx, oclMat &dy, oclMat &mag, oclMat &map, int rows, int cols, float low_thresh, float high_thresh) @@ -328,7 +328,7 @@ void canny::calcMap_gpu(oclMat &dx, oclMat &dy, oclMat &mag, oclMat &map, int ro string kernelName = "calcMap"; size_t localThreads[3] = {16, 16, 1}; - openCLExecuteKernel2(clCxt, &imgproc_canny, kernelName, globalThreads, localThreads, args, -1, -1); + openCLExecuteKernel(clCxt, &imgproc_canny, kernelName, globalThreads, localThreads, args, -1, -1); } void canny::edgesHysteresisLocal_gpu(oclMat &map, oclMat &st1, void *counter, int rows, int cols) @@ -348,7 +348,7 @@ void canny::edgesHysteresisLocal_gpu(oclMat &map, oclMat &st1, void *counter, in size_t globalThreads[3] = {cols, rows, 1}; size_t localThreads[3] = {16, 16, 1}; - openCLExecuteKernel2(clCxt, &imgproc_canny, kernelName, globalThreads, localThreads, args, -1, -1); + openCLExecuteKernel(clCxt, &imgproc_canny, kernelName, globalThreads, localThreads, args, -1, -1); } void canny::edgesHysteresisGlobal_gpu(oclMat &map, oclMat &st1, oclMat &st2, void *counter, int rows, int cols) @@ -378,7 +378,7 @@ void canny::edgesHysteresisGlobal_gpu(oclMat &map, oclMat &st1, oclMat &st2, voi args.push_back( make_pair( sizeof(cl_int), (void *)&map.step)); args.push_back( make_pair( sizeof(cl_int), (void *)&map.offset)); - openCLExecuteKernel2(clCxt, &imgproc_canny, kernelName, globalThreads, localThreads, args, -1, -1, DISABLE); + openCLExecuteKernel(clCxt, &imgproc_canny, kernelName, globalThreads, localThreads, args, -1, -1); openCLSafeCall(clEnqueueReadBuffer(*(cl_command_queue*)getoclCommandQueue(), (cl_mem)counter, 1, 0, sizeof(int), &count, 0, NULL, NULL)); std::swap(st1, st2); } @@ -403,5 +403,5 @@ void canny::getEdges_gpu(oclMat &map, oclMat &dst, int rows, int cols) size_t globalThreads[3] = {cols, rows, 1}; size_t localThreads[3] = {16, 16, 1}; - openCLExecuteKernel2(clCxt, &imgproc_canny, kernelName, globalThreads, localThreads, args, -1, -1); + openCLExecuteKernel(clCxt, &imgproc_canny, kernelName, globalThreads, localThreads, args, -1, -1); } diff --git a/modules/ocl/src/opencl/imgproc_canny.cl b/modules/ocl/src/opencl/imgproc_canny.cl index ceaaed1eb6..5402759e3c 100644 --- a/modules/ocl/src/opencl/imgproc_canny.cl +++ b/modules/ocl/src/opencl/imgproc_canny.cl @@ -297,6 +297,9 @@ calcMap map_step /= sizeof(*map); map_offset /= sizeof(*map); + mag += mag_offset; + map += map_offset; + __local float smem[18][18]; int gidx = get_global_id(0); @@ -389,7 +392,7 @@ edgesHysteresisLocal ( __global int * map, __global ushort2 * st, - volatile __global unsigned int * counter, + __global unsigned int * counter, int rows, int cols, int map_step, @@ -399,6 +402,8 @@ edgesHysteresisLocal map_step /= sizeof(*map); map_offset /= sizeof(*map); + map += map_offset; + __local int smem[18][18]; int gidx = get_global_id(0); @@ -416,12 +421,12 @@ edgesHysteresisLocal if(ly < 14) { smem[ly][lx] = - map[grp_idx + lx + min(grp_idy + ly, rows - 1) * map_step + map_offset]; + map[grp_idx + lx + min(grp_idy + ly, rows - 1) * map_step]; } if(ly < 4 && grp_idy + ly + 14 <= rows && grp_idx + lx <= cols) { smem[ly + 14][lx] = - map[grp_idx + lx + min(grp_idy + ly + 14, rows - 1) * map_step + map_offset]; + map[grp_idx + lx + min(grp_idy + ly + 14, rows - 1) * map_step]; } barrier(CLK_LOCAL_MEM_FENCE); @@ -482,14 +487,17 @@ edgesHysteresisLocal __constant int c_dx[8] = {-1, 0, 1, -1, 1, -1, 0, 1}; __constant int c_dy[8] = {-1, -1, -1, 0, 0, 1, 1, 1}; + #define stack_size 512 __kernel -void edgesHysteresisGlobal +void +__attribute__((reqd_work_group_size(128,1,1))) +edgesHysteresisGlobal ( __global int * map, __global ushort2 * st1, __global ushort2 * st2, - volatile __global int * counter, + __global int * counter, int rows, int cols, int count, @@ -501,6 +509,8 @@ void edgesHysteresisGlobal map_step /= sizeof(*map); map_offset /= sizeof(*map); + map += map_offset; + int gidx = get_global_id(0); int gidy = get_global_id(1); @@ -510,7 +520,7 @@ void edgesHysteresisGlobal int grp_idx = get_group_id(0); int grp_idy = get_group_id(1); - volatile __local unsigned int s_counter; + __local unsigned int s_counter; __local unsigned int s_ind; __local ushort2 s_st[stack_size]; @@ -564,9 +574,9 @@ void edgesHysteresisGlobal pos.x += c_dx[lidx & 7]; pos.y += c_dy[lidx & 7]; - if (map[pos.x + map_offset + pos.y * map_step] == 1) + if (map[pos.x + pos.y * map_step] == 1) { - map[pos.x + map_offset + pos.y * map_step] = 2; + map[pos.x + pos.y * map_step] = 2; ind = atomic_inc(&s_counter); @@ -621,6 +631,6 @@ void getEdges if(gidy < rows && gidx < cols) { - dst[gidx + gidy * dst_step] = (uchar)(-(map[gidx + 1 + (gidy + 1) * map_step] >> 1)); + dst[gidx + gidy * dst_step] = (uchar)(-(map[gidx + 1 + (gidy + 1) * map_step + map_offset] >> 1)); } } diff --git a/modules/ocl/test/test_canny.cpp b/modules/ocl/test/test_canny.cpp index cac6b66f51..10032e897c 100644 --- a/modules/ocl/test/test_canny.cpp +++ b/modules/ocl/test/test_canny.cpp @@ -45,7 +45,6 @@ #include "precomp.hpp" #ifdef HAVE_OPENCL -#define SHOW_RESULT 0 //////////////////////////////////////////////////////// // Canny @@ -59,13 +58,10 @@ PARAM_TEST_CASE(Canny, AppertureSize, L2gradient) bool useL2gradient; cv::Mat edges_gold; - //std::vector oclinfo; virtual void SetUp() { apperture_size = GET_PARAM(0); useL2gradient = GET_PARAM(1); - //int devnums = getDevice(oclinfo); - //CV_Assert(devnums > 0); } }; @@ -77,32 +73,18 @@ TEST_P(Canny, Accuracy) double low_thresh = 50.0; double high_thresh = 100.0; - cv::resize(img, img, cv::Size(512, 384)); cv::ocl::oclMat ocl_img = cv::ocl::oclMat(img); cv::ocl::oclMat edges; cv::ocl::Canny(ocl_img, edges, low_thresh, high_thresh, apperture_size, useL2gradient); - char filename [100]; - sprintf(filename, "G:/Valve_edges_a%d_L2Grad%d.jpg", apperture_size, (int)useL2gradient); - cv::Mat edges_gold; cv::Canny(img, edges_gold, low_thresh, high_thresh, apperture_size, useL2gradient); -#if SHOW_RESULT - cv::Mat edges_x2, ocl_edges(edges); - edges_x2.create(edges.rows, edges.cols * 2, edges.type()); - edges_x2.setTo(0); - cv::add(edges_gold, cv::Mat(edges_x2, cv::Rect(0, 0, edges_gold.cols, edges_gold.rows)), cv::Mat(edges_x2, cv::Rect(0, 0, edges_gold.cols, edges_gold.rows))); - cv::add(ocl_edges, cv::Mat(edges_x2, cv::Rect(edges_gold.cols, 0, edges_gold.cols, edges_gold.rows)), cv::Mat(edges_x2, cv::Rect(edges_gold.cols, 0, edges_gold.cols, edges_gold.rows))); - cv::namedWindow("Canny result (left: cpu, right: ocl)"); - cv::imshow("Canny result (left: cpu, right: ocl)", edges_x2); - cv::waitKey(); -#endif //OUTPUT_RESULT EXPECT_MAT_SIMILAR(edges_gold, edges, 1e-2); } -INSTANTIATE_TEST_CASE_P(GPU_ImgProc, Canny, testing::Combine( +INSTANTIATE_TEST_CASE_P(OCL_ImgProc, Canny, testing::Combine( testing::Values(AppertureSize(3), AppertureSize(5)), testing::Values(L2gradient(false), L2gradient(true)))); #endif \ No newline at end of file From a9b7ff41bd1e17977d75324783d6b61090cfa712 Mon Sep 17 00:00:00 2001 From: yao Date: Wed, 29 May 2013 15:48:56 +0800 Subject: [PATCH 041/178] adjust test cases --- modules/ocl/perf/perf_brute_force_matcher.cpp | 2 +- .../{perf_pyrlk.cpp => perf_opticalflow.cpp} | 70 ++++++++ .../{perf_pyrdown.cpp => perf_pyramid.cpp} | 43 +++++ modules/ocl/perf/perf_pyrup.cpp | 89 ---------- modules/ocl/perf/precomp.cpp | 156 ------------------ 5 files changed, 114 insertions(+), 246 deletions(-) rename modules/ocl/perf/{perf_pyrlk.cpp => perf_opticalflow.cpp} (76%) rename modules/ocl/perf/{perf_pyrdown.cpp => perf_pyramid.cpp} (76%) delete mode 100644 modules/ocl/perf/perf_pyrup.cpp diff --git a/modules/ocl/perf/perf_brute_force_matcher.cpp b/modules/ocl/perf/perf_brute_force_matcher.cpp index 9b2ce89a11..406b46a324 100644 --- a/modules/ocl/perf/perf_brute_force_matcher.cpp +++ b/modules/ocl/perf/perf_brute_force_matcher.cpp @@ -95,7 +95,7 @@ PERFTEST(BruteForceMatcher) GPU_FULL_ON; d_query.upload(query); d_train.upload(train); - d_matcher.match(d_query, d_train, matches[0]); + d_matcher.match(d_query, d_train, d_matches[0]); GPU_FULL_OFF; int diff = abs((int)d_matches[0].size() - (int)matches[0].size()); diff --git a/modules/ocl/perf/perf_pyrlk.cpp b/modules/ocl/perf/perf_opticalflow.cpp similarity index 76% rename from modules/ocl/perf/perf_pyrlk.cpp rename to modules/ocl/perf/perf_opticalflow.cpp index 76442d950a..9887970204 100644 --- a/modules/ocl/perf/perf_pyrlk.cpp +++ b/modules/ocl/perf/perf_opticalflow.cpp @@ -156,3 +156,73 @@ PERFTEST(PyrLKOpticalFlow) } } + + +PERFTEST(tvl1flow) +{ + cv::Mat frame0 = imread("rubberwhale1.png", cv::IMREAD_GRAYSCALE); + assert(!frame0.empty()); + + cv::Mat frame1 = imread("rubberwhale2.png", cv::IMREAD_GRAYSCALE); + assert(!frame1.empty()); + + cv::ocl::OpticalFlowDual_TVL1_OCL d_alg; + cv::ocl::oclMat d_flowx(frame0.size(), CV_32FC1); + cv::ocl::oclMat d_flowy(frame1.size(), CV_32FC1); + + cv::Ptr alg = cv::createOptFlow_DualTVL1(); + cv::Mat flow; + + + SUBTEST << frame0.cols << 'x' << frame0.rows << "; rubberwhale1.png; "<calc(frame0, frame1, flow); + + CPU_ON; + alg->calc(frame0, frame1, flow); + CPU_OFF; + + cv::Mat gold[2]; + cv::split(flow, gold); + + cv::ocl::oclMat d0(frame0.size(), CV_32FC1); + d0.upload(frame0); + cv::ocl::oclMat d1(frame1.size(), CV_32FC1); + d1.upload(frame1); + + WARMUP_ON; + d_alg(d0, d1, d_flowx, d_flowy); + WARMUP_OFF; +/* + double diff1 = 0.0, diff2 = 0.0; + if(ExceptedMatSimilar(gold[0], cv::Mat(d_flowx), 3e-3, diff1) == 1 + &&ExceptedMatSimilar(gold[1], cv::Mat(d_flowy), 3e-3, diff2) == 1) + TestSystem::instance().setAccurate(1); + else + TestSystem::instance().setAccurate(0); + + TestSystem::instance().setDiff(diff1); + TestSystem::instance().setDiff(diff2); +*/ + + + GPU_ON; + d_alg(d0, d1, d_flowx, d_flowy); + d_alg.collectGarbage(); + GPU_OFF; + + + cv::Mat flowx, flowy; + + GPU_FULL_ON; + d0.upload(frame0); + d1.upload(frame1); + d_alg(d0, d1, d_flowx, d_flowy); + d_alg.collectGarbage(); + d_flowx.download(flowx); + d_flowy.download(flowy); + GPU_FULL_OFF; + + TestSystem::instance().ExceptedMatSimilar(gold[0], flowx, 3e-3); + TestSystem::instance().ExceptedMatSimilar(gold[1], flowy, 3e-3); +} \ No newline at end of file diff --git a/modules/ocl/perf/perf_pyrdown.cpp b/modules/ocl/perf/perf_pyramid.cpp similarity index 76% rename from modules/ocl/perf/perf_pyrdown.cpp rename to modules/ocl/perf/perf_pyramid.cpp index b6eca45188..3b96251e5d 100644 --- a/modules/ocl/perf/perf_pyrdown.cpp +++ b/modules/ocl/perf/perf_pyramid.cpp @@ -86,4 +86,47 @@ PERFTEST(pyrDown) TestSystem::instance().ExpectedMatNear(dst, ocl_dst, dst.depth() == CV_32F ? 1e-4f : 1.0f); } } +} + +///////////// pyrUp //////////////////////// +PERFTEST(pyrUp) +{ + Mat src, dst, ocl_dst; + int all_type[] = {CV_8UC1, CV_8UC4}; + std::string type_name[] = {"CV_8UC1", "CV_8UC4"}; + + for (int size = 500; size <= 2000; size *= 2) + { + for (size_t j = 0; j < sizeof(all_type) / sizeof(int); j++) + { + SUBTEST << size << 'x' << size << "; " << type_name[j] ; + + gen(src, size, size, all_type[j], 0, 256); + + pyrUp(src, dst); + + CPU_ON; + pyrUp(src, dst); + CPU_OFF; + + ocl::oclMat d_src(src); + ocl::oclMat d_dst; + + WARMUP_ON; + ocl::pyrUp(d_src, d_dst); + WARMUP_OFF; + + GPU_ON; + ocl::pyrUp(d_src, d_dst); + GPU_OFF; + + GPU_FULL_ON; + d_src.upload(src); + ocl::pyrUp(d_src, d_dst); + d_dst.download(ocl_dst); + GPU_FULL_OFF; + + TestSystem::instance().ExpectedMatNear(dst, ocl_dst, (src.depth() == CV_32F ? 1e-4f : 1.0)); + } + } } \ No newline at end of file diff --git a/modules/ocl/perf/perf_pyrup.cpp b/modules/ocl/perf/perf_pyrup.cpp deleted file mode 100644 index bfefe5e0c2..0000000000 --- a/modules/ocl/perf/perf_pyrup.cpp +++ /dev/null @@ -1,89 +0,0 @@ -/*M/////////////////////////////////////////////////////////////////////////////////////// -// -// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. -// -// By downloading, copying, installing or using the software you agree to this license. -// If you do not agree to this license, do not download, install, -// copy or use the software. -// -// -// License Agreement -// For Open Source Computer Vision Library -// -// Copyright (C) 2010-2012, Multicoreware, Inc., all rights reserved. -// Copyright (C) 2010-2012, Advanced Micro Devices, Inc., all rights reserved. -// Third party copyrights are property of their respective owners. -// -// @Authors -// Fangfang Bai, fangfang@multicorewareinc.com -// Jin Ma, jin@multicorewareinc.com -// -// Redistribution and use in source and binary forms, with or without modification, -// are permitted provided that the following conditions are met: -// -// * Redistribution's of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// * Redistribution's in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other oclMaterials provided with the distribution. -// -// * The name of the copyright holders may not be used to endorse or promote products -// derived from this software without specific prior written permission. -// -// This software is provided by the copyright holders and contributors as is and -// any express or implied warranties, including, but not limited to, the implied -// warranties of merchantability and fitness for a particular purpose are disclaimed. -// In no event shall the Intel Corporation or contributors be liable for any direct, -// indirect, incidental, special, exemplary, or consequential damages -// (including, but not limited to, procurement of substitute goods or services; -// loss of use, data, or profits; or business interruption) however caused -// and on any theory of liability, whether in contract, strict liability, -// or tort (including negligence or otherwise) arising in any way out of -// the use of this software, even if advised of the possibility of such damage. -// -//M*/ -#include "precomp.hpp" - -///////////// pyrUp //////////////////////// -PERFTEST(pyrUp) -{ - Mat src, dst, ocl_dst; - int all_type[] = {CV_8UC1, CV_8UC4}; - std::string type_name[] = {"CV_8UC1", "CV_8UC4"}; - - for (int size = 500; size <= 2000; size *= 2) - { - for (size_t j = 0; j < sizeof(all_type) / sizeof(int); j++) - { - SUBTEST << size << 'x' << size << "; " << type_name[j] ; - - gen(src, size, size, all_type[j], 0, 256); - - pyrUp(src, dst); - - CPU_ON; - pyrUp(src, dst); - CPU_OFF; - - ocl::oclMat d_src(src); - ocl::oclMat d_dst; - - WARMUP_ON; - ocl::pyrUp(d_src, d_dst); - WARMUP_OFF; - - GPU_ON; - ocl::pyrUp(d_src, d_dst); - GPU_OFF; - - GPU_FULL_ON; - d_src.upload(src); - ocl::pyrUp(d_src, d_dst); - d_dst.download(ocl_dst); - GPU_FULL_OFF; - - TestSystem::instance().ExpectedMatNear(dst, ocl_dst, (src.depth() == CV_32F ? 1e-4f : 1.0)); - } - } -} \ No newline at end of file diff --git a/modules/ocl/perf/precomp.cpp b/modules/ocl/perf/precomp.cpp index 616ef4dfd4..71a13a1ee2 100644 --- a/modules/ocl/perf/precomp.cpp +++ b/modules/ocl/perf/precomp.cpp @@ -471,134 +471,6 @@ void gen(Mat &mat, int rows, int cols, int type, Scalar low, Scalar high) RNG rng(0); rng.fill(mat, RNG::UNIFORM, low, high); } -#if 0 -void gen(Mat &mat, int rows, int cols, int type, int low, int high, int n) -{ - assert(n > 0&&n <= cols * rows); - assert(type == CV_8UC1||type == CV_8UC3||type == CV_8UC4 - ||type == CV_32FC1||type == CV_32FC3||type == CV_32FC4); - - RNG rng; - //generate random position without duplication - std::vector pos; - for(int i = 0; i < cols * rows; i++) - { - pos.push_back(i); - } - - for(int i = 0; i < cols * rows; i++) - { - int temp = i + rng.uniform(0, cols * rows - 1 - i); - int temp1 = pos[temp]; - pos[temp]= pos[i]; - pos[i] = temp1; - } - - std::vector selected_pos; - for(int i = 0; i < n; i++) - { - selected_pos.push_back(pos[i]); - } - - pos.clear(); - //end of generating random y without duplication - - if(type == CV_8UC1) - { - typedef struct coorStruct_ - { - int x; - int y; - uchar xy; - }coorStruct; - - coorStruct coor_struct; - - std::vector coor; - - for(int i = 0; i < n; i++) - { - coor_struct.x = -1; - coor_struct.y = -1; - coor_struct.xy = (uchar)rng.uniform(low, high); - coor.push_back(coor_struct); - } - - for(int i = 0; i < n; i++) - { - coor[i].y = selected_pos[i]/cols; - coor[i].x = selected_pos[i]%cols; - } - selected_pos.clear(); - - mat.create(rows, cols, type); - mat.setTo(0); - - for(int i = 0; i < n; i++) - { - mat.at(coor[i].y, coor[i].x) = coor[i].xy; - } - } - - if(type == CV_8UC4 || type == CV_8UC3) - { - mat.create(rows, cols, type); - mat.setTo(0); - - typedef struct Coor - { - int x; - int y; - - uchar r; - uchar g; - uchar b; - uchar alpha; - }coor; - - std::vector coor_vect; - - coor xy_coor; - - for(int i = 0; i < n; i++) - { - xy_coor.r = (uchar)rng.uniform(low, high); - xy_coor.g = (uchar)rng.uniform(low, high); - xy_coor.b = (uchar)rng.uniform(low, high); - if(type == CV_8UC4) - xy_coor.alpha = (uchar)rng.uniform(low, high); - - coor_vect.push_back(xy_coor); - } - - for(int i = 0; i < n; i++) - { - coor_vect[i].y = selected_pos[i]/((int)mat.step1()/mat.elemSize()); - coor_vect[i].x = selected_pos[i]%((int)mat.step1()/mat.elemSize()); - //printf("coor_vect[%d] = (%d, %d)\n", i, coor_vect[i].y, coor_vect[i].x); - } - - if(type == CV_8UC4) - { - for(int i = 0; i < n; i++) - { - mat.at(coor_vect[i].y, 4 * coor_vect[i].x) = coor_vect[i].r; - mat.at(coor_vect[i].y, 4 * coor_vect[i].x + 1) = coor_vect[i].g; - mat.at(coor_vect[i].y, 4 * coor_vect[i].x + 2) = coor_vect[i].b; - mat.at(coor_vect[i].y, 4 * coor_vect[i].x + 3) = coor_vect[i].alpha; - } - }else if(type == CV_8UC3) - { - for(int i = 0; i < n; i++) - { - mat.at(coor_vect[i].y, 3 * coor_vect[i].x) = coor_vect[i].r; - mat.at(coor_vect[i].y, 3 * coor_vect[i].x + 1) = coor_vect[i].g; - mat.at(coor_vect[i].y, 3 * coor_vect[i].x + 2) = coor_vect[i].b; - } - } - } -} -#endif string abspath(const string &relpath) { @@ -631,34 +503,6 @@ double checkSimilarity(const Mat &m1, const Mat &m2) return std::abs(diff.at(0, 0) - 1.f); } -/* -int ExpectedMatNear(cv::Mat dst, cv::Mat cpu_dst, double eps) -{ - assert(dst.type() == cpu_dst.type()); - assert(dst.size() == cpu_dst.size()); - if(checkNorm(cv::Mat(dst), cv::Mat(cpu_dst)) < eps ||checkNorm(cv::Mat(dst), cv::Mat(cpu_dst)) == eps) - return 1; - return 0; -} - -int ExceptDoubleNear(double val1, double val2, double abs_error) -{ - const double diff = fabs(val1 - val2); - if (diff <= abs_error) - return 1; - - return 0; -} -/* -int ExceptedMatSimilar(cv::Mat dst, cv::Mat cpu_dst, double eps) -{ - assert(dst.type() == cpu_dst.type()); - assert(dst.size() == cpu_dst.size()); - if(checkSimilarity(cv::Mat(cpu_dst), cv::Mat(dst)) <= eps) - return 1; - return 0; -} -*/ From e28f6fae4956fea876c31fef558995a63cb06689 Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Wed, 29 May 2013 12:51:26 +0400 Subject: [PATCH 042/178] fixing #3027 (searching JNI even if no ant), fixing java tests status message (ON/OFF) --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 93549c9430..327b3610a8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -402,7 +402,7 @@ if(ANDROID) if(NOT ANDROID_TOOLS_Pkg_Revision GREATER 13) message(WARNING "OpenCV requires Android SDK tools revision 14 or newer. Otherwise tests and samples will no be compiled.") endif() -elseif(ANT_EXECUTABLE) +else() find_package(JNI) endif() @@ -830,7 +830,7 @@ status(" ant:" ANT_EXECUTABLE THEN "${ANT_EXECUTABLE} (ver ${A if(NOT ANDROID) status(" JNI:" JNI_INCLUDE_DIRS THEN "${JNI_INCLUDE_DIRS}" ELSE NO) endif() -status(" Java tests:" BUILD_TESTS AND (NOT ANDROID OR CAN_BUILD_ANDROID_PROJECTS) THEN YES ELSE NO) +status(" Java tests:" BUILD_TESTS AND (CAN_BUILD_ANDROID_PROJECTS OR HAVE_opencv_java) THEN YES ELSE NO) # ========================== documentation ========================== if(BUILD_DOCS) From 6b1c28ce6d0d4c6be25cbc29b64b999d1b9314fc Mon Sep 17 00:00:00 2001 From: Vladislav Vinogradov Date: Wed, 29 May 2013 17:38:32 +0400 Subject: [PATCH 043/178] fixed some gpu tests (different rounding results due to float arithmetics) --- modules/gpu/test/test_core.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/gpu/test/test_core.cpp b/modules/gpu/test/test_core.cpp index 1bc952c7a1..b622ad8ea9 100644 --- a/modules/gpu/test/test_core.cpp +++ b/modules/gpu/test/test_core.cpp @@ -352,7 +352,7 @@ GPU_TEST_P(Add_Scalar, WithOutMask) cv::Mat dst_gold(size, depth.second, cv::Scalar::all(0)); cv::add(mat, val, dst_gold, cv::noArray(), depth.second); - EXPECT_MAT_NEAR(dst_gold, dst, depth.first >= CV_32F || depth.second >= CV_32F ? 1e-4 : 0.0); + EXPECT_MAT_NEAR(dst_gold, dst, depth.first >= CV_32F || depth.second >= CV_32F ? 1e-4 : 1.0); } } @@ -383,7 +383,7 @@ GPU_TEST_P(Add_Scalar, WithMask) cv::Mat dst_gold(size, depth.second, cv::Scalar::all(0)); cv::add(mat, val, dst_gold, mask, depth.second); - EXPECT_MAT_NEAR(dst_gold, dst, depth.first >= CV_32F || depth.second >= CV_32F ? 1e-4 : 0.0); + EXPECT_MAT_NEAR(dst_gold, dst, depth.first >= CV_32F || depth.second >= CV_32F ? 1e-4 : 1.0); } } @@ -567,7 +567,7 @@ GPU_TEST_P(Subtract_Scalar, WithOutMask) cv::Mat dst_gold(size, depth.second, cv::Scalar::all(0)); cv::subtract(mat, val, dst_gold, cv::noArray(), depth.second); - EXPECT_MAT_NEAR(dst_gold, dst, depth.first >= CV_32F || depth.second >= CV_32F ? 1e-4 : 0.0); + EXPECT_MAT_NEAR(dst_gold, dst, depth.first >= CV_32F || depth.second >= CV_32F ? 1e-4 : 1.0); } } @@ -598,7 +598,7 @@ GPU_TEST_P(Subtract_Scalar, WithMask) cv::Mat dst_gold(size, depth.second, cv::Scalar::all(0)); cv::subtract(mat, val, dst_gold, mask, depth.second); - EXPECT_MAT_NEAR(dst_gold, dst, depth.first >= CV_32F || depth.second >= CV_32F ? 1e-4 : 0.0); + EXPECT_MAT_NEAR(dst_gold, dst, depth.first >= CV_32F || depth.second >= CV_32F ? 1e-4 : 1.0); } } @@ -2148,7 +2148,7 @@ GPU_TEST_P(Min, Scalar) cv::Mat dst_gold = cv::min(src, val); - EXPECT_MAT_NEAR(dst_gold, dst, 0.0); + EXPECT_MAT_NEAR(dst_gold, dst, depth < CV_32F ? 1.0 : 1e-5); } } @@ -2231,7 +2231,7 @@ GPU_TEST_P(Max, Scalar) cv::Mat dst_gold = cv::max(src, val); - EXPECT_MAT_NEAR(dst_gold, dst, 0.0); + EXPECT_MAT_NEAR(dst_gold, dst, depth < CV_32F ? 1.0 : 1e-5); } } From fd7ba355ee99b923f975f33d9fa431f2c50c5b04 Mon Sep 17 00:00:00 2001 From: peng xiao Date: Thu, 30 May 2013 14:01:19 +0800 Subject: [PATCH 044/178] Add non-stump based ocl Haar cascade classifier support. For example, haarcascade_frontalface_alt2.xml is now supported. Note that classifier's pattern of a cascade file must be consistent, i.e., all trees must either have two nodes or one node, otherwise unexpected results will occur. Other fixes: Test cases are updated. Some unused codes are removed. Fix some problems of haar when using OclCascadeClassifierBuf. --- modules/ocl/include/opencv2/ocl/ocl.hpp | 2 +- modules/ocl/src/haar.cpp | 639 +++++------------- modules/ocl/src/opencl/haarobjectdetect.cl | 252 ++----- .../src/opencl/haarobjectdetect_scaled2.cl | 88 ++- modules/ocl/test/test_haar.cpp | 34 +- 5 files changed, 337 insertions(+), 678 deletions(-) diff --git a/modules/ocl/include/opencv2/ocl/ocl.hpp b/modules/ocl/include/opencv2/ocl/ocl.hpp index f9fb4b44e5..785248cfc5 100644 --- a/modules/ocl/include/opencv2/ocl/ocl.hpp +++ b/modules/ocl/include/opencv2/ocl/ocl.hpp @@ -817,7 +817,7 @@ namespace cv OclCascadeClassifierBuf() : m_flags(0), initialized(false), m_scaleFactor(0), buffers(NULL) {} - ~OclCascadeClassifierBuf() {} + ~OclCascadeClassifierBuf() { release(); } void detectMultiScale(oclMat &image, CV_OUT std::vector& faces, double scaleFactor = 1.1, int minNeighbors = 3, int flags = 0, diff --git a/modules/ocl/src/haar.cpp b/modules/ocl/src/haar.cpp index 5afe5423ed..565270cdc1 100644 --- a/modules/ocl/src/haar.cpp +++ b/modules/ocl/src/haar.cpp @@ -137,47 +137,22 @@ struct CvHidHaarClassifierCascade }; typedef struct { - //int rows; - //int ystep; int width_height; - //int height; int grpnumperline_totalgrp; - //int totalgrp; int imgoff; float factor; } detect_piramid_info; - -#if defined WIN32 && !defined __MINGW__ && !defined __MINGW32__ +#ifdef WIN32 #define _ALIGNED_ON(_ALIGNMENT) __declspec(align(_ALIGNMENT)) -typedef _ALIGNED_ON(128) struct GpuHidHaarFeature -{ - _ALIGNED_ON(32) struct - { - _ALIGNED_ON(4) int p0 ; - _ALIGNED_ON(4) int p1 ; - _ALIGNED_ON(4) int p2 ; - _ALIGNED_ON(4) int p3 ; - _ALIGNED_ON(4) float weight ; - } - /*_ALIGNED_ON(32)*/ rect[CV_HAAR_FEATURE_MAX] ; -} -GpuHidHaarFeature; - typedef _ALIGNED_ON(128) struct GpuHidHaarTreeNode { _ALIGNED_ON(64) int p[CV_HAAR_FEATURE_MAX][4]; - //_ALIGNED_ON(16) int p1[CV_HAAR_FEATURE_MAX] ; - //_ALIGNED_ON(16) int p2[CV_HAAR_FEATURE_MAX] ; - //_ALIGNED_ON(16) int p3[CV_HAAR_FEATURE_MAX] ; - /*_ALIGNED_ON(16)*/ float weight[CV_HAAR_FEATURE_MAX] ; - /*_ALIGNED_ON(4)*/ float threshold ; - _ALIGNED_ON(8) float alpha[2] ; + _ALIGNED_ON(16) float alpha[3] ; _ALIGNED_ON(4) int left ; _ALIGNED_ON(4) int right ; - // GpuHidHaarFeature feature __attribute__((aligned (128))); } GpuHidHaarTreeNode; @@ -185,7 +160,6 @@ GpuHidHaarTreeNode; typedef _ALIGNED_ON(32) struct GpuHidHaarClassifier { _ALIGNED_ON(4) int count; - //CvHaarFeature* orig_feature; _ALIGNED_ON(8) GpuHidHaarTreeNode *node ; _ALIGNED_ON(8) float *alpha ; } @@ -220,32 +194,16 @@ typedef _ALIGNED_ON(64) struct GpuHidHaarClassifierCascade _ALIGNED_ON(4) int p2 ; _ALIGNED_ON(4) int p3 ; _ALIGNED_ON(4) float inv_window_area ; - // GpuHidHaarStageClassifier* stage_classifier __attribute__((aligned (8))); } GpuHidHaarClassifierCascade; #else #define _ALIGNED_ON(_ALIGNMENT) __attribute__((aligned(_ALIGNMENT) )) -typedef struct _ALIGNED_ON(128) GpuHidHaarFeature -{ - struct _ALIGNED_ON(32) -{ - int p0 _ALIGNED_ON(4); - int p1 _ALIGNED_ON(4); - int p2 _ALIGNED_ON(4); - int p3 _ALIGNED_ON(4); - float weight _ALIGNED_ON(4); -} -rect[CV_HAAR_FEATURE_MAX] _ALIGNED_ON(32); -} -GpuHidHaarFeature; - - typedef struct _ALIGNED_ON(128) GpuHidHaarTreeNode { int p[CV_HAAR_FEATURE_MAX][4] _ALIGNED_ON(64); float weight[CV_HAAR_FEATURE_MAX];// _ALIGNED_ON(16); float threshold;// _ALIGNED_ON(4); - float alpha[2] _ALIGNED_ON(8); + float alpha[3] _ALIGNED_ON(16); int left _ALIGNED_ON(4); int right _ALIGNED_ON(4); } @@ -288,7 +246,6 @@ typedef struct _ALIGNED_ON(64) GpuHidHaarClassifierCascade int p2 _ALIGNED_ON(4); int p3 _ALIGNED_ON(4); float inv_window_area _ALIGNED_ON(4); - // GpuHidHaarStageClassifier* stage_classifier __attribute__((aligned (8))); } GpuHidHaarClassifierCascade; #endif @@ -296,36 +253,6 @@ const int icv_object_win_border = 1; const float icv_stage_threshold_bias = 0.0001f; double globaltime = 0; - -// static CvHaarClassifierCascade * gpuCreateHaarClassifierCascade( int stage_count ) -// { -// CvHaarClassifierCascade *cascade = 0; - -// int block_size = sizeof(*cascade) + stage_count * sizeof(*cascade->stage_classifier); - -// if( stage_count <= 0 ) -// CV_Error( CV_StsOutOfRange, "Number of stages should be positive" ); - -// cascade = (CvHaarClassifierCascade *)cvAlloc( block_size ); -// memset( cascade, 0, block_size ); - -// cascade->stage_classifier = (CvHaarStageClassifier *)(cascade + 1); -// cascade->flags = CV_HAAR_MAGIC_VAL; -// cascade->count = stage_count; - -// return cascade; -// } - -//static int globalcounter = 0; - -// static void gpuReleaseHidHaarClassifierCascade( GpuHidHaarClassifierCascade **_cascade ) -// { -// if( _cascade && *_cascade ) -// { -// cvFree( _cascade ); -// } -// } - /* create more efficient internal representation of haar classifier cascade */ static GpuHidHaarClassifierCascade * gpuCreateHidHaarClassifierCascade( CvHaarClassifierCascade *cascade, int *size, int *totalclassifier) { @@ -441,24 +368,12 @@ static GpuHidHaarClassifierCascade * gpuCreateHidHaarClassifierCascade( CvHaarCl hid_stage_classifier->two_rects = 1; haar_classifier_ptr += stage_classifier->count; - /* - hid_stage_classifier->parent = (stage_classifier->parent == -1) - ? NULL : stage_classifier_ptr + stage_classifier->parent; - hid_stage_classifier->next = (stage_classifier->next == -1) - ? NULL : stage_classifier_ptr + stage_classifier->next; - hid_stage_classifier->child = (stage_classifier->child == -1) - ? NULL : stage_classifier_ptr + stage_classifier->child; - - out->is_tree |= hid_stage_classifier->next != NULL; - */ - for( j = 0; j < stage_classifier->count; j++ ) { CvHaarClassifier *classifier = stage_classifier->classifier + j; GpuHidHaarClassifier *hid_classifier = hid_stage_classifier->classifier + j; int node_count = classifier->count; - // float* alpha_ptr = (float*)(haar_node_ptr + node_count); float *alpha_ptr = &haar_node_ptr->alpha[0]; hid_classifier->count = node_count; @@ -485,16 +400,12 @@ static GpuHidHaarClassifierCascade * gpuCreateHidHaarClassifierCascade( CvHaarCl node->p[2][3] = 0; node->weight[2] = 0; } - // memset( &(node->feature.rect[2]), 0, sizeof(node->feature.rect[2]) ); else hid_stage_classifier->two_rects = 0; + + memcpy( node->alpha, classifier->alpha, (node_count + 1)*sizeof(alpha_ptr[0])); + haar_node_ptr = haar_node_ptr + 1; } - - memcpy( alpha_ptr, classifier->alpha, (node_count + 1)*sizeof(alpha_ptr[0])); - haar_node_ptr = haar_node_ptr + 1; - // (GpuHidHaarTreeNode*)cvAlignPtr(alpha_ptr+node_count+1, sizeof(void*)); - // (GpuHidHaarTreeNode*)(alpha_ptr+node_count+1); - out->is_stump_based &= node_count == 1; } } @@ -507,25 +418,19 @@ static GpuHidHaarClassifierCascade * gpuCreateHidHaarClassifierCascade( CvHaarCl #define sum_elem_ptr(sum,row,col) \ - ((sumtype*)CV_MAT_ELEM_PTR_FAST((sum),(row),(col),sizeof(sumtype))) + ((sumtype*)CV_MAT_ELEM_PTR_FAST((sum),(row),(col),sizeof(sumtype))) #define sqsum_elem_ptr(sqsum,row,col) \ - ((sqsumtype*)CV_MAT_ELEM_PTR_FAST((sqsum),(row),(col),sizeof(sqsumtype))) + ((sqsumtype*)CV_MAT_ELEM_PTR_FAST((sqsum),(row),(col),sizeof(sqsumtype))) #define calc_sum(rect,offset) \ - ((rect).p0[offset] - (rect).p1[offset] - (rect).p2[offset] + (rect).p3[offset]) + ((rect).p0[offset] - (rect).p1[offset] - (rect).p2[offset] + (rect).p3[offset]) static void gpuSetImagesForHaarClassifierCascade( CvHaarClassifierCascade *_cascade, - /* const CvArr* _sum, - const CvArr* _sqsum, - const CvArr* _tilted_sum,*/ double scale, int step) { - // CvMat sum_stub, *sum = (CvMat*)_sum; - // CvMat sqsum_stub, *sqsum = (CvMat*)_sqsum; - // CvMat tilted_stub, *tilted = (CvMat*)_tilted_sum; GpuHidHaarClassifierCascade *cascade; int coi0 = 0, coi1 = 0; int i; @@ -541,61 +446,25 @@ static void gpuSetImagesForHaarClassifierCascade( CvHaarClassifierCascade *_casc if( scale <= 0 ) CV_Error( CV_StsOutOfRange, "Scale must be positive" ); - // sum = cvGetMat( sum, &sum_stub, &coi0 ); - // sqsum = cvGetMat( sqsum, &sqsum_stub, &coi1 ); - if( coi0 || coi1 ) CV_Error( CV_BadCOI, "COI is not supported" ); - // if( !CV_ARE_SIZES_EQ( sum, sqsum )) - // CV_Error( CV_StsUnmatchedSizes, "All integral images must have the same size" ); - - // if( CV_MAT_TYPE(sqsum->type) != CV_64FC1 || - // CV_MAT_TYPE(sum->type) != CV_32SC1 ) - // CV_Error( CV_StsUnsupportedFormat, - // "Only (32s, 64f, 32s) combination of (sum,sqsum,tilted_sum) formats is allowed" ); - if( !_cascade->hid_cascade ) gpuCreateHidHaarClassifierCascade(_cascade, &datasize, &total); cascade = (GpuHidHaarClassifierCascade *) _cascade->hid_cascade; stage_classifier = (GpuHidHaarStageClassifier *) (cascade + 1); - if( cascade->has_tilted_features ) - { - // tilted = cvGetMat( tilted, &tilted_stub, &coi1 ); - - // if( CV_MAT_TYPE(tilted->type) != CV_32SC1 ) - // CV_Error( CV_StsUnsupportedFormat, - // "Only (32s, 64f, 32s) combination of (sum,sqsum,tilted_sum) formats is allowed" ); - - // if( sum->step != tilted->step ) - // CV_Error( CV_StsUnmatchedSizes, - // "Sum and tilted_sum must have the same stride (step, widthStep)" ); - - // if( !CV_ARE_SIZES_EQ( sum, tilted )) - // CV_Error( CV_StsUnmatchedSizes, "All integral images must have the same size" ); - // cascade->tilted = *tilted; - } - _cascade->scale = scale; _cascade->real_window_size.width = cvRound( _cascade->orig_window_size.width * scale ); _cascade->real_window_size.height = cvRound( _cascade->orig_window_size.height * scale ); - //cascade->sum = *sum; - //cascade->sqsum = *sqsum; - equRect.x = equRect.y = cvRound(scale); equRect.width = cvRound((_cascade->orig_window_size.width - 2) * scale); equRect.height = cvRound((_cascade->orig_window_size.height - 2) * scale); weight_scale = 1. / (equRect.width * equRect.height); cascade->inv_window_area = weight_scale; - // cascade->pq0 = equRect.y * step + equRect.x; - // cascade->pq1 = equRect.y * step + equRect.x + equRect.width ; - // cascade->pq2 = (equRect.y + equRect.height)*step + equRect.x; - // cascade->pq3 = (equRect.y + equRect.height)*step + equRect.x + equRect.width ; - cascade->pq0 = equRect.x; cascade->pq1 = equRect.y; cascade->pq2 = equRect.x + equRect.width; @@ -618,10 +487,6 @@ static void gpuSetImagesForHaarClassifierCascade( CvHaarClassifierCascade *_casc { CvHaarFeature *feature = &_cascade->stage_classifier[i].classifier[j].haar_feature[l]; - /* GpuHidHaarClassifier* classifier = - cascade->stage_classifier[i].classifier + j; */ - //GpuHidHaarFeature* hidfeature = - // &cascade->stage_classifier[i].classifier[j].node[l].feature; GpuHidHaarTreeNode *hidnode = &stage_classifier[i].classifier[j].node[l]; double sum0 = 0, area0 = 0; CvRect r[3]; @@ -636,8 +501,6 @@ static void gpuSetImagesForHaarClassifierCascade( CvHaarClassifierCascade *_casc /* align blocks */ for( k = 0; k < CV_HAAR_FEATURE_MAX; k++ ) { - //if( !hidfeature->rect[k].p0 ) - // break; if(!hidnode->p[k][0]) break; r[k] = feature->rect[k].r; @@ -717,15 +580,6 @@ static void gpuSetImagesForHaarClassifierCascade( CvHaarClassifierCascade *_casc if( !feature->tilted ) { - /* hidfeature->rect[k].p0 = tr.y * sum->cols + tr.x; - hidfeature->rect[k].p1 = tr.y * sum->cols + tr.x + tr.width; - hidfeature->rect[k].p2 = (tr.y + tr.height) * sum->cols + tr.x; - hidfeature->rect[k].p3 = (tr.y + tr.height) * sum->cols + tr.x + tr.width; - */ - /*hidnode->p0[k] = tr.y * step + tr.x; - hidnode->p1[k] = tr.y * step + tr.x + tr.width; - hidnode->p2[k] = (tr.y + tr.height) * step + tr.x; - hidnode->p3[k] = (tr.y + tr.height) * step + tr.x + tr.width;*/ hidnode->p[k][0] = tr.x; hidnode->p[k][1] = tr.y; hidnode->p[k][2] = tr.x + tr.width; @@ -733,37 +587,24 @@ static void gpuSetImagesForHaarClassifierCascade( CvHaarClassifierCascade *_casc } else { - /* hidfeature->rect[k].p2 = (tr.y + tr.width) * tilted->cols + tr.x + tr.width; - hidfeature->rect[k].p3 = (tr.y + tr.width + tr.height) * tilted->cols + tr.x + tr.width - tr.height; - hidfeature->rect[k].p0 = tr.y * tilted->cols + tr.x; - hidfeature->rect[k].p1 = (tr.y + tr.height) * tilted->cols + tr.x - tr.height; - */ - hidnode->p[k][2] = (tr.y + tr.width) * step + tr.x + tr.width; hidnode->p[k][3] = (tr.y + tr.width + tr.height) * step + tr.x + tr.width - tr.height; hidnode->p[k][0] = tr.y * step + tr.x; hidnode->p[k][1] = (tr.y + tr.height) * step + tr.x - tr.height; } - - //hidfeature->rect[k].weight = (float)(feature->rect[k].weight * correction_ratio); hidnode->weight[k] = (float)(feature->rect[k].weight * correction_ratio); if( k == 0 ) area0 = tr.width * tr.height; else - //sum0 += hidfeature->rect[k].weight * tr.width * tr.height; sum0 += hidnode->weight[k] * tr.width * tr.height; } - - // hidfeature->rect[0].weight = (float)(-sum0/area0); hidnode->weight[0] = (float)(-sum0 / area0); } /* l */ } /* j */ } } -static void gpuSetHaarClassifierCascade( CvHaarClassifierCascade *_cascade - /*double scale=0.0,*/ - /*int step*/) +static void gpuSetHaarClassifierCascade( CvHaarClassifierCascade *_cascade) { GpuHidHaarClassifierCascade *cascade; int i; @@ -817,11 +658,7 @@ static void gpuSetHaarClassifierCascade( CvHaarClassifierCascade *_cascade if(!hidnode->p[k][0]) break; r[k] = feature->rect[k].r; - // base_w = (int)CV_IMIN( (unsigned)base_w, (unsigned)(r[k].width-1) ); - // base_w = (int)CV_IMIN( (unsigned)base_w, (unsigned)(r[k].x - r[0].x-1) ); - // base_h = (int)CV_IMIN( (unsigned)base_h, (unsigned)(r[k].height-1) ); - // base_h = (int)CV_IMIN( (unsigned)base_h, (unsigned)(r[k].y - r[0].y-1) ); - } + } nr = k; for( k = 0; k < nr; k++ ) @@ -839,7 +676,6 @@ static void gpuSetHaarClassifierCascade( CvHaarClassifierCascade *_cascade hidnode->p[k][3] = tr.height; hidnode->weight[k] = (float)(feature->rect[k].weight * correction_ratio); } - //hidnode->weight[0]=(float)(-sum0/area0); } /* l */ } /* j */ } @@ -852,7 +688,6 @@ CvSeq *cv::ocl::OclCascadeClassifier::oclHaarDetectObjects( oclMat &gimg, CvMemS const double GROUP_EPS = 0.2; CvSeq *result_seq = 0; - cv::Ptr temp_storage; cv::ConcurrentRectVector allCandidates; std::vector rectList; @@ -910,6 +745,7 @@ CvSeq *cv::ocl::OclCascadeClassifier::oclHaarDetectObjects( oclMat &gimg, CvMemS if( gimg.cols < minSize.width || gimg.rows < minSize.height ) CV_Error(CV_StsError, "Image too small"); + cl_command_queue qu = reinterpret_cast(Context::getContext()->oclCommandQueue()); if( (flags & CV_HAAR_SCALE_IMAGE) ) { CvSize winSize0 = cascade->orig_window_size; @@ -952,7 +788,7 @@ CvSeq *cv::ocl::OclCascadeClassifier::oclHaarDetectObjects( oclMat &gimg, CvMemS size_t blocksize = 8; size_t localThreads[3] = { blocksize, blocksize , 1 }; - size_t globalThreads[3] = { grp_per_CU * gsum.clCxt->computeUnits() *localThreads[0], + size_t globalThreads[3] = { grp_per_CU *(gsum.clCxt->computeUnits()) *localThreads[0], localThreads[1], 1 }; int outputsz = 256 * globalThreads[0] / localThreads[0]; @@ -997,7 +833,6 @@ CvSeq *cv::ocl::OclCascadeClassifier::oclHaarDetectObjects( oclMat &gimg, CvMemS gpuSetImagesForHaarClassifierCascade( cascade, 1., gsum.step / 4 ); stagebuffer = openCLCreateBuffer(gsum.clCxt, CL_MEM_READ_ONLY, sizeof(GpuHidHaarStageClassifier) * gcascade->count); - cl_command_queue qu = (cl_command_queue)gsum.clCxt->oclCommandQueue(); openCLSafeCall(clEnqueueWriteBuffer(qu, stagebuffer, 1, 0, sizeof(GpuHidHaarStageClassifier)*gcascade->count, stage, 0, NULL, NULL)); nodebuffer = openCLCreateBuffer(gsum.clCxt, CL_MEM_READ_ONLY, nodenum * sizeof(GpuHidHaarTreeNode)); @@ -1044,7 +879,9 @@ CvSeq *cv::ocl::OclCascadeClassifier::oclHaarDetectObjects( oclMat &gimg, CvMemS args.push_back ( make_pair(sizeof(cl_int4) , (void *)&pq )); args.push_back ( make_pair(sizeof(cl_float) , (void *)&correction )); - openCLExecuteKernel(gsum.clCxt, &haarobjectdetect, "gpuRunHaarClassifierCascade", globalThreads, localThreads, args, -1, -1); + const char * build_options = gcascade->is_stump_based ? "-D STUMP_BASED=1" : "-D STUMP_BASED=0"; + + openCLExecuteKernel(gsum.clCxt, &haarobjectdetect, "gpuRunHaarClassifierCascade", globalThreads, localThreads, args, -1, -1, build_options); openCLReadBuffer( gsum.clCxt, candidatebuffer, candidate, 4 * sizeof(int)*outputsz ); @@ -1059,6 +896,7 @@ CvSeq *cv::ocl::OclCascadeClassifier::oclHaarDetectObjects( oclMat &gimg, CvMemS openCLSafeCall(clReleaseMemObject(scaleinfobuffer)); openCLSafeCall(clReleaseMemObject(nodebuffer)); openCLSafeCall(clReleaseMemObject(candidatebuffer)); + } else { @@ -1118,7 +956,6 @@ CvSeq *cv::ocl::OclCascadeClassifier::oclHaarDetectObjects( oclMat &gimg, CvMemS sizeof(GpuHidHaarStageClassifier) * gcascade->count - sizeof(GpuHidHaarClassifier) * totalclassifier) / sizeof(GpuHidHaarTreeNode); nodebuffer = openCLCreateBuffer(gsum.clCxt, CL_MEM_READ_ONLY, nodenum * sizeof(GpuHidHaarTreeNode)); - cl_command_queue qu = (cl_command_queue)gsum.clCxt->oclCommandQueue(); openCLSafeCall(clEnqueueWriteBuffer(qu, nodebuffer, 1, 0, nodenum * sizeof(GpuHidHaarTreeNode), node, 0, NULL, NULL)); @@ -1160,7 +997,6 @@ CvSeq *cv::ocl::OclCascadeClassifier::oclHaarDetectObjects( oclMat &gimg, CvMemS args1.push_back ( make_pair(sizeof(cl_int) , (void *)&startnodenum )); size_t globalThreads2[3] = {nodenum, 1, 1}; - openCLExecuteKernel(gsum.clCxt, &haarobjectdetect_scaled2, "gpuscaleclassifier", globalThreads2, NULL/*localThreads2*/, args1, -1, -1); } @@ -1195,8 +1031,8 @@ CvSeq *cv::ocl::OclCascadeClassifier::oclHaarDetectObjects( oclMat &gimg, CvMemS args.push_back ( make_pair(sizeof(cl_mem) , (void *)&pbuffer )); args.push_back ( make_pair(sizeof(cl_mem) , (void *)&correctionbuffer )); args.push_back ( make_pair(sizeof(cl_int) , (void *)&nodenum )); - - openCLExecuteKernel(gsum.clCxt, &haarobjectdetect_scaled2, "gpuRunHaarClassifierCascade_scaled2", globalThreads, localThreads, args, -1, -1); + const char * build_options = gcascade->is_stump_based ? "-D STUMP_BASED=1" : "-D STUMP_BASED=0"; + openCLExecuteKernel(gsum.clCxt, &haarobjectdetect_scaled2, "gpuRunHaarClassifierCascade_scaled2", globalThreads, localThreads, args, -1, -1, build_options); candidate = (int *)clEnqueueMapBuffer(qu, candidatebuffer, 1, CL_MAP_READ, 0, 4 * sizeof(int) * outputsz, 0, 0, 0, &status); @@ -1284,7 +1120,7 @@ void cv::ocl::OclCascadeClassifierBuf::detectMultiScale(oclMat &gimg, CV_OUT std int blocksize = 8; int grp_per_CU = 12; size_t localThreads[3] = { blocksize, blocksize, 1 }; - size_t globalThreads[3] = { grp_per_CU * Context::getContext()->computeUnits() * localThreads[0], + size_t globalThreads[3] = { grp_per_CU * cv::ocl::Context::getContext()->computeUnits() *localThreads[0], localThreads[1], 1 }; int outputsz = 256 * globalThreads[0] / localThreads[0]; @@ -1300,8 +1136,6 @@ void cv::ocl::OclCascadeClassifierBuf::detectMultiScale(oclMat &gimg, CV_OUT std CvHaarClassifierCascade *cascade = oldCascade; GpuHidHaarClassifierCascade *gcascade; GpuHidHaarStageClassifier *stage; - GpuHidHaarClassifier *classifier; - GpuHidHaarTreeNode *node; if( CV_MAT_DEPTH(gimg.type()) != CV_8U ) CV_Error( CV_StsUnsupportedFormat, "Only 8-bit images are supported" ); @@ -1314,7 +1148,7 @@ void cv::ocl::OclCascadeClassifierBuf::detectMultiScale(oclMat &gimg, CV_OUT std } int *candidate; - + cl_command_queue qu = reinterpret_cast(Context::getContext()->oclCommandQueue()); if( (flags & CV_HAAR_SCALE_IMAGE) ) { int indexy = 0; @@ -1340,19 +1174,6 @@ void cv::ocl::OclCascadeClassifierBuf::detectMultiScale(oclMat &gimg, CV_OUT std gcascade = (GpuHidHaarClassifierCascade *)(cascade->hid_cascade); stage = (GpuHidHaarStageClassifier *)(gcascade + 1); - classifier = (GpuHidHaarClassifier *)(stage + gcascade->count); - node = (GpuHidHaarTreeNode *)(classifier->node); - - gpuSetImagesForHaarClassifierCascade( cascade, 1., gsum.step / 4 ); - - cl_command_queue qu = (cl_command_queue)gsum.clCxt->oclCommandQueue(); - openCLSafeCall(clEnqueueWriteBuffer(qu, ((OclBuffers *)buffers)->stagebuffer, 1, 0, - sizeof(GpuHidHaarStageClassifier) * gcascade->count, - stage, 0, NULL, NULL)); - - openCLSafeCall(clEnqueueWriteBuffer(qu, ((OclBuffers *)buffers)->nodebuffer, 1, 0, - m_nodenum * sizeof(GpuHidHaarTreeNode), - node, 0, NULL, NULL)); int startstage = 0; int endstage = gcascade->count; @@ -1389,17 +1210,23 @@ void cv::ocl::OclCascadeClassifierBuf::detectMultiScale(oclMat &gimg, CV_OUT std args.push_back ( make_pair(sizeof(cl_int4) , (void *)&pq )); args.push_back ( make_pair(sizeof(cl_float) , (void *)&correction )); - openCLExecuteKernel(gsum.clCxt, &haarobjectdetect, "gpuRunHaarClassifierCascade", globalThreads, localThreads, args, -1, -1); + const char * build_options = gcascade->is_stump_based ? "-D STUMP_BASED=1" : "-D STUMP_BASED=0"; + + openCLExecuteKernel(gsum.clCxt, &haarobjectdetect, "gpuRunHaarClassifierCascade", globalThreads, localThreads, args, -1, -1, build_options); candidate = (int *)malloc(4 * sizeof(int) * outputsz); memset(candidate, 0, 4 * sizeof(int) * outputsz); + openCLReadBuffer( gsum.clCxt, ((OclBuffers *)buffers)->candidatebuffer, candidate, 4 * sizeof(int)*outputsz ); for(int i = 0; i < outputsz; i++) + { if(candidate[4 * i + 2] != 0) + { allCandidates.push_back(Rect(candidate[4 * i], candidate[4 * i + 1], candidate[4 * i + 2], candidate[4 * i + 3])); - + } + } free((void *)candidate); candidate = NULL; } @@ -1407,6 +1234,132 @@ void cv::ocl::OclCascadeClassifierBuf::detectMultiScale(oclMat &gimg, CV_OUT std { cv::ocl::integral(gimg, gsum, gsqsum); + gcascade = (GpuHidHaarClassifierCascade *)cascade->hid_cascade; + + int step = gsum.step / 4; + int startnode = 0; + int splitstage = 3; + + int startstage = 0; + int endstage = gcascade->count; + + vector > args; + args.push_back ( make_pair(sizeof(cl_mem) , (void *)&((OclBuffers *)buffers)->stagebuffer )); + args.push_back ( make_pair(sizeof(cl_mem) , (void *)&((OclBuffers *)buffers)->scaleinfobuffer )); + args.push_back ( make_pair(sizeof(cl_mem) , (void *)&((OclBuffers *)buffers)->newnodebuffer )); + args.push_back ( make_pair(sizeof(cl_mem) , (void *)&gsum.data )); + args.push_back ( make_pair(sizeof(cl_mem) , (void *)&gsqsum.data )); + args.push_back ( make_pair(sizeof(cl_mem) , (void *)&((OclBuffers *)buffers)->candidatebuffer )); + args.push_back ( make_pair(sizeof(cl_int) , (void *)&gsum.rows )); + args.push_back ( make_pair(sizeof(cl_int) , (void *)&gsum.cols )); + args.push_back ( make_pair(sizeof(cl_int) , (void *)&step )); + args.push_back ( make_pair(sizeof(cl_int) , (void *)&m_loopcount )); + args.push_back ( make_pair(sizeof(cl_int) , (void *)&startstage )); + args.push_back ( make_pair(sizeof(cl_int) , (void *)&splitstage )); + args.push_back ( make_pair(sizeof(cl_int) , (void *)&endstage )); + args.push_back ( make_pair(sizeof(cl_int) , (void *)&startnode )); + args.push_back ( make_pair(sizeof(cl_mem) , (void *)&((OclBuffers *)buffers)->pbuffer )); + args.push_back ( make_pair(sizeof(cl_mem) , (void *)&((OclBuffers *)buffers)->correctionbuffer )); + args.push_back ( make_pair(sizeof(cl_int) , (void *)&m_nodenum )); + + const char * build_options = gcascade->is_stump_based ? "-D STUMP_BASED=1" : "-D STUMP_BASED=0"; + openCLExecuteKernel(gsum.clCxt, &haarobjectdetect_scaled2, "gpuRunHaarClassifierCascade_scaled2", globalThreads, localThreads, args, -1, -1, build_options); + + candidate = (int *)clEnqueueMapBuffer(qu, ((OclBuffers *)buffers)->candidatebuffer, 1, CL_MAP_READ, 0, 4 * sizeof(int) * outputsz, 0, 0, 0, NULL); + + for(int i = 0; i < outputsz; i++) + { + if(candidate[4 * i + 2] != 0) + allCandidates.push_back(Rect(candidate[4 * i], candidate[4 * i + 1], + candidate[4 * i + 2], candidate[4 * i + 3])); + } + clEnqueueUnmapMemObject(qu, ((OclBuffers *)buffers)->candidatebuffer, candidate, 0, 0, 0); + } + rectList.resize(allCandidates.size()); + if(!allCandidates.empty()) + std::copy(allCandidates.begin(), allCandidates.end(), rectList.begin()); + + if( minNeighbors != 0 || findBiggestObject ) + groupRectangles(rectList, rweights, std::max(minNeighbors, 1), GROUP_EPS); + else + rweights.resize(rectList.size(), 0); + + GenResult(faces, rectList, rweights); +} + +void cv::ocl::OclCascadeClassifierBuf::Init(const int rows, const int cols, + double scaleFactor, int flags, + const int outputsz, const size_t localThreads[], + CvSize minSize, CvSize maxSize) +{ + if(initialized) + { + return; // we only allow one time initialization + } + CvHaarClassifierCascade *cascade = oldCascade; + + if( !CV_IS_HAAR_CLASSIFIER(cascade) ) + CV_Error( !cascade ? CV_StsNullPtr : CV_StsBadArg, "Invalid classifier cascade" ); + + if( scaleFactor <= 1 ) + CV_Error( CV_StsOutOfRange, "scale factor must be > 1" ); + + if( cols < minSize.width || rows < minSize.height ) + CV_Error(CV_StsError, "Image too small"); + + int datasize=0; + int totalclassifier=0; + + if( !cascade->hid_cascade ) + { + gpuCreateHidHaarClassifierCascade(cascade, &datasize, &totalclassifier); + } + + if( maxSize.height == 0 || maxSize.width == 0 ) + { + maxSize.height = rows; + maxSize.width = cols; + } + + findBiggestObject = (flags & CV_HAAR_FIND_BIGGEST_OBJECT) != 0; + if( findBiggestObject ) + flags &= ~(CV_HAAR_SCALE_IMAGE | CV_HAAR_DO_CANNY_PRUNING); + + CreateBaseBufs(datasize, totalclassifier, flags, outputsz); + CreateFactorRelatedBufs(rows, cols, flags, scaleFactor, localThreads, minSize, maxSize); + + m_scaleFactor = scaleFactor; + m_rows = rows; + m_cols = cols; + m_flags = flags; + m_minSize = minSize; + m_maxSize = maxSize; + + // initialize nodes + GpuHidHaarClassifierCascade *gcascade; + GpuHidHaarStageClassifier *stage; + GpuHidHaarClassifier *classifier; + GpuHidHaarTreeNode *node; + cl_command_queue qu = reinterpret_cast(Context::getContext()->oclCommandQueue()); + if( (flags & CV_HAAR_SCALE_IMAGE) ) + { + gcascade = (GpuHidHaarClassifierCascade *)(cascade->hid_cascade); + stage = (GpuHidHaarStageClassifier *)(gcascade + 1); + classifier = (GpuHidHaarClassifier *)(stage + gcascade->count); + node = (GpuHidHaarTreeNode *)(classifier->node); + + gpuSetImagesForHaarClassifierCascade( cascade, 1., gsum.step / 4 ); + + openCLSafeCall(clEnqueueWriteBuffer(qu, ((OclBuffers *)buffers)->stagebuffer, 1, 0, + sizeof(GpuHidHaarStageClassifier) * gcascade->count, + stage, 0, NULL, NULL)); + + openCLSafeCall(clEnqueueWriteBuffer(qu, ((OclBuffers *)buffers)->nodebuffer, 1, 0, + m_nodenum * sizeof(GpuHidHaarTreeNode), + node, 0, NULL, NULL)); + } + else + { gpuSetHaarClassifierCascade(cascade); gcascade = (GpuHidHaarClassifierCascade *)cascade->hid_cascade; @@ -1414,15 +1367,12 @@ void cv::ocl::OclCascadeClassifierBuf::detectMultiScale(oclMat &gimg, CV_OUT std classifier = (GpuHidHaarClassifier *)(stage + gcascade->count); node = (GpuHidHaarTreeNode *)(classifier->node); - cl_command_queue qu = (cl_command_queue)gsum.clCxt->oclCommandQueue(); openCLSafeCall(clEnqueueWriteBuffer(qu, ((OclBuffers *)buffers)->nodebuffer, 1, 0, - m_nodenum * sizeof(GpuHidHaarTreeNode), - node, 0, NULL, NULL)); + m_nodenum * sizeof(GpuHidHaarTreeNode), + node, 0, NULL, NULL)); cl_int4 *p = (cl_int4 *)malloc(sizeof(cl_int4) * m_loopcount); float *correction = (float *)malloc(sizeof(float) * m_loopcount); - int startstage = 0; - int endstage = gcascade->count; double factor; for(int i = 0; i < m_loopcount; i++) { @@ -1448,105 +1398,15 @@ void cv::ocl::OclCascadeClassifierBuf::detectMultiScale(oclMat &gimg, CV_OUT std size_t globalThreads2[3] = {m_nodenum, 1, 1}; - openCLExecuteKernel(gsum.clCxt, &haarobjectdetect_scaled2, "gpuscaleclassifier", globalThreads2, NULL/*localThreads2*/, args1, -1, -1); + openCLExecuteKernel(Context::getContext(), &haarobjectdetect_scaled2, "gpuscaleclassifier", globalThreads2, NULL/*localThreads2*/, args1, -1, -1); } - - int step = gsum.step / 4; - int startnode = 0; - int splitstage = 3; openCLSafeCall(clEnqueueWriteBuffer(qu, ((OclBuffers *)buffers)->stagebuffer, 1, 0, sizeof(GpuHidHaarStageClassifier)*gcascade->count, stage, 0, NULL, NULL)); openCLSafeCall(clEnqueueWriteBuffer(qu, ((OclBuffers *)buffers)->pbuffer, 1, 0, sizeof(cl_int4)*m_loopcount, p, 0, NULL, NULL)); openCLSafeCall(clEnqueueWriteBuffer(qu, ((OclBuffers *)buffers)->correctionbuffer, 1, 0, sizeof(cl_float)*m_loopcount, correction, 0, NULL, NULL)); - vector > args; - args.push_back ( make_pair(sizeof(cl_mem) , (void *)&((OclBuffers *)buffers)->stagebuffer )); - args.push_back ( make_pair(sizeof(cl_mem) , (void *)&((OclBuffers *)buffers)->scaleinfobuffer )); - args.push_back ( make_pair(sizeof(cl_mem) , (void *)&((OclBuffers *)buffers)->newnodebuffer )); - args.push_back ( make_pair(sizeof(cl_mem) , (void *)&gsum.data )); - args.push_back ( make_pair(sizeof(cl_mem) , (void *)&gsqsum.data )); - args.push_back ( make_pair(sizeof(cl_mem) , (void *)&((OclBuffers *)buffers)->candidatebuffer )); - args.push_back ( make_pair(sizeof(cl_int) , (void *)&gsum.rows )); - args.push_back ( make_pair(sizeof(cl_int) , (void *)&gsum.cols )); - args.push_back ( make_pair(sizeof(cl_int) , (void *)&step )); - args.push_back ( make_pair(sizeof(cl_int) , (void *)&m_loopcount )); - args.push_back ( make_pair(sizeof(cl_int) , (void *)&startstage )); - args.push_back ( make_pair(sizeof(cl_int) , (void *)&splitstage )); - args.push_back ( make_pair(sizeof(cl_int) , (void *)&endstage )); - args.push_back ( make_pair(sizeof(cl_int) , (void *)&startnode )); - args.push_back ( make_pair(sizeof(cl_mem) , (void *)&((OclBuffers *)buffers)->pbuffer )); - args.push_back ( make_pair(sizeof(cl_mem) , (void *)&((OclBuffers *)buffers)->correctionbuffer )); - args.push_back ( make_pair(sizeof(cl_int) , (void *)&m_nodenum )); - - openCLExecuteKernel(gsum.clCxt, &haarobjectdetect_scaled2, "gpuRunHaarClassifierCascade_scaled2", globalThreads, localThreads, args, -1, -1); - - candidate = (int *)clEnqueueMapBuffer(qu, ((OclBuffers *)buffers)->candidatebuffer, 1, CL_MAP_READ, 0, 4 * sizeof(int) * outputsz, 0, 0, 0, NULL); - - for(int i = 0; i < outputsz; i++) - { - if(candidate[4 * i + 2] != 0) - allCandidates.push_back(Rect(candidate[4 * i], candidate[4 * i + 1], - candidate[4 * i + 2], candidate[4 * i + 3])); - } - free(p); free(correction); - clEnqueueUnmapMemObject(qu, ((OclBuffers *)buffers)->candidatebuffer, candidate, 0, 0, 0); } - - rectList.resize(allCandidates.size()); - if(!allCandidates.empty()) - std::copy(allCandidates.begin(), allCandidates.end(), rectList.begin()); - - if( minNeighbors != 0 || findBiggestObject ) - groupRectangles(rectList, rweights, std::max(minNeighbors, 1), GROUP_EPS); - else - rweights.resize(rectList.size(), 0); - - GenResult(faces, rectList, rweights); -} - -void cv::ocl::OclCascadeClassifierBuf::Init(const int rows, const int cols, - double scaleFactor, int flags, - const int outputsz, const size_t localThreads[], - CvSize minSize, CvSize maxSize) -{ - CvHaarClassifierCascade *cascade = oldCascade; - - if( !CV_IS_HAAR_CLASSIFIER(cascade) ) - CV_Error( !cascade ? CV_StsNullPtr : CV_StsBadArg, "Invalid classifier cascade" ); - - if( scaleFactor <= 1 ) - CV_Error( CV_StsOutOfRange, "scale factor must be > 1" ); - - if( cols < minSize.width || rows < minSize.height ) - CV_Error(CV_StsError, "Image too small"); - - int datasize=0; - int totalclassifier=0; - - if( !cascade->hid_cascade ) - gpuCreateHidHaarClassifierCascade(cascade, &datasize, &totalclassifier); - - if( maxSize.height == 0 || maxSize.width == 0 ) - { - maxSize.height = rows; - maxSize.width = cols; - } - - findBiggestObject = (flags & CV_HAAR_FIND_BIGGEST_OBJECT) != 0; - if( findBiggestObject ) - flags &= ~(CV_HAAR_SCALE_IMAGE | CV_HAAR_DO_CANNY_PRUNING); - - CreateBaseBufs(datasize, totalclassifier, flags, outputsz); - CreateFactorRelatedBufs(rows, cols, flags, scaleFactor, localThreads, minSize, maxSize); - - m_scaleFactor = scaleFactor; - m_rows = rows; - m_cols = cols; - m_flags = flags; - m_minSize = minSize; - m_maxSize = maxSize; - initialized = true; } @@ -1645,6 +1505,7 @@ void cv::ocl::OclCascadeClassifierBuf::CreateFactorRelatedBufs( CvSize sz; CvSize winSize0 = oldCascade->orig_window_size; detect_piramid_info *scaleinfo; + cl_command_queue qu = reinterpret_cast(Context::getContext()->oclCommandQueue()); if (flags & CV_HAAR_SCALE_IMAGE) { for(factor = 1.f;; factor *= scaleFactor) @@ -1746,7 +1607,7 @@ void cv::ocl::OclCascadeClassifierBuf::CreateFactorRelatedBufs( ((OclBuffers *)buffers)->scaleinfobuffer = openCLCreateBuffer(cv::ocl::Context::getContext(), CL_MEM_READ_ONLY, sizeof(detect_piramid_info) * loopcount); } - openCLSafeCall(clEnqueueWriteBuffer((cl_command_queue)cv::ocl::Context::getContext()->oclCommandQueue(), ((OclBuffers *)buffers)->scaleinfobuffer, 1, 0, + openCLSafeCall(clEnqueueWriteBuffer(qu, ((OclBuffers *)buffers)->scaleinfobuffer, 1, 0, sizeof(detect_piramid_info)*loopcount, scaleinfo, 0, NULL, NULL)); free(scaleinfo); @@ -1758,7 +1619,8 @@ void cv::ocl::OclCascadeClassifierBuf::GenResult(CV_OUT std::vector& f const std::vector &rectList, const std::vector &rweights) { - CvSeq *result_seq = cvCreateSeq( 0, sizeof(CvSeq), sizeof(CvAvgComp), cvCreateMemStorage(0) ); + MemStorage tempStorage(cvCreateMemStorage(0)); + CvSeq *result_seq = cvCreateSeq( 0, sizeof(CvSeq), sizeof(CvAvgComp), tempStorage ); if( findBiggestObject && rectList.size() ) { @@ -1794,167 +1656,30 @@ void cv::ocl::OclCascadeClassifierBuf::GenResult(CV_OUT std::vector& f void cv::ocl::OclCascadeClassifierBuf::release() { - openCLSafeCall(clReleaseMemObject(((OclBuffers *)buffers)->stagebuffer)); - openCLSafeCall(clReleaseMemObject(((OclBuffers *)buffers)->scaleinfobuffer)); - openCLSafeCall(clReleaseMemObject(((OclBuffers *)buffers)->nodebuffer)); - openCLSafeCall(clReleaseMemObject(((OclBuffers *)buffers)->candidatebuffer)); - - if( (m_flags & CV_HAAR_SCALE_IMAGE) ) + if(initialized) { - cvFree(&oldCascade->hid_cascade); - } - else - { - openCLSafeCall(clReleaseMemObject(((OclBuffers *)buffers)->newnodebuffer)); - openCLSafeCall(clReleaseMemObject(((OclBuffers *)buffers)->correctionbuffer)); - openCLSafeCall(clReleaseMemObject(((OclBuffers *)buffers)->pbuffer)); - } + openCLSafeCall(clReleaseMemObject(((OclBuffers *)buffers)->stagebuffer)); + openCLSafeCall(clReleaseMemObject(((OclBuffers *)buffers)->scaleinfobuffer)); + openCLSafeCall(clReleaseMemObject(((OclBuffers *)buffers)->nodebuffer)); + openCLSafeCall(clReleaseMemObject(((OclBuffers *)buffers)->candidatebuffer)); - free(buffers); - buffers = NULL; + if( (m_flags & CV_HAAR_SCALE_IMAGE) ) + { + cvFree(&oldCascade->hid_cascade); + } + else + { + openCLSafeCall(clReleaseMemObject(((OclBuffers *)buffers)->newnodebuffer)); + openCLSafeCall(clReleaseMemObject(((OclBuffers *)buffers)->correctionbuffer)); + openCLSafeCall(clReleaseMemObject(((OclBuffers *)buffers)->pbuffer)); + } + + free(buffers); + buffers = NULL; + initialized = false; + } } #ifndef _MAX_PATH #define _MAX_PATH 1024 #endif - - -/****************************************************************************************\ -* Persistence functions * -\****************************************************************************************/ - -/* field names */ - -#define ICV_HAAR_SIZE_NAME "size" -#define ICV_HAAR_STAGES_NAME "stages" -#define ICV_HAAR_TREES_NAME "trees" -#define ICV_HAAR_FEATURE_NAME "feature" -#define ICV_HAAR_RECTS_NAME "rects" -#define ICV_HAAR_TILTED_NAME "tilted" -#define ICV_HAAR_THRESHOLD_NAME "threshold" -#define ICV_HAAR_LEFT_NODE_NAME "left_node" -#define ICV_HAAR_LEFT_VAL_NAME "left_val" -#define ICV_HAAR_RIGHT_NODE_NAME "right_node" -#define ICV_HAAR_RIGHT_VAL_NAME "right_val" -#define ICV_HAAR_STAGE_THRESHOLD_NAME "stage_threshold" -#define ICV_HAAR_PARENT_NAME "parent" -#define ICV_HAAR_NEXT_NAME "next" - -static int gpuRunHaarClassifierCascade( /*const CvHaarClassifierCascade *_cascade, CvPoint pt, int start_stage */) -{ - return 1; -} - -namespace cv -{ -namespace ocl -{ - -struct gpuHaarDetectObjects_ScaleImage_Invoker -{ - gpuHaarDetectObjects_ScaleImage_Invoker( const CvHaarClassifierCascade *_cascade, - int _stripSize, double _factor, - const Mat &_sum1, const Mat &_sqsum1, Mat *_norm1, - Mat *_mask1, Rect _equRect, ConcurrentRectVector &_vec ) - { - cascade = _cascade; - stripSize = _stripSize; - factor = _factor; - sum1 = _sum1; - sqsum1 = _sqsum1; - norm1 = _norm1; - mask1 = _mask1; - equRect = _equRect; - vec = &_vec; - } - - void operator()( const BlockedRange &range ) const - { - Size winSize0 = cascade->orig_window_size; - Size winSize(cvRound(winSize0.width * factor), cvRound(winSize0.height * factor)); - int y1 = range.begin() * stripSize, y2 = min(range.end() * stripSize, sum1.rows - 1 - winSize0.height); - Size ssz(sum1.cols - 1 - winSize0.width, y2 - y1); - int x, y, ystep = factor > 2 ? 1 : 2; - - for( y = y1; y < y2; y += ystep ) - for( x = 0; x < ssz.width; x += ystep ) - { - if( gpuRunHaarClassifierCascade( /*cascade, cvPoint(x, y), 0*/ ) > 0 ) - vec->push_back(Rect(cvRound(x * factor), cvRound(y * factor), - winSize.width, winSize.height)); - } - } - - const CvHaarClassifierCascade *cascade; - int stripSize; - double factor; - Mat sum1, sqsum1, *norm1, *mask1; - Rect equRect; - ConcurrentRectVector *vec; -}; - - -struct gpuHaarDetectObjects_ScaleCascade_Invoker -{ - gpuHaarDetectObjects_ScaleCascade_Invoker( const CvHaarClassifierCascade *_cascade, - Size _winsize, const Range &_xrange, double _ystep, - size_t _sumstep, const int **_p, const int **_pq, - ConcurrentRectVector &_vec ) - { - cascade = _cascade; - winsize = _winsize; - xrange = _xrange; - ystep = _ystep; - sumstep = _sumstep; - p = _p; - pq = _pq; - vec = &_vec; - } - - void operator()( const BlockedRange &range ) const - { - int iy, startY = range.begin(), endY = range.end(); - const int *p0 = p[0], *p1 = p[1], *p2 = p[2], *p3 = p[3]; - const int *pq0 = pq[0], *pq1 = pq[1], *pq2 = pq[2], *pq3 = pq[3]; - bool doCannyPruning = p0 != 0; - int sstep = (int)(sumstep / sizeof(p0[0])); - - for( iy = startY; iy < endY; iy++ ) - { - int ix, y = cvRound(iy * ystep), ixstep = 1; - for( ix = xrange.start; ix < xrange.end; ix += ixstep ) - { - int x = cvRound(ix * ystep); // it should really be ystep, not ixstep - - if( doCannyPruning ) - { - int offset = y * sstep + x; - int s = p0[offset] - p1[offset] - p2[offset] + p3[offset]; - int sq = pq0[offset] - pq1[offset] - pq2[offset] + pq3[offset]; - if( s < 100 || sq < 20 ) - { - ixstep = 2; - continue; - } - } - - int result = gpuRunHaarClassifierCascade(/* cascade, cvPoint(x, y), 0 */); - if( result > 0 ) - vec->push_back(Rect(x, y, winsize.width, winsize.height)); - ixstep = result != 0 ? 1 : 2; - } - } - } - - const CvHaarClassifierCascade *cascade; - double ystep; - size_t sumstep; - Size winsize; - Range xrange; - const int **p; - const int **pq; - ConcurrentRectVector *vec; -}; - -} -} diff --git a/modules/ocl/src/opencl/haarobjectdetect.cl b/modules/ocl/src/opencl/haarobjectdetect.cl index e0ab8603b7..4873298af0 100644 --- a/modules/ocl/src/opencl/haarobjectdetect.cl +++ b/modules/ocl/src/opencl/haarobjectdetect.cl @@ -10,6 +10,7 @@ // Wang Weiyan, wangweiyanster@gmail.com // Jia Haipeng, jiahaipeng95@gmail.com // Nathan, liujun@multicorewareinc.com +// Peng Xiao, pengxiao@outlook.com // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: // @@ -45,27 +46,16 @@ typedef int sumtype; typedef float sqsumtype; -typedef struct __attribute__((aligned (128))) GpuHidHaarFeature -{ - struct __attribute__((aligned (32))) -{ - int p0 __attribute__((aligned (4))); - int p1 __attribute__((aligned (4))); - int p2 __attribute__((aligned (4))); - int p3 __attribute__((aligned (4))); - float weight __attribute__((aligned (4))); -} -rect[CV_HAAR_FEATURE_MAX] __attribute__((aligned (32))); -} -GpuHidHaarFeature; - +#ifndef STUMP_BASED +#define STUMP_BASED 1 +#endif typedef struct __attribute__((aligned (128) )) GpuHidHaarTreeNode { int p[CV_HAAR_FEATURE_MAX][4] __attribute__((aligned (64))); - float weight[CV_HAAR_FEATURE_MAX] /*__attribute__((aligned (16)))*/; - float threshold /*__attribute__((aligned (4)))*/; - float alpha[2] __attribute__((aligned (8))); + float weight[CV_HAAR_FEATURE_MAX]; + float threshold; + float alpha[3] __attribute__((aligned (16))); int left __attribute__((aligned (4))); int right __attribute__((aligned (4))); } @@ -111,7 +101,6 @@ typedef struct __attribute__((aligned (64))) GpuHidHaarClassifierCascade float inv_window_area __attribute__((aligned (4))); } GpuHidHaarClassifierCascade; - __kernel void __attribute__((reqd_work_group_size(8,8,1)))gpuRunHaarClassifierCascade( global GpuHidHaarStageClassifier * stagecascadeptr, global int4 * info, @@ -234,7 +223,7 @@ __kernel void __attribute__((reqd_work_group_size(8,8,1)))gpuRunHaarClassifierCa float stage_sum = 0.f; int2 stageinfo = *(global int2*)(stagecascadeptr+stageloop); float stagethreshold = as_float(stageinfo.y); - for(int nodeloop = 0; nodeloop < stageinfo.x; nodeloop++ ) + for(int nodeloop = 0; nodeloop < stageinfo.x; ) { __global GpuHidHaarTreeNode* currentnodeptr = (nodeptr + nodecounter); @@ -242,7 +231,8 @@ __kernel void __attribute__((reqd_work_group_size(8,8,1)))gpuRunHaarClassifierCa int4 info2 = *(__global int4*)(&(currentnodeptr->p[1][0])); int4 info3 = *(__global int4*)(&(currentnodeptr->p[2][0])); float4 w = *(__global float4*)(&(currentnodeptr->weight[0])); - float2 alpha2 = *(__global float2*)(&(currentnodeptr->alpha[0])); + float3 alpha3 = *(__global float3*)(&(currentnodeptr->alpha[0])); + float nodethreshold = w.w * variance_norm_factor; info1.x +=lcl_off; @@ -261,8 +251,34 @@ __kernel void __attribute__((reqd_work_group_size(8,8,1)))gpuRunHaarClassifierCa classsum += (lcldata[mad24(info3.y,readwidth,info3.x)] - lcldata[mad24(info3.y,readwidth,info3.z)] - lcldata[mad24(info3.w,readwidth,info3.x)] + lcldata[mad24(info3.w,readwidth,info3.z)]) * w.z; - stage_sum += classsum >= nodethreshold ? alpha2.y : alpha2.x; + bool passThres = classsum >= nodethreshold; +#if STUMP_BASED + stage_sum += passThres ? alpha3.y : alpha3.x; nodecounter++; + nodeloop++; +#else + bool isRootNode = (nodecounter & 1) == 0; + if(isRootNode) + { + if( (passThres && currentnodeptr->right) || + (!passThres && currentnodeptr->left)) + { + nodecounter ++; + } + else + { + stage_sum += alpha3.x; + nodecounter += 2; + nodeloop ++; + } + } + else + { + stage_sum += passThres ? alpha3.z : alpha3.y; + nodecounter ++; + nodeloop ++; + } +#endif } result = (stage_sum >= stagethreshold); @@ -301,18 +317,20 @@ __kernel void __attribute__((reqd_work_group_size(8,8,1)))gpuRunHaarClassifierCa if(lcl_compute_win_id < queuecount) { - int tempnodecounter = lcl_compute_id; float part_sum = 0.f; - for(int lcl_loop=0; lcl_loopp[0][0])); int4 info2 = *(__global int4*)(&(currentnodeptr->p[1][0])); int4 info3 = *(__global int4*)(&(currentnodeptr->p[2][0])); float4 w = *(__global float4*)(&(currentnodeptr->weight[0])); - float2 alpha2 = *(__global float2*)(&(currentnodeptr->alpha[0])); + float3 alpha3 = *(__global float3*)(&(currentnodeptr->alpha[0])); float nodethreshold = w.w * variance_norm_factor; info1.x +=queue_pixel; @@ -332,8 +350,34 @@ __kernel void __attribute__((reqd_work_group_size(8,8,1)))gpuRunHaarClassifierCa classsum += (lcldata[mad24(info3.y,readwidth,info3.x)] - lcldata[mad24(info3.y,readwidth,info3.z)] - lcldata[mad24(info3.w,readwidth,info3.x)] + lcldata[mad24(info3.w,readwidth,info3.z)]) * w.z; - part_sum += classsum >= nodethreshold ? alpha2.y : alpha2.x; - tempnodecounter +=lcl_compute_win; + bool passThres = classsum >= nodethreshold; +#if STUMP_BASED + part_sum += passThres ? alpha3.y : alpha3.x; + tempnodecounter += lcl_compute_win; + lcl_loop++; +#else + if(root_offset == 0) + { + if( (passThres && currentnodeptr->right) || + (!passThres && currentnodeptr->left)) + { + root_offset = 1; + } + else + { + part_sum += alpha3.x; + tempnodecounter += lcl_compute_win; + lcl_loop++; + } + } + else + { + part_sum += passThres ? alpha3.z : alpha3.y; + tempnodecounter += lcl_compute_win; + lcl_loop++; + root_offset = 0; + } +#endif }//end for(int lcl_loop=0;lcl_looptwo_rects) -{ - #pragma unroll - for( n = 0; n < stagecascade->count; n++ ) - { - t1 = *(node + counter); - t = t1.threshold * variance_norm_factor; - classsum = calc_sum1(t1,p_offset,0) * t1.weight[0]; - - classsum += calc_sum1(t1, p_offset,1) * t1.weight[1]; - stage_sum += classsum >= t ? t1.alpha[1]:t1.alpha[0]; - - counter++; - } -} -else -{ - #pragma unroll - for( n = 0; n < stagecascade->count; n++ ) - { - t = node[counter].threshold*variance_norm_factor; - classsum = calc_sum1(node[counter],p_offset,0) * node[counter].weight[0]; - classsum += calc_sum1(node[counter],p_offset,1) * node[counter].weight[1]; - - if( node[counter].p0[2] ) - classsum += calc_sum1(node[counter],p_offset,2) * node[counter].weight[2]; - - stage_sum += classsum >= t ? node[counter].alpha[1]:node[counter].alpha[0];// modify - - counter++; - } -} -*/ -/* -__kernel void gpuRunHaarClassifierCascade_ScaleWindow( - constant GpuHidHaarClassifierCascade * _cascade, - global GpuHidHaarStageClassifier * stagecascadeptr, - //global GpuHidHaarClassifier * classifierptr, - global GpuHidHaarTreeNode * nodeptr, - global int * sum, - global float * sqsum, - global int * _candidate, - int pixel_step, - int cols, - int rows, - int start_stage, - int end_stage, - //int counts, - int nodenum, - int ystep, - int detect_width, - //int detect_height, - int loopcount, - int outputstep) - //float scalefactor) -{ -unsigned int x1 = get_global_id(0); -unsigned int y1 = get_global_id(1); -int p_offset; -int m, n; -int result; -int counter; -float mean, variance_norm_factor; -for(int i=0;ip1 - cascade->p0; -int window_height = window_width; -result = 1; -counter = 0; -unsigned int x = mul24(x1,ystep); -unsigned int y = mul24(y1,ystep); -if((x < cols - window_width - 1) && (y < rows - window_height -1)) -{ -global GpuHidHaarStageClassifier *stagecascade = stagecascadeptr +cascade->count*i+ start_stage; -//global GpuHidHaarClassifier *classifier = classifierptr; -global GpuHidHaarTreeNode *node = nodeptr + nodenum*i; - -p_offset = mad24(y, pixel_step, x);// modify - -mean = (*(sum + p_offset + (int)cascade->p0) - *(sum + p_offset + (int)cascade->p1) - - *(sum + p_offset + (int)cascade->p2) + *(sum + p_offset + (int)cascade->p3)) - *cascade->inv_window_area; - -variance_norm_factor = *(sqsum + p_offset + cascade->p0) - *(sqsum + cascade->p1 + p_offset) - - *(sqsum + p_offset + cascade->p2) + *(sqsum + cascade->p3 + p_offset); -variance_norm_factor = variance_norm_factor * cascade->inv_window_area - mean * mean; -variance_norm_factor = variance_norm_factor >=0.f ? sqrt(variance_norm_factor) : 1;//modify - -// if( cascade->is_stump_based ) -//{ -for( m = start_stage; m < end_stage; m++ ) -{ -float stage_sum = 0.f; -float t, classsum; -GpuHidHaarTreeNode t1; - -//#pragma unroll -for( n = 0; n < stagecascade->count; n++ ) -{ - t1 = *(node + counter); - t = t1.threshold * variance_norm_factor; - classsum = calc_sum1(t1, p_offset ,0) * t1.weight[0] + calc_sum1(t1, p_offset ,1) * t1.weight[1]; - - if((t1.p0[2]) && (!stagecascade->two_rects)) - classsum += calc_sum1(t1, p_offset, 2) * t1.weight[2]; - - stage_sum += classsum >= t ? t1.alpha[1] : t1.alpha[0];// modify - counter++; -} - -if (stage_sum < stagecascade->threshold) -{ - result = 0; - break; -} - -stagecascade++; - -} -if(result) -{ - candidate[4 * (y1 * detect_width + x1)] = x; - candidate[4 * (y1 * detect_width + x1) + 1] = y; - candidate[4 * (y1 * detect_width + x1)+2] = window_width; - candidate[4 * (y1 * detect_width + x1) + 3] = window_height; -} -//} -} -} -} -*/ - - - - diff --git a/modules/ocl/src/opencl/haarobjectdetect_scaled2.cl b/modules/ocl/src/opencl/haarobjectdetect_scaled2.cl index 44877f3860..8507972ff2 100644 --- a/modules/ocl/src/opencl/haarobjectdetect_scaled2.cl +++ b/modules/ocl/src/opencl/haarobjectdetect_scaled2.cl @@ -17,7 +17,7 @@ // @Authors // Wu Xinglong, wxl370@126.com // Sen Liu, swjtuls1987@126.com -// +// Peng Xiao, pengxiao@outlook.com // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: // @@ -49,25 +49,13 @@ #define CV_HAAR_FEATURE_MAX 3 typedef int sumtype; typedef float sqsumtype; -typedef struct __attribute__((aligned(128))) GpuHidHaarFeature -{ - struct __attribute__((aligned(32))) -{ - int p0 __attribute__((aligned(4))); - int p1 __attribute__((aligned(4))); - int p2 __attribute__((aligned(4))); - int p3 __attribute__((aligned(4))); - float weight __attribute__((aligned(4))); -} -rect[CV_HAAR_FEATURE_MAX] __attribute__((aligned(32))); -} -GpuHidHaarFeature; + typedef struct __attribute__((aligned(128))) GpuHidHaarTreeNode { int p[CV_HAAR_FEATURE_MAX][4] __attribute__((aligned(64))); float weight[CV_HAAR_FEATURE_MAX] /*__attribute__((aligned (16)))*/; float threshold /*__attribute__((aligned (4)))*/; - float alpha[2] __attribute__((aligned(8))); + float alpha[3] __attribute__((aligned(16))); int left __attribute__((aligned(4))); int right __attribute__((aligned(4))); } @@ -174,45 +162,83 @@ __kernel void gpuRunHaarClassifierCascade_scaled2( const int p_offset = mad24(y, step, x); cascadeinfo.x += p_offset; cascadeinfo.z += p_offset; - mean = (sum[clamp(mad24(cascadeinfo.y, step, cascadeinfo.x), 0, max_idx)] - sum[clamp(mad24(cascadeinfo.y, step, cascadeinfo.z), 0, max_idx)] - - sum[clamp(mad24(cascadeinfo.w, step, cascadeinfo.x), 0, max_idx)] + sum[clamp(mad24(cascadeinfo.w, step, cascadeinfo.z), 0, max_idx)]) + mean = (sum[clamp(mad24(cascadeinfo.y, step, cascadeinfo.x), 0, max_idx)] + - sum[clamp(mad24(cascadeinfo.y, step, cascadeinfo.z), 0, max_idx)] - + sum[clamp(mad24(cascadeinfo.w, step, cascadeinfo.x), 0, max_idx)] + + sum[clamp(mad24(cascadeinfo.w, step, cascadeinfo.z), 0, max_idx)]) * correction_t; - variance_norm_factor = sqsum[clamp(mad24(cascadeinfo.y, step, cascadeinfo.x), 0, max_idx)] - sqsum[clamp(mad24(cascadeinfo.y, step, cascadeinfo.z), 0, max_idx)] - - sqsum[clamp(mad24(cascadeinfo.w, step, cascadeinfo.x), 0, max_idx)] + sqsum[clamp(mad24(cascadeinfo.w, step, cascadeinfo.z), 0, max_idx)]; + variance_norm_factor = sqsum[clamp(mad24(cascadeinfo.y, step, cascadeinfo.x), 0, max_idx)] + - sqsum[clamp(mad24(cascadeinfo.y, step, cascadeinfo.z), 0, max_idx)] - + sqsum[clamp(mad24(cascadeinfo.w, step, cascadeinfo.x), 0, max_idx)] + + sqsum[clamp(mad24(cascadeinfo.w, step, cascadeinfo.z), 0, max_idx)]; variance_norm_factor = variance_norm_factor * correction_t - mean * mean; variance_norm_factor = variance_norm_factor >= 0.f ? sqrt(variance_norm_factor) : 1.f; bool result = true; nodecounter = startnode + nodecount * scalei; - for (int stageloop = start_stage; (stageloop < end_stage) && result; stageloop++) { float stage_sum = 0.f; int stagecount = stagecascadeptr[stageloop].count; - for (int nodeloop = 0; nodeloop < stagecount; nodeloop++) + for (int nodeloop = 0; nodeloop < stagecount;) { __global GpuHidHaarTreeNode *currentnodeptr = (nodeptr + nodecounter); int4 info1 = *(__global int4 *)(&(currentnodeptr->p[0][0])); int4 info2 = *(__global int4 *)(&(currentnodeptr->p[1][0])); int4 info3 = *(__global int4 *)(&(currentnodeptr->p[2][0])); float4 w = *(__global float4 *)(&(currentnodeptr->weight[0])); - float2 alpha2 = *(__global float2 *)(&(currentnodeptr->alpha[0])); + float3 alpha3 = *(__global float3 *)(&(currentnodeptr->alpha[0])); float nodethreshold = w.w * variance_norm_factor; + info1.x += p_offset; info1.z += p_offset; info2.x += p_offset; info2.z += p_offset; - float classsum = (sum[clamp(mad24(info1.y, step, info1.x), 0, max_idx)] - sum[clamp(mad24(info1.y, step, info1.z), 0, max_idx)] - - sum[clamp(mad24(info1.w, step, info1.x), 0, max_idx)] + sum[clamp(mad24(info1.w, step, info1.z), 0, max_idx)]) * w.x; - classsum += (sum[clamp(mad24(info2.y, step, info2.x), 0, max_idx)] - sum[clamp(mad24(info2.y, step, info2.z), 0, max_idx)] - - sum[clamp(mad24(info2.w, step, info2.x), 0, max_idx)] + sum[clamp(mad24(info2.w, step, info2.z), 0, max_idx)]) * w.y; info3.x += p_offset; info3.z += p_offset; - classsum += (sum[clamp(mad24(info3.y, step, info3.x), 0, max_idx)] - sum[clamp(mad24(info3.y, step, info3.z), 0, max_idx)] - - sum[clamp(mad24(info3.w, step, info3.x), 0, max_idx)] + sum[clamp(mad24(info3.w, step, info3.z), 0, max_idx)]) * w.z; - stage_sum += classsum >= nodethreshold ? alpha2.y : alpha2.x; + float classsum = (sum[clamp(mad24(info1.y, step, info1.x), 0, max_idx)] + - sum[clamp(mad24(info1.y, step, info1.z), 0, max_idx)] - + sum[clamp(mad24(info1.w, step, info1.x), 0, max_idx)] + + sum[clamp(mad24(info1.w, step, info1.z), 0, max_idx)]) * w.x; + classsum += (sum[clamp(mad24(info2.y, step, info2.x), 0, max_idx)] + - sum[clamp(mad24(info2.y, step, info2.z), 0, max_idx)] - + sum[clamp(mad24(info2.w, step, info2.x), 0, max_idx)] + + sum[clamp(mad24(info2.w, step, info2.z), 0, max_idx)]) * w.y; + classsum += (sum[clamp(mad24(info3.y, step, info3.x), 0, max_idx)] + - sum[clamp(mad24(info3.y, step, info3.z), 0, max_idx)] - + sum[clamp(mad24(info3.w, step, info3.x), 0, max_idx)] + + sum[clamp(mad24(info3.w, step, info3.z), 0, max_idx)]) * w.z; + + bool passThres = classsum >= nodethreshold; + +#if STUMP_BASED + stage_sum += passThres ? alpha3.y : alpha3.x; nodecounter++; + nodeloop++; +#else + bool isRootNode = (nodecounter & 1) == 0; + if(isRootNode) + { + if( (passThres && currentnodeptr->right) || + (!passThres && currentnodeptr->left)) + { + nodecounter ++; + } + else + { + stage_sum += alpha3.x; + nodecounter += 2; + nodeloop ++; + } + } + else + { + stage_sum += (passThres ? alpha3.z : alpha3.y); + nodecounter ++; + nodeloop ++; + } +#endif } - result = (bool)(stage_sum >= stagecascadeptr[stageloop].threshold); + result = (int)(stage_sum >= stagecascadeptr[stageloop].threshold); } barrier(CLK_LOCAL_MEM_FENCE); @@ -222,7 +248,6 @@ __kernel void gpuRunHaarClassifierCascade_scaled2( int queueindex = atomic_inc(lclcount); lcloutindex[queueindex] = (y << 16) | x; } - barrier(CLK_LOCAL_MEM_FENCE); int queuecount = lclcount[0]; @@ -277,5 +302,6 @@ __kernel void gpuscaleclassifier(global GpuHidHaarTreeNode *orinode, global GpuH newnode[counter].threshold = t1.threshold; newnode[counter].alpha[0] = t1.alpha[0]; newnode[counter].alpha[1] = t1.alpha[1]; + newnode[counter].alpha[2] = t1.alpha[2]; } diff --git a/modules/ocl/test/test_haar.cpp b/modules/ocl/test/test_haar.cpp index 96f721146b..52ddbb7c6a 100644 --- a/modules/ocl/test/test_haar.cpp +++ b/modules/ocl/test/test_haar.cpp @@ -55,6 +55,12 @@ using namespace testing; using namespace std; using namespace cv; extern string workdir; + +namespace +{ +IMPLEMENT_PARAM_CLASS(CascadeName, std::string); +CascadeName cascade_frontalface_alt(std::string("haarcascade_frontalface_alt.xml")); +CascadeName cascade_frontalface_alt2(std::string("haarcascade_frontalface_alt2.xml")); struct getRect { Rect operator ()(const CvAvgComp &e) const @@ -62,23 +68,24 @@ struct getRect return e.rect; } }; +} -PARAM_TEST_CASE(Haar, double, int) +PARAM_TEST_CASE(Haar, double, int, CascadeName) { cv::ocl::OclCascadeClassifier cascade, nestedCascade; - cv::ocl::OclCascadeClassifierBuf cascadebuf; cv::CascadeClassifier cpucascade, cpunestedCascade; double scale; int flags; + std::string cascadeName; virtual void SetUp() { scale = GET_PARAM(0); flags = GET_PARAM(1); - string cascadeName = workdir + "../../data/haarcascades/haarcascade_frontalface_alt.xml"; + cascadeName = (workdir + "../../data/haarcascades/").append(GET_PARAM(2)); - if( (!cascade.load( cascadeName )) || (!cpucascade.load(cascadeName)) || (!cascadebuf.load( cascadeName ))) + if( (!cascade.load( cascadeName )) || (!cpucascade.load(cascadeName)) ) { cout << "ERROR: Could not load classifier cascade" << endl; return; @@ -115,7 +122,7 @@ TEST_P(Haar, FaceDetect) Seq(_objects).copyTo(vecAvgComp); oclfaces.resize(vecAvgComp.size()); std::transform(vecAvgComp.begin(), vecAvgComp.end(), oclfaces.begin(), getRect()); - + cpucascade.detectMultiScale( smallImg, faces, 1.1, 3, flags, Size(30, 30), Size(0, 0) ); @@ -136,7 +143,6 @@ TEST_P(Haar, FaceDetectUseBuf) vector faces, oclfaces; Mat gray, smallImg(cvRound (img.rows / scale), cvRound(img.cols / scale), CV_8UC1 ); - MemStorage storage(cvCreateMemStorage(0)); cvtColor( img, gray, CV_BGR2GRAY ); resize( gray, smallImg, smallImg.size(), 0, 0, INTER_LINEAR ); equalizeHist( smallImg, smallImg ); @@ -144,19 +150,31 @@ TEST_P(Haar, FaceDetectUseBuf) cv::ocl::oclMat image; image.upload(smallImg); + cv::ocl::OclCascadeClassifierBuf cascadebuf; + if( !cascadebuf.load( cascadeName ) ) + { + cout << "ERROR: Could not load classifier cascade for FaceDetectUseBuf!" << endl; + return; + } cascadebuf.detectMultiScale( image, oclfaces, 1.1, 3, flags, Size(30, 30), Size(0, 0) ); - cascadebuf.release(); cpucascade.detectMultiScale( smallImg, faces, 1.1, 3, flags, Size(30, 30), Size(0, 0) ); EXPECT_EQ(faces.size(), oclfaces.size()); + + // intentionally run ocl facedetect again and check if it still works after the first run + cascadebuf.detectMultiScale( image, oclfaces, 1.1, 3, + flags, + Size(30, 30)); + cascadebuf.release(); + EXPECT_EQ(faces.size(), oclfaces.size()); } INSTANTIATE_TEST_CASE_P(FaceDetect, Haar, Combine(Values(1.0), - Values(CV_HAAR_SCALE_IMAGE, 0))); + Values(CV_HAAR_SCALE_IMAGE, 0), Values(cascade_frontalface_alt, cascade_frontalface_alt2))); #endif // HAVE_OPENCL From 7ed9c0e87a99eb32a1830ffb8b2a78028b72643e Mon Sep 17 00:00:00 2001 From: yao Date: Thu, 30 May 2013 14:57:15 +0800 Subject: [PATCH 045/178] Fix brute_force_matcher's hung on some Intel CPU OCL --- modules/ocl/src/brute_force_matcher.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/modules/ocl/src/brute_force_matcher.cpp b/modules/ocl/src/brute_force_matcher.cpp index c12fa73064..74da6ddd06 100644 --- a/modules/ocl/src/brute_force_matcher.cpp +++ b/modules/ocl/src/brute_force_matcher.cpp @@ -245,11 +245,12 @@ static void matchDispatcher(const oclMat &query, const oclMat &train, const oclM { const oclMat zeroMask; const oclMat &tempMask = mask.data ? mask : zeroMask; + bool is_cpu = queryDeviceInfo(); if (query.cols <= 64) { matchUnrolledCached<16, 64>(query, train, tempMask, trainIdx, distance, distType); } - else if (query.cols <= 128) + else if (query.cols <= 128 && !is_cpu) { matchUnrolledCached<16, 128>(query, train, tempMask, trainIdx, distance, distType); } @@ -264,11 +265,12 @@ static void matchDispatcher(const oclMat &query, const oclMat *trains, int n, co { const oclMat zeroMask; const oclMat &tempMask = mask.data ? mask : zeroMask; + bool is_cpu = queryDeviceInfo(); if (query.cols <= 64) { matchUnrolledCached<16, 64>(query, trains, n, tempMask, trainIdx, imgIdx, distance, distType); } - else if (query.cols <= 128) + else if (query.cols <= 128 && !is_cpu) { matchUnrolledCached<16, 128>(query, trains, n, tempMask, trainIdx, imgIdx, distance, distType); } @@ -284,11 +286,12 @@ static void matchDispatcher(const oclMat &query, const oclMat &train, float maxD { const oclMat zeroMask; const oclMat &tempMask = mask.data ? mask : zeroMask; + bool is_cpu = queryDeviceInfo(); if (query.cols <= 64) { matchUnrolledCached<16, 64>(query, train, maxDistance, tempMask, trainIdx, distance, nMatches, distType); } - else if (query.cols <= 128) + else if (query.cols <= 128 && !is_cpu) { matchUnrolledCached<16, 128>(query, train, maxDistance, tempMask, trainIdx, distance, nMatches, distType); } @@ -466,11 +469,12 @@ static void calcDistanceDispatcher(const oclMat &query, const oclMat &train, con static void match2Dispatcher(const oclMat &query, const oclMat &train, const oclMat &mask, const oclMat &trainIdx, const oclMat &distance, int distType) { + bool is_cpu = queryDeviceInfo(); if (query.cols <= 64) { knn_matchUnrolledCached<16, 64>(query, train, mask, trainIdx, distance, distType); } - else if (query.cols <= 128) + else if (query.cols <= 128 && !is_cpu) { knn_matchUnrolledCached<16, 128>(query, train, mask, trainIdx, distance, distType); } From 5b598f8a0e9048dd800d479e8abd979e012effdd Mon Sep 17 00:00:00 2001 From: yao Date: Thu, 30 May 2013 16:20:31 +0800 Subject: [PATCH 046/178] a few fixes of ocl::perf test cases --- modules/ocl/perf/perf_arithm.cpp | 5 - modules/ocl/perf/perf_filters.cpp | 8 +- modules/ocl/perf/perf_imgproc.cpp | 197 ++---------------------------- 3 files changed, 10 insertions(+), 200 deletions(-) diff --git a/modules/ocl/perf/perf_arithm.cpp b/modules/ocl/perf/perf_arithm.cpp index 4f690e0912..3ef0634e70 100644 --- a/modules/ocl/perf/perf_arithm.cpp +++ b/modules/ocl/perf/perf_arithm.cpp @@ -62,7 +62,6 @@ PERFTEST(lut) gen(src, size, size, all_type[j], 0, 256); gen(lut, 1, 256, CV_8UC1, 0, 1); - dst = src; LUT(src, lut, dst); @@ -233,8 +232,6 @@ PERFTEST(Mul) gen(src1, size, size, all_type[j], 0, 256); gen(src2, size, size, all_type[j], 0, 256); - dst = src1; - dst.setTo(0); multiply(src1, src2, dst); @@ -281,8 +278,6 @@ PERFTEST(Div) gen(src1, size, size, all_type[j], 0, 256); gen(src2, size, size, all_type[j], 0, 256); - dst = src1; - dst.setTo(0); divide(src1, src2, dst); diff --git a/modules/ocl/perf/perf_filters.cpp b/modules/ocl/perf/perf_filters.cpp index c8c840d0e8..5cf92711e7 100644 --- a/modules/ocl/perf/perf_filters.cpp +++ b/modules/ocl/perf/perf_filters.cpp @@ -291,9 +291,7 @@ PERFTEST(GaussianBlur) { SUBTEST << size << 'x' << size << "; " << type_name[j] ; - gen(src, size, size, all_type[j], 0, 256); - dst = src; - dst.setTo(0); + gen(src, size, size, all_type[j], 5, 16); GaussianBlur(src, dst, Size(9, 9), 0); @@ -346,8 +344,8 @@ PERFTEST(filter2D) Mat kernel; gen(kernel, ksize, ksize, CV_32FC1, 0.0, 1.0); - Mat dst, ocl_dst; - dst.setTo(0); + Mat dst, ocl_dst; + cv::filter2D(src, dst, -1, kernel); CPU_ON; diff --git a/modules/ocl/perf/perf_imgproc.cpp b/modules/ocl/perf/perf_imgproc.cpp index 18c7429e84..0aef8b27e4 100644 --- a/modules/ocl/perf/perf_imgproc.cpp +++ b/modules/ocl/perf/perf_imgproc.cpp @@ -674,8 +674,8 @@ COOR do_meanShift(int x0, int y0, uchar *sptr, uchar *dptr, int sstep, cv::Size coor.y = static_cast(y0); return coor; } -void meanShiftFiltering_(const Mat &src_roi, Mat &dst_roi, int sp, int sr, cv::TermCriteria crit); -void meanShiftFiltering_(const Mat &src_roi, Mat &dst_roi, int sp, int sr, cv::TermCriteria crit) + +static void meanShiftFiltering_(const Mat &src_roi, Mat &dst_roi, int sp, int sr, cv::TermCriteria crit) { if( src_roi.empty() ) CV_Error( CV_StsBadArg, "The input image is empty" ); @@ -683,6 +683,8 @@ void meanShiftFiltering_(const Mat &src_roi, Mat &dst_roi, int sp, int sr, cv::T if( src_roi.depth() != CV_8U || src_roi.channels() != 4 ) CV_Error( CV_StsUnsupportedFormat, "Only 8-bit, 4-channel images are supported" ); + dst_roi.create(src_roi.size(), src_roi.type()); + CV_Assert( (src_roi.cols == dst_roi.cols) && (src_roi.rows == dst_roi.rows) ); CV_Assert( !(dst_roi.step & 0x3) ); @@ -725,9 +727,6 @@ PERFTEST(meanShiftFiltering) SUBTEST << size << 'x' << size << "; 8UC3 vs 8UC4"; gen(src, size, size, CV_8UC4, Scalar::all(0), Scalar::all(256)); - //gen(dst, size, size, CV_8UC4, Scalar::all(0), Scalar::all(256)); - dst = src; - dst.setTo(0); cv::TermCriteria crit(cv::TermCriteria::COUNT + cv::TermCriteria::EPS, 5, 1); @@ -756,201 +755,21 @@ PERFTEST(meanShiftFiltering) TestSystem::instance().ExpectedMatNear(dst, ocl_dst, 0.0); } } -///////////// meanShiftProc//////////////////////// -#if 0 -COOR do_meanShift(int x0, int y0, uchar *sptr, uchar *dptr, int sstep, cv::Size size, int sp, int sr, int maxIter, float eps, int *tab) -{ - - int isr2 = sr * sr; - int c0, c1, c2, c3; - int iter; - uchar *ptr = NULL; - uchar *pstart = NULL; - int revx = 0, revy = 0; - c0 = sptr[0]; - c1 = sptr[1]; - c2 = sptr[2]; - c3 = sptr[3]; - - // iterate meanshift procedure - for (iter = 0; iter < maxIter; iter++) - { - int count = 0; - int s0 = 0, s1 = 0, s2 = 0, sx = 0, sy = 0; - - //mean shift: process pixels in window (p-sigmaSp)x(p+sigmaSp) - int minx = x0 - sp; - int miny = y0 - sp; - int maxx = x0 + sp; - int maxy = y0 + sp; - - //deal with the image boundary - if (minx < 0) - { - minx = 0; - } - - if (miny < 0) - { - miny = 0; - } - - if (maxx >= size.width) - { - maxx = size.width - 1; - } - - if (maxy >= size.height) - { - maxy = size.height - 1; - } - - if (iter == 0) - { - pstart = sptr; - } - else - { - pstart = pstart + revy * sstep + (revx << 2); //point to the new position - } - - ptr = pstart; - ptr = ptr + (miny - y0) * sstep + ((minx - x0) << 2); //point to the start in the row - - for (int y = miny; y <= maxy; y++, ptr += sstep - ((maxx - minx + 1) << 2)) - { - int rowCount = 0; - int x = minx; -#if CV_ENABLE_UNROLLED - - for (; x + 4 <= maxx; x += 4, ptr += 16) - { - int t0, t1, t2; - t0 = ptr[0], t1 = ptr[1], t2 = ptr[2]; - - if (tab[t0 - c0 + 255] + tab[t1 - c1 + 255] + tab[t2 - c2 + 255] <= isr2) - { - s0 += t0; - s1 += t1; - s2 += t2; - sx += x; - rowCount++; - } - - t0 = ptr[4], t1 = ptr[5], t2 = ptr[6]; - - if (tab[t0 - c0 + 255] + tab[t1 - c1 + 255] + tab[t2 - c2 + 255] <= isr2) - { - s0 += t0; - s1 += t1; - s2 += t2; - sx += x + 1; - rowCount++; - } - - t0 = ptr[8], t1 = ptr[9], t2 = ptr[10]; - - if (tab[t0 - c0 + 255] + tab[t1 - c1 + 255] + tab[t2 - c2 + 255] <= isr2) - { - s0 += t0; - s1 += t1; - s2 += t2; - sx += x + 2; - rowCount++; - } - - t0 = ptr[12], t1 = ptr[13], t2 = ptr[14]; - - if (tab[t0 - c0 + 255] + tab[t1 - c1 + 255] + tab[t2 - c2 + 255] <= isr2) - { - s0 += t0; - s1 += t1; - s2 += t2; - sx += x + 3; - rowCount++; - } - } - -#endif - - for (; x <= maxx; x++, ptr += 4) - { - int t0 = ptr[0], t1 = ptr[1], t2 = ptr[2]; - - if (tab[t0 - c0 + 255] + tab[t1 - c1 + 255] + tab[t2 - c2 + 255] <= isr2) - { - s0 += t0; - s1 += t1; - s2 += t2; - sx += x; - rowCount++; - } - } - - if (rowCount == 0) - { - continue; - } - - count += rowCount; - sy += y * rowCount; - } - - if (count == 0) - { - break; - } - - int x1 = sx / count; - int y1 = sy / count; - s0 = s0 / count; - s1 = s1 / count; - s2 = s2 / count; - - bool stopFlag = (x0 == x1 && y0 == y1) || (abs(x1 - x0) + abs(y1 - y0) + - tab[s0 - c0 + 255] + tab[s1 - c1 + 255] + tab[s2 - c2 + 255] <= eps); - - //revise the pointer corresponding to the new (y0,x0) - revx = x1 - x0; - revy = y1 - y0; - - x0 = x1; - y0 = y1; - c0 = s0; - c1 = s1; - c2 = s2; - - if (stopFlag) - { - break; - } - } //for iter - - dptr[0] = (uchar)c0; - dptr[1] = (uchar)c1; - dptr[2] = (uchar)c2; - dptr[3] = (uchar)c3; - - COOR coor; - coor.x = static_cast(x0); - coor.y = static_cast(y0); - return coor; -} -#endif void meanShiftProc_(const Mat &src_roi, Mat &dst_roi, Mat &dstCoor_roi, int sp, int sr, cv::TermCriteria crit) { - if (src_roi.empty()) { CV_Error(CV_StsBadArg, "The input image is empty"); } - if (src_roi.depth() != CV_8U || src_roi.channels() != 4) { CV_Error(CV_StsUnsupportedFormat, "Only 8-bit, 4-channel images are supported"); } + dst_roi.create(src_roi.size(), src_roi.type()); + dstCoor_roi.create(src_roi.size(), CV_16SC2); + CV_Assert((src_roi.cols == dst_roi.cols) && (src_roi.rows == dst_roi.rows) && (src_roi.cols == dstCoor_roi.cols) && (src_roi.rows == dstCoor_roi.rows)); CV_Assert(!(dstCoor_roi.step & 0x3)); @@ -1008,8 +827,6 @@ PERFTEST(meanShiftProc) SUBTEST << size << 'x' << size << "; 8UC4 and CV_16SC2 "; gen(src, size, size, CV_8UC4, Scalar::all(0), Scalar::all(256)); - gen(dst[0], size, size, CV_8UC4, Scalar::all(0), Scalar::all(256)); - gen(dst[1], size, size, CV_16SC2, Scalar::all(0), Scalar::all(256)); meanShiftProc_(src, dst[0], dst[1], 5, 6, crit); From fdc133d8c9e112d4a20ffc9f2efdfbb7754f048b Mon Sep 17 00:00:00 2001 From: peng xiao Date: Thu, 30 May 2013 09:37:05 +0800 Subject: [PATCH 047/178] Fix ocl::pyrup kernel build on Mac. --- modules/ocl/src/opencl/pyr_up.cl | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/modules/ocl/src/opencl/pyr_up.cl b/modules/ocl/src/opencl/pyr_up.cl index ef41c9408d..4afa7b7104 100644 --- a/modules/ocl/src/opencl/pyr_up.cl +++ b/modules/ocl/src/opencl/pyr_up.cl @@ -389,8 +389,8 @@ __kernel void pyrUp_C4_D0(__global uchar4* src,__global uchar4* dst, float4 sum = (float4)(0,0,0,0); - const int evenFlag = (int)((tidx & 1) == 0); - const int oddFlag = (int)((tidx & 1) != 0); + const float4 evenFlag = (float4)((tidx & 1) == 0); + const float4 oddFlag = (float4)((tidx & 1) != 0); const bool eveny = ((tidy & 1) == 0); float4 co1 = (float4)(0.375f, 0.375f, 0.375f, 0.375f); @@ -455,6 +455,7 @@ __kernel void pyrUp_C4_D0(__global uchar4* src,__global uchar4* dst, dst[x + y * dstStep] = convert_uchar4_sat_rte(4.0f * sum); } } + /////////////////////////////////////////////////////////////////////// ////////////////////////// CV_16UC4 ////////////////////////////////// /////////////////////////////////////////////////////////////////////// @@ -492,8 +493,8 @@ __kernel void pyrUp_C4_D2(__global ushort4* src,__global ushort4* dst, float4 sum = (float4)(0,0,0,0); - const int evenFlag = (int)((get_local_id(0) & 1) == 0); - const int oddFlag = (int)((get_local_id(0) & 1) != 0); + const float4 evenFlag = (float4)((get_local_id(0) & 1) == 0); + const float4 oddFlag = (float4)((get_local_id(0) & 1) != 0); const bool eveny = ((get_local_id(1) & 1) == 0); const int tidx = get_local_id(0); @@ -604,8 +605,8 @@ __kernel void pyrUp_C4_D5(__global float4* src,__global float4* dst, float4 sum = (float4)(0,0,0,0); - const int evenFlag = (int)((tidx & 1) == 0); - const int oddFlag = (int)((tidx & 1) != 0); + const float4 evenFlag = (float4)((tidx & 1) == 0); + const float4 oddFlag = (float4)((tidx & 1) != 0); const bool eveny = ((tidy & 1) == 0); float4 co1 = (float4)(0.375f, 0.375f, 0.375f, 0.375f); @@ -669,4 +670,4 @@ __kernel void pyrUp_C4_D5(__global float4* src,__global float4* dst, { dst[x + y * dstStep] = 4.0f * sum; } -} \ No newline at end of file +} From b1c248fcc9eaa24baabf55fde7fcff096d62dcb2 Mon Sep 17 00:00:00 2001 From: peng xiao Date: Fri, 31 May 2013 10:53:52 +0800 Subject: [PATCH 048/178] Fix ocl::filter2D. In current implementation, this function only works when anchor point is in the kernel center and kernel size supported is either 3x3 or 5x5. --- modules/ocl/include/opencv2/ocl/ocl.hpp | 2 ++ modules/ocl/src/filtering.cpp | 10 +++++++--- modules/ocl/src/opencl/filtering_laplacian.cl | 12 ++++++------ 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/modules/ocl/include/opencv2/ocl/ocl.hpp b/modules/ocl/include/opencv2/ocl/ocl.hpp index 785248cfc5..01b0f72d2a 100644 --- a/modules/ocl/include/opencv2/ocl/ocl.hpp +++ b/modules/ocl/include/opencv2/ocl/ocl.hpp @@ -689,6 +689,8 @@ namespace cv } //! applies non-separable 2D linear filter to the image + // Note, at the moment this function only works when anchor point is in the kernel center + // and kernel size supported is either 3x3 or 5x5; otherwise the function will fail to output valid result CV_EXPORTS void filter2D(const oclMat &src, oclMat &dst, int ddepth, const Mat &kernel, Point anchor = Point(-1, -1), int borderType = BORDER_DEFAULT); diff --git a/modules/ocl/src/filtering.cpp b/modules/ocl/src/filtering.cpp index 56a70ae539..f35a26e332 100644 --- a/modules/ocl/src/filtering.cpp +++ b/modules/ocl/src/filtering.cpp @@ -645,7 +645,11 @@ static void GPUFilter2D(const oclMat &src, oclMat &dst, oclMat &mat_kernel, args.push_back(make_pair(sizeof(cl_int), (void *)&src.wholecols)); args.push_back(make_pair(sizeof(cl_int), (void *)&src.wholerows)); - openCLExecuteKernel(clCxt, &filtering_laplacian, kernelName, globalThreads, localThreads, args, cn, depth); + const int buffer_size = 100; + char opt_buffer [buffer_size] = ""; + sprintf(opt_buffer, "-DANCHOR=%d -DANX=%d -DANY=%d", ksize.width, anchor.x, anchor.y); + + openCLExecuteKernel(clCxt, &filtering_laplacian, kernelName, globalThreads, localThreads, args, cn, depth, opt_buffer); } Ptr cv::ocl::getLinearFilter_GPU(int srcType, int dstType, const Mat &kernel, const Size &ksize, Point anchor, int borderType) @@ -656,7 +660,7 @@ Ptr cv::ocl::getLinearFilter_GPU(int srcType, int dstType, const oclMat gpu_krnl; int nDivisor; - normalizeKernel(kernel, gpu_krnl, CV_32S, &nDivisor, true); + normalizeKernel(kernel, gpu_krnl, CV_32S, &nDivisor, false); normalizeAnchor(anchor, ksize); return Ptr(new LinearFilter_GPU(ksize, anchor, gpu_krnl, GPUFilter2D_callers[CV_MAT_CN(srcType)], @@ -1172,7 +1176,7 @@ void linearRowFilter_gpu(const oclMat &src, const oclMat &dst, oclMat mat_kernel args.push_back(make_pair(sizeof(cl_int), (void *)&ridusy)); args.push_back(make_pair(sizeof(cl_mem), (void *)&mat_kernel.data)); - openCLExecuteKernel2(clCxt, &filter_sep_row, kernelName, globalThreads, localThreads, args, channels, src.depth(), compile_option, CLFLUSH); + openCLExecuteKernel(clCxt, &filter_sep_row, kernelName, globalThreads, localThreads, args, channels, src.depth(), compile_option); } Ptr cv::ocl::getLinearRowFilter_GPU(int srcType, int /*bufType*/, const Mat &rowKernel, int anchor, int bordertype) diff --git a/modules/ocl/src/opencl/filtering_laplacian.cl b/modules/ocl/src/opencl/filtering_laplacian.cl index 96a2f51ef4..8535eb1a54 100644 --- a/modules/ocl/src/opencl/filtering_laplacian.cl +++ b/modules/ocl/src/opencl/filtering_laplacian.cl @@ -82,9 +82,9 @@ ////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////Macro for define elements number per thread///////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// -#define ANCHOR 3 -#define ANX 1 -#define ANY 1 +//#define ANCHOR 3 +//#define ANX 1 +//#define ANY 1 #define ROWS_PER_GROUP 4 #define ROWS_PER_GROUP_BITS 2 @@ -185,7 +185,7 @@ __kernel void filter2D_C1_D0(__global uchar *src, int src_step, int src_offset_x for(int i = 0; i < ANCHOR; i++) { -#pragma unroll 3 +#pragma unroll for(int j = 0; j < ANCHOR; j++) { if(dst_rows_index < dst_rows_end) @@ -295,7 +295,7 @@ __kernel void filter2D_C1_D5(__global float *src, int src_step, int src_offset_x for(int i = 0; i < ANCHOR; i++) { -#pragma unroll 3 +#pragma unroll for(int j = 0; j < ANCHOR; j++) { if(dst_rows_index < dst_rows_end) @@ -410,7 +410,7 @@ __kernel void filter2D_C4_D0(__global uchar4 *src, int src_step, int src_offset_ for(int i = 0; i < ANCHOR; i++) { -#pragma unroll 3 +#pragma unroll for(int j = 0; j < ANCHOR; j++) { if(dst_rows_index < dst_rows_end) From abefcc60612617cdc48b278e347bdf0df4b0139b Mon Sep 17 00:00:00 2001 From: yao Date: Fri, 31 May 2013 15:16:03 +0800 Subject: [PATCH 049/178] Adjust perf_filters, as this function only supports 3x3 kernel --- modules/ocl/perf/perf_filters.cpp | 47 +++++++++++++++---------------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/modules/ocl/perf/perf_filters.cpp b/modules/ocl/perf/perf_filters.cpp index 5cf92711e7..a05301b34c 100644 --- a/modules/ocl/perf/perf_filters.cpp +++ b/modules/ocl/perf/perf_filters.cpp @@ -337,39 +337,38 @@ PERFTEST(filter2D) { gen(src, size, size, all_type[j], 0, 256); - for (int ksize = 3; ksize <= 15; ksize = 2*ksize+1) - { - SUBTEST << "ksize = " << ksize << "; " << size << 'x' << size << "; " << type_name[j] ; + const int ksize = 3; - Mat kernel; - gen(kernel, ksize, ksize, CV_32FC1, 0.0, 1.0); + SUBTEST << "ksize = " << ksize << "; " << size << 'x' << size << "; " << type_name[j] ; - Mat dst, ocl_dst; + Mat kernel; + gen(kernel, ksize, ksize, CV_32SC1, -3.0, 3.0); - cv::filter2D(src, dst, -1, kernel); + Mat dst, ocl_dst; - CPU_ON; - cv::filter2D(src, dst, -1, kernel); - CPU_OFF; + cv::filter2D(src, dst, -1, kernel); - ocl::oclMat d_src(src), d_dst; + CPU_ON; + cv::filter2D(src, dst, -1, kernel); + CPU_OFF; - WARMUP_ON; - ocl::filter2D(d_src, d_dst, -1, kernel); - WARMUP_OFF; + ocl::oclMat d_src(src), d_dst; - GPU_ON; - ocl::filter2D(d_src, d_dst, -1, kernel); - GPU_OFF; + WARMUP_ON; + ocl::filter2D(d_src, d_dst, -1, kernel); + WARMUP_OFF; - GPU_FULL_ON; - d_src.upload(src); - ocl::filter2D(d_src, d_dst, -1, kernel); - d_dst.download(ocl_dst); - GPU_FULL_OFF; + GPU_ON; + ocl::filter2D(d_src, d_dst, -1, kernel); + GPU_OFF; - TestSystem::instance().ExpectedMatNear(ocl_dst, dst, 1e-5); - } + GPU_FULL_ON; + d_src.upload(src); + ocl::filter2D(d_src, d_dst, -1, kernel); + d_dst.download(ocl_dst); + GPU_FULL_OFF; + + TestSystem::instance().ExpectedMatNear(ocl_dst, dst, 1e-5); } From 15a213d3fca17d785441044d7126300d4f4ed4ef Mon Sep 17 00:00:00 2001 From: yao Date: Fri, 31 May 2013 15:35:54 +0800 Subject: [PATCH 050/178] fix a crash on Linux --- modules/ocl/src/pyrlk.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ocl/src/pyrlk.cpp b/modules/ocl/src/pyrlk.cpp index a3e65dde3f..8e9420480c 100644 --- a/modules/ocl/src/pyrlk.cpp +++ b/modules/ocl/src/pyrlk.cpp @@ -142,7 +142,7 @@ static void lkSparse_run(oclMat &I, oclMat &J, int wave_size = queryDeviceInfo(kernel); openCLSafeCall(clReleaseKernel(kernel)); - static char opt[16] = {0}; + static char opt[32] = {0}; sprintf(opt, " -D WAVE_SIZE=%d", wave_size); openCLExecuteKernel(clCxt, &pyrlk, kernelName, globalThreads, localThreads, From d85f27b53791c144c70a7746ab3002d8d281e30e Mon Sep 17 00:00:00 2001 From: peng xiao Date: Fri, 31 May 2013 16:06:56 +0800 Subject: [PATCH 051/178] Update ocl::surf_matcher sample. The new sample adjust some parameters thus it should always be able to calculate valid homography matrix when input is box.png and box_in_scene.png. Pure cpp surf and bfmatcher implementation is also added to show the user its accuracy and performance. --- samples/ocl/surf_matcher.cpp | 413 ++++++++++++++++++++++++----------- 1 file changed, 285 insertions(+), 128 deletions(-) diff --git a/samples/ocl/surf_matcher.cpp b/samples/ocl/surf_matcher.cpp index ea6ee97cb2..8734e3f813 100644 --- a/samples/ocl/surf_matcher.cpp +++ b/samples/ocl/surf_matcher.cpp @@ -46,156 +46,101 @@ #include #include #include "opencv2/core/core.hpp" -#include "opencv2/features2d/features2d.hpp" #include "opencv2/highgui/highgui.hpp" #include "opencv2/ocl/ocl.hpp" -#include "opencv2/nonfree/nonfree.hpp" #include "opencv2/nonfree/ocl.hpp" #include "opencv2/calib3d/calib3d.hpp" +#include "opencv2/nonfree/nonfree.hpp" -using namespace std; using namespace cv; using namespace cv::ocl; -//#define USE_CPU_DESCRIPTOR // use cpu descriptor extractor until ocl descriptor extractor is fixed -//#define USE_CPU_BFMATCHER +const int LOOP_NUM = 10; +const int GOOD_PTS_MAX = 50; +const float GOOD_PORTION = 0.15f; + +namespace +{ void help(); void help() { - cout << "\nThis program demonstrates using SURF_OCL features detector and descriptor extractor" << endl; - cout << "\nUsage:\n\tsurf_matcher --left --right " << endl; + std::cout << "\nThis program demonstrates using SURF_OCL features detector and descriptor extractor" << std::endl; + std::cout << "\nUsage:\n\tsurf_matcher --left --right [-c]" << std::endl; + std::cout << "\nExample:\n\tsurf_matcher --left box.png --right box_in_scene.png" << std::endl; } +int64 work_begin = 0; +int64 work_end = 0; -//////////////////////////////////////////////////// -// This program demonstrates the usage of SURF_OCL. -// use cpu findHomography interface to calculate the transformation matrix -int main(int argc, char* argv[]) +void workBegin() +{ + work_begin = getTickCount(); +} +void workEnd() { - if (argc != 5 && argc != 1) + work_end = getTickCount() - work_begin; +} +double getTime(){ + return work_end /((double)cvGetTickFrequency() * 1000.); +} + +template +struct SURFDetector +{ + KPDetector surf; + SURFDetector(double hessian = 800.0) + :surf(hessian) { - help(); - return -1; } - vector info; - if(!cv::ocl::getDevice(info)) + template + void operator()(const T& in, const T& mask, vector& pts, T& descriptors, bool useProvided = false) { - cout << "Error: Did not find a valid OpenCL device!" << endl; - return -1; + surf(in, mask, pts, descriptors, useProvided); } - Mat cpu_img1, cpu_img2, cpu_img1_grey, cpu_img2_grey; - oclMat img1, img2; - if(argc != 5) +}; + +template +struct SURFMatcher +{ + KPMatcher matcher; + template + void match(const T& in1, const T& in2, vector& matches) { - cpu_img1 = imread("o.png"); - cvtColor(cpu_img1, cpu_img1_grey, CV_BGR2GRAY); - img1 = cpu_img1_grey; - CV_Assert(!img1.empty()); - - cpu_img2 = imread("r2.png"); - cvtColor(cpu_img2, cpu_img2_grey, CV_BGR2GRAY); - img2 = cpu_img2_grey; - } - else - { - for (int i = 1; i < argc; ++i) - { - if (string(argv[i]) == "--left") - { - cpu_img1 = imread(argv[++i]); - cvtColor(cpu_img1, cpu_img1_grey, CV_BGR2GRAY); - img1 = cpu_img1_grey; - CV_Assert(!img1.empty()); - } - else if (string(argv[i]) == "--right") - { - cpu_img2 = imread(argv[++i]); - cvtColor(cpu_img2, cpu_img2_grey, CV_BGR2GRAY); - img2 = cpu_img2_grey; - } - else if (string(argv[i]) == "--help") - { - help(); - return -1; - } - } + matcher.match(in1, in2, matches); } +}; - SURF_OCL surf; - //surf.hessianThreshold = 400.f; - //surf.extended = false; - - // detecting keypoints & computing descriptors - oclMat keypoints1GPU, keypoints2GPU; - oclMat descriptors1GPU, descriptors2GPU; - - // downloading results - vector keypoints1, keypoints2; - vector matches; - - -#ifndef USE_CPU_DESCRIPTOR - surf(img1, oclMat(), keypoints1GPU, descriptors1GPU); - surf(img2, oclMat(), keypoints2GPU, descriptors2GPU); - - surf.downloadKeypoints(keypoints1GPU, keypoints1); - surf.downloadKeypoints(keypoints2GPU, keypoints2); - - -#ifdef USE_CPU_BFMATCHER - //BFMatcher - BFMatcher matcher(cv::NORM_L2); - matcher.match(Mat(descriptors1GPU), Mat(descriptors2GPU), matches); -#else - BruteForceMatcher_OCL_base matcher(BruteForceMatcher_OCL_base::L2Dist); - matcher.match(descriptors1GPU, descriptors2GPU, matches); -#endif - -#else - surf(img1, oclMat(), keypoints1GPU); - surf(img2, oclMat(), keypoints2GPU); - surf.downloadKeypoints(keypoints1GPU, keypoints1); - surf.downloadKeypoints(keypoints2GPU, keypoints2); - - // use SURF_OCL to detect keypoints and use SURF to extract descriptors - SURF surf_cpu; - Mat descriptors1, descriptors2; - surf_cpu(cpu_img1, Mat(), keypoints1, descriptors1, true); - surf_cpu(cpu_img2, Mat(), keypoints2, descriptors2, true); - matcher.match(descriptors1, descriptors2, matches); -#endif - cout << "OCL: FOUND " << keypoints1GPU.cols << " keypoints on first image" << endl; - cout << "OCL: FOUND " << keypoints2GPU.cols << " keypoints on second image" << endl; - - double max_dist = 0; double min_dist = 100; - //-- Quick calculation of max and min distances between keypoints - for( size_t i = 0; i < keypoints1.size(); i++ ) - { - double dist = matches[i].distance; - if( dist < min_dist ) min_dist = dist; - if( dist > max_dist ) max_dist = dist; - } - - printf("-- Max dist : %f \n", max_dist ); - printf("-- Min dist : %f \n", min_dist ); - - //-- Draw only "good" matches (i.e. whose distance is less than 2.5*min_dist ) +Mat drawGoodMatches( + const Mat& cpu_img1, + const Mat& cpu_img2, + const vector& keypoints1, + const vector& keypoints2, + vector& matches, + vector& scene_corners_ + ) +{ + //-- Sort matches and preserve top 10% matches + std::sort(matches.begin(), matches.end()); std::vector< DMatch > good_matches; + double minDist = matches.front().distance, + maxDist = matches.back().distance; - for( size_t i = 0; i < keypoints1.size(); i++ ) + const int ptsPairs = std::min(GOOD_PTS_MAX, (int)(matches.size() * GOOD_PORTION)); + for( int i = 0; i < ptsPairs; i++ ) { - if( matches[i].distance < 3*min_dist ) - { - good_matches.push_back( matches[i]); - } + good_matches.push_back( matches[i] ); } + std::cout << "\nMax distance: " << maxDist << std::endl; + std::cout << "Min distance: " << minDist << std::endl; + + std::cout << "Calculating homography using " << ptsPairs << " point pairs." << std::endl; // drawing the results Mat img_matches; drawMatches( cpu_img1, keypoints1, cpu_img2, keypoints2, good_matches, img_matches, Scalar::all(-1), Scalar::all(-1), - vector(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS ); + vector(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS ); //-- Localize the object std::vector obj; @@ -207,26 +152,238 @@ int main(int argc, char* argv[]) obj.push_back( keypoints1[ good_matches[i].queryIdx ].pt ); scene.push_back( keypoints2[ good_matches[i].trainIdx ].pt ); } - Mat H = findHomography( obj, scene, CV_RANSAC ); - //-- Get the corners from the image_1 ( the object to be "detected" ) std::vector obj_corners(4); obj_corners[0] = cvPoint(0,0); obj_corners[1] = cvPoint( cpu_img1.cols, 0 ); obj_corners[2] = cvPoint( cpu_img1.cols, cpu_img1.rows ); obj_corners[3] = cvPoint( 0, cpu_img1.rows ); std::vector scene_corners(4); - + + Mat H = findHomography( obj, scene, CV_RANSAC ); perspectiveTransform( obj_corners, scene_corners, H); + scene_corners_ = scene_corners; + //-- Draw lines between the corners (the mapped object in the scene - image_2 ) - line( img_matches, scene_corners[0] + Point2f( (float)cpu_img1.cols, 0), scene_corners[1] + Point2f( (float)cpu_img1.cols, 0), Scalar( 0, 255, 0), 4 ); - line( img_matches, scene_corners[1] + Point2f( (float)cpu_img1.cols, 0), scene_corners[2] + Point2f( (float)cpu_img1.cols, 0), Scalar( 0, 255, 0), 4 ); - line( img_matches, scene_corners[2] + Point2f( (float)cpu_img1.cols, 0), scene_corners[3] + Point2f( (float)cpu_img1.cols, 0), Scalar( 0, 255, 0), 4 ); - line( img_matches, scene_corners[3] + Point2f( (float)cpu_img1.cols, 0), scene_corners[0] + Point2f( (float)cpu_img1.cols, 0), Scalar( 0, 255, 0), 4 ); + line( img_matches, + scene_corners[0] + Point2f( (float)cpu_img1.cols, 0), scene_corners[1] + Point2f( (float)cpu_img1.cols, 0), + Scalar( 0, 255, 0), 2, CV_AA ); + line( img_matches, + scene_corners[1] + Point2f( (float)cpu_img1.cols, 0), scene_corners[2] + Point2f( (float)cpu_img1.cols, 0), + Scalar( 0, 255, 0), 2, CV_AA ); + line( img_matches, + scene_corners[2] + Point2f( (float)cpu_img1.cols, 0), scene_corners[3] + Point2f( (float)cpu_img1.cols, 0), + Scalar( 0, 255, 0), 2, CV_AA ); + line( img_matches, + scene_corners[3] + Point2f( (float)cpu_img1.cols, 0), scene_corners[0] + Point2f( (float)cpu_img1.cols, 0), + Scalar( 0, 255, 0), 2, CV_AA ); + return img_matches; +} + +} +//////////////////////////////////////////////////// +// This program demonstrates the usage of SURF_OCL. +// use cpu findHomography interface to calculate the transformation matrix +int main(int argc, char* argv[]) +{ + vector info; + if(cv::ocl::getDevice(info) == 0) + { + std::cout << "Error: Did not find a valid OpenCL device!" << std::endl; + return -1; + } + ocl::setDevice(info[0]); + + Mat cpu_img1, cpu_img2, cpu_img1_grey, cpu_img2_grey; + oclMat img1, img2; + bool useCPU = false; + bool useGPU = false; + bool useALL = false; + + for (int i = 1; i < argc; ++i) + { + if (string(argv[i]) == "--left") + { + cpu_img1 = imread(argv[++i]); + CV_Assert(!cpu_img1.empty()); + cvtColor(cpu_img1, cpu_img1_grey, CV_BGR2GRAY); + img1 = cpu_img1_grey; + } + else if (string(argv[i]) == "--right") + { + cpu_img2 = imread(argv[++i]); + CV_Assert(!cpu_img2.empty()); + cvtColor(cpu_img2, cpu_img2_grey, CV_BGR2GRAY); + img2 = cpu_img2_grey; + } + else if (string(argv[i]) == "-c") + { + useCPU = true; + useGPU = false; + useALL = false; + }else if(string(argv[i]) == "-g") + { + useGPU = true; + useCPU = false; + useALL = false; + }else if(string(argv[i]) == "-a") + { + useALL = true; + useCPU = false; + useGPU = false; + } + else if (string(argv[i]) == "--help") + { + help(); + return -1; + } + } + if(!useCPU) + { + std::cout + << "Device name:" + << info[0].DeviceName[0] + << std::endl; + } + double surf_time = 0.; + + //declare input/output + vector keypoints1, keypoints2; + vector matches; + + vector gpu_keypoints1; + vector gpu_keypoints2; + vector gpu_matches; + + Mat descriptors1CPU, descriptors2CPU; + + oclMat keypoints1GPU, keypoints2GPU; + oclMat descriptors1GPU, descriptors2GPU; + + //instantiate detectors/matchers + SURFDetector cpp_surf; + SURFDetector ocl_surf; + + SURFMatcher cpp_matcher; + SURFMatcher ocl_matcher; + + //-- start of timing section + if (useCPU) + { + for (int i = 0; i <= LOOP_NUM; i++) + { + if(i == 1) workBegin(); + cpp_surf(cpu_img1_grey, Mat(), keypoints1, descriptors1CPU); + cpp_surf(cpu_img2_grey, Mat(), keypoints2, descriptors2CPU); + cpp_matcher.match(descriptors1CPU, descriptors2CPU, matches); + } + workEnd(); + std::cout << "CPP: FOUND " << keypoints1.size() << " keypoints on first image" << std::endl; + std::cout << "CPP: FOUND " << keypoints2.size() << " keypoints on second image" << std::endl; + + surf_time = getTime(); + std::cout << "SURF run time: " << surf_time / LOOP_NUM << " ms" << std::endl<<"\n"; + } + else if(useGPU) + { + for (int i = 0; i <= LOOP_NUM; i++) + { + if(i == 1) workBegin(); + ocl_surf(img1, oclMat(), keypoints1, descriptors1GPU); + ocl_surf(img2, oclMat(), keypoints2, descriptors2GPU); + ocl_matcher.match(descriptors1GPU, descriptors2GPU, matches); + } + workEnd(); + std::cout << "OCL: FOUND " << keypoints1.size() << " keypoints on first image" << std::endl; + std::cout << "OCL: FOUND " << keypoints2.size() << " keypoints on second image" << std::endl; + + surf_time = getTime(); + std::cout << "SURF run time: " << surf_time / LOOP_NUM << " ms" << std::endl<<"\n"; + }else + { + //cpu runs + for (int i = 0; i <= LOOP_NUM; i++) + { + if(i == 1) workBegin(); + cpp_surf(cpu_img1_grey, Mat(), keypoints1, descriptors1CPU); + cpp_surf(cpu_img2_grey, Mat(), keypoints2, descriptors2CPU); + cpp_matcher.match(descriptors1CPU, descriptors2CPU, matches); + } + workEnd(); + std::cout << "\nCPP: FOUND " << keypoints1.size() << " keypoints on first image" << std::endl; + std::cout << "CPP: FOUND " << keypoints2.size() << " keypoints on second image" << std::endl; + + surf_time = getTime(); + std::cout << "(CPP)SURF run time: " << surf_time / LOOP_NUM << " ms" << std::endl; + + //gpu runs + for (int i = 0; i <= LOOP_NUM; i++) + { + if(i == 1) workBegin(); + ocl_surf(img1, oclMat(), gpu_keypoints1, descriptors1GPU); + ocl_surf(img2, oclMat(), gpu_keypoints2, descriptors2GPU); + ocl_matcher.match(descriptors1GPU, descriptors2GPU, gpu_matches); + } + workEnd(); + std::cout << "\nOCL: FOUND " << keypoints1.size() << " keypoints on first image" << std::endl; + std::cout << "OCL: FOUND " << keypoints2.size() << " keypoints on second image" << std::endl; + + surf_time = getTime(); + std::cout << "(OCL)SURF run time: " << surf_time / LOOP_NUM << " ms" << std::endl<<"\n"; + + } + + //-------------------------------------------------------------------------- + std::vector cpu_corner; + Mat img_matches = drawGoodMatches(cpu_img1, cpu_img2, keypoints1, keypoints2, matches, cpu_corner); + + std::vector gpu_corner; + Mat ocl_img_matches; + if(useALL || (!useCPU&&!useGPU)) + { + ocl_img_matches = drawGoodMatches(cpu_img1, cpu_img2, gpu_keypoints1, gpu_keypoints2, gpu_matches, gpu_corner); + + //check accuracy + std::cout<<"\nCheck accuracy:\n"; + + if(cpu_corner.size()!=gpu_corner.size()) + std::cout<<"Failed\n"; + else + { + bool result = false; + for(int i = 0; i < cpu_corner.size(); i++) + { + if((std::abs(cpu_corner[i].x - gpu_corner[i].x) > 10) + ||(std::abs(cpu_corner[i].y - gpu_corner[i].y) > 10)) + { + std::cout<<"Failed\n"; + result = false; + break; + } + result = true; + } + if(result) + std::cout<<"Passed\n"; + } + } //-- Show detected matches - namedWindow("ocl surf matches", 0); - imshow("ocl surf matches", img_matches); - waitKey(0); + if (useCPU) + { + namedWindow("cpu surf matches", 0); + imshow("cpu surf matches", img_matches); + } + else if(useGPU) + { + namedWindow("ocl surf matches", 0); + imshow("ocl surf matches", img_matches); + }else + { + namedWindow("cpu surf matches", 0); + imshow("cpu surf matches", img_matches); + namedWindow("ocl surf matches", 0); + imshow("ocl surf matches", ocl_img_matches); + } + waitKey(0); return 0; } From 29b13ec1def5e0a643aee73a5d13ebdfb9c07d30 Mon Sep 17 00:00:00 2001 From: Roman Donchenko Date: Thu, 30 May 2013 18:44:33 +0400 Subject: [PATCH 052/178] Replaced most of the usages of parallel_for with that of parallel_for_. This should allow many algorithms to take advantage of more parallelization technologies. --- apps/traincascade/boost.cpp | 30 +++--- modules/calib3d/src/solvepnp.cpp | 25 ----- modules/calib3d/src/stereobm.cpp | 20 ++-- modules/features2d/src/detectors.cpp | 38 +++----- modules/gpu/src/calib3d.cpp | 8 +- modules/imgproc/src/color.cpp | 96 ++++++++----------- modules/imgproc/src/distransform.cpp | 16 ++-- modules/imgproc/src/histogram.cpp | 57 ++++------- modules/imgproc/src/morph.cpp | 16 ++-- modules/ml/src/ann_mlp.cpp | 53 +++++----- modules/ml/src/gbt.cpp | 54 ++++------- modules/ml/src/knearest.cpp | 10 +- modules/ml/src/nbayes.cpp | 12 +-- modules/ml/src/svm.cpp | 8 +- modules/nonfree/src/surf.cpp | 44 ++++----- modules/objdetect/src/cascadedetect.cpp | 5 - modules/objdetect/src/latentsvm.cpp | 8 -- modules/photo/src/denoising.cpp | 12 +-- .../src/fast_nlmeans_denoising_invoker.hpp | 10 +- .../fast_nlmeans_multi_denoising_invoker.hpp | 10 +- modules/stitching/src/matchers.cpp | 10 +- modules/video/src/bgfg_gaussmix2.cpp | 24 ++--- modules/video/src/lkpyramid.cpp | 14 +-- modules/video/src/lkpyramid.hpp | 4 +- 24 files changed, 232 insertions(+), 352 deletions(-) diff --git a/apps/traincascade/boost.cpp b/apps/traincascade/boost.cpp index 2d29f338b0..4f91d5a29d 100644 --- a/apps/traincascade/boost.cpp +++ b/apps/traincascade/boost.cpp @@ -766,7 +766,7 @@ float CvCascadeBoostTrainData::getVarValue( int vi, int si ) } -struct FeatureIdxOnlyPrecalc +struct FeatureIdxOnlyPrecalc : ParallelLoopBody { FeatureIdxOnlyPrecalc( const CvFeatureEvaluator* _featureEvaluator, CvMat* _buf, int _sample_count, bool _is_buf_16u ) { @@ -776,11 +776,11 @@ struct FeatureIdxOnlyPrecalc idst = _buf->data.i; is_buf_16u = _is_buf_16u; } - void operator()( const BlockedRange& range ) const + void operator()( const Range& range ) const { cv::AutoBuffer valCache(sample_count); float* valCachePtr = (float*)valCache; - for ( int fi = range.begin(); fi < range.end(); fi++) + for ( int fi = range.start; fi < range.end; fi++) { for( int si = 0; si < sample_count; si++ ) { @@ -803,7 +803,7 @@ struct FeatureIdxOnlyPrecalc bool is_buf_16u; }; -struct FeatureValAndIdxPrecalc +struct FeatureValAndIdxPrecalc : ParallelLoopBody { FeatureValAndIdxPrecalc( const CvFeatureEvaluator* _featureEvaluator, CvMat* _buf, Mat* _valCache, int _sample_count, bool _is_buf_16u ) { @@ -814,9 +814,9 @@ struct FeatureValAndIdxPrecalc idst = _buf->data.i; is_buf_16u = _is_buf_16u; } - void operator()( const BlockedRange& range ) const + void operator()( const Range& range ) const { - for ( int fi = range.begin(); fi < range.end(); fi++) + for ( int fi = range.start; fi < range.end; fi++) { for( int si = 0; si < sample_count; si++ ) { @@ -840,7 +840,7 @@ struct FeatureValAndIdxPrecalc bool is_buf_16u; }; -struct FeatureValOnlyPrecalc +struct FeatureValOnlyPrecalc : ParallelLoopBody { FeatureValOnlyPrecalc( const CvFeatureEvaluator* _featureEvaluator, Mat* _valCache, int _sample_count ) { @@ -848,9 +848,9 @@ struct FeatureValOnlyPrecalc valCache = _valCache; sample_count = _sample_count; } - void operator()( const BlockedRange& range ) const + void operator()( const Range& range ) const { - for ( int fi = range.begin(); fi < range.end(); fi++) + for ( int fi = range.start; fi < range.end; fi++) for( int si = 0; si < sample_count; si++ ) valCache->at(fi,si) = (*featureEvaluator)( fi, si ); } @@ -864,12 +864,12 @@ void CvCascadeBoostTrainData::precalculate() int minNum = MIN( numPrecalcVal, numPrecalcIdx); double proctime = -TIME( 0 ); - parallel_for( BlockedRange(numPrecalcVal, numPrecalcIdx), - FeatureIdxOnlyPrecalc(featureEvaluator, buf, sample_count, is_buf_16u!=0) ); - parallel_for( BlockedRange(0, minNum), - FeatureValAndIdxPrecalc(featureEvaluator, buf, &valCache, sample_count, is_buf_16u!=0) ); - parallel_for( BlockedRange(minNum, numPrecalcVal), - FeatureValOnlyPrecalc(featureEvaluator, &valCache, sample_count) ); + parallel_for_( Range(numPrecalcVal, numPrecalcIdx), + FeatureIdxOnlyPrecalc(featureEvaluator, buf, sample_count, is_buf_16u!=0) ); + parallel_for_( Range(0, minNum), + FeatureValAndIdxPrecalc(featureEvaluator, buf, &valCache, sample_count, is_buf_16u!=0) ); + parallel_for_( Range(minNum, numPrecalcVal), + FeatureValOnlyPrecalc(featureEvaluator, &valCache, sample_count) ); cout << "Precalculation time: " << (proctime + TIME( 0 )) << endl; } diff --git a/modules/calib3d/src/solvepnp.cpp b/modules/calib3d/src/solvepnp.cpp index 25988be48a..3d2c0c2c47 100644 --- a/modules/calib3d/src/solvepnp.cpp +++ b/modules/calib3d/src/solvepnp.cpp @@ -115,31 +115,6 @@ namespace cv transform(points, modif_points, transformation); } - class Mutex - { - public: - Mutex() { - } - void lock() - { -#ifdef HAVE_TBB - resultsMutex.lock(); -#endif - } - - void unlock() - { -#ifdef HAVE_TBB - resultsMutex.unlock(); -#endif - } - - private: -#ifdef HAVE_TBB - tbb::mutex resultsMutex; -#endif - }; - struct CameraParameters { void init(Mat _intrinsics, Mat _distCoeffs) diff --git a/modules/calib3d/src/stereobm.cpp b/modules/calib3d/src/stereobm.cpp index 32514276b5..623883df74 100644 --- a/modules/calib3d/src/stereobm.cpp +++ b/modules/calib3d/src/stereobm.cpp @@ -699,7 +699,7 @@ struct PrefilterInvoker }; -struct FindStereoCorrespInvoker +struct FindStereoCorrespInvoker : ParallelLoopBody { FindStereoCorrespInvoker( const Mat& _left, const Mat& _right, Mat& _disp, CvStereoBMState* _state, @@ -713,12 +713,12 @@ struct FindStereoCorrespInvoker validDisparityRect = _validDisparityRect; } - void operator()( const BlockedRange& range ) const + void operator()( const Range& range ) const { int cols = left->cols, rows = left->rows; - int _row0 = min(cvRound(range.begin() * rows / nstripes), rows); - int _row1 = min(cvRound(range.end() * rows / nstripes), rows); - uchar *ptr = state->slidingSumBuf->data.ptr + range.begin() * stripeBufSize; + int _row0 = min(cvRound(range.start * rows / nstripes), rows); + int _row1 = min(cvRound(range.end * rows / nstripes), rows); + uchar *ptr = state->slidingSumBuf->data.ptr + range.start * stripeBufSize; int FILTERED = (state->minDisparity - 1)*16; Rect roi = validDisparityRect & Rect(0, _row0, cols, _row1 - _row0); @@ -871,14 +871,10 @@ static void findStereoCorrespondenceBM( const Mat& left0, const Mat& right0, Mat const bool useShorts = false; #endif -#ifdef HAVE_TBB const double SAD_overhead_coeff = 10.0; double N0 = 8000000 / (useShorts ? 1 : 4); // approx tbb's min number instructions reasonable for one thread double maxStripeSize = min(max(N0 / (width * ndisp), (wsz-1) * SAD_overhead_coeff), (double)height); int nstripes = cvCeil(height / maxStripeSize); -#else - const int nstripes = 1; -#endif int bufSize = max(bufSize0 * nstripes, max(bufSize1 * 2, bufSize2)); @@ -898,9 +894,9 @@ static void findStereoCorrespondenceBM( const Mat& left0, const Mat& right0, Mat state->minDisparity, state->numberOfDisparities, state->SADWindowSize); - parallel_for(BlockedRange(0, nstripes), - FindStereoCorrespInvoker(left, right, disp, state, nstripes, - bufSize0, useShorts, validDisparityRect)); + parallel_for_(Range(0, nstripes), + FindStereoCorrespInvoker(left, right, disp, state, nstripes, + bufSize0, useShorts, validDisparityRect)); if( state->speckleRange >= 0 && state->speckleWindowSize > 0 ) { diff --git a/modules/features2d/src/detectors.cpp b/modules/features2d/src/detectors.cpp index 2efd5a652a..a1e389a435 100644 --- a/modules/features2d/src/detectors.cpp +++ b/modules/features2d/src/detectors.cpp @@ -214,7 +214,7 @@ static void keepStrongest( int N, vector& keypoints ) } namespace { -class GridAdaptedFeatureDetectorInvoker +class GridAdaptedFeatureDetectorInvoker : public ParallelLoopBody { private: int gridRows_, gridCols_; @@ -223,29 +223,24 @@ private: const Mat& image_; const Mat& mask_; const Ptr& detector_; -#ifdef HAVE_TBB - tbb::mutex* kptLock_; -#endif + Mutex* kptLock_; GridAdaptedFeatureDetectorInvoker& operator=(const GridAdaptedFeatureDetectorInvoker&); // to quiet MSVC public: - GridAdaptedFeatureDetectorInvoker(const Ptr& detector, const Mat& image, const Mat& mask, vector& keypoints, int maxPerCell, int gridRows, int gridCols -#ifdef HAVE_TBB - , tbb::mutex* kptLock -#endif - ) : gridRows_(gridRows), gridCols_(gridCols), maxPerCell_(maxPerCell), - keypoints_(keypoints), image_(image), mask_(mask), detector_(detector) -#ifdef HAVE_TBB - , kptLock_(kptLock) -#endif + GridAdaptedFeatureDetectorInvoker(const Ptr& detector, const Mat& image, const Mat& mask, + vector& keypoints, int maxPerCell, int gridRows, int gridCols, + cv::Mutex* kptLock) + : gridRows_(gridRows), gridCols_(gridCols), maxPerCell_(maxPerCell), + keypoints_(keypoints), image_(image), mask_(mask), detector_(detector), + kptLock_(kptLock) { } - void operator() (const BlockedRange& range) const + void operator() (const Range& range) const { - for (int i = range.begin(); i < range.end(); ++i) + for (int i = range.start; i < range.end; ++i) { int celly = i / gridCols_; int cellx = i - celly * gridCols_; @@ -270,9 +265,8 @@ public: it->pt.x += col_range.start; it->pt.y += row_range.start; } -#ifdef HAVE_TBB - tbb::mutex::scoped_lock join_keypoints(*kptLock_); -#endif + + cv::AutoLock join_keypoints(*kptLock_); keypoints_.insert( keypoints_.end(), sub_keypoints.begin(), sub_keypoints.end() ); } } @@ -289,13 +283,9 @@ void GridAdaptedFeatureDetector::detectImpl( const Mat& image, vector& keypoints.reserve(maxTotalKeypoints); int maxPerCell = maxTotalKeypoints / (gridRows * gridCols); -#ifdef HAVE_TBB - tbb::mutex kptLock; - cv::parallel_for(cv::BlockedRange(0, gridRows * gridCols), + cv::Mutex kptLock; + cv::parallel_for_(cv::Range(0, gridRows * gridCols), GridAdaptedFeatureDetectorInvoker(detector, image, mask, keypoints, maxPerCell, gridRows, gridCols, &kptLock)); -#else - GridAdaptedFeatureDetectorInvoker(detector, image, mask, keypoints, maxPerCell, gridRows, gridCols)(cv::BlockedRange(0, gridRows * gridCols)); -#endif } /* diff --git a/modules/gpu/src/calib3d.cpp b/modules/gpu/src/calib3d.cpp index e83213f90f..b84f09d0ab 100644 --- a/modules/gpu/src/calib3d.cpp +++ b/modules/gpu/src/calib3d.cpp @@ -151,7 +151,7 @@ namespace } // Computes rotation, translation pair for small subsets if the input data - class TransformHypothesesGenerator + class TransformHypothesesGenerator : public ParallelLoopBody { public: TransformHypothesesGenerator(const Mat& object_, const Mat& image_, const Mat& dist_coef_, @@ -161,7 +161,7 @@ namespace num_points(num_points_), subset_size(subset_size_), rot_matrices(rot_matrices_), transl_vectors(transl_vectors_) {} - void operator()(const BlockedRange& range) const + void operator()(const Range& range) const { // Input data for generation of the current hypothesis vector subset_indices(subset_size); @@ -173,7 +173,7 @@ namespace Mat rot_mat(3, 3, CV_64F); Mat transl_vec(1, 3, CV_64F); - for (int iter = range.begin(); iter < range.end(); ++iter) + for (int iter = range.start; iter < range.end; ++iter) { selectRandom(subset_size, num_points, subset_indices); for (int i = 0; i < subset_size; ++i) @@ -239,7 +239,7 @@ void cv::gpu::solvePnPRansac(const Mat& object, const Mat& image, const Mat& cam // Generate set of hypotheses using small subsets of the input data TransformHypothesesGenerator body(object, image_normalized, empty_dist_coef, eye_camera_mat, num_points, subset_size, rot_matrices, transl_vectors); - parallel_for(BlockedRange(0, num_iters), body); + parallel_for_(Range(0, num_iters), body); // Compute scores (i.e. number of inliers) for each hypothesis GpuMat d_object(object); diff --git a/modules/imgproc/src/color.cpp b/modules/imgproc/src/color.cpp index 3799d435e3..41ca2db9c0 100644 --- a/modules/imgproc/src/color.cpp +++ b/modules/imgproc/src/color.cpp @@ -2755,7 +2755,7 @@ const int ITUR_BT_601_CGV = -385875; const int ITUR_BT_601_CBV = -74448; template -struct YUV420sp2RGB888Invoker +struct YUV420sp2RGB888Invoker : ParallelLoopBody { Mat* dst; const uchar* my1, *muv; @@ -2764,10 +2764,10 @@ struct YUV420sp2RGB888Invoker YUV420sp2RGB888Invoker(Mat* _dst, int _stride, const uchar* _y1, const uchar* _uv) : dst(_dst), my1(_y1), muv(_uv), width(_dst->cols), stride(_stride) {} - void operator()(const BlockedRange& range) const + void operator()(const Range& range) const { - int rangeBegin = range.begin() * 2; - int rangeEnd = range.end() * 2; + int rangeBegin = range.start * 2; + int rangeEnd = range.end * 2; //R = 1.164(Y - 16) + 1.596(V - 128) //G = 1.164(Y - 16) - 0.813(V - 128) - 0.391(U - 128) @@ -2824,7 +2824,7 @@ struct YUV420sp2RGB888Invoker }; template -struct YUV420sp2RGBA8888Invoker +struct YUV420sp2RGBA8888Invoker : ParallelLoopBody { Mat* dst; const uchar* my1, *muv; @@ -2833,10 +2833,10 @@ struct YUV420sp2RGBA8888Invoker YUV420sp2RGBA8888Invoker(Mat* _dst, int _stride, const uchar* _y1, const uchar* _uv) : dst(_dst), my1(_y1), muv(_uv), width(_dst->cols), stride(_stride) {} - void operator()(const BlockedRange& range) const + void operator()(const Range& range) const { - int rangeBegin = range.begin() * 2; - int rangeEnd = range.end() * 2; + int rangeBegin = range.start * 2; + int rangeEnd = range.end * 2; //R = 1.164(Y - 16) + 1.596(V - 128) //G = 1.164(Y - 16) - 0.813(V - 128) - 0.391(U - 128) @@ -2897,7 +2897,7 @@ struct YUV420sp2RGBA8888Invoker }; template -struct YUV420p2RGB888Invoker +struct YUV420p2RGB888Invoker : ParallelLoopBody { Mat* dst; const uchar* my1, *mu, *mv; @@ -2907,19 +2907,19 @@ struct YUV420p2RGB888Invoker YUV420p2RGB888Invoker(Mat* _dst, int _stride, const uchar* _y1, const uchar* _u, const uchar* _v, int _ustepIdx, int _vstepIdx) : dst(_dst), my1(_y1), mu(_u), mv(_v), width(_dst->cols), stride(_stride), ustepIdx(_ustepIdx), vstepIdx(_vstepIdx) {} - void operator()(const BlockedRange& range) const + void operator()(const Range& range) const { - const int rangeBegin = range.begin() * 2; - const int rangeEnd = range.end() * 2; + const int rangeBegin = range.start * 2; + const int rangeEnd = range.end * 2; size_t uvsteps[2] = {width/2, stride - width/2}; int usIdx = ustepIdx, vsIdx = vstepIdx; const uchar* y1 = my1 + rangeBegin * stride; - const uchar* u1 = mu + (range.begin() / 2) * stride; - const uchar* v1 = mv + (range.begin() / 2) * stride; + const uchar* u1 = mu + (range.start / 2) * stride; + const uchar* v1 = mv + (range.start / 2) * stride; - if(range.begin() % 2 == 1) + if(range.start % 2 == 1) { u1 += uvsteps[(usIdx++) & 1]; v1 += uvsteps[(vsIdx++) & 1]; @@ -2965,7 +2965,7 @@ struct YUV420p2RGB888Invoker }; template -struct YUV420p2RGBA8888Invoker +struct YUV420p2RGBA8888Invoker : ParallelLoopBody { Mat* dst; const uchar* my1, *mu, *mv; @@ -2975,19 +2975,19 @@ struct YUV420p2RGBA8888Invoker YUV420p2RGBA8888Invoker(Mat* _dst, int _stride, const uchar* _y1, const uchar* _u, const uchar* _v, int _ustepIdx, int _vstepIdx) : dst(_dst), my1(_y1), mu(_u), mv(_v), width(_dst->cols), stride(_stride), ustepIdx(_ustepIdx), vstepIdx(_vstepIdx) {} - void operator()(const BlockedRange& range) const + void operator()(const Range& range) const { - int rangeBegin = range.begin() * 2; - int rangeEnd = range.end() * 2; + int rangeBegin = range.start * 2; + int rangeEnd = range.end * 2; size_t uvsteps[2] = {width/2, stride - width/2}; int usIdx = ustepIdx, vsIdx = vstepIdx; const uchar* y1 = my1 + rangeBegin * stride; - const uchar* u1 = mu + (range.begin() / 2) * stride; - const uchar* v1 = mv + (range.begin() / 2) * stride; + const uchar* u1 = mu + (range.start / 2) * stride; + const uchar* v1 = mv + (range.start / 2) * stride; - if(range.begin() % 2 == 1) + if(range.start % 2 == 1) { u1 += uvsteps[(usIdx++) & 1]; v1 += uvsteps[(vsIdx++) & 1]; @@ -3042,48 +3042,40 @@ template inline void cvtYUV420sp2RGB(Mat& _dst, int _stride, const uchar* _y1, const uchar* _uv) { YUV420sp2RGB888Invoker converter(&_dst, _stride, _y1, _uv); -#ifdef HAVE_TBB if (_dst.total() >= MIN_SIZE_FOR_PARALLEL_YUV420_CONVERSION) - parallel_for(BlockedRange(0, _dst.rows/2), converter); + parallel_for_(Range(0, _dst.rows/2), converter); else -#endif - converter(BlockedRange(0, _dst.rows/2)); + converter(Range(0, _dst.rows/2)); } template inline void cvtYUV420sp2RGBA(Mat& _dst, int _stride, const uchar* _y1, const uchar* _uv) { YUV420sp2RGBA8888Invoker converter(&_dst, _stride, _y1, _uv); -#ifdef HAVE_TBB if (_dst.total() >= MIN_SIZE_FOR_PARALLEL_YUV420_CONVERSION) - parallel_for(BlockedRange(0, _dst.rows/2), converter); + parallel_for_(Range(0, _dst.rows/2), converter); else -#endif - converter(BlockedRange(0, _dst.rows/2)); + converter(Range(0, _dst.rows/2)); } template inline void cvtYUV420p2RGB(Mat& _dst, int _stride, const uchar* _y1, const uchar* _u, const uchar* _v, int ustepIdx, int vstepIdx) { YUV420p2RGB888Invoker converter(&_dst, _stride, _y1, _u, _v, ustepIdx, vstepIdx); -#ifdef HAVE_TBB if (_dst.total() >= MIN_SIZE_FOR_PARALLEL_YUV420_CONVERSION) - parallel_for(BlockedRange(0, _dst.rows/2), converter); + parallel_for_(Range(0, _dst.rows/2), converter); else -#endif - converter(BlockedRange(0, _dst.rows/2)); + converter(Range(0, _dst.rows/2)); } template inline void cvtYUV420p2RGBA(Mat& _dst, int _stride, const uchar* _y1, const uchar* _u, const uchar* _v, int ustepIdx, int vstepIdx) { YUV420p2RGBA8888Invoker converter(&_dst, _stride, _y1, _u, _v, ustepIdx, vstepIdx); -#ifdef HAVE_TBB if (_dst.total() >= MIN_SIZE_FOR_PARALLEL_YUV420_CONVERSION) - parallel_for(BlockedRange(0, _dst.rows/2), converter); + parallel_for_(Range(0, _dst.rows/2), converter); else -#endif - converter(BlockedRange(0, _dst.rows/2)); + converter(Range(0, _dst.rows/2)); } ///////////////////////////////////// RGB -> YUV420p ///////////////////////////////////// @@ -3167,7 +3159,7 @@ static void cvtRGBtoYUV420p(const Mat& src, Mat& dst) ///////////////////////////////////// YUV422 -> RGB ///////////////////////////////////// template -struct YUV422toRGB888Invoker +struct YUV422toRGB888Invoker : ParallelLoopBody { Mat* dst; const uchar* src; @@ -3176,10 +3168,10 @@ struct YUV422toRGB888Invoker YUV422toRGB888Invoker(Mat* _dst, int _stride, const uchar* _yuv) : dst(_dst), src(_yuv), width(_dst->cols), stride(_stride) {} - void operator()(const BlockedRange& range) const + void operator()(const Range& range) const { - int rangeBegin = range.begin(); - int rangeEnd = range.end(); + int rangeBegin = range.start; + int rangeEnd = range.end; const int uidx = 1 - yIdx + uIdx * 2; const int vidx = (2 + uidx) % 4; @@ -3213,7 +3205,7 @@ struct YUV422toRGB888Invoker }; template -struct YUV422toRGBA8888Invoker +struct YUV422toRGBA8888Invoker : ParallelLoopBody { Mat* dst; const uchar* src; @@ -3222,10 +3214,10 @@ struct YUV422toRGBA8888Invoker YUV422toRGBA8888Invoker(Mat* _dst, int _stride, const uchar* _yuv) : dst(_dst), src(_yuv), width(_dst->cols), stride(_stride) {} - void operator()(const BlockedRange& range) const + void operator()(const Range& range) const { - int rangeBegin = range.begin(); - int rangeEnd = range.end(); + int rangeBegin = range.start; + int rangeEnd = range.end; const int uidx = 1 - yIdx + uIdx * 2; const int vidx = (2 + uidx) % 4; @@ -3266,24 +3258,20 @@ template inline void cvtYUV422toRGB(Mat& _dst, int _stride, const uchar* _yuv) { YUV422toRGB888Invoker converter(&_dst, _stride, _yuv); -#ifdef HAVE_TBB if (_dst.total() >= MIN_SIZE_FOR_PARALLEL_YUV422_CONVERSION) - parallel_for(BlockedRange(0, _dst.rows), converter); + parallel_for_(Range(0, _dst.rows), converter); else -#endif - converter(BlockedRange(0, _dst.rows)); + converter(Range(0, _dst.rows)); } template inline void cvtYUV422toRGBA(Mat& _dst, int _stride, const uchar* _yuv) { YUV422toRGBA8888Invoker converter(&_dst, _stride, _yuv); -#ifdef HAVE_TBB if (_dst.total() >= MIN_SIZE_FOR_PARALLEL_YUV422_CONVERSION) - parallel_for(BlockedRange(0, _dst.rows), converter); + parallel_for_(Range(0, _dst.rows), converter); else -#endif - converter(BlockedRange(0, _dst.rows)); + converter(Range(0, _dst.rows)); } /////////////////////////// RGBA <-> mRGBA (alpha premultiplied) ////////////// diff --git a/modules/imgproc/src/distransform.cpp b/modules/imgproc/src/distransform.cpp index 89d3a550f4..d3e6f90242 100644 --- a/modules/imgproc/src/distransform.cpp +++ b/modules/imgproc/src/distransform.cpp @@ -443,7 +443,7 @@ icvGetDistanceTransformMask( int maskType, float *metrics ) namespace cv { -struct DTColumnInvoker +struct DTColumnInvoker : ParallelLoopBody { DTColumnInvoker( const CvMat* _src, CvMat* _dst, const int* _sat_tab, const float* _sqr_tab) { @@ -453,9 +453,9 @@ struct DTColumnInvoker sqr_tab = _sqr_tab; } - void operator()( const BlockedRange& range ) const + void operator()( const Range& range ) const { - int i, i1 = range.begin(), i2 = range.end(); + int i, i1 = range.start, i2 = range.end; int m = src->rows; size_t sstep = src->step, dstep = dst->step/sizeof(float); AutoBuffer _d(m); @@ -490,7 +490,7 @@ struct DTColumnInvoker }; -struct DTRowInvoker +struct DTRowInvoker : ParallelLoopBody { DTRowInvoker( CvMat* _dst, const float* _sqr_tab, const float* _inv_tab ) { @@ -499,10 +499,10 @@ struct DTRowInvoker inv_tab = _inv_tab; } - void operator()( const BlockedRange& range ) const + void operator()( const Range& range ) const { const float inf = 1e15f; - int i, i1 = range.begin(), i2 = range.end(); + int i, i1 = range.start, i2 = range.end; int n = dst->cols; AutoBuffer _buf((n+2)*2*sizeof(float) + (n+2)*sizeof(int)); float* f = (float*)(uchar*)_buf; @@ -586,7 +586,7 @@ icvTrueDistTrans( const CvMat* src, CvMat* dst ) for( ; i <= m*3; i++ ) sat_tab[i] = i - shift; - cv::parallel_for(cv::BlockedRange(0, n), cv::DTColumnInvoker(src, dst, sat_tab, sqr_tab)); + cv::parallel_for_(cv::Range(0, n), cv::DTColumnInvoker(src, dst, sat_tab, sqr_tab)); // stage 2: compute modified distance transform for each row float* inv_tab = sqr_tab + n; @@ -598,7 +598,7 @@ icvTrueDistTrans( const CvMat* src, CvMat* dst ) sqr_tab[i] = (float)(i*i); } - cv::parallel_for(cv::BlockedRange(0, m), cv::DTRowInvoker(dst, sqr_tab, inv_tab)); + cv::parallel_for_(cv::Range(0, m), cv::DTRowInvoker(dst, sqr_tab, inv_tab)); } diff --git a/modules/imgproc/src/histogram.cpp b/modules/imgproc/src/histogram.cpp index 22dd9beb1f..5ca6c9d15b 100644 --- a/modules/imgproc/src/histogram.cpp +++ b/modules/imgproc/src/histogram.cpp @@ -2986,29 +2986,23 @@ cvCalcProbDensity( const CvHistogram* hist, const CvHistogram* hist_mask, } } -class EqualizeHistCalcHist_Invoker +class EqualizeHistCalcHist_Invoker : public cv::ParallelLoopBody { public: enum {HIST_SZ = 256}; -#ifdef HAVE_TBB - typedef tbb::mutex* MutextPtr; -#else - typedef void* MutextPtr; -#endif - - EqualizeHistCalcHist_Invoker(cv::Mat& src, int* histogram, MutextPtr histogramLock) + EqualizeHistCalcHist_Invoker(cv::Mat& src, int* histogram, cv::Mutex* histogramLock) : src_(src), globalHistogram_(histogram), histogramLock_(histogramLock) { } - void operator()( const cv::BlockedRange& rowRange ) const + void operator()( const cv::Range& rowRange ) const { int localHistogram[HIST_SZ] = {0, }; const size_t sstep = src_.step; int width = src_.cols; - int height = rowRange.end() - rowRange.begin(); + int height = rowRange.end - rowRange.start; if (src_.isContinuous()) { @@ -3016,7 +3010,7 @@ public: height = 1; } - for (const uchar* ptr = src_.ptr(rowRange.begin()); height--; ptr += sstep) + for (const uchar* ptr = src_.ptr(rowRange.start); height--; ptr += sstep) { int x = 0; for (; x <= width - 4; x += 4) @@ -3031,9 +3025,7 @@ public: localHistogram[ptr[x]]++; } -#ifdef HAVE_TBB - tbb::mutex::scoped_lock lock(*histogramLock_); -#endif + cv::AutoLock lock(*histogramLock_); for( int i = 0; i < HIST_SZ; i++ ) globalHistogram_[i] += localHistogram[i]; @@ -3041,12 +3033,7 @@ public: static bool isWorthParallel( const cv::Mat& src ) { -#ifdef HAVE_TBB return ( src.total() >= 640*480 ); -#else - (void)src; - return false; -#endif } private: @@ -3054,10 +3041,10 @@ private: cv::Mat& src_; int* globalHistogram_; - MutextPtr histogramLock_; + cv::Mutex* histogramLock_; }; -class EqualizeHistLut_Invoker +class EqualizeHistLut_Invoker : public cv::ParallelLoopBody { public: EqualizeHistLut_Invoker( cv::Mat& src, cv::Mat& dst, int* lut ) @@ -3066,13 +3053,13 @@ public: lut_(lut) { } - void operator()( const cv::BlockedRange& rowRange ) const + void operator()( const cv::Range& rowRange ) const { const size_t sstep = src_.step; const size_t dstep = dst_.step; int width = src_.cols; - int height = rowRange.end() - rowRange.begin(); + int height = rowRange.end - rowRange.start; int* lut = lut_; if (src_.isContinuous() && dst_.isContinuous()) @@ -3081,8 +3068,8 @@ public: height = 1; } - const uchar* sptr = src_.ptr(rowRange.begin()); - uchar* dptr = dst_.ptr(rowRange.begin()); + const uchar* sptr = src_.ptr(rowRange.start); + uchar* dptr = dst_.ptr(rowRange.start); for (; height--; sptr += sstep, dptr += dstep) { @@ -3111,12 +3098,7 @@ public: static bool isWorthParallel( const cv::Mat& src ) { -#ifdef HAVE_TBB return ( src.total() >= 640*480 ); -#else - (void)src; - return false; -#endif } private: @@ -3143,23 +3125,18 @@ void cv::equalizeHist( InputArray _src, OutputArray _dst ) if(src.empty()) return; -#ifdef HAVE_TBB - tbb::mutex histogramLockInstance; - EqualizeHistCalcHist_Invoker::MutextPtr histogramLock = &histogramLockInstance; -#else - EqualizeHistCalcHist_Invoker::MutextPtr histogramLock = 0; -#endif + Mutex histogramLockInstance; const int hist_sz = EqualizeHistCalcHist_Invoker::HIST_SZ; int hist[hist_sz] = {0,}; int lut[hist_sz]; - EqualizeHistCalcHist_Invoker calcBody(src, hist, histogramLock); + EqualizeHistCalcHist_Invoker calcBody(src, hist, &histogramLockInstance); EqualizeHistLut_Invoker lutBody(src, dst, lut); - cv::BlockedRange heightRange(0, src.rows); + cv::Range heightRange(0, src.rows); if(EqualizeHistCalcHist_Invoker::isWorthParallel(src)) - parallel_for(heightRange, calcBody); + parallel_for_(heightRange, calcBody); else calcBody(heightRange); @@ -3183,7 +3160,7 @@ void cv::equalizeHist( InputArray _src, OutputArray _dst ) } if(EqualizeHistLut_Invoker::isWorthParallel(src)) - parallel_for(heightRange, lutBody); + parallel_for_(heightRange, lutBody); else lutBody(heightRange); } diff --git a/modules/imgproc/src/morph.cpp b/modules/imgproc/src/morph.cpp index a63e08ff01..53d2347ec4 100644 --- a/modules/imgproc/src/morph.cpp +++ b/modules/imgproc/src/morph.cpp @@ -1081,7 +1081,7 @@ cv::Mat cv::getStructuringElement(int shape, Size ksize, Point anchor) namespace cv { -class MorphologyRunner +class MorphologyRunner : public ParallelLoopBody { public: MorphologyRunner(Mat _src, Mat _dst, int _nStripes, int _iterations, @@ -1102,14 +1102,14 @@ public: columnBorderType = _columnBorderType; } - void operator () ( const BlockedRange& range ) const + void operator () ( const Range& range ) const { - int row0 = min(cvRound(range.begin() * src.rows / nStripes), src.rows); - int row1 = min(cvRound(range.end() * src.rows / nStripes), src.rows); + int row0 = min(cvRound(range.start * src.rows / nStripes), src.rows); + int row1 = min(cvRound(range.end * src.rows / nStripes), src.rows); /*if(0) printf("Size = (%d, %d), range[%d,%d), row0 = %d, row1 = %d\n", - src.rows, src.cols, range.begin(), range.end(), row0, row1);*/ + src.rows, src.cols, range.start, range.end, row0, row1);*/ Mat srcStripe = src.rowRange(row0, row1); Mat dstStripe = dst.rowRange(row0, row1); @@ -1173,15 +1173,15 @@ static void morphOp( int op, InputArray _src, OutputArray _dst, } int nStripes = 1; -#if defined HAVE_TBB && defined HAVE_TEGRA_OPTIMIZATION +#if defined HAVE_TEGRA_OPTIMIZATION if (src.data != dst.data && iterations == 1 && //NOTE: threads are not used for inplace processing (borderType & BORDER_ISOLATED) == 0 && //TODO: check border types src.rows >= 64 ) //NOTE: just heuristics nStripes = 4; #endif - parallel_for(BlockedRange(0, nStripes), - MorphologyRunner(src, dst, nStripes, iterations, op, kernel, anchor, borderType, borderType, borderValue)); + parallel_for_(Range(0, nStripes), + MorphologyRunner(src, dst, nStripes, iterations, op, kernel, anchor, borderType, borderType, borderValue)); //Ptr f = createMorphologyFilter(op, src.type(), // kernel, anchor, borderType, borderType, borderValue ); diff --git a/modules/ml/src/ann_mlp.cpp b/modules/ml/src/ann_mlp.cpp index 438872ae8c..bf85425b9c 100644 --- a/modules/ml/src/ann_mlp.cpp +++ b/modules/ml/src/ann_mlp.cpp @@ -40,10 +40,6 @@ #include "precomp.hpp" -#ifdef HAVE_TBB -#include -#endif - CvANN_MLP_TrainParams::CvANN_MLP_TrainParams() { term_crit = cvTermCriteria( CV_TERMCRIT_ITER + CV_TERMCRIT_EPS, 1000, 0.01 ); @@ -1022,7 +1018,7 @@ int CvANN_MLP::train_backprop( CvVectors x0, CvVectors u, const double* sw ) return iter; } -struct rprop_loop { +struct rprop_loop : cv::ParallelLoopBody { rprop_loop(const CvANN_MLP* _point, double**& _weights, int& _count, int& _ivcount, CvVectors* _x0, int& _l_count, CvMat*& _layer_sizes, int& _ovcount, int& _max_count, CvVectors* _u, const double*& _sw, double& _inv_count, CvMat*& _dEdw, int& _dcount0, double* _E, int _buf_sz) @@ -1063,7 +1059,7 @@ struct rprop_loop { int buf_sz; - void operator()( const cv::BlockedRange& range ) const + void operator()( const cv::Range& range ) const { double* buf_ptr; double** x = 0; @@ -1084,7 +1080,7 @@ struct rprop_loop { buf_ptr += (df[i] - x[i])*2; } - for(int si = range.begin(); si < range.end(); si++ ) + for(int si = range.start; si < range.end; si++ ) { if (si % dcount0 != 0) continue; int n1, n2, k; @@ -1170,36 +1166,33 @@ struct rprop_loop { } // backward pass, update dEdw - #ifdef HAVE_TBB - static tbb::spin_mutex mutex; - tbb::spin_mutex::scoped_lock lock; - #endif + static cv::Mutex mutex; + for(int i = l_count-1; i > 0; i-- ) { n1 = layer_sizes->data.i[i-1]; n2 = layer_sizes->data.i[i]; cvInitMatHeader( &_df, dcount, n2, CV_64F, df[i] ); cvMul( grad1, &_df, grad1 ); - #ifdef HAVE_TBB - lock.acquire(mutex); - #endif - cvInitMatHeader( &_dEdw, n1, n2, CV_64F, dEdw->data.db+(weights[i]-weights[0]) ); - cvInitMatHeader( x1, dcount, n1, CV_64F, x[i-1] ); - cvGEMM( x1, grad1, 1, &_dEdw, 1, &_dEdw, CV_GEMM_A_T ); - // update bias part of dEdw - for( k = 0; k < dcount; k++ ) - { - double* dst = _dEdw.data.db + n1*n2; - const double* src = grad1->data.db + k*n2; - for(int j = 0; j < n2; j++ ) - dst[j] += src[j]; + { + cv::AutoLock lock(mutex); + cvInitMatHeader( &_dEdw, n1, n2, CV_64F, dEdw->data.db+(weights[i]-weights[0]) ); + cvInitMatHeader( x1, dcount, n1, CV_64F, x[i-1] ); + cvGEMM( x1, grad1, 1, &_dEdw, 1, &_dEdw, CV_GEMM_A_T ); + + // update bias part of dEdw + for( k = 0; k < dcount; k++ ) + { + double* dst = _dEdw.data.db + n1*n2; + const double* src = grad1->data.db + k*n2; + for(int j = 0; j < n2; j++ ) + dst[j] += src[j]; + } + + if (i > 1) + cvInitMatHeader( &_w, n1, n2, CV_64F, weights[i] ); } - if (i > 1) - cvInitMatHeader( &_w, n1, n2, CV_64F, weights[i] ); - #ifdef HAVE_TBB - lock.release(); - #endif cvInitMatHeader( grad2, dcount, n1, CV_64F, grad2->data.db ); if( i > 1 ) cvGEMM( grad1, &_w, 1, 0, 0, grad2, CV_GEMM_B_T ); @@ -1297,7 +1290,7 @@ int CvANN_MLP::train_rprop( CvVectors x0, CvVectors u, const double* sw ) double E = 0; // first, iterate through all the samples and compute dEdw - cv::parallel_for(cv::BlockedRange(0, count), + cv::parallel_for_(cv::Range(0, count), rprop_loop(this, weights, count, ivcount, &x0, l_count, layer_sizes, ovcount, max_count, &u, sw, inv_count, dEdw, dcount0, &E, buf_sz) ); diff --git a/modules/ml/src/gbt.cpp b/modules/ml/src/gbt.cpp index 6671a3495b..b52ffbe5a3 100644 --- a/modules/ml/src/gbt.cpp +++ b/modules/ml/src/gbt.cpp @@ -900,7 +900,7 @@ float CvGBTrees::predict_serial( const CvMat* _sample, const CvMat* _missing, } -class Tree_predictor +class Tree_predictor : public cv::ParallelLoopBody { private: pCvSeq* weak; @@ -910,9 +910,7 @@ private: const CvMat* missing; const float shrinkage; -#ifdef HAVE_TBB - static tbb::spin_mutex SumMutex; -#endif + static cv::Mutex SumMutex; public: @@ -931,14 +929,11 @@ public: Tree_predictor& operator=( const Tree_predictor& ) { return *this; } - virtual void operator()(const cv::BlockedRange& range) const + virtual void operator()(const cv::Range& range) const { -#ifdef HAVE_TBB - tbb::spin_mutex::scoped_lock lock; -#endif CvSeqReader reader; - int begin = range.begin(); - int end = range.end(); + int begin = range.start; + int end = range.end; int weak_count = end - begin; CvDTree* tree; @@ -956,13 +951,11 @@ public: tmp_sum += shrinkage*(float)(tree->predict(sample, missing)->value); } } -#ifdef HAVE_TBB - lock.acquire(SumMutex); - sum[i] += tmp_sum; - lock.release(); -#else - sum[i] += tmp_sum; -#endif + + { + cv::AutoLock lock(SumMutex); + sum[i] += tmp_sum; + } } } // Tree_predictor::operator() @@ -970,11 +963,7 @@ public: }; // class Tree_predictor - -#ifdef HAVE_TBB -tbb::spin_mutex Tree_predictor::SumMutex; -#endif - +cv::Mutex Tree_predictor::SumMutex; float CvGBTrees::predict( const CvMat* _sample, const CvMat* _missing, @@ -992,12 +981,7 @@ float CvGBTrees::predict( const CvMat* _sample, const CvMat* _missing, Tree_predictor predictor = Tree_predictor(weak_seq, class_count, params.shrinkage, _sample, _missing, sum); -//#ifdef HAVE_TBB -// tbb::parallel_for(cv::BlockedRange(begin, end), predictor, -// tbb::auto_partitioner()); -//#else - cv::parallel_for(cv::BlockedRange(begin, end), predictor); -//#endif + cv::parallel_for_(cv::Range(begin, end), predictor); for (int i=0; i *resp ) Sample_predictor predictor = Sample_predictor(this, pred_resp, _data->get_values(), _data->get_missing(), _sample_idx); -//#ifdef HAVE_TBB -// tbb::parallel_for(cv::BlockedRange(0,n), predictor, tbb::auto_partitioner()); -//#else - cv::parallel_for(cv::BlockedRange(0,n), predictor); -//#endif + cv::parallel_for_(cv::Range(0,n), predictor); int* sidx = _sample_idx ? _sample_idx->data.i : 0; int r_step = CV_IS_MAT_CONT(response->type) ? diff --git a/modules/ml/src/knearest.cpp b/modules/ml/src/knearest.cpp index 3c2f9ebada..6b6f5e6afa 100644 --- a/modules/ml/src/knearest.cpp +++ b/modules/ml/src/knearest.cpp @@ -306,7 +306,7 @@ float CvKNearest::write_results( int k, int k1, int start, int end, return result; } -struct P1 { +struct P1 : cv::ParallelLoopBody { P1(const CvKNearest* _pointer, int _buf_sz, int _k, const CvMat* __samples, const float** __neighbors, int _k1, CvMat* __results, CvMat* __neighbor_responses, CvMat* __dist, float* _result) { @@ -333,10 +333,10 @@ struct P1 { float* result; int buf_sz; - void operator()( const cv::BlockedRange& range ) const + void operator()( const cv::Range& range ) const { cv::AutoBuffer buf(buf_sz); - for(int i = range.begin(); i < range.end(); i += 1 ) + for(int i = range.start; i < range.end; i += 1 ) { float* neighbor_responses = &buf[0]; float* dist = neighbor_responses + 1*k; @@ -410,8 +410,8 @@ float CvKNearest::find_nearest( const CvMat* _samples, int k, CvMat* _results, int k1 = get_sample_count(); k1 = MIN( k1, k ); - cv::parallel_for(cv::BlockedRange(0, count), P1(this, buf_sz, k, _samples, _neighbors, k1, - _results, _neighbor_responses, _dist, &result) + cv::parallel_for_(cv::Range(0, count), P1(this, buf_sz, k, _samples, _neighbors, k1, + _results, _neighbor_responses, _dist, &result) ); return result; diff --git a/modules/ml/src/nbayes.cpp b/modules/ml/src/nbayes.cpp index 15146d6f4e..f1f7a24ec0 100644 --- a/modules/ml/src/nbayes.cpp +++ b/modules/ml/src/nbayes.cpp @@ -277,7 +277,7 @@ bool CvNormalBayesClassifier::train( const CvMat* _train_data, const CvMat* _res return result; } -struct predict_body { +struct predict_body : cv::ParallelLoopBody { predict_body(CvMat* _c, CvMat** _cov_rotate_mats, CvMat** _inv_eigen_values, CvMat** _avg, const CvMat* _samples, const int* _vidx, CvMat* _cls_labels, CvMat* _results, float* _value, int _var_count1 @@ -307,7 +307,7 @@ struct predict_body { float* value; int var_count1; - void operator()( const cv::BlockedRange& range ) const + void operator()( const cv::Range& range ) const { int cls = -1; @@ -324,7 +324,7 @@ struct predict_body { cv::AutoBuffer buffer(nclasses + var_count1); CvMat diff = cvMat( 1, var_count1, CV_64FC1, &buffer[0] ); - for(int k = range.begin(); k < range.end(); k += 1 ) + for(int k = range.start; k < range.end; k += 1 ) { int ival; double opt = FLT_MAX; @@ -397,9 +397,9 @@ float CvNormalBayesClassifier::predict( const CvMat* samples, CvMat* results ) c const int* vidx = var_idx ? var_idx->data.i : 0; - cv::parallel_for(cv::BlockedRange(0, samples->rows), predict_body(c, cov_rotate_mats, inv_eigen_values, avg, samples, - vidx, cls_labels, results, &value, var_count - )); + cv::parallel_for_(cv::Range(0, samples->rows), + predict_body(c, cov_rotate_mats, inv_eigen_values, avg, samples, + vidx, cls_labels, results, &value, var_count)); return value; } diff --git a/modules/ml/src/svm.cpp b/modules/ml/src/svm.cpp index 9752848b9a..2e1b2e3565 100644 --- a/modules/ml/src/svm.cpp +++ b/modules/ml/src/svm.cpp @@ -2143,7 +2143,7 @@ float CvSVM::predict( const CvMat* sample, bool returnDFVal ) const return result; } -struct predict_body_svm { +struct predict_body_svm : ParallelLoopBody { predict_body_svm(const CvSVM* _pointer, float* _result, const CvMat* _samples, CvMat* _results) { pointer = _pointer; @@ -2157,9 +2157,9 @@ struct predict_body_svm { const CvMat* samples; CvMat* results; - void operator()( const cv::BlockedRange& range ) const + void operator()( const cv::Range& range ) const { - for(int i = range.begin(); i < range.end(); i++ ) + for(int i = range.start; i < range.end; i++ ) { CvMat sample; cvGetRow( samples, &sample, i ); @@ -2175,7 +2175,7 @@ struct predict_body_svm { float CvSVM::predict(const CvMat* samples, CV_OUT CvMat* results) const { float result = 0; - cv::parallel_for(cv::BlockedRange(0, samples->rows), + cv::parallel_for_(cv::Range(0, samples->rows), predict_body_svm(this, &result, samples, results) ); return result; diff --git a/modules/nonfree/src/surf.cpp b/modules/nonfree/src/surf.cpp index bb6d53e4b9..2fc459fb61 100644 --- a/modules/nonfree/src/surf.cpp +++ b/modules/nonfree/src/surf.cpp @@ -258,7 +258,7 @@ interpolateKeypoint( float N9[3][9], int dx, int dy, int ds, KeyPoint& kpt ) } // Multi-threaded construction of the scale-space pyramid -struct SURFBuildInvoker +struct SURFBuildInvoker : ParallelLoopBody { SURFBuildInvoker( const Mat& _sum, const vector& _sizes, const vector& _sampleSteps, @@ -271,9 +271,9 @@ struct SURFBuildInvoker traces = &_traces; } - void operator()(const BlockedRange& range) const + void operator()(const Range& range) const { - for( int i=range.begin(); i& _dets, const vector& _traces, @@ -310,9 +310,9 @@ struct SURFFindInvoker const vector& sizes, vector& keypoints, int octave, int layer, float hessianThreshold, int sampleStep ); - void operator()(const BlockedRange& range) const + void operator()(const Range& range) const { - for( int i=range.begin(); i& object int stripCount, stripSize; - #ifdef HAVE_TBB const int PTS_PER_THREAD = 1000; stripCount = ((processingRectSize.width/yStep)*(processingRectSize.height + yStep-1)/yStep + PTS_PER_THREAD/2)/PTS_PER_THREAD; stripCount = std::min(std::max(stripCount, 1), 100); stripSize = (((processingRectSize.height + stripCount - 1)/stripCount + yStep-1)/yStep)*yStep; - #else - stripCount = 1; - stripSize = processingRectSize.height; - #endif if( !detectSingleScale( scaledImage, stripCount, processingRectSize, stripSize, yStep, factor, candidates, rejectLevels, levelWeights, outputRejectLevels ) ) diff --git a/modules/objdetect/src/latentsvm.cpp b/modules/objdetect/src/latentsvm.cpp index 521f0fdf56..5a45965e77 100644 --- a/modules/objdetect/src/latentsvm.cpp +++ b/modules/objdetect/src/latentsvm.cpp @@ -582,7 +582,6 @@ int searchObjectThresholdSomeComponents(const CvLSVMFeaturePyramid *H, // For each component perform searching for (i = 0; i < kComponents; i++) { -#ifdef HAVE_TBB int error = searchObjectThreshold(H, &(filters[componentIndex]), kPartFilters[i], b[i], maxXBorder, maxYBorder, scoreThreshold, &(pointsArr[i]), &(levelsArr[i]), &(kPointsArr[i]), @@ -598,13 +597,6 @@ int searchObjectThresholdSomeComponents(const CvLSVMFeaturePyramid *H, free(partsDisplacementArr); return LATENT_SVM_SEARCH_OBJECT_FAILED; } -#else - (void)numThreads; - searchObjectThreshold(H, &(filters[componentIndex]), kPartFilters[i], - b[i], maxXBorder, maxYBorder, scoreThreshold, - &(pointsArr[i]), &(levelsArr[i]), &(kPointsArr[i]), - &(scoreArr[i]), &(partsDisplacementArr[i])); -#endif estimateBoxes(pointsArr[i], levelsArr[i], kPointsArr[i], filters[componentIndex]->sizeX, filters[componentIndex]->sizeY, &(oppPointsArr[i])); componentIndex += (kPartFilters[i] + 1); diff --git a/modules/photo/src/denoising.cpp b/modules/photo/src/denoising.cpp index 02d7a6f620..191926ccb7 100644 --- a/modules/photo/src/denoising.cpp +++ b/modules/photo/src/denoising.cpp @@ -59,17 +59,17 @@ void cv::fastNlMeansDenoising( InputArray _src, OutputArray _dst, float h, switch (src.type()) { case CV_8U: - parallel_for(cv::BlockedRange(0, src.rows), + parallel_for_(cv::Range(0, src.rows), FastNlMeansDenoisingInvoker( src, dst, templateWindowSize, searchWindowSize, h)); break; case CV_8UC2: - parallel_for(cv::BlockedRange(0, src.rows), + parallel_for_(cv::Range(0, src.rows), FastNlMeansDenoisingInvoker( src, dst, templateWindowSize, searchWindowSize, h)); break; case CV_8UC3: - parallel_for(cv::BlockedRange(0, src.rows), + parallel_for_(cv::Range(0, src.rows), FastNlMeansDenoisingInvoker( src, dst, templateWindowSize, searchWindowSize, h)); break; @@ -159,19 +159,19 @@ void cv::fastNlMeansDenoisingMulti( InputArrayOfArrays _srcImgs, OutputArray _ds switch (srcImgs[0].type()) { case CV_8U: - parallel_for(cv::BlockedRange(0, srcImgs[0].rows), + parallel_for_(cv::Range(0, srcImgs[0].rows), FastNlMeansMultiDenoisingInvoker( srcImgs, imgToDenoiseIndex, temporalWindowSize, dst, templateWindowSize, searchWindowSize, h)); break; case CV_8UC2: - parallel_for(cv::BlockedRange(0, srcImgs[0].rows), + parallel_for_(cv::Range(0, srcImgs[0].rows), FastNlMeansMultiDenoisingInvoker( srcImgs, imgToDenoiseIndex, temporalWindowSize, dst, templateWindowSize, searchWindowSize, h)); break; case CV_8UC3: - parallel_for(cv::BlockedRange(0, srcImgs[0].rows), + parallel_for_(cv::Range(0, srcImgs[0].rows), FastNlMeansMultiDenoisingInvoker( srcImgs, imgToDenoiseIndex, temporalWindowSize, dst, templateWindowSize, searchWindowSize, h)); diff --git a/modules/photo/src/fast_nlmeans_denoising_invoker.hpp b/modules/photo/src/fast_nlmeans_denoising_invoker.hpp index c4f13826d2..8824f17c0d 100644 --- a/modules/photo/src/fast_nlmeans_denoising_invoker.hpp +++ b/modules/photo/src/fast_nlmeans_denoising_invoker.hpp @@ -55,12 +55,12 @@ using namespace std; using namespace cv; template -struct FastNlMeansDenoisingInvoker { +struct FastNlMeansDenoisingInvoker : ParallelLoopBody { public: FastNlMeansDenoisingInvoker(const Mat& src, Mat& dst, int template_window_size, int search_window_size, const float h); - void operator() (const BlockedRange& range) const; + void operator() (const Range& range) const; private: void operator= (const FastNlMeansDenoisingInvoker&); @@ -156,9 +156,9 @@ FastNlMeansDenoisingInvoker::FastNlMeansDenoisingInvoker( } template -void FastNlMeansDenoisingInvoker::operator() (const BlockedRange& range) const { - int row_from = range.begin(); - int row_to = range.end() - 1; +void FastNlMeansDenoisingInvoker::operator() (const Range& range) const { + int row_from = range.start; + int row_to = range.end - 1; Array2d dist_sums(search_window_size_, search_window_size_); diff --git a/modules/photo/src/fast_nlmeans_multi_denoising_invoker.hpp b/modules/photo/src/fast_nlmeans_multi_denoising_invoker.hpp index 2ae5054e00..8b32eded18 100644 --- a/modules/photo/src/fast_nlmeans_multi_denoising_invoker.hpp +++ b/modules/photo/src/fast_nlmeans_multi_denoising_invoker.hpp @@ -55,13 +55,13 @@ using namespace std; using namespace cv; template -struct FastNlMeansMultiDenoisingInvoker { +struct FastNlMeansMultiDenoisingInvoker : ParallelLoopBody { public: FastNlMeansMultiDenoisingInvoker( const std::vector& srcImgs, int imgToDenoiseIndex, int temporalWindowSize, Mat& dst, int template_window_size, int search_window_size, const float h); - void operator() (const BlockedRange& range) const; + void operator() (const Range& range) const; private: void operator= (const FastNlMeansMultiDenoisingInvoker&); @@ -175,9 +175,9 @@ FastNlMeansMultiDenoisingInvoker::FastNlMeansMultiDenoisingInvoker( } template -void FastNlMeansMultiDenoisingInvoker::operator() (const BlockedRange& range) const { - int row_from = range.begin(); - int row_to = range.end() - 1; +void FastNlMeansMultiDenoisingInvoker::operator() (const Range& range) const { + int row_from = range.start; + int row_to = range.end - 1; Array3d dist_sums(temporal_window_size_, search_window_size_, search_window_size_); diff --git a/modules/stitching/src/matchers.cpp b/modules/stitching/src/matchers.cpp index 9bab58c52f..b5bd8ad4d1 100644 --- a/modules/stitching/src/matchers.cpp +++ b/modules/stitching/src/matchers.cpp @@ -66,7 +66,7 @@ struct DistIdxPair }; -struct MatchPairsBody +struct MatchPairsBody : ParallelLoopBody { MatchPairsBody(const MatchPairsBody& other) : matcher(other.matcher), features(other.features), @@ -77,10 +77,10 @@ struct MatchPairsBody : matcher(_matcher), features(_features), pairwise_matches(_pairwise_matches), near_pairs(_near_pairs) {} - void operator ()(const BlockedRange &r) const + void operator ()(const Range &r) const { const int num_images = static_cast(features.size()); - for (int i = r.begin(); i < r.end(); ++i) + for (int i = r.start; i < r.end; ++i) { int from = near_pairs[i].first; int to = near_pairs[i].second; @@ -526,9 +526,9 @@ void FeaturesMatcher::operator ()(const vector &features, vector< MatchPairsBody body(*this, features, pairwise_matches, near_pairs); if (is_thread_safe_) - parallel_for(BlockedRange(0, static_cast(near_pairs.size())), body); + parallel_for_(Range(0, static_cast(near_pairs.size())), body); else - body(BlockedRange(0, static_cast(near_pairs.size()))); + body(Range(0, static_cast(near_pairs.size()))); LOGLN_CHAT(""); } diff --git a/modules/video/src/bgfg_gaussmix2.cpp b/modules/video/src/bgfg_gaussmix2.cpp index e532af2ae6..6bbb960482 100644 --- a/modules/video/src/bgfg_gaussmix2.cpp +++ b/modules/video/src/bgfg_gaussmix2.cpp @@ -248,7 +248,7 @@ detectShadowGMM(const float* data, int nchannels, int nmodes, //IEEE Trans. on Pattern Analysis and Machine Intelligence, vol.26, no.5, pages 651-656, 2004 //http://www.zoranz.net/Publications/zivkovic2004PAMI.pdf -struct MOG2Invoker +struct MOG2Invoker : ParallelLoopBody { MOG2Invoker(const Mat& _src, Mat& _dst, GMM* _gmm, float* _mean, @@ -280,9 +280,9 @@ struct MOG2Invoker cvtfunc = src->depth() != CV_32F ? getConvertFunc(src->depth(), CV_32F) : 0; } - void operator()(const BlockedRange& range) const + void operator()(const Range& range) const { - int y0 = range.begin(), y1 = range.end(); + int y0 = range.start, y1 = range.end; int ncols = src->cols, nchannels = src->channels(); AutoBuffer buf(src->cols*nchannels); float alpha1 = 1.f - alphaT; @@ -562,15 +562,15 @@ void BackgroundSubtractorMOG2::operator()(InputArray _image, OutputArray _fgmask learningRate = learningRate >= 0 && nframes > 1 ? learningRate : 1./min( 2*nframes, history ); CV_Assert(learningRate >= 0); - parallel_for(BlockedRange(0, image.rows), - MOG2Invoker(image, fgmask, - (GMM*)bgmodel.data, - (float*)(bgmodel.data + sizeof(GMM)*nmixtures*image.rows*image.cols), - bgmodelUsedModes.data, nmixtures, (float)learningRate, - (float)varThreshold, - backgroundRatio, varThresholdGen, - fVarInit, fVarMin, fVarMax, float(-learningRate*fCT), fTau, - bShadowDetection, nShadowDetection)); + parallel_for_(Range(0, image.rows), + MOG2Invoker(image, fgmask, + (GMM*)bgmodel.data, + (float*)(bgmodel.data + sizeof(GMM)*nmixtures*image.rows*image.cols), + bgmodelUsedModes.data, nmixtures, (float)learningRate, + (float)varThreshold, + backgroundRatio, varThresholdGen, + fVarInit, fVarMin, fVarMax, float(-learningRate*fCT), fTau, + bShadowDetection, nShadowDetection)); } void BackgroundSubtractorMOG2::getBackgroundImage(OutputArray backgroundImage) const diff --git a/modules/video/src/lkpyramid.cpp b/modules/video/src/lkpyramid.cpp index 9e47eb8029..291cb86a26 100644 --- a/modules/video/src/lkpyramid.cpp +++ b/modules/video/src/lkpyramid.cpp @@ -156,7 +156,7 @@ cv::detail::LKTrackerInvoker::LKTrackerInvoker( minEigThreshold = _minEigThreshold; } -void cv::detail::LKTrackerInvoker::operator()(const BlockedRange& range) const +void cv::detail::LKTrackerInvoker::operator()(const Range& range) const { Point2f halfWin((winSize.width-1)*0.5f, (winSize.height-1)*0.5f); const Mat& I = *prevImg; @@ -170,7 +170,7 @@ void cv::detail::LKTrackerInvoker::operator()(const BlockedRange& range) const Mat IWinBuf(winSize, CV_MAKETYPE(derivDepth, cn), (deriv_type*)_buf); Mat derivIWinBuf(winSize, CV_MAKETYPE(derivDepth, cn2), (deriv_type*)_buf + winSize.area()*cn); - for( int ptidx = range.begin(); ptidx < range.end(); ptidx++ ) + for( int ptidx = range.start; ptidx < range.end; ptidx++ ) { Point2f prevPt = prevPts[ptidx]*(float)(1./(1 << level)); Point2f nextPt; @@ -733,11 +733,11 @@ void cv::calcOpticalFlowPyrLK( InputArray _prevImg, InputArray _nextImg, typedef cv::detail::LKTrackerInvoker LKTrackerInvoker; #endif - parallel_for(BlockedRange(0, npoints), LKTrackerInvoker(prevPyr[level * lvlStep1], derivI, - nextPyr[level * lvlStep2], prevPts, nextPts, - status, err, - winSize, criteria, level, maxLevel, - flags, (float)minEigThreshold)); + parallel_for_(Range(0, npoints), LKTrackerInvoker(prevPyr[level * lvlStep1], derivI, + nextPyr[level * lvlStep2], prevPts, nextPts, + status, err, + winSize, criteria, level, maxLevel, + flags, (float)minEigThreshold)); } } diff --git a/modules/video/src/lkpyramid.hpp b/modules/video/src/lkpyramid.hpp index 390e46bf99..4aff37ef84 100644 --- a/modules/video/src/lkpyramid.hpp +++ b/modules/video/src/lkpyramid.hpp @@ -7,7 +7,7 @@ namespace detail typedef short deriv_type; - struct LKTrackerInvoker + struct LKTrackerInvoker : ParallelLoopBody { LKTrackerInvoker( const Mat& _prevImg, const Mat& _prevDeriv, const Mat& _nextImg, const Point2f* _prevPts, Point2f* _nextPts, @@ -15,7 +15,7 @@ namespace detail Size _winSize, TermCriteria _criteria, int _level, int _maxLevel, int _flags, float _minEigThreshold ); - void operator()(const BlockedRange& range) const; + void operator()(const Range& range) const; const Mat* prevImg; const Mat* nextImg; From f90fd5b0da289759a29e3129242a20c67922443f Mon Sep 17 00:00:00 2001 From: Roman Donchenko Date: Thu, 30 May 2013 19:05:59 +0400 Subject: [PATCH 053/178] Split CLAHE into its own file, because it's faster that way. Yes, it's as ludicrous as it sounds, but it's still true. Bizarrely, the previous commit makes CLAHE run about 10% slower on Android, even though it doesn't even touch any CLAHE code. Splitting it off fixes that, although the reason it does is a mystery for the ages. It's cleaner when it's in its own file, anyway. ;=] --- modules/imgproc/src/clahe.cpp | 334 ++++++++++++++++++++++++++++++ modules/imgproc/src/histogram.cpp | 292 -------------------------- 2 files changed, 334 insertions(+), 292 deletions(-) create mode 100644 modules/imgproc/src/clahe.cpp diff --git a/modules/imgproc/src/clahe.cpp b/modules/imgproc/src/clahe.cpp new file mode 100644 index 0000000000..4ce479713e --- /dev/null +++ b/modules/imgproc/src/clahe.cpp @@ -0,0 +1,334 @@ +/*M/////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// License Agreement +// For Open Source Computer Vision Library +// +// Copyright (C) 2013, NVIDIA Corporation, all rights reserved. +// Third party copyrights are property of their respective owners. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * The name of the copyright holders may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors "as is" and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the copyright holders or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +//M*/ + +#include "precomp.hpp" + +// ---------------------------------------------------------------------- +// CLAHE + +namespace +{ + class CLAHE_CalcLut_Body : public cv::ParallelLoopBody + { + public: + CLAHE_CalcLut_Body(const cv::Mat& src, cv::Mat& lut, cv::Size tileSize, int tilesX, int tilesY, int clipLimit, float lutScale) : + src_(src), lut_(lut), tileSize_(tileSize), tilesX_(tilesX), tilesY_(tilesY), clipLimit_(clipLimit), lutScale_(lutScale) + { + } + + void operator ()(const cv::Range& range) const; + + private: + cv::Mat src_; + mutable cv::Mat lut_; + + cv::Size tileSize_; + int tilesX_; + int tilesY_; + int clipLimit_; + float lutScale_; + }; + + void CLAHE_CalcLut_Body::operator ()(const cv::Range& range) const + { + const int histSize = 256; + + uchar* tileLut = lut_.ptr(range.start); + const size_t lut_step = lut_.step; + + for (int k = range.start; k < range.end; ++k, tileLut += lut_step) + { + const int ty = k / tilesX_; + const int tx = k % tilesX_; + + // retrieve tile submatrix + + cv::Rect tileROI; + tileROI.x = tx * tileSize_.width; + tileROI.y = ty * tileSize_.height; + tileROI.width = tileSize_.width; + tileROI.height = tileSize_.height; + + const cv::Mat tile = src_(tileROI); + + // calc histogram + + int tileHist[histSize] = {0, }; + + int height = tileROI.height; + const size_t sstep = tile.step; + for (const uchar* ptr = tile.ptr(0); height--; ptr += sstep) + { + int x = 0; + for (; x <= tileROI.width - 4; x += 4) + { + int t0 = ptr[x], t1 = ptr[x+1]; + tileHist[t0]++; tileHist[t1]++; + t0 = ptr[x+2]; t1 = ptr[x+3]; + tileHist[t0]++; tileHist[t1]++; + } + + for (; x < tileROI.width; ++x) + tileHist[ptr[x]]++; + } + + // clip histogram + + if (clipLimit_ > 0) + { + // how many pixels were clipped + int clipped = 0; + for (int i = 0; i < histSize; ++i) + { + if (tileHist[i] > clipLimit_) + { + clipped += tileHist[i] - clipLimit_; + tileHist[i] = clipLimit_; + } + } + + // redistribute clipped pixels + int redistBatch = clipped / histSize; + int residual = clipped - redistBatch * histSize; + + for (int i = 0; i < histSize; ++i) + tileHist[i] += redistBatch; + + for (int i = 0; i < residual; ++i) + tileHist[i]++; + } + + // calc Lut + + int sum = 0; + for (int i = 0; i < histSize; ++i) + { + sum += tileHist[i]; + tileLut[i] = cv::saturate_cast(sum * lutScale_); + } + } + } + + class CLAHE_Interpolation_Body : public cv::ParallelLoopBody + { + public: + CLAHE_Interpolation_Body(const cv::Mat& src, cv::Mat& dst, const cv::Mat& lut, cv::Size tileSize, int tilesX, int tilesY) : + src_(src), dst_(dst), lut_(lut), tileSize_(tileSize), tilesX_(tilesX), tilesY_(tilesY) + { + } + + void operator ()(const cv::Range& range) const; + + private: + cv::Mat src_; + mutable cv::Mat dst_; + cv::Mat lut_; + + cv::Size tileSize_; + int tilesX_; + int tilesY_; + }; + + void CLAHE_Interpolation_Body::operator ()(const cv::Range& range) const + { + const size_t lut_step = lut_.step; + + for (int y = range.start; y < range.end; ++y) + { + const uchar* srcRow = src_.ptr(y); + uchar* dstRow = dst_.ptr(y); + + const float tyf = (static_cast(y) / tileSize_.height) - 0.5f; + + int ty1 = cvFloor(tyf); + int ty2 = ty1 + 1; + + const float ya = tyf - ty1; + + ty1 = std::max(ty1, 0); + ty2 = std::min(ty2, tilesY_ - 1); + + const uchar* lutPlane1 = lut_.ptr(ty1 * tilesX_); + const uchar* lutPlane2 = lut_.ptr(ty2 * tilesX_); + + for (int x = 0; x < src_.cols; ++x) + { + const float txf = (static_cast(x) / tileSize_.width) - 0.5f; + + int tx1 = cvFloor(txf); + int tx2 = tx1 + 1; + + const float xa = txf - tx1; + + tx1 = std::max(tx1, 0); + tx2 = std::min(tx2, tilesX_ - 1); + + const int srcVal = srcRow[x]; + + const size_t ind1 = tx1 * lut_step + srcVal; + const size_t ind2 = tx2 * lut_step + srcVal; + + float res = 0; + + res += lutPlane1[ind1] * ((1.0f - xa) * (1.0f - ya)); + res += lutPlane1[ind2] * ((xa) * (1.0f - ya)); + res += lutPlane2[ind1] * ((1.0f - xa) * (ya)); + res += lutPlane2[ind2] * ((xa) * (ya)); + + dstRow[x] = cv::saturate_cast(res); + } + } + } + + class CLAHE_Impl : public cv::CLAHE + { + public: + CLAHE_Impl(double clipLimit = 40.0, int tilesX = 8, int tilesY = 8); + + cv::AlgorithmInfo* info() const; + + void apply(cv::InputArray src, cv::OutputArray dst); + + void setClipLimit(double clipLimit); + double getClipLimit() const; + + void setTilesGridSize(cv::Size tileGridSize); + cv::Size getTilesGridSize() const; + + void collectGarbage(); + + private: + double clipLimit_; + int tilesX_; + int tilesY_; + + cv::Mat srcExt_; + cv::Mat lut_; + }; + + CLAHE_Impl::CLAHE_Impl(double clipLimit, int tilesX, int tilesY) : + clipLimit_(clipLimit), tilesX_(tilesX), tilesY_(tilesY) + { + } + + CV_INIT_ALGORITHM(CLAHE_Impl, "CLAHE", + obj.info()->addParam(obj, "clipLimit", obj.clipLimit_); + obj.info()->addParam(obj, "tilesX", obj.tilesX_); + obj.info()->addParam(obj, "tilesY", obj.tilesY_)) + + void CLAHE_Impl::apply(cv::InputArray _src, cv::OutputArray _dst) + { + cv::Mat src = _src.getMat(); + + CV_Assert( src.type() == CV_8UC1 ); + + _dst.create( src.size(), src.type() ); + cv::Mat dst = _dst.getMat(); + + const int histSize = 256; + + lut_.create(tilesX_ * tilesY_, histSize, CV_8UC1); + + cv::Size tileSize; + cv::Mat srcForLut; + + if (src.cols % tilesX_ == 0 && src.rows % tilesY_ == 0) + { + tileSize = cv::Size(src.cols / tilesX_, src.rows / tilesY_); + srcForLut = src; + } + else + { + cv::copyMakeBorder(src, srcExt_, 0, tilesY_ - (src.rows % tilesY_), 0, tilesX_ - (src.cols % tilesX_), cv::BORDER_REFLECT_101); + + tileSize = cv::Size(srcExt_.cols / tilesX_, srcExt_.rows / tilesY_); + srcForLut = srcExt_; + } + + const int tileSizeTotal = tileSize.area(); + const float lutScale = static_cast(histSize - 1) / tileSizeTotal; + + int clipLimit = 0; + if (clipLimit_ > 0.0) + { + clipLimit = static_cast(clipLimit_ * tileSizeTotal / histSize); + clipLimit = std::max(clipLimit, 1); + } + + CLAHE_CalcLut_Body calcLutBody(srcForLut, lut_, tileSize, tilesX_, tilesY_, clipLimit, lutScale); + cv::parallel_for_(cv::Range(0, tilesX_ * tilesY_), calcLutBody); + + CLAHE_Interpolation_Body interpolationBody(src, dst, lut_, tileSize, tilesX_, tilesY_); + cv::parallel_for_(cv::Range(0, src.rows), interpolationBody); + } + + void CLAHE_Impl::setClipLimit(double clipLimit) + { + clipLimit_ = clipLimit; + } + + double CLAHE_Impl::getClipLimit() const + { + return clipLimit_; + } + + void CLAHE_Impl::setTilesGridSize(cv::Size tileGridSize) + { + tilesX_ = tileGridSize.width; + tilesY_ = tileGridSize.height; + } + + cv::Size CLAHE_Impl::getTilesGridSize() const + { + return cv::Size(tilesX_, tilesY_); + } + + void CLAHE_Impl::collectGarbage() + { + srcExt_.release(); + lut_.release(); + } +} + +cv::Ptr cv::createCLAHE(double clipLimit, cv::Size tileGridSize) +{ + return new CLAHE_Impl(clipLimit, tileGridSize.width, tileGridSize.height); +} diff --git a/modules/imgproc/src/histogram.cpp b/modules/imgproc/src/histogram.cpp index 5ca6c9d15b..bfcdee515f 100644 --- a/modules/imgproc/src/histogram.cpp +++ b/modules/imgproc/src/histogram.cpp @@ -3165,298 +3165,6 @@ void cv::equalizeHist( InputArray _src, OutputArray _dst ) lutBody(heightRange); } -// ---------------------------------------------------------------------- -// CLAHE - -namespace -{ - class CLAHE_CalcLut_Body : public cv::ParallelLoopBody - { - public: - CLAHE_CalcLut_Body(const cv::Mat& src, cv::Mat& lut, cv::Size tileSize, int tilesX, int tilesY, int clipLimit, float lutScale) : - src_(src), lut_(lut), tileSize_(tileSize), tilesX_(tilesX), tilesY_(tilesY), clipLimit_(clipLimit), lutScale_(lutScale) - { - } - - void operator ()(const cv::Range& range) const; - - private: - cv::Mat src_; - mutable cv::Mat lut_; - - cv::Size tileSize_; - int tilesX_; - int tilesY_; - int clipLimit_; - float lutScale_; - }; - - void CLAHE_CalcLut_Body::operator ()(const cv::Range& range) const - { - const int histSize = 256; - - uchar* tileLut = lut_.ptr(range.start); - const size_t lut_step = lut_.step; - - for (int k = range.start; k < range.end; ++k, tileLut += lut_step) - { - const int ty = k / tilesX_; - const int tx = k % tilesX_; - - // retrieve tile submatrix - - cv::Rect tileROI; - tileROI.x = tx * tileSize_.width; - tileROI.y = ty * tileSize_.height; - tileROI.width = tileSize_.width; - tileROI.height = tileSize_.height; - - const cv::Mat tile = src_(tileROI); - - // calc histogram - - int tileHist[histSize] = {0, }; - - int height = tileROI.height; - const size_t sstep = tile.step; - for (const uchar* ptr = tile.ptr(0); height--; ptr += sstep) - { - int x = 0; - for (; x <= tileROI.width - 4; x += 4) - { - int t0 = ptr[x], t1 = ptr[x+1]; - tileHist[t0]++; tileHist[t1]++; - t0 = ptr[x+2]; t1 = ptr[x+3]; - tileHist[t0]++; tileHist[t1]++; - } - - for (; x < tileROI.width; ++x) - tileHist[ptr[x]]++; - } - - // clip histogram - - if (clipLimit_ > 0) - { - // how many pixels were clipped - int clipped = 0; - for (int i = 0; i < histSize; ++i) - { - if (tileHist[i] > clipLimit_) - { - clipped += tileHist[i] - clipLimit_; - tileHist[i] = clipLimit_; - } - } - - // redistribute clipped pixels - int redistBatch = clipped / histSize; - int residual = clipped - redistBatch * histSize; - - for (int i = 0; i < histSize; ++i) - tileHist[i] += redistBatch; - - for (int i = 0; i < residual; ++i) - tileHist[i]++; - } - - // calc Lut - - int sum = 0; - for (int i = 0; i < histSize; ++i) - { - sum += tileHist[i]; - tileLut[i] = cv::saturate_cast(sum * lutScale_); - } - } - } - - class CLAHE_Interpolation_Body : public cv::ParallelLoopBody - { - public: - CLAHE_Interpolation_Body(const cv::Mat& src, cv::Mat& dst, const cv::Mat& lut, cv::Size tileSize, int tilesX, int tilesY) : - src_(src), dst_(dst), lut_(lut), tileSize_(tileSize), tilesX_(tilesX), tilesY_(tilesY) - { - } - - void operator ()(const cv::Range& range) const; - - private: - cv::Mat src_; - mutable cv::Mat dst_; - cv::Mat lut_; - - cv::Size tileSize_; - int tilesX_; - int tilesY_; - }; - - void CLAHE_Interpolation_Body::operator ()(const cv::Range& range) const - { - const size_t lut_step = lut_.step; - - for (int y = range.start; y < range.end; ++y) - { - const uchar* srcRow = src_.ptr(y); - uchar* dstRow = dst_.ptr(y); - - const float tyf = (static_cast(y) / tileSize_.height) - 0.5f; - - int ty1 = cvFloor(tyf); - int ty2 = ty1 + 1; - - const float ya = tyf - ty1; - - ty1 = std::max(ty1, 0); - ty2 = std::min(ty2, tilesY_ - 1); - - const uchar* lutPlane1 = lut_.ptr(ty1 * tilesX_); - const uchar* lutPlane2 = lut_.ptr(ty2 * tilesX_); - - for (int x = 0; x < src_.cols; ++x) - { - const float txf = (static_cast(x) / tileSize_.width) - 0.5f; - - int tx1 = cvFloor(txf); - int tx2 = tx1 + 1; - - const float xa = txf - tx1; - - tx1 = std::max(tx1, 0); - tx2 = std::min(tx2, tilesX_ - 1); - - const int srcVal = srcRow[x]; - - const size_t ind1 = tx1 * lut_step + srcVal; - const size_t ind2 = tx2 * lut_step + srcVal; - - float res = 0; - - res += lutPlane1[ind1] * ((1.0f - xa) * (1.0f - ya)); - res += lutPlane1[ind2] * ((xa) * (1.0f - ya)); - res += lutPlane2[ind1] * ((1.0f - xa) * (ya)); - res += lutPlane2[ind2] * ((xa) * (ya)); - - dstRow[x] = cv::saturate_cast(res); - } - } - } - - class CLAHE_Impl : public cv::CLAHE - { - public: - CLAHE_Impl(double clipLimit = 40.0, int tilesX = 8, int tilesY = 8); - - cv::AlgorithmInfo* info() const; - - void apply(cv::InputArray src, cv::OutputArray dst); - - void setClipLimit(double clipLimit); - double getClipLimit() const; - - void setTilesGridSize(cv::Size tileGridSize); - cv::Size getTilesGridSize() const; - - void collectGarbage(); - - private: - double clipLimit_; - int tilesX_; - int tilesY_; - - cv::Mat srcExt_; - cv::Mat lut_; - }; - - CLAHE_Impl::CLAHE_Impl(double clipLimit, int tilesX, int tilesY) : - clipLimit_(clipLimit), tilesX_(tilesX), tilesY_(tilesY) - { - } - - CV_INIT_ALGORITHM(CLAHE_Impl, "CLAHE", - obj.info()->addParam(obj, "clipLimit", obj.clipLimit_); - obj.info()->addParam(obj, "tilesX", obj.tilesX_); - obj.info()->addParam(obj, "tilesY", obj.tilesY_)) - - void CLAHE_Impl::apply(cv::InputArray _src, cv::OutputArray _dst) - { - cv::Mat src = _src.getMat(); - - CV_Assert( src.type() == CV_8UC1 ); - - _dst.create( src.size(), src.type() ); - cv::Mat dst = _dst.getMat(); - - const int histSize = 256; - - lut_.create(tilesX_ * tilesY_, histSize, CV_8UC1); - - cv::Size tileSize; - cv::Mat srcForLut; - - if (src.cols % tilesX_ == 0 && src.rows % tilesY_ == 0) - { - tileSize = cv::Size(src.cols / tilesX_, src.rows / tilesY_); - srcForLut = src; - } - else - { - cv::copyMakeBorder(src, srcExt_, 0, tilesY_ - (src.rows % tilesY_), 0, tilesX_ - (src.cols % tilesX_), cv::BORDER_REFLECT_101); - - tileSize = cv::Size(srcExt_.cols / tilesX_, srcExt_.rows / tilesY_); - srcForLut = srcExt_; - } - - const int tileSizeTotal = tileSize.area(); - const float lutScale = static_cast(histSize - 1) / tileSizeTotal; - - int clipLimit = 0; - if (clipLimit_ > 0.0) - { - clipLimit = static_cast(clipLimit_ * tileSizeTotal / histSize); - clipLimit = std::max(clipLimit, 1); - } - - CLAHE_CalcLut_Body calcLutBody(srcForLut, lut_, tileSize, tilesX_, tilesY_, clipLimit, lutScale); - cv::parallel_for_(cv::Range(0, tilesX_ * tilesY_), calcLutBody); - - CLAHE_Interpolation_Body interpolationBody(src, dst, lut_, tileSize, tilesX_, tilesY_); - cv::parallel_for_(cv::Range(0, src.rows), interpolationBody); - } - - void CLAHE_Impl::setClipLimit(double clipLimit) - { - clipLimit_ = clipLimit; - } - - double CLAHE_Impl::getClipLimit() const - { - return clipLimit_; - } - - void CLAHE_Impl::setTilesGridSize(cv::Size tileGridSize) - { - tilesX_ = tileGridSize.width; - tilesY_ = tileGridSize.height; - } - - cv::Size CLAHE_Impl::getTilesGridSize() const - { - return cv::Size(tilesX_, tilesY_); - } - - void CLAHE_Impl::collectGarbage() - { - srcExt_.release(); - lut_.release(); - } -} - -cv::Ptr cv::createCLAHE(double clipLimit, cv::Size tileGridSize) -{ - return new CLAHE_Impl(clipLimit, tileGridSize.width, tileGridSize.height); -} - // ---------------------------------------------------------------------- /* Implementation of RTTI and Generic Functions for CvHistogram */ From 97b86aa259ec1b33d1c5b28bd04d6247e7671a93 Mon Sep 17 00:00:00 2001 From: peng xiao Date: Fri, 31 May 2013 16:48:40 +0800 Subject: [PATCH 054/178] Initialize OpenCL context at the end of getDevice call. Added for better compatibility with the current samples/test cases. User now will be able to initialize OpenCL context explicitly with ocl::getDevice api. This may be obsoleted in future releases. --- modules/ocl/src/initialization.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/ocl/src/initialization.cpp b/modules/ocl/src/initialization.cpp index fd462ad916..a9cd08b9f4 100644 --- a/modules/ocl/src/initialization.cpp +++ b/modules/ocl/src/initialization.cpp @@ -333,6 +333,10 @@ namespace cv oclinfo.push_back(ocltmpinfo); } } + if(devcienums > 0) + { + setDevice(oclinfo[0]); + } return devcienums; } From cdb16f112074697445cc3c960142bc05dbfa916b Mon Sep 17 00:00:00 2001 From: peng xiao Date: Fri, 31 May 2013 17:29:55 +0800 Subject: [PATCH 055/178] Fix build error --- samples/ocl/surf_matcher.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/ocl/surf_matcher.cpp b/samples/ocl/surf_matcher.cpp index 8734e3f813..038a8dc5cd 100644 --- a/samples/ocl/surf_matcher.cpp +++ b/samples/ocl/surf_matcher.cpp @@ -350,7 +350,7 @@ int main(int argc, char* argv[]) else { bool result = false; - for(int i = 0; i < cpu_corner.size(); i++) + for(size_t i = 0; i < cpu_corner.size(); i++) { if((std::abs(cpu_corner[i].x - gpu_corner[i].x) > 10) ||(std::abs(cpu_corner[i].y - gpu_corner[i].y) > 10)) From 8a4090fe3f6daa00f5421e2b9fefd2f14790c126 Mon Sep 17 00:00:00 2001 From: Roman Donchenko Date: Fri, 31 May 2013 17:27:42 +0400 Subject: [PATCH 056/178] Make AutoLock noncopyable (it would break on copying, anyway). --- modules/core/include/opencv2/core/core.hpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/core/include/opencv2/core/core.hpp b/modules/core/include/opencv2/core/core.hpp index 1c8e0e2cac..2b7791958f 100644 --- a/modules/core/include/opencv2/core/core.hpp +++ b/modules/core/include/opencv2/core/core.hpp @@ -4813,6 +4813,9 @@ public: ~AutoLock() { mutex->unlock(); } protected: Mutex* mutex; +private: + AutoLock(const AutoLock&); + AutoLock& operator = (const AutoLock&); }; } From 6f006e50dc2cff6ba8b316640d769db1ac08e2ca Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Fri, 31 May 2013 18:58:30 +0400 Subject: [PATCH 057/178] setting 'char' to be signed by default since some tests fail when it's wrong (e.g. native compilation on ARM Linux) --- cmake/OpenCVCompilerOptions.cmake | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmake/OpenCVCompilerOptions.cmake b/cmake/OpenCVCompilerOptions.cmake index aeed112ae0..bded2d41fe 100644 --- a/cmake/OpenCVCompilerOptions.cmake +++ b/cmake/OpenCVCompilerOptions.cmake @@ -47,6 +47,9 @@ macro(add_extra_compiler_option option) endif() endmacro() +# some OpenCV tests fail when 'char' is 'unsigned' by default +add_extra_compiler_option(-fsigned-char) + if(MINGW) # http://gcc.gnu.org/bugzilla/show_bug.cgi?id=40838 # here we are trying to workaround the problem From 081c47e3df9819e025520ea7b77cc627cd4ccdbe Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Fri, 31 May 2013 19:55:51 +0400 Subject: [PATCH 058/178] making the comment less ambigous --- cmake/OpenCVCompilerOptions.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/OpenCVCompilerOptions.cmake b/cmake/OpenCVCompilerOptions.cmake index bded2d41fe..7a91b188ae 100644 --- a/cmake/OpenCVCompilerOptions.cmake +++ b/cmake/OpenCVCompilerOptions.cmake @@ -47,7 +47,7 @@ macro(add_extra_compiler_option option) endif() endmacro() -# some OpenCV tests fail when 'char' is 'unsigned' by default +# OpenCV fails some tests when 'char' is 'unsigned' by default add_extra_compiler_option(-fsigned-char) if(MINGW) From 013581f3717da193342a9c31c3f62b493907d036 Mon Sep 17 00:00:00 2001 From: Vladislav Vinogradov Date: Mon, 3 Jun 2013 13:24:57 +0400 Subject: [PATCH 059/178] fixed GPU module compialtion with CMake 2.8.11 CMake 2.8.11 removed linkage with CUDA driver library, but it's used by gpu video encoding/decoding --- modules/gpu/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/gpu/CMakeLists.txt b/modules/gpu/CMakeLists.txt index 5509226419..87c172da5f 100644 --- a/modules/gpu/CMakeLists.txt +++ b/modules/gpu/CMakeLists.txt @@ -42,7 +42,7 @@ if(HAVE_CUDA) ocv_cuda_compile(cuda_objs ${lib_cuda} ${ncv_cuda}) - set(cuda_link_libs ${CUDA_LIBRARIES} ${CUDA_npp_LIBRARY}) + set(cuda_link_libs ${CUDA_LIBRARIES} ${CUDA_CUDA_LIBRARY} ${CUDA_npp_LIBRARY}) if(WITH_NVCUVID) set(cuda_link_libs ${cuda_link_libs} ${CUDA_nvcuvid_LIBRARY}) From 09a7e86a39d926eaffe60d660148d089d66e5f95 Mon Sep 17 00:00:00 2001 From: Vladislav Vinogradov Date: Mon, 3 Jun 2013 13:33:53 +0400 Subject: [PATCH 060/178] fixed NPP library search (it was splitted) --- cmake/OpenCVDetectCUDA.cmake | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/cmake/OpenCVDetectCUDA.cmake b/cmake/OpenCVDetectCUDA.cmake index f3d101ab21..8db667762e 100644 --- a/cmake/OpenCVDetectCUDA.cmake +++ b/cmake/OpenCVDetectCUDA.cmake @@ -26,6 +26,15 @@ if(CUDA_FOUND) set(HAVE_CUBLAS 1) endif() + if(${CUDA_VERSION} VERSION_LESS "5.5") + find_cuda_helper_libs(npp) + else() + find_cuda_helper_libs(nppc) + find_cuda_helper_libs(nppi) + find_cuda_helper_libs(npps) + set(CUDA_npp_LIBRARY ${CUDA_nppc_LIBRARY} ${CUDA_nppi_LIBRARY} ${CUDA_npps_LIBRARY}) + endif() + if(WITH_NVCUVID) find_cuda_helper_libs(nvcuvid) set(HAVE_NVCUVID 1) @@ -136,8 +145,6 @@ if(CUDA_FOUND) mark_as_advanced(CUDA_BUILD_CUBIN CUDA_BUILD_EMULATION CUDA_VERBOSE_BUILD CUDA_SDK_ROOT_DIR) - find_cuda_helper_libs(npp) - macro(ocv_cuda_compile VAR) foreach(var CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_RELEASE CMAKE_CXX_FLAGS_DEBUG) set(${var}_backup_in_cuda_compile_ "${${var}}") From 0521e8908c2dd72cba2e8adcbe287b55ee37c387 Mon Sep 17 00:00:00 2001 From: Vladislav Vinogradov Date: Mon, 3 Jun 2013 13:34:41 +0400 Subject: [PATCH 061/178] fixed NPP error constants usage --- modules/gpu/src/error.cpp | 74 +++++++++++++++++++++++++++++-------- modules/gpu/src/precomp.hpp | 4 +- 2 files changed, 61 insertions(+), 17 deletions(-) diff --git a/modules/gpu/src/error.cpp b/modules/gpu/src/error.cpp index c155aa83bf..7f5d5f38d5 100644 --- a/modules/gpu/src/error.cpp +++ b/modules/gpu/src/error.cpp @@ -81,48 +81,90 @@ namespace const ErrorEntry npp_errors [] = { - error_entry( NPP_NOT_SUPPORTED_MODE_ERROR ), - error_entry( NPP_ROUND_MODE_NOT_SUPPORTED_ERROR ), - error_entry( NPP_RESIZE_NO_OPERATION_ERROR ), - #if defined (_MSC_VER) error_entry( NPP_NOT_SUFFICIENT_COMPUTE_CAPABILITY ), #endif +#if NPP_VERSION < 5500 error_entry( NPP_BAD_ARG_ERROR ), - error_entry( NPP_LUT_NUMBER_OF_LEVELS_ERROR ), - error_entry( NPP_TEXTURE_BIND_ERROR ), error_entry( NPP_COEFF_ERROR ), error_entry( NPP_RECT_ERROR ), error_entry( NPP_QUAD_ERROR ), + error_entry( NPP_MEMFREE_ERR ), + error_entry( NPP_MEMSET_ERR ), + error_entry( NPP_MEM_ALLOC_ERR ), + error_entry( NPP_HISTO_NUMBER_OF_LEVELS_ERROR ), + error_entry( NPP_MIRROR_FLIP_ERR ), + error_entry( NPP_INVALID_INPUT ), + error_entry( NPP_POINTER_ERROR ), + error_entry( NPP_WARNING ), + error_entry( NPP_ODD_ROI_WARNING ), +#else + error_entry( NPP_INVALID_HOST_POINTER_ERROR ), + error_entry( NPP_INVALID_DEVICE_POINTER_ERROR ), + error_entry( NPP_LUT_PALETTE_BITSIZE_ERROR ), + error_entry( NPP_ZC_MODE_NOT_SUPPORTED_ERROR ), + error_entry( NPP_MEMFREE_ERROR ), + error_entry( NPP_MEMSET_ERROR ), + error_entry( NPP_QUALITY_INDEX_ERROR ), + error_entry( NPP_HISTOGRAM_NUMBER_OF_LEVELS_ERROR ), + error_entry( NPP_CHANNEL_ORDER_ERROR ), + error_entry( NPP_ZERO_MASK_VALUE_ERROR ), + error_entry( NPP_QUADRANGLE_ERROR ), + error_entry( NPP_RECTANGLE_ERROR ), + error_entry( NPP_COEFFICIENT_ERROR ), + error_entry( NPP_NUMBER_OF_CHANNELS_ERROR ), + error_entry( NPP_COI_ERROR ), + error_entry( NPP_DIVISOR_ERROR ), + error_entry( NPP_CHANNEL_ERROR ), + error_entry( NPP_STRIDE_ERROR ), + error_entry( NPP_ANCHOR_ERROR ), + error_entry( NPP_MASK_SIZE_ERROR ), + error_entry( NPP_MIRROR_FLIP_ERROR ), + error_entry( NPP_MOMENT_00_ZERO_ERROR ), + error_entry( NPP_THRESHOLD_NEGATIVE_LEVEL_ERROR ), + error_entry( NPP_THRESHOLD_ERROR ), + error_entry( NPP_CONTEXT_MATCH_ERROR ), + error_entry( NPP_FFT_FLAG_ERROR ), + error_entry( NPP_FFT_ORDER_ERROR ), + error_entry( NPP_SCALE_RANGE_ERROR ), + error_entry( NPP_DATA_TYPE_ERROR ), + error_entry( NPP_OUT_OFF_RANGE_ERROR ), + error_entry( NPP_DIVIDE_BY_ZERO_ERROR ), + error_entry( NPP_MEMORY_ALLOCATION_ERR ), + error_entry( NPP_RANGE_ERROR ), + error_entry( NPP_BAD_ARGUMENT_ERROR ), + error_entry( NPP_NO_MEMORY_ERROR ), + error_entry( NPP_ERROR_RESERVED ), + error_entry( NPP_NO_OPERATION_WARNING ), + error_entry( NPP_DIVIDE_BY_ZERO_WARNING ), + error_entry( NPP_WRONG_INTERSECTION_ROI_WARNING ), +#endif + + error_entry( NPP_NOT_SUPPORTED_MODE_ERROR ), + error_entry( NPP_ROUND_MODE_NOT_SUPPORTED_ERROR ), + error_entry( NPP_RESIZE_NO_OPERATION_ERROR ), + error_entry( NPP_LUT_NUMBER_OF_LEVELS_ERROR ), + error_entry( NPP_TEXTURE_BIND_ERROR ), error_entry( NPP_WRONG_INTERSECTION_ROI_ERROR ), error_entry( NPP_NOT_EVEN_STEP_ERROR ), error_entry( NPP_INTERPOLATION_ERROR ), error_entry( NPP_RESIZE_FACTOR_ERROR ), error_entry( NPP_HAAR_CLASSIFIER_PIXEL_MATCH_ERROR ), - error_entry( NPP_MEMFREE_ERR ), - error_entry( NPP_MEMSET_ERR ), error_entry( NPP_MEMCPY_ERROR ), - error_entry( NPP_MEM_ALLOC_ERR ), - error_entry( NPP_HISTO_NUMBER_OF_LEVELS_ERROR ), - error_entry( NPP_MIRROR_FLIP_ERR ), - error_entry( NPP_INVALID_INPUT ), error_entry( NPP_ALIGNMENT_ERROR ), error_entry( NPP_STEP_ERROR ), error_entry( NPP_SIZE_ERROR ), - error_entry( NPP_POINTER_ERROR ), error_entry( NPP_NULL_POINTER_ERROR ), error_entry( NPP_CUDA_KERNEL_EXECUTION_ERROR ), error_entry( NPP_NOT_IMPLEMENTED_ERROR ), error_entry( NPP_ERROR ), error_entry( NPP_NO_ERROR ), error_entry( NPP_SUCCESS ), - error_entry( NPP_WARNING ), error_entry( NPP_WRONG_INTERSECTION_QUAD_WARNING ), error_entry( NPP_MISALIGNED_DST_ROI_WARNING ), error_entry( NPP_AFFINE_QUAD_INCORRECT_WARNING ), - error_entry( NPP_DOUBLE_SIZE_WARNING ), - error_entry( NPP_ODD_ROI_WARNING ) + error_entry( NPP_DOUBLE_SIZE_WARNING ) }; const size_t npp_error_num = sizeof(npp_errors) / sizeof(npp_errors[0]); diff --git a/modules/gpu/src/precomp.hpp b/modules/gpu/src/precomp.hpp index f219089321..06d5386405 100644 --- a/modules/gpu/src/precomp.hpp +++ b/modules/gpu/src/precomp.hpp @@ -116,11 +116,13 @@ #define CUDART_MINIMUM_REQUIRED_VERSION 4010 #define NPP_MINIMUM_REQUIRED_VERSION 4100 + #define NPP_VERSION (NPP_VERSION_MAJOR * 1000 + NPP_VERSION_MINOR * 100 + NPP_VERSION_BUILD) + #if (CUDART_VERSION < CUDART_MINIMUM_REQUIRED_VERSION) #error "Insufficient Cuda Runtime library version, please update it." #endif - #if (NPP_VERSION_MAJOR * 1000 + NPP_VERSION_MINOR * 100 + NPP_VERSION_BUILD < NPP_MINIMUM_REQUIRED_VERSION) + #if (NPP_VERSION < NPP_MINIMUM_REQUIRED_VERSION) #error "Insufficient NPP version, please update it." #endif From 58e472754a424297883ac6a9f3e9306525ebcb00 Mon Sep 17 00:00:00 2001 From: Vladislav Vinogradov Date: Mon, 3 Jun 2013 13:36:02 +0400 Subject: [PATCH 062/178] fixed norm diff function (it uses pre-allocated buffer now) --- modules/gpu/src/matrix_reductions.cpp | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/modules/gpu/src/matrix_reductions.cpp b/modules/gpu/src/matrix_reductions.cpp index 761abb525f..056e5ef701 100644 --- a/modules/gpu/src/matrix_reductions.cpp +++ b/modules/gpu/src/matrix_reductions.cpp @@ -187,10 +187,20 @@ double cv::gpu::norm(const GpuMat& src1, const GpuMat& src2, int normType) CV_Assert(src1.size() == src2.size() && src1.type() == src2.type()); CV_Assert(normType == NORM_INF || normType == NORM_L1 || normType == NORM_L2); - typedef NppStatus (*npp_norm_diff_func_t)(const Npp8u* pSrc1, int nSrcStep1, const Npp8u* pSrc2, int nSrcStep2, - NppiSize oSizeROI, Npp64f* pRetVal); +#if CUDA_VERSION < 5050 + typedef NppStatus (*func_t)(const Npp8u* pSrc1, int nSrcStep1, const Npp8u* pSrc2, int nSrcStep2, NppiSize oSizeROI, Npp64f* pRetVal); - static const npp_norm_diff_func_t npp_norm_diff_func[] = {nppiNormDiff_Inf_8u_C1R, nppiNormDiff_L1_8u_C1R, nppiNormDiff_L2_8u_C1R}; + static const func_t funcs[] = {nppiNormDiff_Inf_8u_C1R, nppiNormDiff_L1_8u_C1R, nppiNormDiff_L2_8u_C1R}; +#else + typedef NppStatus (*func_t)(const Npp8u* pSrc1, int nSrcStep1, const Npp8u* pSrc2, int nSrcStep2, + NppiSize oSizeROI, Npp64f* pRetVal, Npp8u * pDeviceBuffer); + + typedef NppStatus (*buf_size_func_t)(NppiSize oSizeROI, int* hpBufferSize); + + static const func_t funcs[] = {nppiNormDiff_Inf_8u_C1R, nppiNormDiff_L1_8u_C1R, nppiNormDiff_L2_8u_C1R}; + + static const buf_size_func_t buf_size_funcs[] = {nppiNormDiffInfGetBufferHostSize_8u_C1R, nppiNormDiffL1GetBufferHostSize_8u_C1R, nppiNormDiffL2GetBufferHostSize_8u_C1R}; +#endif NppiSize sz; sz.width = src1.cols; @@ -202,7 +212,16 @@ double cv::gpu::norm(const GpuMat& src1, const GpuMat& src2, int normType) DeviceBuffer dbuf; - nppSafeCall( npp_norm_diff_func[funcIdx](src1.ptr(), static_cast(src1.step), src2.ptr(), static_cast(src2.step), sz, dbuf) ); +#if CUDA_VERSION < 5050 + nppSafeCall( funcs[funcIdx](src1.ptr(), static_cast(src1.step), src2.ptr(), static_cast(src2.step), sz, dbuf) ); +#else + int bufSize; + buf_size_funcs[funcIdx](sz, &bufSize); + + GpuMat buf(1, bufSize, CV_8UC1); + + nppSafeCall( funcs[funcIdx](src1.ptr(), static_cast(src1.step), src2.ptr(), static_cast(src2.step), sz, dbuf, buf.data) ); +#endif cudaSafeCall( cudaDeviceSynchronize() ); From ff28bf831f156eceba443479fc08aafbe32b1ee6 Mon Sep 17 00:00:00 2001 From: Vladislav Vinogradov Date: Mon, 3 Jun 2013 14:01:04 +0400 Subject: [PATCH 063/178] disabled samples with driver api --- samples/gpu/cascadeclassifier_nvidia_api.cpp | 15 ++++++++++++--- samples/gpu/driver_api_multi.cpp | 6 +++++- samples/gpu/driver_api_stereo_multi.cpp | 6 +++++- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/samples/gpu/cascadeclassifier_nvidia_api.cpp b/samples/gpu/cascadeclassifier_nvidia_api.cpp index 99c95ab977..98195b35c2 100644 --- a/samples/gpu/cascadeclassifier_nvidia_api.cpp +++ b/samples/gpu/cascadeclassifier_nvidia_api.cpp @@ -17,12 +17,21 @@ using namespace std; using namespace cv; -#if !defined(HAVE_CUDA) +#if !defined(HAVE_CUDA) || defined(__arm__) + int main( int, const char** ) { - cout << "Please compile the library with CUDA support" << endl; - return -1; +#if !defined(HAVE_CUDA) + std::cout << "CUDA support is required (CMake key 'WITH_CUDA' must be true)." << std::endl; +#endif + +#if defined(__arm__) + std::cout << "Unsupported for ARM CUDA library." << std::endl; +#endif + + return 0; } + #else diff --git a/samples/gpu/driver_api_multi.cpp b/samples/gpu/driver_api_multi.cpp index 2d743f0e9c..c829830e72 100644 --- a/samples/gpu/driver_api_multi.cpp +++ b/samples/gpu/driver_api_multi.cpp @@ -11,7 +11,7 @@ #include "opencv2/core/core.hpp" #include "opencv2/gpu/gpu.hpp" -#if !defined(HAVE_CUDA) || !defined(HAVE_TBB) +#if !defined(HAVE_CUDA) || !defined(HAVE_TBB) || defined(__arm__) int main() { @@ -23,6 +23,10 @@ int main() std::cout << "TBB support is required (CMake key 'WITH_TBB' must be true).\n"; #endif +#if defined(__arm__) + std::cout << "Unsupported for ARM CUDA library." << std::endl; +#endif + return 0; } diff --git a/samples/gpu/driver_api_stereo_multi.cpp b/samples/gpu/driver_api_stereo_multi.cpp index 10c3974771..d4d0af451c 100644 --- a/samples/gpu/driver_api_stereo_multi.cpp +++ b/samples/gpu/driver_api_stereo_multi.cpp @@ -13,7 +13,7 @@ #include "opencv2/highgui/highgui.hpp" #include "opencv2/gpu/gpu.hpp" -#if !defined(HAVE_CUDA) || !defined(HAVE_TBB) +#if !defined(HAVE_CUDA) || !defined(HAVE_TBB) || defined(__arm__) int main() { @@ -25,6 +25,10 @@ int main() std::cout << "TBB support is required (CMake key 'WITH_TBB' must be true).\n"; #endif +#if defined(__arm__) + std::cout << "Unsupported for ARM CUDA library." << std::endl; +#endif + return 0; } From bcf8bdb40156ccd4fa77201ae5d7e5ad127c8b67 Mon Sep 17 00:00:00 2001 From: Vladislav Vinogradov Date: Mon, 3 Jun 2013 14:41:23 +0400 Subject: [PATCH 064/178] fixed constructors for functional objects (added __host__ modifier) --- .../gpu/device/detail/color_detail.hpp | 181 +++++++-------- .../include/opencv2/gpu/device/functional.hpp | 214 +++++++++--------- .../include/opencv2/gpu/device/utility.hpp | 4 +- modules/gpu/src/cuda/calib3d.cu | 8 +- modules/gpu/src/cuda/canny.cu | 12 +- modules/gpu/src/cuda/element_operations.cu | 100 ++++---- 6 files changed, 246 insertions(+), 273 deletions(-) diff --git a/modules/gpu/include/opencv2/gpu/device/detail/color_detail.hpp b/modules/gpu/include/opencv2/gpu/device/detail/color_detail.hpp index d02027f244..5b422849bd 100644 --- a/modules/gpu/include/opencv2/gpu/device/detail/color_detail.hpp +++ b/modules/gpu/include/opencv2/gpu/device/detail/color_detail.hpp @@ -120,11 +120,8 @@ namespace cv { namespace gpu { namespace device return dst; } - __device__ __forceinline__ RGB2RGB() - : unary_function::vec_type, typename TypeVec::vec_type>(){} - - __device__ __forceinline__ RGB2RGB(const RGB2RGB& other_) - :unary_function::vec_type, typename TypeVec::vec_type>(){} + __host__ __device__ __forceinline__ RGB2RGB() {} + __host__ __device__ __forceinline__ RGB2RGB(const RGB2RGB&) {} }; template <> struct RGB2RGB : unary_function @@ -141,8 +138,8 @@ namespace cv { namespace gpu { namespace device return dst; } - __device__ __forceinline__ RGB2RGB():unary_function(){} - __device__ __forceinline__ RGB2RGB(const RGB2RGB& other_):unary_function(){} + __host__ __device__ __forceinline__ RGB2RGB() {} + __host__ __device__ __forceinline__ RGB2RGB(const RGB2RGB&) {} }; } @@ -203,8 +200,8 @@ namespace cv { namespace gpu { namespace device return RGB2RGB5x5Converter::cvt(src); } - __device__ __forceinline__ RGB2RGB5x5():unary_function(){} - __device__ __forceinline__ RGB2RGB5x5(const RGB2RGB5x5& other_):unary_function(){} + __host__ __device__ __forceinline__ RGB2RGB5x5() {} + __host__ __device__ __forceinline__ RGB2RGB5x5(const RGB2RGB5x5&) {} }; template struct RGB2RGB5x5<4, bidx,green_bits> : unary_function @@ -214,8 +211,8 @@ namespace cv { namespace gpu { namespace device return RGB2RGB5x5Converter::cvt(src); } - __device__ __forceinline__ RGB2RGB5x5():unary_function(){} - __device__ __forceinline__ RGB2RGB5x5(const RGB2RGB5x5& other_):unary_function(){} + __host__ __device__ __forceinline__ RGB2RGB5x5() {} + __host__ __device__ __forceinline__ RGB2RGB5x5(const RGB2RGB5x5&) {} }; } @@ -282,8 +279,8 @@ namespace cv { namespace gpu { namespace device RGB5x52RGBConverter::cvt(src, dst); return dst; } - __device__ __forceinline__ RGB5x52RGB():unary_function(){} - __device__ __forceinline__ RGB5x52RGB(const RGB5x52RGB& other_):unary_function(){} + __host__ __device__ __forceinline__ RGB5x52RGB() {} + __host__ __device__ __forceinline__ RGB5x52RGB(const RGB5x52RGB&) {} }; @@ -295,8 +292,8 @@ namespace cv { namespace gpu { namespace device RGB5x52RGBConverter::cvt(src, dst); return dst; } - __device__ __forceinline__ RGB5x52RGB():unary_function(){} - __device__ __forceinline__ RGB5x52RGB(const RGB5x52RGB& other_):unary_function(){} + __host__ __device__ __forceinline__ RGB5x52RGB() {} + __host__ __device__ __forceinline__ RGB5x52RGB(const RGB5x52RGB&) {} }; } @@ -325,9 +322,8 @@ namespace cv { namespace gpu { namespace device return dst; } - __device__ __forceinline__ Gray2RGB():unary_function::vec_type>(){} - __device__ __forceinline__ Gray2RGB(const Gray2RGB& other_) - : unary_function::vec_type>(){} + __host__ __device__ __forceinline__ Gray2RGB() {} + __host__ __device__ __forceinline__ Gray2RGB(const Gray2RGB&) {} }; template <> struct Gray2RGB : unary_function @@ -342,8 +338,8 @@ namespace cv { namespace gpu { namespace device return dst; } - __device__ __forceinline__ Gray2RGB():unary_function(){} - __device__ __forceinline__ Gray2RGB(const Gray2RGB& other_):unary_function(){} + __host__ __device__ __forceinline__ Gray2RGB() {} + __host__ __device__ __forceinline__ Gray2RGB(const Gray2RGB&) {} }; } @@ -384,8 +380,8 @@ namespace cv { namespace gpu { namespace device return Gray2RGB5x5Converter::cvt(src); } - __device__ __forceinline__ Gray2RGB5x5():unary_function(){} - __device__ __forceinline__ Gray2RGB5x5(const Gray2RGB5x5& other_):unary_function(){} + __host__ __device__ __forceinline__ Gray2RGB5x5() {} + __host__ __device__ __forceinline__ Gray2RGB5x5(const Gray2RGB5x5&) {} }; } @@ -426,8 +422,8 @@ namespace cv { namespace gpu { namespace device { return RGB5x52GrayConverter::cvt(src); } - __device__ __forceinline__ RGB5x52Gray() : unary_function(){} - __device__ __forceinline__ RGB5x52Gray(const RGB5x52Gray& other_) : unary_function(){} + __host__ __device__ __forceinline__ RGB5x52Gray() {} + __host__ __device__ __forceinline__ RGB5x52Gray(const RGB5x52Gray&) {} }; } @@ -467,9 +463,8 @@ namespace cv { namespace gpu { namespace device { return RGB2GrayConvert(&src.x); } - __device__ __forceinline__ RGB2Gray() : unary_function::vec_type, T>(){} - __device__ __forceinline__ RGB2Gray(const RGB2Gray& other_) - : unary_function::vec_type, T>(){} + __host__ __device__ __forceinline__ RGB2Gray() {} + __host__ __device__ __forceinline__ RGB2Gray(const RGB2Gray&) {} }; template struct RGB2Gray : unary_function @@ -478,8 +473,8 @@ namespace cv { namespace gpu { namespace device { return RGB2GrayConvert(src); } - __device__ __forceinline__ RGB2Gray() : unary_function(){} - __device__ __forceinline__ RGB2Gray(const RGB2Gray& other_) : unary_function(){} + __host__ __device__ __forceinline__ RGB2Gray() {} + __host__ __device__ __forceinline__ RGB2Gray(const RGB2Gray&) {} }; } @@ -529,10 +524,8 @@ namespace cv { namespace gpu { namespace device RGB2YUVConvert(&src.x, dst); return dst; } - __device__ __forceinline__ RGB2YUV() - : unary_function::vec_type, typename TypeVec::vec_type>(){} - __device__ __forceinline__ RGB2YUV(const RGB2YUV& other_) - : unary_function::vec_type, typename TypeVec::vec_type>(){} + __host__ __device__ __forceinline__ RGB2YUV() {} + __host__ __device__ __forceinline__ RGB2YUV(const RGB2YUV&) {} }; } @@ -609,10 +602,8 @@ namespace cv { namespace gpu { namespace device return dst; } - __device__ __forceinline__ YUV2RGB() - : unary_function::vec_type, typename TypeVec::vec_type>(){} - __device__ __forceinline__ YUV2RGB(const YUV2RGB& other_) - : unary_function::vec_type, typename TypeVec::vec_type>(){} + __host__ __device__ __forceinline__ YUV2RGB() {} + __host__ __device__ __forceinline__ YUV2RGB(const YUV2RGB&) {} }; template struct YUV2RGB : unary_function @@ -621,8 +612,8 @@ namespace cv { namespace gpu { namespace device { return YUV2RGBConvert(src); } - __device__ __forceinline__ YUV2RGB() : unary_function(){} - __device__ __forceinline__ YUV2RGB(const YUV2RGB& other_) : unary_function(){} + __host__ __device__ __forceinline__ YUV2RGB() {} + __host__ __device__ __forceinline__ YUV2RGB(const YUV2RGB&) {} }; } @@ -689,10 +680,8 @@ namespace cv { namespace gpu { namespace device RGB2YCrCbConvert(&src.x, dst); return dst; } - __device__ __forceinline__ RGB2YCrCb() - : unary_function::vec_type, typename TypeVec::vec_type>(){} - __device__ __forceinline__ RGB2YCrCb(const RGB2YCrCb& other_) - : unary_function::vec_type, typename TypeVec::vec_type>(){} + __host__ __device__ __forceinline__ RGB2YCrCb() {} + __host__ __device__ __forceinline__ RGB2YCrCb(const RGB2YCrCb&) {} }; template struct RGB2YCrCb : unary_function @@ -702,8 +691,8 @@ namespace cv { namespace gpu { namespace device return RGB2YCrCbConvert(src); } - __device__ __forceinline__ RGB2YCrCb() : unary_function(){} - __device__ __forceinline__ RGB2YCrCb(const RGB2YCrCb& other_) : unary_function(){} + __host__ __device__ __forceinline__ RGB2YCrCb() {} + __host__ __device__ __forceinline__ RGB2YCrCb(const RGB2YCrCb&) {} }; } @@ -771,10 +760,8 @@ namespace cv { namespace gpu { namespace device return dst; } - __device__ __forceinline__ YCrCb2RGB() - : unary_function::vec_type, typename TypeVec::vec_type>(){} - __device__ __forceinline__ YCrCb2RGB(const YCrCb2RGB& other_) - : unary_function::vec_type, typename TypeVec::vec_type>(){} + __host__ __device__ __forceinline__ YCrCb2RGB() {} + __host__ __device__ __forceinline__ YCrCb2RGB(const YCrCb2RGB&) {} }; template struct YCrCb2RGB : unary_function @@ -783,8 +770,8 @@ namespace cv { namespace gpu { namespace device { return YCrCb2RGBConvert(src); } - __device__ __forceinline__ YCrCb2RGB() : unary_function(){} - __device__ __forceinline__ YCrCb2RGB(const YCrCb2RGB& other_) : unary_function(){} + __host__ __device__ __forceinline__ YCrCb2RGB() {} + __host__ __device__ __forceinline__ YCrCb2RGB(const YCrCb2RGB&) {} }; } @@ -849,10 +836,8 @@ namespace cv { namespace gpu { namespace device return dst; } - __device__ __forceinline__ RGB2XYZ() - : unary_function::vec_type, typename TypeVec::vec_type>(){} - __device__ __forceinline__ RGB2XYZ(const RGB2XYZ& other_) - : unary_function::vec_type, typename TypeVec::vec_type>(){} + __host__ __device__ __forceinline__ RGB2XYZ() {} + __host__ __device__ __forceinline__ RGB2XYZ(const RGB2XYZ&) {} }; template struct RGB2XYZ : unary_function @@ -861,8 +846,8 @@ namespace cv { namespace gpu { namespace device { return RGB2XYZConvert(src); } - __device__ __forceinline__ RGB2XYZ() : unary_function(){} - __device__ __forceinline__ RGB2XYZ(const RGB2XYZ& other_) : unary_function(){} + __host__ __device__ __forceinline__ RGB2XYZ() {} + __host__ __device__ __forceinline__ RGB2XYZ(const RGB2XYZ&) {} }; } @@ -926,10 +911,8 @@ namespace cv { namespace gpu { namespace device return dst; } - __device__ __forceinline__ XYZ2RGB() - : unary_function::vec_type, typename TypeVec::vec_type>(){} - __device__ __forceinline__ XYZ2RGB(const XYZ2RGB& other_) - : unary_function::vec_type, typename TypeVec::vec_type>(){} + __host__ __device__ __forceinline__ XYZ2RGB() {} + __host__ __device__ __forceinline__ XYZ2RGB(const XYZ2RGB&) {} }; template struct XYZ2RGB : unary_function @@ -938,8 +921,8 @@ namespace cv { namespace gpu { namespace device { return XYZ2RGBConvert(src); } - __device__ __forceinline__ XYZ2RGB() : unary_function(){} - __device__ __forceinline__ XYZ2RGB(const XYZ2RGB& other_) : unary_function(){} + __host__ __device__ __forceinline__ XYZ2RGB() {} + __host__ __device__ __forceinline__ XYZ2RGB(const XYZ2RGB&) {} }; } @@ -1066,10 +1049,8 @@ namespace cv { namespace gpu { namespace device return dst; } - __device__ __forceinline__ RGB2HSV() - : unary_function::vec_type, typename TypeVec::vec_type>(){} - __device__ __forceinline__ RGB2HSV(const RGB2HSV& other_) - : unary_function::vec_type, typename TypeVec::vec_type>(){} + __host__ __device__ __forceinline__ RGB2HSV() {} + __host__ __device__ __forceinline__ RGB2HSV(const RGB2HSV&) {} }; template struct RGB2HSV : unary_function @@ -1078,8 +1059,8 @@ namespace cv { namespace gpu { namespace device { return RGB2HSVConvert(src); } - __device__ __forceinline__ RGB2HSV():unary_function(){} - __device__ __forceinline__ RGB2HSV(const RGB2HSV& other_):unary_function(){} + __host__ __device__ __forceinline__ RGB2HSV() {} + __host__ __device__ __forceinline__ RGB2HSV(const RGB2HSV&) {} }; } @@ -1208,10 +1189,8 @@ namespace cv { namespace gpu { namespace device return dst; } - __device__ __forceinline__ HSV2RGB() - : unary_function::vec_type, typename TypeVec::vec_type>(){} - __device__ __forceinline__ HSV2RGB(const HSV2RGB& other_) - : unary_function::vec_type, typename TypeVec::vec_type>(){} + __host__ __device__ __forceinline__ HSV2RGB() {} + __host__ __device__ __forceinline__ HSV2RGB(const HSV2RGB&) {} }; template struct HSV2RGB : unary_function @@ -1220,8 +1199,8 @@ namespace cv { namespace gpu { namespace device { return HSV2RGBConvert(src); } - __device__ __forceinline__ HSV2RGB():unary_function(){} - __device__ __forceinline__ HSV2RGB(const HSV2RGB& other_):unary_function(){} + __host__ __device__ __forceinline__ HSV2RGB() {} + __host__ __device__ __forceinline__ HSV2RGB(const HSV2RGB&) {} }; } @@ -1343,10 +1322,8 @@ namespace cv { namespace gpu { namespace device return dst; } - __device__ __forceinline__ RGB2HLS() - : unary_function::vec_type, typename TypeVec::vec_type>(){} - __device__ __forceinline__ RGB2HLS(const RGB2HLS& other_) - : unary_function::vec_type, typename TypeVec::vec_type>(){} + __host__ __device__ __forceinline__ RGB2HLS() {} + __host__ __device__ __forceinline__ RGB2HLS(const RGB2HLS&) {} }; template struct RGB2HLS : unary_function @@ -1355,8 +1332,8 @@ namespace cv { namespace gpu { namespace device { return RGB2HLSConvert(src); } - __device__ __forceinline__ RGB2HLS() : unary_function(){} - __device__ __forceinline__ RGB2HLS(const RGB2HLS& other_) : unary_function(){} + __host__ __device__ __forceinline__ RGB2HLS() {} + __host__ __device__ __forceinline__ RGB2HLS(const RGB2HLS&) {} }; } @@ -1485,10 +1462,8 @@ namespace cv { namespace gpu { namespace device return dst; } - __device__ __forceinline__ HLS2RGB() - : unary_function::vec_type, typename TypeVec::vec_type>(){} - __device__ __forceinline__ HLS2RGB(const HLS2RGB& other_) - : unary_function::vec_type, typename TypeVec::vec_type>(){} + __host__ __device__ __forceinline__ HLS2RGB() {} + __host__ __device__ __forceinline__ HLS2RGB(const HLS2RGB&) {} }; template struct HLS2RGB : unary_function @@ -1497,8 +1472,8 @@ namespace cv { namespace gpu { namespace device { return HLS2RGBConvert(src); } - __device__ __forceinline__ HLS2RGB() : unary_function(){} - __device__ __forceinline__ HLS2RGB(const HLS2RGB& other_) : unary_function(){} + __host__ __device__ __forceinline__ HLS2RGB() {} + __host__ __device__ __forceinline__ HLS2RGB(const HLS2RGB&) {} }; } @@ -1651,8 +1626,8 @@ namespace cv { namespace gpu { namespace device return dst; } - __device__ __forceinline__ RGB2Lab() {} - __device__ __forceinline__ RGB2Lab(const RGB2Lab& other_) {} + __host__ __device__ __forceinline__ RGB2Lab() {} + __host__ __device__ __forceinline__ RGB2Lab(const RGB2Lab&) {} }; template struct RGB2Lab @@ -1666,8 +1641,8 @@ namespace cv { namespace gpu { namespace device return dst; } - __device__ __forceinline__ RGB2Lab() {} - __device__ __forceinline__ RGB2Lab(const RGB2Lab& other_) {} + __host__ __device__ __forceinline__ RGB2Lab() {} + __host__ __device__ __forceinline__ RGB2Lab(const RGB2Lab&) {} }; } @@ -1764,8 +1739,8 @@ namespace cv { namespace gpu { namespace device return dst; } - __device__ __forceinline__ Lab2RGB() {} - __device__ __forceinline__ Lab2RGB(const Lab2RGB& other_) {} + __host__ __device__ __forceinline__ Lab2RGB() {} + __host__ __device__ __forceinline__ Lab2RGB(const Lab2RGB&) {} }; template struct Lab2RGB @@ -1779,8 +1754,8 @@ namespace cv { namespace gpu { namespace device return dst; } - __device__ __forceinline__ Lab2RGB() {} - __device__ __forceinline__ Lab2RGB(const Lab2RGB& other_) {} + __host__ __device__ __forceinline__ Lab2RGB() {} + __host__ __device__ __forceinline__ Lab2RGB(const Lab2RGB&) {} }; } @@ -1863,8 +1838,8 @@ namespace cv { namespace gpu { namespace device return dst; } - __device__ __forceinline__ RGB2Luv() {} - __device__ __forceinline__ RGB2Luv(const RGB2Luv& other_) {} + __host__ __device__ __forceinline__ RGB2Luv() {} + __host__ __device__ __forceinline__ RGB2Luv(const RGB2Luv&) {} }; template struct RGB2Luv @@ -1878,8 +1853,8 @@ namespace cv { namespace gpu { namespace device return dst; } - __device__ __forceinline__ RGB2Luv() {} - __device__ __forceinline__ RGB2Luv(const RGB2Luv& other_) {} + __host__ __device__ __forceinline__ RGB2Luv() {} + __host__ __device__ __forceinline__ RGB2Luv(const RGB2Luv&) {} }; } @@ -1964,8 +1939,8 @@ namespace cv { namespace gpu { namespace device return dst; } - __device__ __forceinline__ Luv2RGB() {} - __device__ __forceinline__ Luv2RGB(const Luv2RGB& other_) {} + __host__ __device__ __forceinline__ Luv2RGB() {} + __host__ __device__ __forceinline__ Luv2RGB(const Luv2RGB&) {} }; template struct Luv2RGB @@ -1979,8 +1954,8 @@ namespace cv { namespace gpu { namespace device return dst; } - __device__ __forceinline__ Luv2RGB() {} - __device__ __forceinline__ Luv2RGB(const Luv2RGB& other_) {} + __host__ __device__ __forceinline__ Luv2RGB() {} + __host__ __device__ __forceinline__ Luv2RGB(const Luv2RGB&) {} }; } diff --git a/modules/gpu/include/opencv2/gpu/device/functional.hpp b/modules/gpu/include/opencv2/gpu/device/functional.hpp index 6064e8e99c..db264735e3 100644 --- a/modules/gpu/include/opencv2/gpu/device/functional.hpp +++ b/modules/gpu/include/opencv2/gpu/device/functional.hpp @@ -63,8 +63,8 @@ namespace cv { namespace gpu { namespace device { return a + b; } - __device__ __forceinline__ plus(const plus& other):binary_function(){} - __device__ __forceinline__ plus():binary_function(){} + __host__ __device__ __forceinline__ plus() {} + __host__ __device__ __forceinline__ plus(const plus&) {} }; template struct minus : binary_function @@ -74,8 +74,8 @@ namespace cv { namespace gpu { namespace device { return a - b; } - __device__ __forceinline__ minus(const minus& other):binary_function(){} - __device__ __forceinline__ minus():binary_function(){} + __host__ __device__ __forceinline__ minus() {} + __host__ __device__ __forceinline__ minus(const minus&) {} }; template struct multiplies : binary_function @@ -85,8 +85,8 @@ namespace cv { namespace gpu { namespace device { return a * b; } - __device__ __forceinline__ multiplies(const multiplies& other):binary_function(){} - __device__ __forceinline__ multiplies():binary_function(){} + __host__ __device__ __forceinline__ multiplies() {} + __host__ __device__ __forceinline__ multiplies(const multiplies&) {} }; template struct divides : binary_function @@ -96,8 +96,8 @@ namespace cv { namespace gpu { namespace device { return a / b; } - __device__ __forceinline__ divides(const divides& other):binary_function(){} - __device__ __forceinline__ divides():binary_function(){} + __host__ __device__ __forceinline__ divides() {} + __host__ __device__ __forceinline__ divides(const divides&) {} }; template struct modulus : binary_function @@ -107,8 +107,8 @@ namespace cv { namespace gpu { namespace device { return a % b; } - __device__ __forceinline__ modulus(const modulus& other):binary_function(){} - __device__ __forceinline__ modulus():binary_function(){} + __host__ __device__ __forceinline__ modulus() {} + __host__ __device__ __forceinline__ modulus(const modulus&) {} }; template struct negate : unary_function @@ -117,8 +117,8 @@ namespace cv { namespace gpu { namespace device { return -a; } - __device__ __forceinline__ negate(const negate& other):unary_function(){} - __device__ __forceinline__ negate():unary_function(){} + __host__ __device__ __forceinline__ negate() {} + __host__ __device__ __forceinline__ negate(const negate&) {} }; // Comparison Operations @@ -129,8 +129,8 @@ namespace cv { namespace gpu { namespace device { return a == b; } - __device__ __forceinline__ equal_to(const equal_to& other):binary_function(){} - __device__ __forceinline__ equal_to():binary_function(){} + __host__ __device__ __forceinline__ equal_to() {} + __host__ __device__ __forceinline__ equal_to(const equal_to&) {} }; template struct not_equal_to : binary_function @@ -140,8 +140,8 @@ namespace cv { namespace gpu { namespace device { return a != b; } - __device__ __forceinline__ not_equal_to(const not_equal_to& other):binary_function(){} - __device__ __forceinline__ not_equal_to():binary_function(){} + __host__ __device__ __forceinline__ not_equal_to() {} + __host__ __device__ __forceinline__ not_equal_to(const not_equal_to&) {} }; template struct greater : binary_function @@ -151,8 +151,8 @@ namespace cv { namespace gpu { namespace device { return a > b; } - __device__ __forceinline__ greater(const greater& other):binary_function(){} - __device__ __forceinline__ greater():binary_function(){} + __host__ __device__ __forceinline__ greater() {} + __host__ __device__ __forceinline__ greater(const greater&) {} }; template struct less : binary_function @@ -162,8 +162,8 @@ namespace cv { namespace gpu { namespace device { return a < b; } - __device__ __forceinline__ less(const less& other):binary_function(){} - __device__ __forceinline__ less():binary_function(){} + __host__ __device__ __forceinline__ less() {} + __host__ __device__ __forceinline__ less(const less&) {} }; template struct greater_equal : binary_function @@ -173,8 +173,8 @@ namespace cv { namespace gpu { namespace device { return a >= b; } - __device__ __forceinline__ greater_equal(const greater_equal& other):binary_function(){} - __device__ __forceinline__ greater_equal():binary_function(){} + __host__ __device__ __forceinline__ greater_equal() {} + __host__ __device__ __forceinline__ greater_equal(const greater_equal&) {} }; template struct less_equal : binary_function @@ -184,8 +184,8 @@ namespace cv { namespace gpu { namespace device { return a <= b; } - __device__ __forceinline__ less_equal(const less_equal& other):binary_function(){} - __device__ __forceinline__ less_equal():binary_function(){} + __host__ __device__ __forceinline__ less_equal() {} + __host__ __device__ __forceinline__ less_equal(const less_equal&) {} }; // Logical Operations @@ -196,8 +196,8 @@ namespace cv { namespace gpu { namespace device { return a && b; } - __device__ __forceinline__ logical_and(const logical_and& other):binary_function(){} - __device__ __forceinline__ logical_and():binary_function(){} + __host__ __device__ __forceinline__ logical_and() {} + __host__ __device__ __forceinline__ logical_and(const logical_and&) {} }; template struct logical_or : binary_function @@ -207,8 +207,8 @@ namespace cv { namespace gpu { namespace device { return a || b; } - __device__ __forceinline__ logical_or(const logical_or& other):binary_function(){} - __device__ __forceinline__ logical_or():binary_function(){} + __host__ __device__ __forceinline__ logical_or() {} + __host__ __device__ __forceinline__ logical_or(const logical_or&) {} }; template struct logical_not : unary_function @@ -217,8 +217,8 @@ namespace cv { namespace gpu { namespace device { return !a; } - __device__ __forceinline__ logical_not(const logical_not& other):unary_function(){} - __device__ __forceinline__ logical_not():unary_function(){} + __host__ __device__ __forceinline__ logical_not() {} + __host__ __device__ __forceinline__ logical_not(const logical_not&) {} }; // Bitwise Operations @@ -229,8 +229,8 @@ namespace cv { namespace gpu { namespace device { return a & b; } - __device__ __forceinline__ bit_and(const bit_and& other):binary_function(){} - __device__ __forceinline__ bit_and():binary_function(){} + __host__ __device__ __forceinline__ bit_and() {} + __host__ __device__ __forceinline__ bit_and(const bit_and&) {} }; template struct bit_or : binary_function @@ -240,8 +240,8 @@ namespace cv { namespace gpu { namespace device { return a | b; } - __device__ __forceinline__ bit_or(const bit_or& other):binary_function(){} - __device__ __forceinline__ bit_or():binary_function(){} + __host__ __device__ __forceinline__ bit_or() {} + __host__ __device__ __forceinline__ bit_or(const bit_or&) {} }; template struct bit_xor : binary_function @@ -251,8 +251,8 @@ namespace cv { namespace gpu { namespace device { return a ^ b; } - __device__ __forceinline__ bit_xor(const bit_xor& other):binary_function(){} - __device__ __forceinline__ bit_xor():binary_function(){} + __host__ __device__ __forceinline__ bit_xor() {} + __host__ __device__ __forceinline__ bit_xor(const bit_xor&) {} }; template struct bit_not : unary_function @@ -261,8 +261,8 @@ namespace cv { namespace gpu { namespace device { return ~v; } - __device__ __forceinline__ bit_not(const bit_not& other):unary_function(){} - __device__ __forceinline__ bit_not():unary_function(){} + __host__ __device__ __forceinline__ bit_not() {} + __host__ __device__ __forceinline__ bit_not(const bit_not&) {} }; // Generalized Identity Operations @@ -272,8 +272,8 @@ namespace cv { namespace gpu { namespace device { return x; } - __device__ __forceinline__ identity(const identity& other):unary_function(){} - __device__ __forceinline__ identity():unary_function(){} + __host__ __device__ __forceinline__ identity() {} + __host__ __device__ __forceinline__ identity(const identity&) {} }; template struct project1st : binary_function @@ -282,8 +282,8 @@ namespace cv { namespace gpu { namespace device { return lhs; } - __device__ __forceinline__ project1st(const project1st& other):binary_function(){} - __device__ __forceinline__ project1st():binary_function(){} + __host__ __device__ __forceinline__ project1st() {} + __host__ __device__ __forceinline__ project1st(const project1st&) {} }; template struct project2nd : binary_function @@ -292,8 +292,8 @@ namespace cv { namespace gpu { namespace device { return rhs; } - __device__ __forceinline__ project2nd(const project2nd& other):binary_function(){} - __device__ __forceinline__ project2nd():binary_function(){} + __host__ __device__ __forceinline__ project2nd() {} + __host__ __device__ __forceinline__ project2nd(const project2nd&) {} }; // Min/Max Operations @@ -302,8 +302,8 @@ namespace cv { namespace gpu { namespace device template <> struct name : binary_function \ { \ __device__ __forceinline__ type operator()(type lhs, type rhs) const {return op(lhs, rhs);} \ - __device__ __forceinline__ name() {}\ - __device__ __forceinline__ name(const name&) {}\ + __host__ __device__ __forceinline__ name() {}\ + __host__ __device__ __forceinline__ name(const name&) {}\ }; template struct maximum : binary_function @@ -312,8 +312,8 @@ namespace cv { namespace gpu { namespace device { return max(lhs, rhs); } - __device__ __forceinline__ maximum() {} - __device__ __forceinline__ maximum(const maximum&) {} + __host__ __device__ __forceinline__ maximum() {} + __host__ __device__ __forceinline__ maximum(const maximum&) {} }; OPENCV_GPU_IMPLEMENT_MINMAX(maximum, uchar, ::max) @@ -332,8 +332,8 @@ namespace cv { namespace gpu { namespace device { return min(lhs, rhs); } - __device__ __forceinline__ minimum() {} - __device__ __forceinline__ minimum(const minimum&) {} + __host__ __device__ __forceinline__ minimum() {} + __host__ __device__ __forceinline__ minimum(const minimum&) {} }; OPENCV_GPU_IMPLEMENT_MINMAX(minimum, uchar, ::min) @@ -349,7 +349,6 @@ namespace cv { namespace gpu { namespace device #undef OPENCV_GPU_IMPLEMENT_MINMAX // Math functions -///bound========================================= template struct abs_func : unary_function { @@ -358,8 +357,8 @@ namespace cv { namespace gpu { namespace device return abs(x); } - __device__ __forceinline__ abs_func() {} - __device__ __forceinline__ abs_func(const abs_func&) {} + __host__ __device__ __forceinline__ abs_func() {} + __host__ __device__ __forceinline__ abs_func(const abs_func&) {} }; template <> struct abs_func : unary_function { @@ -368,8 +367,8 @@ namespace cv { namespace gpu { namespace device return x; } - __device__ __forceinline__ abs_func() {} - __device__ __forceinline__ abs_func(const abs_func&) {} + __host__ __device__ __forceinline__ abs_func() {} + __host__ __device__ __forceinline__ abs_func(const abs_func&) {} }; template <> struct abs_func : unary_function { @@ -378,8 +377,8 @@ namespace cv { namespace gpu { namespace device return ::abs((int)x); } - __device__ __forceinline__ abs_func() {} - __device__ __forceinline__ abs_func(const abs_func&) {} + __host__ __device__ __forceinline__ abs_func() {} + __host__ __device__ __forceinline__ abs_func(const abs_func&) {} }; template <> struct abs_func : unary_function { @@ -388,8 +387,8 @@ namespace cv { namespace gpu { namespace device return ::abs((int)x); } - __device__ __forceinline__ abs_func() {} - __device__ __forceinline__ abs_func(const abs_func&) {} + __host__ __device__ __forceinline__ abs_func() {} + __host__ __device__ __forceinline__ abs_func(const abs_func&) {} }; template <> struct abs_func : unary_function { @@ -398,8 +397,8 @@ namespace cv { namespace gpu { namespace device return x; } - __device__ __forceinline__ abs_func() {} - __device__ __forceinline__ abs_func(const abs_func&) {} + __host__ __device__ __forceinline__ abs_func() {} + __host__ __device__ __forceinline__ abs_func(const abs_func&) {} }; template <> struct abs_func : unary_function { @@ -408,8 +407,8 @@ namespace cv { namespace gpu { namespace device return ::abs((int)x); } - __device__ __forceinline__ abs_func() {} - __device__ __forceinline__ abs_func(const abs_func&) {} + __host__ __device__ __forceinline__ abs_func() {} + __host__ __device__ __forceinline__ abs_func(const abs_func&) {} }; template <> struct abs_func : unary_function { @@ -418,8 +417,8 @@ namespace cv { namespace gpu { namespace device return x; } - __device__ __forceinline__ abs_func() {} - __device__ __forceinline__ abs_func(const abs_func&) {} + __host__ __device__ __forceinline__ abs_func() {} + __host__ __device__ __forceinline__ abs_func(const abs_func&) {} }; template <> struct abs_func : unary_function { @@ -428,8 +427,8 @@ namespace cv { namespace gpu { namespace device return ::abs(x); } - __device__ __forceinline__ abs_func() {} - __device__ __forceinline__ abs_func(const abs_func&) {} + __host__ __device__ __forceinline__ abs_func() {} + __host__ __device__ __forceinline__ abs_func(const abs_func&) {} }; template <> struct abs_func : unary_function { @@ -438,8 +437,8 @@ namespace cv { namespace gpu { namespace device return ::fabsf(x); } - __device__ __forceinline__ abs_func() {} - __device__ __forceinline__ abs_func(const abs_func&) {} + __host__ __device__ __forceinline__ abs_func() {} + __host__ __device__ __forceinline__ abs_func(const abs_func&) {} }; template <> struct abs_func : unary_function { @@ -448,8 +447,8 @@ namespace cv { namespace gpu { namespace device return ::fabs(x); } - __device__ __forceinline__ abs_func() {} - __device__ __forceinline__ abs_func(const abs_func&) {} + __host__ __device__ __forceinline__ abs_func() {} + __host__ __device__ __forceinline__ abs_func(const abs_func&) {} }; #define OPENCV_GPU_IMPLEMENT_UN_FUNCTOR(name, func) \ @@ -459,8 +458,8 @@ namespace cv { namespace gpu { namespace device { \ return func ## f(v); \ } \ - __device__ __forceinline__ name ## _func() {} \ - __device__ __forceinline__ name ## _func(const name ## _func&) {} \ + __host__ __device__ __forceinline__ name ## _func() {} \ + __host__ __device__ __forceinline__ name ## _func(const name ## _func&) {} \ }; \ template <> struct name ## _func : unary_function \ { \ @@ -468,8 +467,8 @@ namespace cv { namespace gpu { namespace device { \ return func(v); \ } \ - __device__ __forceinline__ name ## _func() {} \ - __device__ __forceinline__ name ## _func(const name ## _func&) {} \ + __host__ __device__ __forceinline__ name ## _func() {} \ + __host__ __device__ __forceinline__ name ## _func(const name ## _func&) {} \ }; #define OPENCV_GPU_IMPLEMENT_BIN_FUNCTOR(name, func) \ @@ -479,6 +478,8 @@ namespace cv { namespace gpu { namespace device { \ return func ## f(v1, v2); \ } \ + __host__ __device__ __forceinline__ name ## _func() {} \ + __host__ __device__ __forceinline__ name ## _func(const name ## _func&) {} \ }; \ template <> struct name ## _func : binary_function \ { \ @@ -486,6 +487,8 @@ namespace cv { namespace gpu { namespace device { \ return func(v1, v2); \ } \ + __host__ __device__ __forceinline__ name ## _func() {} \ + __host__ __device__ __forceinline__ name ## _func(const name ## _func&) {} \ }; OPENCV_GPU_IMPLEMENT_UN_FUNCTOR(sqrt, ::sqrt) @@ -522,8 +525,8 @@ namespace cv { namespace gpu { namespace device { return src1 * src1 + src2 * src2; } - __device__ __forceinline__ hypot_sqr_func(const hypot_sqr_func& other) : binary_function(){} - __device__ __forceinline__ hypot_sqr_func() : binary_function(){} + __host__ __device__ __forceinline__ hypot_sqr_func() {} + __host__ __device__ __forceinline__ hypot_sqr_func(const hypot_sqr_func&) {} }; // Saturate Cast Functor @@ -533,8 +536,8 @@ namespace cv { namespace gpu { namespace device { return saturate_cast(v); } - __device__ __forceinline__ saturate_cast_func(const saturate_cast_func& other):unary_function(){} - __device__ __forceinline__ saturate_cast_func():unary_function(){} + __host__ __device__ __forceinline__ saturate_cast_func() {} + __host__ __device__ __forceinline__ saturate_cast_func(const saturate_cast_func&) {} }; // Threshold Functors @@ -547,10 +550,9 @@ namespace cv { namespace gpu { namespace device return (src > thresh) * maxVal; } - __device__ __forceinline__ thresh_binary_func(const thresh_binary_func& other) - : unary_function(), thresh(other.thresh), maxVal(other.maxVal){} - - __device__ __forceinline__ thresh_binary_func():unary_function(){} + __host__ __device__ __forceinline__ thresh_binary_func() {} + __host__ __device__ __forceinline__ thresh_binary_func(const thresh_binary_func& other) + : thresh(other.thresh), maxVal(other.maxVal) {} const T thresh; const T maxVal; @@ -565,10 +567,9 @@ namespace cv { namespace gpu { namespace device return (src <= thresh) * maxVal; } - __device__ __forceinline__ thresh_binary_inv_func(const thresh_binary_inv_func& other) - : unary_function(), thresh(other.thresh), maxVal(other.maxVal){} - - __device__ __forceinline__ thresh_binary_inv_func():unary_function(){} + __host__ __device__ __forceinline__ thresh_binary_inv_func() {} + __host__ __device__ __forceinline__ thresh_binary_inv_func(const thresh_binary_inv_func& other) + : thresh(other.thresh), maxVal(other.maxVal) {} const T thresh; const T maxVal; @@ -583,10 +584,9 @@ namespace cv { namespace gpu { namespace device return minimum()(src, thresh); } - __device__ __forceinline__ thresh_trunc_func(const thresh_trunc_func& other) - : unary_function(), thresh(other.thresh){} - - __device__ __forceinline__ thresh_trunc_func():unary_function(){} + __host__ __device__ __forceinline__ thresh_trunc_func() {} + __host__ __device__ __forceinline__ thresh_trunc_func(const thresh_trunc_func& other) + : thresh(other.thresh) {} const T thresh; }; @@ -599,10 +599,10 @@ namespace cv { namespace gpu { namespace device { return (src > thresh) * src; } - __device__ __forceinline__ thresh_to_zero_func(const thresh_to_zero_func& other) - : unary_function(), thresh(other.thresh){} - __device__ __forceinline__ thresh_to_zero_func():unary_function(){} + __host__ __device__ __forceinline__ thresh_to_zero_func() {} + __host__ __device__ __forceinline__ thresh_to_zero_func(const thresh_to_zero_func& other) + : thresh(other.thresh) {} const T thresh; }; @@ -615,14 +615,14 @@ namespace cv { namespace gpu { namespace device { return (src <= thresh) * src; } - __device__ __forceinline__ thresh_to_zero_inv_func(const thresh_to_zero_inv_func& other) - : unary_function(), thresh(other.thresh){} - __device__ __forceinline__ thresh_to_zero_inv_func():unary_function(){} + __host__ __device__ __forceinline__ thresh_to_zero_inv_func() {} + __host__ __device__ __forceinline__ thresh_to_zero_inv_func(const thresh_to_zero_inv_func& other) + : thresh(other.thresh) {} const T thresh; }; -//bound!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ============> + // Function Object Adaptors template struct unary_negate : unary_function { @@ -633,8 +633,8 @@ namespace cv { namespace gpu { namespace device return !pred(x); } - __device__ __forceinline__ unary_negate(const unary_negate& other) : unary_function(){} - __device__ __forceinline__ unary_negate() : unary_function(){} + __host__ __device__ __forceinline__ unary_negate() {} + __host__ __device__ __forceinline__ unary_negate(const unary_negate& other) : pred(other.pred) {} const Predicate pred; }; @@ -653,11 +653,9 @@ namespace cv { namespace gpu { namespace device { return !pred(x,y); } - __device__ __forceinline__ binary_negate(const binary_negate& other) - : binary_function(){} - __device__ __forceinline__ binary_negate() : - binary_function(){} + __host__ __device__ __forceinline__ binary_negate() {} + __host__ __device__ __forceinline__ binary_negate(const binary_negate& other) : pred(other.pred) {} const Predicate pred; }; @@ -676,8 +674,8 @@ namespace cv { namespace gpu { namespace device return op(arg1, a); } - __device__ __forceinline__ binder1st(const binder1st& other) : - unary_function(){} + __host__ __device__ __forceinline__ binder1st() {} + __host__ __device__ __forceinline__ binder1st(const binder1st& other) : op(other.op), arg1(other.arg1) {} const Op op; const typename Op::first_argument_type arg1; @@ -697,8 +695,8 @@ namespace cv { namespace gpu { namespace device return op(a, arg2); } - __device__ __forceinline__ binder2nd(const binder2nd& other) : - unary_function(), op(other.op), arg2(other.arg2){} + __host__ __device__ __forceinline__ binder2nd() {} + __host__ __device__ __forceinline__ binder2nd(const binder2nd& other) : op(other.op), arg2(other.arg2) {} const Op op; const typename Op::second_argument_type arg2; diff --git a/modules/gpu/include/opencv2/gpu/device/utility.hpp b/modules/gpu/include/opencv2/gpu/device/utility.hpp index 83eaaa21ce..85e81acf08 100644 --- a/modules/gpu/include/opencv2/gpu/device/utility.hpp +++ b/modules/gpu/include/opencv2/gpu/device/utility.hpp @@ -124,8 +124,8 @@ namespace cv { namespace gpu { namespace device struct WithOutMask { - __device__ __forceinline__ WithOutMask(){} - __device__ __forceinline__ WithOutMask(const WithOutMask& mask){} + __host__ __device__ __forceinline__ WithOutMask(){} + __host__ __device__ __forceinline__ WithOutMask(const WithOutMask&){} __device__ __forceinline__ void next() const { diff --git a/modules/gpu/src/cuda/calib3d.cu b/modules/gpu/src/cuda/calib3d.cu index 0fd482c41a..f29471f025 100644 --- a/modules/gpu/src/cuda/calib3d.cu +++ b/modules/gpu/src/cuda/calib3d.cu @@ -67,8 +67,8 @@ namespace cv { namespace gpu { namespace device crot1.x * p.x + crot1.y * p.y + crot1.z * p.z + ctransl.y, crot2.x * p.x + crot2.y * p.y + crot2.z * p.z + ctransl.z); } - __device__ __forceinline__ TransformOp() {} - __device__ __forceinline__ TransformOp(const TransformOp&) {} + __host__ __device__ __forceinline__ TransformOp() {} + __host__ __device__ __forceinline__ TransformOp(const TransformOp&) {} }; void call(const PtrStepSz src, const float* rot, @@ -106,8 +106,8 @@ namespace cv { namespace gpu { namespace device (cproj0.x * t.x + cproj0.y * t.y) / t.z + cproj0.z, (cproj1.x * t.x + cproj1.y * t.y) / t.z + cproj1.z); } - __device__ __forceinline__ ProjectOp() {} - __device__ __forceinline__ ProjectOp(const ProjectOp&) {} + __host__ __device__ __forceinline__ ProjectOp() {} + __host__ __device__ __forceinline__ ProjectOp(const ProjectOp&) {} }; void call(const PtrStepSz src, const float* rot, diff --git a/modules/gpu/src/cuda/canny.cu b/modules/gpu/src/cuda/canny.cu index 1afcddc9c9..aab922f22c 100644 --- a/modules/gpu/src/cuda/canny.cu +++ b/modules/gpu/src/cuda/canny.cu @@ -62,8 +62,8 @@ namespace canny return ::abs(x) + ::abs(y); } - __device__ __forceinline__ L1() {} - __device__ __forceinline__ L1(const L1&) {} + __host__ __device__ __forceinline__ L1() {} + __host__ __device__ __forceinline__ L1(const L1&) {} }; struct L2 : binary_function { @@ -72,8 +72,8 @@ namespace canny return ::sqrtf(x * x + y * y); } - __device__ __forceinline__ L2() {} - __device__ __forceinline__ L2(const L2&) {} + __host__ __device__ __forceinline__ L2() {} + __host__ __device__ __forceinline__ L2(const L2&) {} }; } @@ -470,8 +470,8 @@ namespace canny return (uchar)(-(e >> 1)); } - __device__ __forceinline__ GetEdges() {} - __device__ __forceinline__ GetEdges(const GetEdges&) {} + __host__ __device__ __forceinline__ GetEdges() {} + __host__ __device__ __forceinline__ GetEdges(const GetEdges&) {} }; } diff --git a/modules/gpu/src/cuda/element_operations.cu b/modules/gpu/src/cuda/element_operations.cu index e9397e534f..876d4ad3c4 100644 --- a/modules/gpu/src/cuda/element_operations.cu +++ b/modules/gpu/src/cuda/element_operations.cu @@ -162,8 +162,8 @@ namespace arithm return vadd4(a, b); } - __device__ __forceinline__ VAdd4() {} - __device__ __forceinline__ VAdd4(const VAdd4& other) {} + __host__ __device__ __forceinline__ VAdd4() {} + __host__ __device__ __forceinline__ VAdd4(const VAdd4&) {} }; //////////////////////////////////// @@ -175,8 +175,8 @@ namespace arithm return vadd2(a, b); } - __device__ __forceinline__ VAdd2() {} - __device__ __forceinline__ VAdd2(const VAdd2& other) {} + __host__ __device__ __forceinline__ VAdd2() {} + __host__ __device__ __forceinline__ VAdd2(const VAdd2&) {} }; //////////////////////////////////// @@ -188,8 +188,8 @@ namespace arithm return saturate_cast(a + b); } - __device__ __forceinline__ AddMat() {} - __device__ __forceinline__ AddMat(const AddMat& other) {} + __host__ __device__ __forceinline__ AddMat() {} + __host__ __device__ __forceinline__ AddMat(const AddMat&) {} }; } @@ -397,8 +397,8 @@ namespace arithm return vsub4(a, b); } - __device__ __forceinline__ VSub4() {} - __device__ __forceinline__ VSub4(const VSub4& other) {} + __host__ __device__ __forceinline__ VSub4() {} + __host__ __device__ __forceinline__ VSub4(const VSub4&) {} }; //////////////////////////////////// @@ -410,8 +410,8 @@ namespace arithm return vsub2(a, b); } - __device__ __forceinline__ VSub2() {} - __device__ __forceinline__ VSub2(const VSub2& other) {} + __host__ __device__ __forceinline__ VSub2() {} + __host__ __device__ __forceinline__ VSub2(const VSub2&) {} }; //////////////////////////////////// @@ -423,8 +423,8 @@ namespace arithm return saturate_cast(a - b); } - __device__ __forceinline__ SubMat() {} - __device__ __forceinline__ SubMat(const SubMat& other) {} + __host__ __device__ __forceinline__ SubMat() {} + __host__ __device__ __forceinline__ SubMat(const SubMat&) {} }; } @@ -617,8 +617,8 @@ namespace arithm return res; } - __device__ __forceinline__ Mul_8uc4_32f() {} - __device__ __forceinline__ Mul_8uc4_32f(const Mul_8uc4_32f& other) {} + __host__ __device__ __forceinline__ Mul_8uc4_32f() {} + __host__ __device__ __forceinline__ Mul_8uc4_32f(const Mul_8uc4_32f&) {} }; struct Mul_16sc4_32f : binary_function @@ -629,8 +629,8 @@ namespace arithm saturate_cast(a.z * b), saturate_cast(a.w * b)); } - __device__ __forceinline__ Mul_16sc4_32f() {} - __device__ __forceinline__ Mul_16sc4_32f(const Mul_16sc4_32f& other) {} + __host__ __device__ __forceinline__ Mul_16sc4_32f() {} + __host__ __device__ __forceinline__ Mul_16sc4_32f(const Mul_16sc4_32f&) {} }; template struct Mul : binary_function @@ -640,8 +640,8 @@ namespace arithm return saturate_cast(a * b); } - __device__ __forceinline__ Mul() {} - __device__ __forceinline__ Mul(const Mul& other) {} + __host__ __device__ __forceinline__ Mul() {} + __host__ __device__ __forceinline__ Mul(const Mul&) {} }; template struct MulScale : binary_function @@ -888,8 +888,8 @@ namespace arithm return b != 0 ? saturate_cast(a / b) : 0; } - __device__ __forceinline__ Div() {} - __device__ __forceinline__ Div(const Div& other) {} + __host__ __device__ __forceinline__ Div() {} + __host__ __device__ __forceinline__ Div(const Div&) {} }; template struct Div : binary_function { @@ -898,8 +898,8 @@ namespace arithm return b != 0 ? static_cast(a) / b : 0; } - __device__ __forceinline__ Div() {} - __device__ __forceinline__ Div(const Div& other) {} + __host__ __device__ __forceinline__ Div() {} + __host__ __device__ __forceinline__ Div(const Div&) {} }; template struct Div : binary_function { @@ -908,8 +908,8 @@ namespace arithm return b != 0 ? static_cast(a) / b : 0; } - __device__ __forceinline__ Div() {} - __device__ __forceinline__ Div(const Div& other) {} + __host__ __device__ __forceinline__ Div() {} + __host__ __device__ __forceinline__ Div(const Div&) {} }; template struct DivScale : binary_function @@ -1196,8 +1196,8 @@ namespace arithm return vabsdiff4(a, b); } - __device__ __forceinline__ VAbsDiff4() {} - __device__ __forceinline__ VAbsDiff4(const VAbsDiff4& other) {} + __host__ __device__ __forceinline__ VAbsDiff4() {} + __host__ __device__ __forceinline__ VAbsDiff4(const VAbsDiff4&) {} }; //////////////////////////////////// @@ -1209,8 +1209,8 @@ namespace arithm return vabsdiff2(a, b); } - __device__ __forceinline__ VAbsDiff2() {} - __device__ __forceinline__ VAbsDiff2(const VAbsDiff2& other) {} + __host__ __device__ __forceinline__ VAbsDiff2() {} + __host__ __device__ __forceinline__ VAbsDiff2(const VAbsDiff2&) {} }; //////////////////////////////////// @@ -1235,8 +1235,8 @@ namespace arithm return saturate_cast(_abs(a - b)); } - __device__ __forceinline__ AbsDiffMat() {} - __device__ __forceinline__ AbsDiffMat(const AbsDiffMat& other) {} + __host__ __device__ __forceinline__ AbsDiffMat() {} + __host__ __device__ __forceinline__ AbsDiffMat(const AbsDiffMat&) {} }; } @@ -1370,8 +1370,8 @@ namespace arithm return saturate_cast(x * x); } - __device__ __forceinline__ Sqr() {} - __device__ __forceinline__ Sqr(const Sqr& other) {} + __host__ __device__ __forceinline__ Sqr() {} + __host__ __device__ __forceinline__ Sqr(const Sqr&) {} }; } @@ -1466,8 +1466,8 @@ namespace arithm return saturate_cast(f(x)); } - __device__ __forceinline__ Exp() {} - __device__ __forceinline__ Exp(const Exp& other) {} + __host__ __device__ __forceinline__ Exp() {} + __host__ __device__ __forceinline__ Exp(const Exp&) {} }; } @@ -1507,8 +1507,8 @@ namespace arithm return vcmpeq4(a, b); } - __device__ __forceinline__ VCmpEq4() {} - __device__ __forceinline__ VCmpEq4(const VCmpEq4& other) {} + __host__ __device__ __forceinline__ VCmpEq4() {} + __host__ __device__ __forceinline__ VCmpEq4(const VCmpEq4&) {} }; struct VCmpNe4 : binary_function { @@ -1517,8 +1517,8 @@ namespace arithm return vcmpne4(a, b); } - __device__ __forceinline__ VCmpNe4() {} - __device__ __forceinline__ VCmpNe4(const VCmpNe4& other) {} + __host__ __device__ __forceinline__ VCmpNe4() {} + __host__ __device__ __forceinline__ VCmpNe4(const VCmpNe4&) {} }; struct VCmpLt4 : binary_function { @@ -1527,8 +1527,8 @@ namespace arithm return vcmplt4(a, b); } - __device__ __forceinline__ VCmpLt4() {} - __device__ __forceinline__ VCmpLt4(const VCmpLt4& other) {} + __host__ __device__ __forceinline__ VCmpLt4() {} + __host__ __device__ __forceinline__ VCmpLt4(const VCmpLt4&) {} }; struct VCmpLe4 : binary_function { @@ -1537,8 +1537,8 @@ namespace arithm return vcmple4(a, b); } - __device__ __forceinline__ VCmpLe4() {} - __device__ __forceinline__ VCmpLe4(const VCmpLe4& other) {} + __host__ __device__ __forceinline__ VCmpLe4() {} + __host__ __device__ __forceinline__ VCmpLe4(const VCmpLe4&) {} }; //////////////////////////////////// @@ -2008,8 +2008,8 @@ namespace arithm return vmin4(a, b); } - __device__ __forceinline__ VMin4() {} - __device__ __forceinline__ VMin4(const VMin4& other) {} + __host__ __device__ __forceinline__ VMin4() {} + __host__ __device__ __forceinline__ VMin4(const VMin4&) {} }; //////////////////////////////////// @@ -2021,8 +2021,8 @@ namespace arithm return vmin2(a, b); } - __device__ __forceinline__ VMin2() {} - __device__ __forceinline__ VMin2(const VMin2& other) {} + __host__ __device__ __forceinline__ VMin2() {} + __host__ __device__ __forceinline__ VMin2(const VMin2&) {} }; } @@ -2100,8 +2100,8 @@ namespace arithm return vmax4(a, b); } - __device__ __forceinline__ VMax4() {} - __device__ __forceinline__ VMax4(const VMax4& other) {} + __host__ __device__ __forceinline__ VMax4() {} + __host__ __device__ __forceinline__ VMax4(const VMax4&) {} }; //////////////////////////////////// @@ -2113,8 +2113,8 @@ namespace arithm return vmax2(a, b); } - __device__ __forceinline__ VMax2() {} - __device__ __forceinline__ VMax2(const VMax2& other) {} + __host__ __device__ __forceinline__ VMax2() {} + __host__ __device__ __forceinline__ VMax2(const VMax2&) {} }; } From dc937c10f994029c7c83b2f2ffa8106ca1354257 Mon Sep 17 00:00:00 2001 From: yao Date: Tue, 4 Jun 2013 11:31:54 +0800 Subject: [PATCH 065/178] change a test image of pyrlk --- modules/ocl/perf/perf_opticalflow.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ocl/perf/perf_opticalflow.cpp b/modules/ocl/perf/perf_opticalflow.cpp index 9887970204..97283b206c 100644 --- a/modules/ocl/perf/perf_opticalflow.cpp +++ b/modules/ocl/perf/perf_opticalflow.cpp @@ -48,8 +48,8 @@ ///////////// PyrLKOpticalFlow //////////////////////// PERFTEST(PyrLKOpticalFlow) { - std::string images1[] = {"rubberwhale1.png", "aloeL.jpg"}; - std::string images2[] = {"rubberwhale2.png", "aloeR.jpg"}; + std::string images1[] = {"rubberwhale1.png", "basketball1.png"}; + std::string images2[] = {"rubberwhale2.png", "basketball2.png"}; for (size_t i = 0; i < sizeof(images1) / sizeof(std::string); i++) { From a7a94de74ae46f99b8ab7606dab1faef93c6e7fa Mon Sep 17 00:00:00 2001 From: peng xiao Date: Tue, 4 Jun 2013 15:55:33 +0800 Subject: [PATCH 066/178] Fix a bug of gfft. When user provided corners buffer is big enough to be copied to from tmpCorners_, we allow the buffer to be reused other than allocate a new cl_mem object. --- modules/ocl/src/gfft.cpp | 3 ++- modules/ocl/test/test_optflow.cpp | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/ocl/src/gfft.cpp b/modules/ocl/src/gfft.cpp index af7580bd43..7fd5e3a174 100644 --- a/modules/ocl/src/gfft.cpp +++ b/modules/ocl/src/gfft.cpp @@ -257,7 +257,8 @@ void cv::ocl::GoodFeaturesToTrackDetector_OCL::operator ()(const oclMat& image, if (minDistance < 1) { - corners = tmpCorners_(Rect(0, 0, maxCorners > 0 ? std::min(maxCorners, total) : total, 1)); + Rect roi_range(0, 0, maxCorners > 0 ? std::min(maxCorners, total) : total, 1); + tmpCorners_(roi_range).copyTo(corners); } else { diff --git a/modules/ocl/test/test_optflow.cpp b/modules/ocl/test/test_optflow.cpp index 0121be8f9e..72689f3f77 100644 --- a/modules/ocl/test/test_optflow.cpp +++ b/modules/ocl/test/test_optflow.cpp @@ -121,7 +121,7 @@ TEST_P(GoodFeaturesToTrack, EmptyCorners) cv::ocl::GoodFeaturesToTrackDetector_OCL detector(maxCorners, qualityLevel, minDistance); - cv::ocl::oclMat src(100, 100, CV_8UC1, cv::Scalar::all(0)); + cv::ocl::oclMat src(100, 128, CV_8UC1, cv::Scalar::all(0)); cv::ocl::oclMat corners(1, maxCorners, CV_32FC2); detector(src, corners); From f049aa7674f3f3ef985f14e65de3debfeb163b3a Mon Sep 17 00:00:00 2001 From: yao Date: Tue, 4 Jun 2013 15:59:21 +0800 Subject: [PATCH 067/178] use GoodFeaturesToTrackDetector_OCL --- samples/ocl/pyrlk_optical_flow.cpp | 54 ++++++++++++++---------------- 1 file changed, 25 insertions(+), 29 deletions(-) diff --git a/samples/ocl/pyrlk_optical_flow.cpp b/samples/ocl/pyrlk_optical_flow.cpp index 1b2b1d3798..cc8d886f79 100644 --- a/samples/ocl/pyrlk_optical_flow.cpp +++ b/samples/ocl/pyrlk_optical_flow.cpp @@ -29,6 +29,7 @@ static double getTime(){ static void download(const oclMat& d_mat, vector& vec) { + vec.clear(); vec.resize(d_mat.cols); Mat mat(1, d_mat.cols, CV_32FC2, (void*)&vec[0]); d_mat.download(mat); @@ -36,6 +37,7 @@ static void download(const oclMat& d_mat, vector& vec) static void download(const oclMat& d_mat, vector& vec) { + vec.clear(); vec.resize(d_mat.cols); Mat mat(1, d_mat.cols, CV_8UC1, (void*)&vec[0]); d_mat.download(mat); @@ -119,14 +121,15 @@ int main(int argc, const char* argv[]) bool useCPU = cmd.get("s"); bool useCamera = cmd.get("c"); int inputName = cmd.get("c"); - oclMat d_nextPts, d_status; + oclMat d_nextPts, d_status; + GoodFeaturesToTrackDetector_OCL d_features(points); Mat frame0 = imread(fname0, cv::IMREAD_GRAYSCALE); Mat frame1 = imread(fname1, cv::IMREAD_GRAYSCALE); PyrLKOpticalFlow d_pyrLK; - vector pts; - vector nextPts; - vector status; + vector pts(points); + vector nextPts(points); + vector status(points); vector err; if (frame0.empty() || frame1.empty()) @@ -199,29 +202,24 @@ int main(int argc, const char* argv[]) ptr1 = frame0Gray; } - pts.clear(); - - cv::goodFeaturesToTrack(ptr0, pts, points, 0.01, 0.0); - - if (pts.size() == 0) - { - continue; - } - if (useCPU) { - cv::calcOpticalFlowPyrLK(ptr0, ptr1, pts, nextPts, status, err); + pts.clear(); + goodFeaturesToTrack(ptr0, pts, points, 0.01, 0.0); + if(pts.size() == 0) + continue; + calcOpticalFlowPyrLK(ptr0, ptr1, pts, nextPts, status, err); } else { - oclMat d_prevPts(1, points, CV_32FC2, (void*)&pts[0]); - - d_pyrLK.sparse(oclMat(ptr0), oclMat(ptr1), d_prevPts, d_nextPts, d_status); - - download(d_prevPts, pts); + oclMat d_img(ptr0), d_prevPts; + d_features(d_img, d_prevPts); + if(!d_prevPts.rows || !d_prevPts.cols) + continue; + d_pyrLK.sparse(d_img, oclMat(ptr1), d_prevPts, d_nextPts, d_status); + d_features.downloadPoints(d_prevPts,pts); download(d_nextPts, nextPts); download(d_status, status); - } if (i%2 == 1) frame1.copyTo(frameCopy); @@ -246,21 +244,19 @@ nocamera: for(int i = 0; i <= LOOP_NUM;i ++) { cout << "loop" << i << endl; - if (i > 0) workBegin(); - - cv::goodFeaturesToTrack(frame0, pts, points, 0.01, minDist); + if (i > 0) workBegin(); if (useCPU) { - cv::calcOpticalFlowPyrLK(frame0, frame1, pts, nextPts, status, err); + goodFeaturesToTrack(frame0, pts, points, 0.01, minDist); + calcOpticalFlowPyrLK(frame0, frame1, pts, nextPts, status, err); } else { - oclMat d_prevPts(1, points, CV_32FC2, (void*)&pts[0]); - - d_pyrLK.sparse(oclMat(frame0), oclMat(frame1), d_prevPts, d_nextPts, d_status); - - download(d_prevPts, pts); + oclMat d_img(frame0), d_prevPts; + d_features(d_img, d_prevPts); + d_pyrLK.sparse(d_img, oclMat(frame1), d_prevPts, d_nextPts, d_status); + d_features.downloadPoints(d_prevPts, pts); download(d_nextPts, nextPts); download(d_status, status); } From 3aea7e8f8dc4cf572d919d81b14d1f69c9bd2f0d Mon Sep 17 00:00:00 2001 From: Vladislav Vinogradov Date: Tue, 4 Jun 2013 12:51:36 +0400 Subject: [PATCH 068/178] fixed gpu module build on arm platform links with CUDA driver library only if we use video encoding/decoding --- modules/gpu/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/gpu/CMakeLists.txt b/modules/gpu/CMakeLists.txt index 87c172da5f..a471da0088 100644 --- a/modules/gpu/CMakeLists.txt +++ b/modules/gpu/CMakeLists.txt @@ -42,10 +42,10 @@ if(HAVE_CUDA) ocv_cuda_compile(cuda_objs ${lib_cuda} ${ncv_cuda}) - set(cuda_link_libs ${CUDA_LIBRARIES} ${CUDA_CUDA_LIBRARY} ${CUDA_npp_LIBRARY}) + set(cuda_link_libs ${CUDA_LIBRARIES} ${CUDA_npp_LIBRARY}) if(WITH_NVCUVID) - set(cuda_link_libs ${cuda_link_libs} ${CUDA_nvcuvid_LIBRARY}) + set(cuda_link_libs ${cuda_link_libs} ${CUDA_CUDA_LIBRARY} ${CUDA_nvcuvid_LIBRARY}) endif() if(WIN32) From 918381875adb0b4cb4fd9a5d061b47ba99b3db7e Mon Sep 17 00:00:00 2001 From: Vladislav Vinogradov Date: Tue, 4 Jun 2013 13:57:35 +0400 Subject: [PATCH 069/178] rewrite gpu/device/vec_math.hpp file old version isn't compiled with CUDA 5.5 new version doesn't depend on functional.hpp --- .../include/opencv2/gpu/device/vec_math.hpp | 1106 +++++++++++++---- modules/gpu/src/cuda/ccomponetns.cu | 4 +- modules/gpu/src/cuda/hough.cu | 5 +- 3 files changed, 854 insertions(+), 261 deletions(-) diff --git a/modules/gpu/include/opencv2/gpu/device/vec_math.hpp b/modules/gpu/include/opencv2/gpu/device/vec_math.hpp index 1c46dc0c33..a6cb43a2fa 100644 --- a/modules/gpu/include/opencv2/gpu/device/vec_math.hpp +++ b/modules/gpu/include/opencv2/gpu/device/vec_math.hpp @@ -43,288 +43,880 @@ #ifndef __OPENCV_GPU_VECMATH_HPP__ #define __OPENCV_GPU_VECMATH_HPP__ -#include "saturate_cast.hpp" #include "vec_traits.hpp" -#include "functional.hpp" +#include "saturate_cast.hpp" namespace cv { namespace gpu { namespace device { - namespace vec_math_detail - { - template struct SatCastHelper; - template struct SatCastHelper<1, VecD> - { - template static __device__ __forceinline__ VecD cast(const VecS& v) - { - typedef typename VecTraits::elem_type D; - return VecTraits::make(saturate_cast(v.x)); - } - }; - template struct SatCastHelper<2, VecD> - { - template static __device__ __forceinline__ VecD cast(const VecS& v) - { - typedef typename VecTraits::elem_type D; - return VecTraits::make(saturate_cast(v.x), saturate_cast(v.y)); - } - }; - template struct SatCastHelper<3, VecD> - { - template static __device__ __forceinline__ VecD cast(const VecS& v) - { - typedef typename VecTraits::elem_type D; - return VecTraits::make(saturate_cast(v.x), saturate_cast(v.y), saturate_cast(v.z)); - } - }; - template struct SatCastHelper<4, VecD> - { - template static __device__ __forceinline__ VecD cast(const VecS& v) - { - typedef typename VecTraits::elem_type D; - return VecTraits::make(saturate_cast(v.x), saturate_cast(v.y), saturate_cast(v.z), saturate_cast(v.w)); - } - }; - template static __device__ __forceinline__ VecD saturate_cast_caller(const VecS& v) +// saturate_cast + +namespace vec_math_detail +{ + template struct SatCastHelper; + template struct SatCastHelper<1, VecD> + { + template static __device__ __forceinline__ VecD cast(const VecS& v) { - return SatCastHelper::cn, VecD>::cast(v); + typedef typename VecTraits::elem_type D; + return VecTraits::make(saturate_cast(v.x)); } - } - - template static __device__ __forceinline__ _Tp saturate_cast(const uchar1& v) {return vec_math_detail::saturate_cast_caller<_Tp>(v);} - template static __device__ __forceinline__ _Tp saturate_cast(const char1& v) {return vec_math_detail::saturate_cast_caller<_Tp>(v);} - template static __device__ __forceinline__ _Tp saturate_cast(const ushort1& v) {return vec_math_detail::saturate_cast_caller<_Tp>(v);} - template static __device__ __forceinline__ _Tp saturate_cast(const short1& v) {return vec_math_detail::saturate_cast_caller<_Tp>(v);} - template static __device__ __forceinline__ _Tp saturate_cast(const uint1& v) {return vec_math_detail::saturate_cast_caller<_Tp>(v);} - template static __device__ __forceinline__ _Tp saturate_cast(const int1& v) {return vec_math_detail::saturate_cast_caller<_Tp>(v);} - template static __device__ __forceinline__ _Tp saturate_cast(const float1& v) {return vec_math_detail::saturate_cast_caller<_Tp>(v);} - template static __device__ __forceinline__ _Tp saturate_cast(const double1& v) {return vec_math_detail::saturate_cast_caller<_Tp>(v);} - - template static __device__ __forceinline__ _Tp saturate_cast(const uchar2& v) {return vec_math_detail::saturate_cast_caller<_Tp>(v);} - template static __device__ __forceinline__ _Tp saturate_cast(const char2& v) {return vec_math_detail::saturate_cast_caller<_Tp>(v);} - template static __device__ __forceinline__ _Tp saturate_cast(const ushort2& v) {return vec_math_detail::saturate_cast_caller<_Tp>(v);} - template static __device__ __forceinline__ _Tp saturate_cast(const short2& v) {return vec_math_detail::saturate_cast_caller<_Tp>(v);} - template static __device__ __forceinline__ _Tp saturate_cast(const uint2& v) {return vec_math_detail::saturate_cast_caller<_Tp>(v);} - template static __device__ __forceinline__ _Tp saturate_cast(const int2& v) {return vec_math_detail::saturate_cast_caller<_Tp>(v);} - template static __device__ __forceinline__ _Tp saturate_cast(const float2& v) {return vec_math_detail::saturate_cast_caller<_Tp>(v);} - template static __device__ __forceinline__ _Tp saturate_cast(const double2& v) {return vec_math_detail::saturate_cast_caller<_Tp>(v);} - - template static __device__ __forceinline__ _Tp saturate_cast(const uchar3& v) {return vec_math_detail::saturate_cast_caller<_Tp>(v);} - template static __device__ __forceinline__ _Tp saturate_cast(const char3& v) {return vec_math_detail::saturate_cast_caller<_Tp>(v);} - template static __device__ __forceinline__ _Tp saturate_cast(const ushort3& v) {return vec_math_detail::saturate_cast_caller<_Tp>(v);} - template static __device__ __forceinline__ _Tp saturate_cast(const short3& v) {return vec_math_detail::saturate_cast_caller<_Tp>(v);} - template static __device__ __forceinline__ _Tp saturate_cast(const uint3& v) {return vec_math_detail::saturate_cast_caller<_Tp>(v);} - template static __device__ __forceinline__ _Tp saturate_cast(const int3& v) {return vec_math_detail::saturate_cast_caller<_Tp>(v);} - template static __device__ __forceinline__ _Tp saturate_cast(const float3& v) {return vec_math_detail::saturate_cast_caller<_Tp>(v);} - template static __device__ __forceinline__ _Tp saturate_cast(const double3& v) {return vec_math_detail::saturate_cast_caller<_Tp>(v);} - - template static __device__ __forceinline__ _Tp saturate_cast(const uchar4& v) {return vec_math_detail::saturate_cast_caller<_Tp>(v);} - template static __device__ __forceinline__ _Tp saturate_cast(const char4& v) {return vec_math_detail::saturate_cast_caller<_Tp>(v);} - template static __device__ __forceinline__ _Tp saturate_cast(const ushort4& v) {return vec_math_detail::saturate_cast_caller<_Tp>(v);} - template static __device__ __forceinline__ _Tp saturate_cast(const short4& v) {return vec_math_detail::saturate_cast_caller<_Tp>(v);} - template static __device__ __forceinline__ _Tp saturate_cast(const uint4& v) {return vec_math_detail::saturate_cast_caller<_Tp>(v);} - template static __device__ __forceinline__ _Tp saturate_cast(const int4& v) {return vec_math_detail::saturate_cast_caller<_Tp>(v);} - template static __device__ __forceinline__ _Tp saturate_cast(const float4& v) {return vec_math_detail::saturate_cast_caller<_Tp>(v);} - template static __device__ __forceinline__ _Tp saturate_cast(const double4& v) {return vec_math_detail::saturate_cast_caller<_Tp>(v);} - -#define OPENCV_GPU_IMPLEMENT_VEC_UNOP(type, op, func) \ - __device__ __forceinline__ TypeVec::result_type, 1>::vec_type op(const type ## 1 & a) \ - { \ - func f; \ - return VecTraits::result_type, 1>::vec_type>::make(f(a.x)); \ - } \ - __device__ __forceinline__ TypeVec::result_type, 2>::vec_type op(const type ## 2 & a) \ - { \ - func f; \ - return VecTraits::result_type, 2>::vec_type>::make(f(a.x), f(a.y)); \ - } \ - __device__ __forceinline__ TypeVec::result_type, 3>::vec_type op(const type ## 3 & a) \ - { \ - func f; \ - return VecTraits::result_type, 3>::vec_type>::make(f(a.x), f(a.y), f(a.z)); \ - } \ - __device__ __forceinline__ TypeVec::result_type, 4>::vec_type op(const type ## 4 & a) \ - { \ - func f; \ - return VecTraits::result_type, 4>::vec_type>::make(f(a.x), f(a.y), f(a.z), f(a.w)); \ - } - - namespace vec_math_detail + }; + template struct SatCastHelper<2, VecD> { - template struct BinOpTraits + template static __device__ __forceinline__ VecD cast(const VecS& v) { - typedef int argument_type; - }; - template struct BinOpTraits + typedef typename VecTraits::elem_type D; + return VecTraits::make(saturate_cast(v.x), saturate_cast(v.y)); + } + }; + template struct SatCastHelper<3, VecD> + { + template static __device__ __forceinline__ VecD cast(const VecS& v) { - typedef T argument_type; - }; - template struct BinOpTraits + typedef typename VecTraits::elem_type D; + return VecTraits::make(saturate_cast(v.x), saturate_cast(v.y), saturate_cast(v.z)); + } + }; + template struct SatCastHelper<4, VecD> + { + template static __device__ __forceinline__ VecD cast(const VecS& v) { - typedef double argument_type; - }; - template struct BinOpTraits - { - typedef double argument_type; - }; - template <> struct BinOpTraits - { - typedef double argument_type; - }; - template struct BinOpTraits - { - typedef float argument_type; - }; - template struct BinOpTraits - { - typedef float argument_type; - }; - template <> struct BinOpTraits - { - typedef float argument_type; - }; - template <> struct BinOpTraits - { - typedef double argument_type; - }; - template <> struct BinOpTraits - { - typedef double argument_type; - }; + typedef typename VecTraits::elem_type D; + return VecTraits::make(saturate_cast(v.x), saturate_cast(v.y), saturate_cast(v.z), saturate_cast(v.w)); + } + }; + + template static __device__ __forceinline__ VecD saturate_cast_helper(const VecS& v) + { + return SatCastHelper::cn, VecD>::cast(v); + } +} + +template static __device__ __forceinline__ T saturate_cast(const uchar1& v) {return vec_math_detail::saturate_cast_helper(v);} +template static __device__ __forceinline__ T saturate_cast(const char1& v) {return vec_math_detail::saturate_cast_helper(v);} +template static __device__ __forceinline__ T saturate_cast(const ushort1& v) {return vec_math_detail::saturate_cast_helper(v);} +template static __device__ __forceinline__ T saturate_cast(const short1& v) {return vec_math_detail::saturate_cast_helper(v);} +template static __device__ __forceinline__ T saturate_cast(const uint1& v) {return vec_math_detail::saturate_cast_helper(v);} +template static __device__ __forceinline__ T saturate_cast(const int1& v) {return vec_math_detail::saturate_cast_helper(v);} +template static __device__ __forceinline__ T saturate_cast(const float1& v) {return vec_math_detail::saturate_cast_helper(v);} +template static __device__ __forceinline__ T saturate_cast(const double1& v) {return vec_math_detail::saturate_cast_helper(v);} + +template static __device__ __forceinline__ T saturate_cast(const uchar2& v) {return vec_math_detail::saturate_cast_helper(v);} +template static __device__ __forceinline__ T saturate_cast(const char2& v) {return vec_math_detail::saturate_cast_helper(v);} +template static __device__ __forceinline__ T saturate_cast(const ushort2& v) {return vec_math_detail::saturate_cast_helper(v);} +template static __device__ __forceinline__ T saturate_cast(const short2& v) {return vec_math_detail::saturate_cast_helper(v);} +template static __device__ __forceinline__ T saturate_cast(const uint2& v) {return vec_math_detail::saturate_cast_helper(v);} +template static __device__ __forceinline__ T saturate_cast(const int2& v) {return vec_math_detail::saturate_cast_helper(v);} +template static __device__ __forceinline__ T saturate_cast(const float2& v) {return vec_math_detail::saturate_cast_helper(v);} +template static __device__ __forceinline__ T saturate_cast(const double2& v) {return vec_math_detail::saturate_cast_helper(v);} + +template static __device__ __forceinline__ T saturate_cast(const uchar3& v) {return vec_math_detail::saturate_cast_helper(v);} +template static __device__ __forceinline__ T saturate_cast(const char3& v) {return vec_math_detail::saturate_cast_helper(v);} +template static __device__ __forceinline__ T saturate_cast(const ushort3& v) {return vec_math_detail::saturate_cast_helper(v);} +template static __device__ __forceinline__ T saturate_cast(const short3& v) {return vec_math_detail::saturate_cast_helper(v);} +template static __device__ __forceinline__ T saturate_cast(const uint3& v) {return vec_math_detail::saturate_cast_helper(v);} +template static __device__ __forceinline__ T saturate_cast(const int3& v) {return vec_math_detail::saturate_cast_helper(v);} +template static __device__ __forceinline__ T saturate_cast(const float3& v) {return vec_math_detail::saturate_cast_helper(v);} +template static __device__ __forceinline__ T saturate_cast(const double3& v) {return vec_math_detail::saturate_cast_helper(v);} + +template static __device__ __forceinline__ T saturate_cast(const uchar4& v) {return vec_math_detail::saturate_cast_helper(v);} +template static __device__ __forceinline__ T saturate_cast(const char4& v) {return vec_math_detail::saturate_cast_helper(v);} +template static __device__ __forceinline__ T saturate_cast(const ushort4& v) {return vec_math_detail::saturate_cast_helper(v);} +template static __device__ __forceinline__ T saturate_cast(const short4& v) {return vec_math_detail::saturate_cast_helper(v);} +template static __device__ __forceinline__ T saturate_cast(const uint4& v) {return vec_math_detail::saturate_cast_helper(v);} +template static __device__ __forceinline__ T saturate_cast(const int4& v) {return vec_math_detail::saturate_cast_helper(v);} +template static __device__ __forceinline__ T saturate_cast(const float4& v) {return vec_math_detail::saturate_cast_helper(v);} +template static __device__ __forceinline__ T saturate_cast(const double4& v) {return vec_math_detail::saturate_cast_helper(v);} + +// unary operators + +#define CV_CUDEV_IMPLEMENT_VEC_UNARY_OP(op, input_type, output_type) \ + __device__ __forceinline__ output_type ## 1 operator op(const input_type ## 1 & a) \ + { \ + return VecTraits::make(op (a.x)); \ + } \ + __device__ __forceinline__ output_type ## 2 operator op(const input_type ## 2 & a) \ + { \ + return VecTraits::make(op (a.x), op (a.y)); \ + } \ + __device__ __forceinline__ output_type ## 3 operator op(const input_type ## 3 & a) \ + { \ + return VecTraits::make(op (a.x), op (a.y), op (a.z)); \ + } \ + __device__ __forceinline__ output_type ## 4 operator op(const input_type ## 4 & a) \ + { \ + return VecTraits::make(op (a.x), op (a.y), op (a.z), op (a.w)); \ } -#define OPENCV_GPU_IMPLEMENT_VEC_BINOP(type, op, func) \ - __device__ __forceinline__ TypeVec::result_type, 1>::vec_type op(const type ## 1 & a, const type ## 1 & b) \ +CV_CUDEV_IMPLEMENT_VEC_UNARY_OP(-, char, char) +CV_CUDEV_IMPLEMENT_VEC_UNARY_OP(-, short, short) +CV_CUDEV_IMPLEMENT_VEC_UNARY_OP(-, int, int) +CV_CUDEV_IMPLEMENT_VEC_UNARY_OP(-, float, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_OP(-, double, double) + +CV_CUDEV_IMPLEMENT_VEC_UNARY_OP(!, uchar, uchar) +CV_CUDEV_IMPLEMENT_VEC_UNARY_OP(!, char, uchar) +CV_CUDEV_IMPLEMENT_VEC_UNARY_OP(!, ushort, uchar) +CV_CUDEV_IMPLEMENT_VEC_UNARY_OP(!, short, uchar) +CV_CUDEV_IMPLEMENT_VEC_UNARY_OP(!, int, uchar) +CV_CUDEV_IMPLEMENT_VEC_UNARY_OP(!, uint, uchar) +CV_CUDEV_IMPLEMENT_VEC_UNARY_OP(!, float, uchar) +CV_CUDEV_IMPLEMENT_VEC_UNARY_OP(!, double, uchar) + +CV_CUDEV_IMPLEMENT_VEC_UNARY_OP(~, uchar, uchar) +CV_CUDEV_IMPLEMENT_VEC_UNARY_OP(~, char, char) +CV_CUDEV_IMPLEMENT_VEC_UNARY_OP(~, ushort, ushort) +CV_CUDEV_IMPLEMENT_VEC_UNARY_OP(~, short, short) +CV_CUDEV_IMPLEMENT_VEC_UNARY_OP(~, int, int) +CV_CUDEV_IMPLEMENT_VEC_UNARY_OP(~, uint, uint) + +#undef CV_CUDEV_IMPLEMENT_VEC_UNARY_OP + +// unary functions + +#define CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(func_name, func, input_type, output_type) \ + __device__ __forceinline__ output_type ## 1 func_name(const input_type ## 1 & a) \ { \ - func f; \ - return VecTraits::result_type, 1>::vec_type>::make(f(a.x, b.x)); \ + return VecTraits::make(func (a.x)); \ } \ - template \ - __device__ __forceinline__ typename TypeVec::argument_type>::result_type, 1>::vec_type op(const type ## 1 & v, T s) \ + __device__ __forceinline__ output_type ## 2 func_name(const input_type ## 2 & a) \ { \ - func::argument_type> f; \ - return VecTraits::argument_type>::result_type, 1>::vec_type>::make(f(v.x, s)); \ + return VecTraits::make(func (a.x), func (a.y)); \ } \ - template \ - __device__ __forceinline__ typename TypeVec::argument_type>::result_type, 1>::vec_type op(T s, const type ## 1 & v) \ + __device__ __forceinline__ output_type ## 3 func_name(const input_type ## 3 & a) \ { \ - func::argument_type> f; \ - return VecTraits::argument_type>::result_type, 1>::vec_type>::make(f(s, v.x)); \ + return VecTraits::make(func (a.x), func (a.y), func (a.z)); \ } \ - __device__ __forceinline__ TypeVec::result_type, 2>::vec_type op(const type ## 2 & a, const type ## 2 & b) \ + __device__ __forceinline__ output_type ## 4 func_name(const input_type ## 4 & a) \ { \ - func f; \ - return VecTraits::result_type, 2>::vec_type>::make(f(a.x, b.x), f(a.y, b.y)); \ - } \ - template \ - __device__ __forceinline__ typename TypeVec::argument_type>::result_type, 2>::vec_type op(const type ## 2 & v, T s) \ - { \ - func::argument_type> f; \ - return VecTraits::argument_type>::result_type, 2>::vec_type>::make(f(v.x, s), f(v.y, s)); \ - } \ - template \ - __device__ __forceinline__ typename TypeVec::argument_type>::result_type, 2>::vec_type op(T s, const type ## 2 & v) \ - { \ - func::argument_type> f; \ - return VecTraits::argument_type>::result_type, 2>::vec_type>::make(f(s, v.x), f(s, v.y)); \ - } \ - __device__ __forceinline__ TypeVec::result_type, 3>::vec_type op(const type ## 3 & a, const type ## 3 & b) \ - { \ - func f; \ - return VecTraits::result_type, 3>::vec_type>::make(f(a.x, b.x), f(a.y, b.y), f(a.z, b.z)); \ - } \ - template \ - __device__ __forceinline__ typename TypeVec::argument_type>::result_type, 3>::vec_type op(const type ## 3 & v, T s) \ - { \ - func::argument_type> f; \ - return VecTraits::argument_type>::result_type, 3>::vec_type>::make(f(v.x, s), f(v.y, s), f(v.z, s)); \ - } \ - template \ - __device__ __forceinline__ typename TypeVec::argument_type>::result_type, 3>::vec_type op(T s, const type ## 3 & v) \ - { \ - func::argument_type> f; \ - return VecTraits::argument_type>::result_type, 3>::vec_type>::make(f(s, v.x), f(s, v.y), f(s, v.z)); \ - } \ - __device__ __forceinline__ TypeVec::result_type, 4>::vec_type op(const type ## 4 & a, const type ## 4 & b) \ - { \ - func f; \ - return VecTraits::result_type, 4>::vec_type>::make(f(a.x, b.x), f(a.y, b.y), f(a.z, b.z), f(a.w, b.w)); \ - } \ - template \ - __device__ __forceinline__ typename TypeVec::argument_type>::result_type, 4>::vec_type op(const type ## 4 & v, T s) \ - { \ - func::argument_type> f; \ - return VecTraits::argument_type>::result_type, 4>::vec_type>::make(f(v.x, s), f(v.y, s), f(v.z, s), f(v.w, s)); \ - } \ - template \ - __device__ __forceinline__ typename TypeVec::argument_type>::result_type, 4>::vec_type op(T s, const type ## 4 & v) \ - { \ - func::argument_type> f; \ - return VecTraits::argument_type>::result_type, 4>::vec_type>::make(f(s, v.x), f(s, v.y), f(s, v.z), f(s, v.w)); \ + return VecTraits::make(func (a.x), func (a.y), func (a.z), func (a.w)); \ } -#define OPENCV_GPU_IMPLEMENT_VEC_OP(type) \ - OPENCV_GPU_IMPLEMENT_VEC_BINOP(type, operator +, plus) \ - OPENCV_GPU_IMPLEMENT_VEC_BINOP(type, operator -, minus) \ - OPENCV_GPU_IMPLEMENT_VEC_BINOP(type, operator *, multiplies) \ - OPENCV_GPU_IMPLEMENT_VEC_BINOP(type, operator /, divides) \ - OPENCV_GPU_IMPLEMENT_VEC_UNOP (type, operator -, negate) \ - OPENCV_GPU_IMPLEMENT_VEC_BINOP(type, operator ==, equal_to) \ - OPENCV_GPU_IMPLEMENT_VEC_BINOP(type, operator !=, not_equal_to) \ - OPENCV_GPU_IMPLEMENT_VEC_BINOP(type, operator > , greater) \ - OPENCV_GPU_IMPLEMENT_VEC_BINOP(type, operator < , less) \ - OPENCV_GPU_IMPLEMENT_VEC_BINOP(type, operator >=, greater_equal) \ - OPENCV_GPU_IMPLEMENT_VEC_BINOP(type, operator <=, less_equal) \ - OPENCV_GPU_IMPLEMENT_VEC_BINOP(type, operator &&, logical_and) \ - OPENCV_GPU_IMPLEMENT_VEC_BINOP(type, operator ||, logical_or) \ - OPENCV_GPU_IMPLEMENT_VEC_UNOP (type, operator ! , logical_not) \ - OPENCV_GPU_IMPLEMENT_VEC_BINOP(type, max, maximum) \ - OPENCV_GPU_IMPLEMENT_VEC_BINOP(type, min, minimum) \ - OPENCV_GPU_IMPLEMENT_VEC_UNOP(type, abs, abs_func) \ - OPENCV_GPU_IMPLEMENT_VEC_UNOP(type, sqrt, sqrt_func) \ - OPENCV_GPU_IMPLEMENT_VEC_UNOP(type, exp, exp_func) \ - OPENCV_GPU_IMPLEMENT_VEC_UNOP(type, exp2, exp2_func) \ - OPENCV_GPU_IMPLEMENT_VEC_UNOP(type, exp10, exp10_func) \ - OPENCV_GPU_IMPLEMENT_VEC_UNOP(type, log, log_func) \ - OPENCV_GPU_IMPLEMENT_VEC_UNOP(type, log2, log2_func) \ - OPENCV_GPU_IMPLEMENT_VEC_UNOP(type, log10, log10_func) \ - OPENCV_GPU_IMPLEMENT_VEC_UNOP(type, sin, sin_func) \ - OPENCV_GPU_IMPLEMENT_VEC_UNOP(type, cos, cos_func) \ - OPENCV_GPU_IMPLEMENT_VEC_UNOP(type, tan, tan_func) \ - OPENCV_GPU_IMPLEMENT_VEC_UNOP(type, asin, asin_func) \ - OPENCV_GPU_IMPLEMENT_VEC_UNOP(type, acos, acos_func) \ - OPENCV_GPU_IMPLEMENT_VEC_UNOP(type, atan, atan_func) \ - OPENCV_GPU_IMPLEMENT_VEC_UNOP(type, sinh, sinh_func) \ - OPENCV_GPU_IMPLEMENT_VEC_UNOP(type, cosh, cosh_func) \ - OPENCV_GPU_IMPLEMENT_VEC_UNOP(type, tanh, tanh_func) \ - OPENCV_GPU_IMPLEMENT_VEC_UNOP(type, asinh, asinh_func) \ - OPENCV_GPU_IMPLEMENT_VEC_UNOP(type, acosh, acosh_func) \ - OPENCV_GPU_IMPLEMENT_VEC_UNOP(type, atanh, atanh_func) \ - OPENCV_GPU_IMPLEMENT_VEC_BINOP(type, hypot, hypot_func) \ - OPENCV_GPU_IMPLEMENT_VEC_BINOP(type, atan2, atan2_func) \ - OPENCV_GPU_IMPLEMENT_VEC_BINOP(type, pow, pow_func) \ - OPENCV_GPU_IMPLEMENT_VEC_BINOP(type, hypot_sqr, hypot_sqr_func) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(abs, /*::abs*/, uchar, uchar) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(abs, ::abs, char, char) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(abs, /*::abs*/, ushort, ushort) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(abs, ::abs, short, short) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(abs, ::abs, int, int) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(abs, /*::abs*/, uint, uint) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(abs, ::fabsf, float, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(abs, ::fabs, double, double) -#define OPENCV_GPU_IMPLEMENT_VEC_INT_OP(type) \ - OPENCV_GPU_IMPLEMENT_VEC_OP(type) \ - OPENCV_GPU_IMPLEMENT_VEC_BINOP(type, operator &, bit_and) \ - OPENCV_GPU_IMPLEMENT_VEC_BINOP(type, operator |, bit_or) \ - OPENCV_GPU_IMPLEMENT_VEC_BINOP(type, operator ^, bit_xor) \ - OPENCV_GPU_IMPLEMENT_VEC_UNOP (type, operator ~, bit_not) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(sqrt, ::sqrtf, uchar, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(sqrt, ::sqrtf, char, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(sqrt, ::sqrtf, ushort, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(sqrt, ::sqrtf, short, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(sqrt, ::sqrtf, int, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(sqrt, ::sqrtf, uint, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(sqrt, ::sqrtf, float, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(sqrt, ::sqrt, double, double) - OPENCV_GPU_IMPLEMENT_VEC_INT_OP(uchar) - OPENCV_GPU_IMPLEMENT_VEC_INT_OP(char) - OPENCV_GPU_IMPLEMENT_VEC_INT_OP(ushort) - OPENCV_GPU_IMPLEMENT_VEC_INT_OP(short) - OPENCV_GPU_IMPLEMENT_VEC_INT_OP(int) - OPENCV_GPU_IMPLEMENT_VEC_INT_OP(uint) - OPENCV_GPU_IMPLEMENT_VEC_OP(float) - OPENCV_GPU_IMPLEMENT_VEC_OP(double) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(exp, ::expf, uchar, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(exp, ::expf, char, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(exp, ::expf, ushort, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(exp, ::expf, short, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(exp, ::expf, int, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(exp, ::expf, uint, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(exp, ::expf, float, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(exp, ::exp, double, double) + +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(exp2, ::exp2f, uchar, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(exp2, ::exp2f, char, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(exp2, ::exp2f, ushort, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(exp2, ::exp2f, short, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(exp2, ::exp2f, int, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(exp2, ::exp2f, uint, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(exp2, ::exp2f, float, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(exp2, ::exp2, double, double) + +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(exp10, ::exp10f, uchar, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(exp10, ::exp10f, char, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(exp10, ::exp10f, ushort, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(exp10, ::exp10f, short, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(exp10, ::exp10f, int, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(exp10, ::exp10f, uint, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(exp10, ::exp10f, float, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(exp10, ::exp10, double, double) + +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(log, ::logf, uchar, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(log, ::logf, char, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(log, ::logf, ushort, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(log, ::logf, short, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(log, ::logf, int, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(log, ::logf, uint, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(log, ::logf, float, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(log, ::log, double, double) + +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(log2, ::log2f, uchar, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(log2, ::log2f, char, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(log2, ::log2f, ushort, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(log2, ::log2f, short, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(log2, ::log2f, int, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(log2, ::log2f, uint, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(log2, ::log2f, float, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(log2, ::log2, double, double) + +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(log10, ::log10f, uchar, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(log10, ::log10f, char, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(log10, ::log10f, ushort, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(log10, ::log10f, short, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(log10, ::log10f, int, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(log10, ::log10f, uint, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(log10, ::log10f, float, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(log10, ::log10, double, double) + +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(sin, ::sinf, uchar, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(sin, ::sinf, char, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(sin, ::sinf, ushort, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(sin, ::sinf, short, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(sin, ::sinf, int, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(sin, ::sinf, uint, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(sin, ::sinf, float, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(sin, ::sin, double, double) + +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(cos, ::cosf, uchar, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(cos, ::cosf, char, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(cos, ::cosf, ushort, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(cos, ::cosf, short, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(cos, ::cosf, int, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(cos, ::cosf, uint, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(cos, ::cosf, float, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(cos, ::cos, double, double) + +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(tan, ::tanf, uchar, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(tan, ::tanf, char, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(tan, ::tanf, ushort, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(tan, ::tanf, short, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(tan, ::tanf, int, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(tan, ::tanf, uint, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(tan, ::tanf, float, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(tan, ::tan, double, double) + +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(asin, ::asinf, uchar, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(asin, ::asinf, char, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(asin, ::asinf, ushort, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(asin, ::asinf, short, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(asin, ::asinf, int, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(asin, ::asinf, uint, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(asin, ::asinf, float, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(asin, ::asin, double, double) + +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(acos, ::acosf, uchar, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(acos, ::acosf, char, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(acos, ::acosf, ushort, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(acos, ::acosf, short, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(acos, ::acosf, int, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(acos, ::acosf, uint, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(acos, ::acosf, float, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(acos, ::acos, double, double) + +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(atan, ::atanf, uchar, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(atan, ::atanf, char, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(atan, ::atanf, ushort, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(atan, ::atanf, short, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(atan, ::atanf, int, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(atan, ::atanf, uint, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(atan, ::atanf, float, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(atan, ::atan, double, double) + +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(sinh, ::sinhf, uchar, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(sinh, ::sinhf, char, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(sinh, ::sinhf, ushort, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(sinh, ::sinhf, short, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(sinh, ::sinhf, int, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(sinh, ::sinhf, uint, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(sinh, ::sinhf, float, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(sinh, ::sinh, double, double) + +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(cosh, ::coshf, uchar, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(cosh, ::coshf, char, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(cosh, ::coshf, ushort, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(cosh, ::coshf, short, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(cosh, ::coshf, int, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(cosh, ::coshf, uint, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(cosh, ::coshf, float, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(cosh, ::cosh, double, double) + +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(tanh, ::tanhf, uchar, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(tanh, ::tanhf, char, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(tanh, ::tanhf, ushort, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(tanh, ::tanhf, short, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(tanh, ::tanhf, int, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(tanh, ::tanhf, uint, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(tanh, ::tanhf, float, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(tanh, ::tanh, double, double) + +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(asinh, ::asinhf, uchar, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(asinh, ::asinhf, char, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(asinh, ::asinhf, ushort, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(asinh, ::asinhf, short, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(asinh, ::asinhf, int, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(asinh, ::asinhf, uint, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(asinh, ::asinhf, float, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(asinh, ::asinh, double, double) + +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(acosh, ::acoshf, uchar, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(acosh, ::acoshf, char, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(acosh, ::acoshf, ushort, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(acosh, ::acoshf, short, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(acosh, ::acoshf, int, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(acosh, ::acoshf, uint, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(acosh, ::acoshf, float, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(acosh, ::acosh, double, double) + +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(atanh, ::atanhf, uchar, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(atanh, ::atanhf, char, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(atanh, ::atanhf, ushort, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(atanh, ::atanhf, short, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(atanh, ::atanhf, int, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(atanh, ::atanhf, uint, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(atanh, ::atanhf, float, float) +CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC(atanh, ::atanh, double, double) + +#undef CV_CUDEV_IMPLEMENT_VEC_UNARY_FUNC + +// binary operators (vec & vec) + +#define CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(op, input_type, output_type) \ + __device__ __forceinline__ output_type ## 1 operator op(const input_type ## 1 & a, const input_type ## 1 & b) \ + { \ + return VecTraits::make(a.x op b.x); \ + } \ + __device__ __forceinline__ output_type ## 2 operator op(const input_type ## 2 & a, const input_type ## 2 & b) \ + { \ + return VecTraits::make(a.x op b.x, a.y op b.y); \ + } \ + __device__ __forceinline__ output_type ## 3 operator op(const input_type ## 3 & a, const input_type ## 3 & b) \ + { \ + return VecTraits::make(a.x op b.x, a.y op b.y, a.z op b.z); \ + } \ + __device__ __forceinline__ output_type ## 4 operator op(const input_type ## 4 & a, const input_type ## 4 & b) \ + { \ + return VecTraits::make(a.x op b.x, a.y op b.y, a.z op b.z, a.w op b.w); \ + } + +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(+, uchar, int) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(+, char, int) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(+, ushort, int) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(+, short, int) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(+, int, int) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(+, uint, uint) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(+, float, float) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(+, double, double) + +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(-, uchar, int) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(-, char, int) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(-, ushort, int) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(-, short, int) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(-, int, int) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(-, uint, uint) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(-, float, float) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(-, double, double) + +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(*, uchar, int) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(*, char, int) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(*, ushort, int) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(*, short, int) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(*, int, int) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(*, uint, uint) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(*, float, float) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(*, double, double) + +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(/, uchar, int) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(/, char, int) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(/, ushort, int) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(/, short, int) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(/, int, int) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(/, uint, uint) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(/, float, float) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(/, double, double) + +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(==, uchar, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(==, char, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(==, ushort, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(==, short, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(==, int, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(==, uint, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(==, float, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(==, double, uchar) + +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(!=, uchar, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(!=, char, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(!=, ushort, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(!=, short, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(!=, int, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(!=, uint, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(!=, float, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(!=, double, uchar) + +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(>, uchar, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(>, char, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(>, ushort, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(>, short, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(>, int, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(>, uint, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(>, float, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(>, double, uchar) + +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(<, uchar, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(<, char, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(<, ushort, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(<, short, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(<, int, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(<, uint, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(<, float, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(<, double, uchar) + +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(>=, uchar, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(>=, char, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(>=, ushort, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(>=, short, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(>=, int, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(>=, uint, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(>=, float, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(>=, double, uchar) + +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(<=, uchar, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(<=, char, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(<=, ushort, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(<=, short, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(<=, int, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(<=, uint, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(<=, float, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(<=, double, uchar) + +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(&&, uchar, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(&&, char, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(&&, ushort, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(&&, short, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(&&, int, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(&&, uint, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(&&, float, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(&&, double, uchar) + +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(||, uchar, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(||, char, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(||, ushort, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(||, short, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(||, int, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(||, uint, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(||, float, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(||, double, uchar) + +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(&, uchar, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(&, char, char) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(&, ushort, ushort) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(&, short, short) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(&, int, int) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(&, uint, uint) + +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(|, uchar, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(|, char, char) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(|, ushort, ushort) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(|, short, short) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(|, int, int) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(|, uint, uint) + +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(^, uchar, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(^, char, char) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(^, ushort, ushort) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(^, short, short) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(^, int, int) +CV_CUDEV_IMPLEMENT_VEC_BINARY_OP(^, uint, uint) + +#undef CV_CUDEV_IMPLEMENT_VEC_BINARY_OP + +// binary operators (vec & scalar) + +#define CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(op, input_type, scalar_type, output_type) \ + __device__ __forceinline__ output_type ## 1 operator op(const input_type ## 1 & a, scalar_type s) \ + { \ + return VecTraits::make(a.x op s); \ + } \ + __device__ __forceinline__ output_type ## 1 operator op(scalar_type s, const input_type ## 1 & b) \ + { \ + return VecTraits::make(s op b.x); \ + } \ + __device__ __forceinline__ output_type ## 2 operator op(const input_type ## 2 & a, scalar_type s) \ + { \ + return VecTraits::make(a.x op s, a.y op s); \ + } \ + __device__ __forceinline__ output_type ## 2 operator op(scalar_type s, const input_type ## 2 & b) \ + { \ + return VecTraits::make(s op b.x, s op b.y); \ + } \ + __device__ __forceinline__ output_type ## 3 operator op(const input_type ## 3 & a, scalar_type s) \ + { \ + return VecTraits::make(a.x op s, a.y op s, a.z op s); \ + } \ + __device__ __forceinline__ output_type ## 3 operator op(scalar_type s, const input_type ## 3 & b) \ + { \ + return VecTraits::make(s op b.x, s op b.y, s op b.z); \ + } \ + __device__ __forceinline__ output_type ## 4 operator op(const input_type ## 4 & a, scalar_type s) \ + { \ + return VecTraits::make(a.x op s, a.y op s, a.z op s, a.w op s); \ + } \ + __device__ __forceinline__ output_type ## 4 operator op(scalar_type s, const input_type ## 4 & b) \ + { \ + return VecTraits::make(s op b.x, s op b.y, s op b.z, s op b.w); \ + } + +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(+, uchar, int, int) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(+, uchar, float, float) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(+, uchar, double, double) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(+, char, int, int) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(+, char, float, float) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(+, char, double, double) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(+, ushort, int, int) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(+, ushort, float, float) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(+, ushort, double, double) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(+, short, int, int) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(+, short, float, float) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(+, short, double, double) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(+, int, int, int) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(+, int, float, float) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(+, int, double, double) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(+, uint, uint, uint) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(+, uint, float, float) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(+, uint, double, double) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(+, float, float, float) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(+, float, double, double) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(+, double, double, double) + +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(-, uchar, int, int) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(-, uchar, float, float) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(-, uchar, double, double) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(-, char, int, int) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(-, char, float, float) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(-, char, double, double) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(-, ushort, int, int) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(-, ushort, float, float) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(-, ushort, double, double) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(-, short, int, int) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(-, short, float, float) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(-, short, double, double) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(-, int, int, int) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(-, int, float, float) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(-, int, double, double) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(-, uint, uint, uint) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(-, uint, float, float) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(-, uint, double, double) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(-, float, float, float) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(-, float, double, double) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(-, double, double, double) + +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(*, uchar, int, int) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(*, uchar, float, float) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(*, uchar, double, double) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(*, char, int, int) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(*, char, float, float) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(*, char, double, double) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(*, ushort, int, int) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(*, ushort, float, float) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(*, ushort, double, double) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(*, short, int, int) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(*, short, float, float) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(*, short, double, double) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(*, int, int, int) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(*, int, float, float) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(*, int, double, double) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(*, uint, uint, uint) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(*, uint, float, float) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(*, uint, double, double) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(*, float, float, float) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(*, float, double, double) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(*, double, double, double) + +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(/, uchar, int, int) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(/, uchar, float, float) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(/, uchar, double, double) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(/, char, int, int) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(/, char, float, float) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(/, char, double, double) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(/, ushort, int, int) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(/, ushort, float, float) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(/, ushort, double, double) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(/, short, int, int) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(/, short, float, float) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(/, short, double, double) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(/, int, int, int) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(/, int, float, float) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(/, int, double, double) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(/, uint, uint, uint) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(/, uint, float, float) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(/, uint, double, double) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(/, float, float, float) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(/, float, double, double) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(/, double, double, double) + +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(==, uchar, uchar, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(==, char, char, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(==, ushort, ushort, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(==, short, short, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(==, int, int, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(==, uint, uint, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(==, float, float, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(==, double, double, uchar) + +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(!=, uchar, uchar, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(!=, char, char, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(!=, ushort, ushort, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(!=, short, short, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(!=, int, int, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(!=, uint, uint, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(!=, float, float, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(!=, double, double, uchar) + +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(>, uchar, uchar, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(>, char, char, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(>, ushort, ushort, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(>, short, short, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(>, int, int, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(>, uint, uint, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(>, float, float, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(>, double, double, uchar) + +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(<, uchar, uchar, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(<, char, char, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(<, ushort, ushort, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(<, short, short, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(<, int, int, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(<, uint, uint, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(<, float, float, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(<, double, double, uchar) + +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(>=, uchar, uchar, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(>=, char, char, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(>=, ushort, ushort, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(>=, short, short, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(>=, int, int, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(>=, uint, uint, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(>=, float, float, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(>=, double, double, uchar) + +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(<=, uchar, uchar, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(<=, char, char, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(<=, ushort, ushort, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(<=, short, short, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(<=, int, int, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(<=, uint, uint, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(<=, float, float, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(<=, double, double, uchar) + +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(&&, uchar, uchar, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(&&, char, char, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(&&, ushort, ushort, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(&&, short, short, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(&&, int, int, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(&&, uint, uint, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(&&, float, float, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(&&, double, double, uchar) + +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(||, uchar, uchar, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(||, char, char, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(||, ushort, ushort, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(||, short, short, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(||, int, int, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(||, uint, uint, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(||, float, float, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(||, double, double, uchar) + +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(&, uchar, uchar, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(&, char, char, char) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(&, ushort, ushort, ushort) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(&, short, short, short) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(&, int, int, int) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(&, uint, uint, uint) + +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(|, uchar, uchar, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(|, char, char, char) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(|, ushort, ushort, ushort) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(|, short, short, short) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(|, int, int, int) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(|, uint, uint, uint) + +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(^, uchar, uchar, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(^, char, char, char) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(^, ushort, ushort, ushort) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(^, short, short, short) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(^, int, int, int) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP(^, uint, uint, uint) + +#undef CV_CUDEV_IMPLEMENT_SCALAR_BINARY_OP + +// binary function (vec & vec) + +#define CV_CUDEV_IMPLEMENT_VEC_BINARY_FUNC(func_name, func, input_type, output_type) \ + __device__ __forceinline__ output_type ## 1 func_name(const input_type ## 1 & a, const input_type ## 1 & b) \ + { \ + return VecTraits::make(func (a.x, b.x)); \ + } \ + __device__ __forceinline__ output_type ## 2 func_name(const input_type ## 2 & a, const input_type ## 2 & b) \ + { \ + return VecTraits::make(func (a.x, b.x), func (a.y, b.y)); \ + } \ + __device__ __forceinline__ output_type ## 3 func_name(const input_type ## 3 & a, const input_type ## 3 & b) \ + { \ + return VecTraits::make(func (a.x, b.x), func (a.y, b.y), func (a.z, b.z)); \ + } \ + __device__ __forceinline__ output_type ## 4 func_name(const input_type ## 4 & a, const input_type ## 4 & b) \ + { \ + return VecTraits::make(func (a.x, b.x), func (a.y, b.y), func (a.z, b.z), func (a.w, b.w)); \ + } + +CV_CUDEV_IMPLEMENT_VEC_BINARY_FUNC(max, ::max, uchar, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_FUNC(max, ::max, char, char) +CV_CUDEV_IMPLEMENT_VEC_BINARY_FUNC(max, ::max, ushort, ushort) +CV_CUDEV_IMPLEMENT_VEC_BINARY_FUNC(max, ::max, short, short) +CV_CUDEV_IMPLEMENT_VEC_BINARY_FUNC(max, ::max, uint, uint) +CV_CUDEV_IMPLEMENT_VEC_BINARY_FUNC(max, ::max, int, int) +CV_CUDEV_IMPLEMENT_VEC_BINARY_FUNC(max, ::fmaxf, float, float) +CV_CUDEV_IMPLEMENT_VEC_BINARY_FUNC(max, ::fmax, double, double) + +CV_CUDEV_IMPLEMENT_VEC_BINARY_FUNC(min, ::min, uchar, uchar) +CV_CUDEV_IMPLEMENT_VEC_BINARY_FUNC(min, ::min, char, char) +CV_CUDEV_IMPLEMENT_VEC_BINARY_FUNC(min, ::min, ushort, ushort) +CV_CUDEV_IMPLEMENT_VEC_BINARY_FUNC(min, ::min, short, short) +CV_CUDEV_IMPLEMENT_VEC_BINARY_FUNC(min, ::min, uint, uint) +CV_CUDEV_IMPLEMENT_VEC_BINARY_FUNC(min, ::min, int, int) +CV_CUDEV_IMPLEMENT_VEC_BINARY_FUNC(min, ::fminf, float, float) +CV_CUDEV_IMPLEMENT_VEC_BINARY_FUNC(min, ::fmin, double, double) + +CV_CUDEV_IMPLEMENT_VEC_BINARY_FUNC(hypot, ::hypotf, uchar, float) +CV_CUDEV_IMPLEMENT_VEC_BINARY_FUNC(hypot, ::hypotf, char, float) +CV_CUDEV_IMPLEMENT_VEC_BINARY_FUNC(hypot, ::hypotf, ushort, float) +CV_CUDEV_IMPLEMENT_VEC_BINARY_FUNC(hypot, ::hypotf, short, float) +CV_CUDEV_IMPLEMENT_VEC_BINARY_FUNC(hypot, ::hypotf, uint, float) +CV_CUDEV_IMPLEMENT_VEC_BINARY_FUNC(hypot, ::hypotf, int, float) +CV_CUDEV_IMPLEMENT_VEC_BINARY_FUNC(hypot, ::hypotf, float, float) +CV_CUDEV_IMPLEMENT_VEC_BINARY_FUNC(hypot, ::hypot, double, double) + +CV_CUDEV_IMPLEMENT_VEC_BINARY_FUNC(atan2, ::atan2f, uchar, float) +CV_CUDEV_IMPLEMENT_VEC_BINARY_FUNC(atan2, ::atan2f, char, float) +CV_CUDEV_IMPLEMENT_VEC_BINARY_FUNC(atan2, ::atan2f, ushort, float) +CV_CUDEV_IMPLEMENT_VEC_BINARY_FUNC(atan2, ::atan2f, short, float) +CV_CUDEV_IMPLEMENT_VEC_BINARY_FUNC(atan2, ::atan2f, uint, float) +CV_CUDEV_IMPLEMENT_VEC_BINARY_FUNC(atan2, ::atan2f, int, float) +CV_CUDEV_IMPLEMENT_VEC_BINARY_FUNC(atan2, ::atan2f, float, float) +CV_CUDEV_IMPLEMENT_VEC_BINARY_FUNC(atan2, ::atan2, double, double) + +#undef CV_CUDEV_IMPLEMENT_VEC_BINARY_FUNC + +// binary function (vec & scalar) + +#define CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(func_name, func, input_type, scalar_type, output_type) \ + __device__ __forceinline__ output_type ## 1 func_name(const input_type ## 1 & a, scalar_type s) \ + { \ + return VecTraits::make(func ((output_type) a.x, (output_type) s)); \ + } \ + __device__ __forceinline__ output_type ## 1 func_name(scalar_type s, const input_type ## 1 & b) \ + { \ + return VecTraits::make(func ((output_type) s, (output_type) b.x)); \ + } \ + __device__ __forceinline__ output_type ## 2 func_name(const input_type ## 2 & a, scalar_type s) \ + { \ + return VecTraits::make(func ((output_type) a.x, (output_type) s), func ((output_type) a.y, (output_type) s)); \ + } \ + __device__ __forceinline__ output_type ## 2 func_name(scalar_type s, const input_type ## 2 & b) \ + { \ + return VecTraits::make(func ((output_type) s, (output_type) b.x), func ((output_type) s, (output_type) b.y)); \ + } \ + __device__ __forceinline__ output_type ## 3 func_name(const input_type ## 3 & a, scalar_type s) \ + { \ + return VecTraits::make(func ((output_type) a.x, (output_type) s), func ((output_type) a.y, (output_type) s), func ((output_type) a.z, (output_type) s)); \ + } \ + __device__ __forceinline__ output_type ## 3 func_name(scalar_type s, const input_type ## 3 & b) \ + { \ + return VecTraits::make(func ((output_type) s, (output_type) b.x), func ((output_type) s, (output_type) b.y), func ((output_type) s, (output_type) b.z)); \ + } \ + __device__ __forceinline__ output_type ## 4 func_name(const input_type ## 4 & a, scalar_type s) \ + { \ + return VecTraits::make(func ((output_type) a.x, (output_type) s), func ((output_type) a.y, (output_type) s), func ((output_type) a.z, (output_type) s), func ((output_type) a.w, (output_type) s)); \ + } \ + __device__ __forceinline__ output_type ## 4 func_name(scalar_type s, const input_type ## 4 & b) \ + { \ + return VecTraits::make(func ((output_type) s, (output_type) b.x), func ((output_type) s, (output_type) b.y), func ((output_type) s, (output_type) b.z), func ((output_type) s, (output_type) b.w)); \ + } + +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(max, ::max, uchar, uchar, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(max, ::fmaxf, uchar, float, float) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(max, ::fmax, uchar, double, double) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(max, ::max, char, char, char) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(max, ::fmaxf, char, float, float) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(max, ::fmax, char, double, double) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(max, ::max, ushort, ushort, ushort) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(max, ::fmaxf, ushort, float, float) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(max, ::fmax, ushort, double, double) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(max, ::max, short, short, short) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(max, ::fmaxf, short, float, float) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(max, ::fmax, short, double, double) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(max, ::max, uint, uint, uint) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(max, ::fmaxf, uint, float, float) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(max, ::fmax, uint, double, double) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(max, ::max, int, int, int) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(max, ::fmaxf, int, float, float) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(max, ::fmax, int, double, double) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(max, ::fmaxf, float, float, float) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(max, ::fmax, float, double, double) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(max, ::fmax, double, double, double) + +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(min, ::min, uchar, uchar, uchar) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(min, ::fminf, uchar, float, float) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(min, ::fmin, uchar, double, double) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(min, ::min, char, char, char) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(min, ::fminf, char, float, float) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(min, ::fmin, char, double, double) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(min, ::min, ushort, ushort, ushort) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(min, ::fminf, ushort, float, float) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(min, ::fmin, ushort, double, double) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(min, ::min, short, short, short) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(min, ::fminf, short, float, float) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(min, ::fmin, short, double, double) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(min, ::min, uint, uint, uint) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(min, ::fminf, uint, float, float) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(min, ::fmin, uint, double, double) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(min, ::min, int, int, int) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(min, ::fminf, int, float, float) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(min, ::fmin, int, double, double) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(min, ::fminf, float, float, float) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(min, ::fmin, float, double, double) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(min, ::fmin, double, double, double) + +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(hypot, ::hypotf, uchar, float, float) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(hypot, ::hypot, uchar, double, double) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(hypot, ::hypotf, char, float, float) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(hypot, ::hypot, char, double, double) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(hypot, ::hypotf, ushort, float, float) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(hypot, ::hypot, ushort, double, double) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(hypot, ::hypotf, short, float, float) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(hypot, ::hypot, short, double, double) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(hypot, ::hypotf, uint, float, float) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(hypot, ::hypot, uint, double, double) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(hypot, ::hypotf, int, float, float) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(hypot, ::hypot, int, double, double) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(hypot, ::hypotf, float, float, float) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(hypot, ::hypot, float, double, double) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(hypot, ::hypot, double, double, double) + +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(atan2, ::atan2f, uchar, float, float) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(atan2, ::atan2, uchar, double, double) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(atan2, ::atan2f, char, float, float) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(atan2, ::atan2, char, double, double) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(atan2, ::atan2f, ushort, float, float) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(atan2, ::atan2, ushort, double, double) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(atan2, ::atan2f, short, float, float) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(atan2, ::atan2, short, double, double) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(atan2, ::atan2f, uint, float, float) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(atan2, ::atan2, uint, double, double) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(atan2, ::atan2f, int, float, float) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(atan2, ::atan2, int, double, double) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(atan2, ::atan2f, float, float, float) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(atan2, ::atan2, float, double, double) +CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC(atan2, ::atan2, double, double, double) + +#undef CV_CUDEV_IMPLEMENT_SCALAR_BINARY_FUNC - #undef OPENCV_GPU_IMPLEMENT_VEC_UNOP - #undef OPENCV_GPU_IMPLEMENT_VEC_BINOP - #undef OPENCV_GPU_IMPLEMENT_VEC_OP - #undef OPENCV_GPU_IMPLEMENT_VEC_INT_OP }}} // namespace cv { namespace gpu { namespace device #endif // __OPENCV_GPU_VECMATH_HPP__ diff --git a/modules/gpu/src/cuda/ccomponetns.cu b/modules/gpu/src/cuda/ccomponetns.cu index 7f3d4ae338..c4d79bd80b 100644 --- a/modules/gpu/src/cuda/ccomponetns.cu +++ b/modules/gpu/src/cuda/ccomponetns.cu @@ -153,7 +153,7 @@ namespace cv { namespace gpu { namespace device template __device__ __forceinline__ bool operator() (const I& a, const I& b) const { - I d = a - b; + I d = saturate_cast(a - b); return lo.x <= d.x && d.x <= hi.x && lo.y <= d.y && d.y <= hi.y && lo.z <= d.z && d.z <= hi.z; @@ -169,7 +169,7 @@ namespace cv { namespace gpu { namespace device template __device__ __forceinline__ bool operator() (const I& a, const I& b) const { - I d = a - b; + I d = saturate_cast(a - b); return lo.x <= d.x && d.x <= hi.x && lo.y <= d.y && d.y <= hi.y && lo.z <= d.z && d.z <= hi.z && diff --git a/modules/gpu/src/cuda/hough.cu b/modules/gpu/src/cuda/hough.cu index faec89b95c..59eba26081 100644 --- a/modules/gpu/src/cuda/hough.cu +++ b/modules/gpu/src/cuda/hough.cu @@ -48,6 +48,7 @@ #include "opencv2/gpu/device/common.hpp" #include "opencv2/gpu/device/emulation.hpp" #include "opencv2/gpu/device/vec_math.hpp" +#include "opencv2/gpu/device/functional.hpp" #include "opencv2/gpu/device/limits.hpp" #include "opencv2/gpu/device/dynamic_smem.hpp" @@ -811,7 +812,7 @@ namespace cv { namespace gpu { namespace device const int ind = ::atomicAdd(r_sizes + n, 1); if (ind < maxSize) - r_table(n, ind) = p - templCenter; + r_table(n, ind) = saturate_cast(p - templCenter); } void buildRTable_gpu(const unsigned int* coordList, const float* thetaList, int pointsCount, @@ -855,7 +856,7 @@ namespace cv { namespace gpu { namespace device for (int j = 0; j < r_row_size; ++j) { - short2 c = p - r_row[j]; + int2 c = p - r_row[j]; c.x = __float2int_rn(c.x * idp); c.y = __float2int_rn(c.y * idp); From 516e5b2563100329882948f68aceb15b5fe8fe60 Mon Sep 17 00:00:00 2001 From: Vladislav Vinogradov Date: Tue, 4 Jun 2013 13:58:45 +0400 Subject: [PATCH 070/178] fixed BroxOpticalFlow regression test the output of BroxOpticalFlow differs a bit in CUDA 5.5 --- modules/gpu/test/test_optflow.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/gpu/test/test_optflow.cpp b/modules/gpu/test/test_optflow.cpp index 9e30b92087..53b93a096b 100644 --- a/modules/gpu/test/test_optflow.cpp +++ b/modules/gpu/test/test_optflow.cpp @@ -102,8 +102,8 @@ GPU_TEST_P(BroxOpticalFlow, Regression) for (int i = 0; i < v_gold.rows; ++i) f.read(v_gold.ptr(i), v_gold.cols * sizeof(float)); - EXPECT_MAT_NEAR(u_gold, u, 0); - EXPECT_MAT_NEAR(v_gold, v, 0); + EXPECT_MAT_SIMILAR(u_gold, u, 1e-3); + EXPECT_MAT_SIMILAR(v_gold, v, 1e-3); #else std::ofstream f(fname.c_str(), std::ios_base::binary); From 4a770535c475be597f7487833927f0d5ab084988 Mon Sep 17 00:00:00 2001 From: Vladislav Vinogradov Date: Tue, 4 Jun 2013 14:59:47 +0400 Subject: [PATCH 071/178] fixed BoxFilter sanity test (different rounding results) --- modules/gpu/perf/perf_filters.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/gpu/perf/perf_filters.cpp b/modules/gpu/perf/perf_filters.cpp index 40d88aad45..adfc294f6d 100644 --- a/modules/gpu/perf/perf_filters.cpp +++ b/modules/gpu/perf/perf_filters.cpp @@ -72,7 +72,7 @@ PERF_TEST_P(Sz_Type_KernelSz, Filters_Blur, TEST_CYCLE() cv::gpu::blur(d_src, dst, cv::Size(ksize, ksize)); - GPU_SANITY_CHECK(dst); + GPU_SANITY_CHECK(dst, 1); } else { From 89f3c40d791a6dd1951fd23ba7c1d9a8df8f1454 Mon Sep 17 00:00:00 2001 From: Vladislav Vinogradov Date: Tue, 4 Jun 2013 15:01:06 +0400 Subject: [PATCH 072/178] fixed BroxOpticalFlow sanity test (increase epsilon value) + interpolateFrames and createOpticalFlowNeedleMap --- modules/gpu/perf/perf_video.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/gpu/perf/perf_video.cpp b/modules/gpu/perf/perf_video.cpp index 1ab01a75be..672d657b21 100644 --- a/modules/gpu/perf/perf_video.cpp +++ b/modules/gpu/perf/perf_video.cpp @@ -103,7 +103,7 @@ PERF_TEST_P(ImagePair, Video_InterpolateFrames, TEST_CYCLE() cv::gpu::interpolateFrames(d_frame0, d_frame1, d_fu, d_fv, d_bu, d_bv, 0.5f, newFrame, d_buf); - GPU_SANITY_CHECK(newFrame); + GPU_SANITY_CHECK(newFrame, 1e-4); } else { @@ -142,7 +142,7 @@ PERF_TEST_P(ImagePair, Video_CreateOpticalFlowNeedleMap, TEST_CYCLE() cv::gpu::createOpticalFlowNeedleMap(u, v, vertex, colors); - GPU_SANITY_CHECK(vertex); + GPU_SANITY_CHECK(vertex, 1e-6); GPU_SANITY_CHECK(colors); } else @@ -219,8 +219,8 @@ PERF_TEST_P(ImagePair, Video_BroxOpticalFlow, TEST_CYCLE() d_flow(d_frame0, d_frame1, u, v); - GPU_SANITY_CHECK(u); - GPU_SANITY_CHECK(v); + GPU_SANITY_CHECK(u, 1e-1); + GPU_SANITY_CHECK(v, 1e-1); } else { From 93a44d423618c7c6c04135dd99b47209cc88ead6 Mon Sep 17 00:00:00 2001 From: Roman Donchenko Date: Wed, 5 Jun 2013 13:51:11 +0400 Subject: [PATCH 073/178] Fix typo in .gitattributes. --- .gitattributes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitattributes b/.gitattributes index af704cdf0c..cd4359ba34 100644 --- a/.gitattributes +++ b/.gitattributes @@ -33,7 +33,7 @@ CMakeLists.txt text whitespace=tabwidth=2 *.png binary -*.jepg binary +*.jpeg binary *.jpg binary *.exr binary *.ico binary From 31a5f7ef3c2187b82a91290a430651adcc0b19d1 Mon Sep 17 00:00:00 2001 From: Vladislav Vinogradov Date: Wed, 5 Jun 2013 14:08:55 +0400 Subject: [PATCH 074/178] fixed bug #3069 (infinite loop in GPU LBP Cascade detectMultiScale) --- modules/gpu/src/cascadeclassifier.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/gpu/src/cascadeclassifier.cpp b/modules/gpu/src/cascadeclassifier.cpp index 814a96bc0a..7b95b69091 100644 --- a/modules/gpu/src/cascadeclassifier.cpp +++ b/modules/gpu/src/cascadeclassifier.cpp @@ -406,7 +406,7 @@ public: GpuMat dclassified(1, 1, CV_32S); cudaSafeCall( cudaMemcpy(dclassified.ptr(), &classified, sizeof(int), cudaMemcpyHostToDevice) ); - PyrLavel level(0, 1.0f, image.size(), NxM, minObjectSize); + PyrLavel level(0, scaleFactor, image.size(), NxM, minObjectSize); while (level.isFeasible(maxObjectSize)) { From a2adafd5089065d44f82933c45cc4e7591141ed9 Mon Sep 17 00:00:00 2001 From: caorong Date: Thu, 16 May 2013 11:26:37 +0800 Subject: [PATCH 075/178] fix a bug(DetectorType never change) changed line281 -> line220 Presentation: because line 220 give the globle var mDetectorType,and in line 230 it will be compared with mDeteorType !!! it will never be unequal ~ fix: change mDetectorType(previous globle var) to a new local val tmpDetectorType --- .../src/org/opencv/samples/facedetect/FdActivity.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/samples/android/face-detection/src/org/opencv/samples/facedetect/FdActivity.java b/samples/android/face-detection/src/org/opencv/samples/facedetect/FdActivity.java index 23727739e4..b06b2cc1c5 100644 --- a/samples/android/face-detection/src/org/opencv/samples/facedetect/FdActivity.java +++ b/samples/android/face-detection/src/org/opencv/samples/facedetect/FdActivity.java @@ -215,9 +215,9 @@ public class FdActivity extends Activity implements CvCameraViewListener2 { else if (item == mItemFace20) setMinFaceSize(0.2f); else if (item == mItemType) { - mDetectorType = (mDetectorType + 1) % mDetectorName.length; - item.setTitle(mDetectorName[mDetectorType]); - setDetectorType(mDetectorType); + int tmpDetectorType = (mDetectorType + 1) % mDetectorName.length; + item.setTitle(mDetectorName[tmpDetectorType]); + setDetectorType(tmpDetectorType); } return true; } From 985bfea556ff0511eb16d99ef83a6ef78c80e38d Mon Sep 17 00:00:00 2001 From: Roman Donchenko Date: Wed, 5 Jun 2013 15:54:27 +0400 Subject: [PATCH 076/178] Don't resolve symlinks when looking for modules. We don't really need it, it makes the code longer, and it can lead to inconsistent paths when OpenCV is itself inside a symlink. --- cmake/OpenCVModule.cmake | 4 ++-- cmake/OpenCVUtils.cmake | 10 ---------- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/cmake/OpenCVModule.cmake b/cmake/OpenCVModule.cmake index 8312845fe0..e8619ad7dd 100644 --- a/cmake/OpenCVModule.cmake +++ b/cmake/OpenCVModule.cmake @@ -303,7 +303,7 @@ macro(ocv_glob_modules) # collect modules set(OPENCV_INITIAL_PASS ON) foreach(__path ${ARGN}) - ocv_get_real_path(__path "${__path}") + get_filename_component(__path "${__path}" ABSOLUTE) list(FIND __directories_observed "${__path}" __pathIdx) if(__pathIdx GREATER -1) @@ -315,7 +315,7 @@ macro(ocv_glob_modules) if(__ocvmodules) list(SORT __ocvmodules) foreach(mod ${__ocvmodules}) - ocv_get_real_path(__modpath "${__path}/${mod}") + get_filename_component(__modpath "${__path}/${mod}" ABSOLUTE) if(EXISTS "${__modpath}/CMakeLists.txt") list(FIND __directories_observed "${__modpath}" __pathIdx) diff --git a/cmake/OpenCVUtils.cmake b/cmake/OpenCVUtils.cmake index db24c99708..5547a113c7 100644 --- a/cmake/OpenCVUtils.cmake +++ b/cmake/OpenCVUtils.cmake @@ -411,16 +411,6 @@ macro(ocv_regex_escape var regex) endmacro() -# get absolute path with symlinks resolved -macro(ocv_get_real_path VAR PATHSTR) - if(CMAKE_VERSION VERSION_LESS 2.8) - get_filename_component(${VAR} "${PATHSTR}" ABSOLUTE) - else() - get_filename_component(${VAR} "${PATHSTR}" REALPATH) - endif() -endmacro() - - # convert list of paths to full paths macro(ocv_convert_to_full_paths VAR) if(${VAR}) From a954d3630f8a567317a105300c8ccd735f2550a4 Mon Sep 17 00:00:00 2001 From: Roman Donchenko Date: Wed, 5 Jun 2013 18:09:47 +0400 Subject: [PATCH 077/178] Add support for adding custom OpenCV modules. --- CMakeLists.txt | 4 ++++ modules/CMakeLists.txt | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5f1edaa537..2c735e0051 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -286,6 +286,10 @@ set(OPENCV_CONFIG_FILE_INCLUDE_DIR "${CMAKE_BINARY_DIR}/" CACHE PATH "Where to c add_definitions(-DHAVE_CVCONFIG_H) ocv_include_directories(${OPENCV_CONFIG_FILE_INCLUDE_DIR}) +# ---------------------------------------------------------------------------- +# Path for additional modules +# ---------------------------------------------------------------------------- +set(OPENCV_EXTRA_MODULES_PATH "" CACHE PATH "Where to look for additional OpenCV modules") # ---------------------------------------------------------------------------- # Autodetect if we are in a GIT repository diff --git a/modules/CMakeLists.txt b/modules/CMakeLists.txt index 4a6ed6d11e..3e1ad708e6 100644 --- a/modules/CMakeLists.txt +++ b/modules/CMakeLists.txt @@ -2,4 +2,4 @@ if(NOT OPENCV_MODULES_PATH) set(OPENCV_MODULES_PATH "${CMAKE_CURRENT_SOURCE_DIR}") endif() -ocv_glob_modules(${OPENCV_MODULES_PATH}) +ocv_glob_modules(${OPENCV_MODULES_PATH} ${OPENCV_EXTRA_MODULES_PATH}) From 429f84e59e45a66f56540e67f5ab3a9c8dc34249 Mon Sep 17 00:00:00 2001 From: peng xiao Date: Thu, 6 Jun 2013 11:44:35 +0800 Subject: [PATCH 078/178] Fix a bug of cornerHarris and cornerMinEigenVal. The bug is a buffer overrun when border type is reflect101. It is found that gfft crashed with input of size 100x100 on Intel CPU. --- modules/ocl/src/opencl/imgproc_calcHarris.cl | 15 ++++++++------- .../ocl/src/opencl/imgproc_calcMinEigenVal.cl | 16 +++++++++------- modules/ocl/test/test_optflow.cpp | 2 +- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/modules/ocl/src/opencl/imgproc_calcHarris.cl b/modules/ocl/src/opencl/imgproc_calcHarris.cl index 15742d6c5e..1911a72016 100644 --- a/modules/ocl/src/opencl/imgproc_calcHarris.cl +++ b/modules/ocl/src/opencl/imgproc_calcHarris.cl @@ -130,28 +130,29 @@ __kernel void calcHarris(__global const float *Dx,__global const float *Dy, __gl data[2][i] = dy_data[i] * dy_data[i]; } #else - for(int i=0; i < ksY+1; i++) - { + int clamped_col = min(dst_cols, col); + for(int i=0; i < ksY+1; i++) + { int dx_selected_row; int dx_selected_col; dx_selected_row = ADDR_H(dx_startY+i, 0, dx_whole_rows); dx_selected_row = ADDR_B(dx_startY+i, dx_whole_rows, dx_selected_row); - dx_selected_col = ADDR_L(dx_startX+col, 0, dx_whole_cols); - dx_selected_col = ADDR_R(dx_startX+col, dx_whole_cols, dx_selected_col); + dx_selected_col = ADDR_L(dx_startX+clamped_col, 0, dx_whole_cols); + dx_selected_col = ADDR_R(dx_startX+clamped_col, dx_whole_cols, dx_selected_col); dx_data[i] = Dx[dx_selected_row * (dx_step>>2) + dx_selected_col]; int dy_selected_row; int dy_selected_col; dy_selected_row = ADDR_H(dy_startY+i, 0, dy_whole_rows); dy_selected_row = ADDR_B(dy_startY+i, dy_whole_rows, dy_selected_row); - dy_selected_col = ADDR_L(dy_startX+col, 0, dy_whole_cols); - dy_selected_col = ADDR_R(dy_startX+col, dy_whole_cols, dy_selected_col); + dy_selected_col = ADDR_L(dy_startX+clamped_col, 0, dy_whole_cols); + dy_selected_col = ADDR_R(dy_startX+clamped_col, dy_whole_cols, dy_selected_col); dy_data[i] = Dy[dy_selected_row * (dy_step>>2) + dy_selected_col]; data[0][i] = dx_data[i] * dx_data[i]; data[1][i] = dx_data[i] * dy_data[i]; data[2][i] = dy_data[i] * dy_data[i]; - } + } #endif float sum0 = 0.0, sum1 = 0.0, sum2 = 0.0; for(int i=1; i < ksY; i++) diff --git a/modules/ocl/src/opencl/imgproc_calcMinEigenVal.cl b/modules/ocl/src/opencl/imgproc_calcMinEigenVal.cl index 662fbb07b9..462ec77925 100644 --- a/modules/ocl/src/opencl/imgproc_calcMinEigenVal.cl +++ b/modules/ocl/src/opencl/imgproc_calcMinEigenVal.cl @@ -130,28 +130,30 @@ __kernel void calcMinEigenVal(__global const float *Dx,__global const float *Dy, data[2][i] = dy_data[i] * dy_data[i]; } #else - for(int i=0; i < ksY+1; i++) - { + int clamped_col = min(dst_cols, col); + + for(int i=0; i < ksY+1; i++) + { int dx_selected_row; int dx_selected_col; dx_selected_row = ADDR_H(dx_startY+i, 0, dx_whole_rows); dx_selected_row = ADDR_B(dx_startY+i, dx_whole_rows, dx_selected_row); - dx_selected_col = ADDR_L(dx_startX+col, 0, dx_whole_cols); - dx_selected_col = ADDR_R(dx_startX+col, dx_whole_cols, dx_selected_col); + dx_selected_col = ADDR_L(dx_startX+clamped_col, 0, dx_whole_cols); + dx_selected_col = ADDR_R(dx_startX+clamped_col, dx_whole_cols, dx_selected_col); dx_data[i] = Dx[dx_selected_row * (dx_step>>2) + dx_selected_col]; int dy_selected_row; int dy_selected_col; dy_selected_row = ADDR_H(dy_startY+i, 0, dy_whole_rows); dy_selected_row = ADDR_B(dy_startY+i, dy_whole_rows, dy_selected_row); - dy_selected_col = ADDR_L(dy_startX+col, 0, dy_whole_cols); - dy_selected_col = ADDR_R(dy_startX+col, dy_whole_cols, dy_selected_col); + dy_selected_col = ADDR_L(dy_startX+clamped_col, 0, dy_whole_cols); + dy_selected_col = ADDR_R(dy_startX+clamped_col, dy_whole_cols, dy_selected_col); dy_data[i] = Dy[dy_selected_row * (dy_step>>2) + dy_selected_col]; data[0][i] = dx_data[i] * dx_data[i]; data[1][i] = dx_data[i] * dy_data[i]; data[2][i] = dy_data[i] * dy_data[i]; - } + } #endif float sum0 = 0.0, sum1 = 0.0, sum2 = 0.0; for(int i=1; i < ksY; i++) diff --git a/modules/ocl/test/test_optflow.cpp b/modules/ocl/test/test_optflow.cpp index 72689f3f77..0121be8f9e 100644 --- a/modules/ocl/test/test_optflow.cpp +++ b/modules/ocl/test/test_optflow.cpp @@ -121,7 +121,7 @@ TEST_P(GoodFeaturesToTrack, EmptyCorners) cv::ocl::GoodFeaturesToTrackDetector_OCL detector(maxCorners, qualityLevel, minDistance); - cv::ocl::oclMat src(100, 128, CV_8UC1, cv::Scalar::all(0)); + cv::ocl::oclMat src(100, 100, CV_8UC1, cv::Scalar::all(0)); cv::ocl::oclMat corners(1, maxCorners, CV_32FC2); detector(src, corners); From 8714cbac91e6e8f90395d9af3fd57f3c6b35235b Mon Sep 17 00:00:00 2001 From: Roman Donchenko Date: Thu, 6 Jun 2013 14:09:33 +0400 Subject: [PATCH 079/178] Fix a missing header path when building with Qt 4. Also, removing explicit include path configuration, since QT_USE_FILE takes care of that. --- cmake/OpenCVFindLibsGUI.cmake | 2 +- modules/highgui/CMakeLists.txt | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/cmake/OpenCVFindLibsGUI.cmake b/cmake/OpenCVFindLibsGUI.cmake index 2ea864c16f..59ce1cd05d 100644 --- a/cmake/OpenCVFindLibsGUI.cmake +++ b/cmake/OpenCVFindLibsGUI.cmake @@ -33,7 +33,7 @@ if(WITH_QT) endif() if(NOT HAVE_QT) - find_package(Qt4) + find_package(Qt4 REQUIRED QtCore QtGui QtTest) if(QT4_FOUND) set(HAVE_QT TRUE) add_definitions(-DHAVE_QT) # We need to define the macro this way, using cvconfig.h does not work diff --git a/modules/highgui/CMakeLists.txt b/modules/highgui/CMakeLists.txt index 4c60867af3..fad2562c88 100644 --- a/modules/highgui/CMakeLists.txt +++ b/modules/highgui/CMakeLists.txt @@ -95,14 +95,10 @@ elseif(HAVE_QT) endif() include(${QT_USE_FILE}) - if(QT_INCLUDE_DIR) - ocv_include_directories(${QT_INCLUDE_DIR}) - endif() - QT4_ADD_RESOURCES(_RCC_OUTFILES src/window_QT.qrc) QT4_WRAP_CPP(_MOC_OUTFILES src/window_QT.h) - list(APPEND HIGHGUI_LIBRARIES ${QT_LIBRARIES} ${QT_QTTEST_LIBRARY}) + list(APPEND HIGHGUI_LIBRARIES ${QT_LIBRARIES}) list(APPEND highgui_srcs src/window_QT.cpp ${_MOC_OUTFILES} ${_RCC_OUTFILES}) ocv_check_flag_support(CXX -Wno-missing-declarations _have_flag) if(${_have_flag}) From 9a1cc06ebe8039ee5be508f4bade8e038c1a31e8 Mon Sep 17 00:00:00 2001 From: Leszek Swirski Date: Tue, 21 May 2013 17:53:36 +0100 Subject: [PATCH 080/178] Fix pixel value rendering for non-fixed-size QT windows --- modules/highgui/src/window_QT.cpp | 67 +++++++++++++++---------------- 1 file changed, 33 insertions(+), 34 deletions(-) diff --git a/modules/highgui/src/window_QT.cpp b/modules/highgui/src/window_QT.cpp index 50f2b9e787..438c356f73 100644 --- a/modules/highgui/src/window_QT.cpp +++ b/modules/highgui/src/window_QT.cpp @@ -2651,17 +2651,16 @@ void DefaultViewPort::paintEvent(QPaintEvent* evnt) //Now disable matrixWorld for overlay display myPainter.setWorldMatrixEnabled(false); + //overlay pixel values if zoomed in far enough + if (param_matrixWorld.m11()*ratioX >= threshold_zoom_img_region && + param_matrixWorld.m11()*ratioY >= threshold_zoom_img_region) + { + drawImgRegion(&myPainter); + } + //in mode zoom/panning if (param_matrixWorld.m11() > 1) { - if (param_matrixWorld.m11() >= threshold_zoom_img_region) - { - if (centralWidget->param_flags == CV_WINDOW_NORMAL) - startDisplayInfo("WARNING: The values displayed are the resized image's values. If you want the original image's values, use CV_WINDOW_AUTOSIZE", 1000); - - drawImgRegion(&myPainter); - } - drawViewOverview(&myPainter); } @@ -2887,22 +2886,24 @@ void DefaultViewPort::drawStatusBar() //accept only CV_8UC1 and CV_8UC8 image for now void DefaultViewPort::drawImgRegion(QPainter *painter) { - if (nbChannelOriginImage!=CV_8UC1 && nbChannelOriginImage!=CV_8UC3) return; - qreal offsetX = param_matrixWorld.dx()/param_matrixWorld.m11(); + double pixel_width = param_matrixWorld.m11()*ratioX; + double pixel_height = param_matrixWorld.m11()*ratioY; + + qreal offsetX = param_matrixWorld.dx()/pixel_width; offsetX = offsetX - floor(offsetX); - qreal offsetY = param_matrixWorld.dy()/param_matrixWorld.m11(); + qreal offsetY = param_matrixWorld.dy()/pixel_height; offsetY = offsetY - floor(offsetY); QSize view = size(); QVarLengthArray linesX; - for (qreal _x = offsetX*param_matrixWorld.m11(); _x < view.width(); _x += param_matrixWorld.m11() ) + for (qreal _x = offsetX*pixel_width; _x < view.width(); _x += pixel_width ) linesX.append(QLineF(_x, 0, _x, view.height())); QVarLengthArray linesY; - for (qreal _y = offsetY*param_matrixWorld.m11(); _y < view.height(); _y += param_matrixWorld.m11() ) + for (qreal _y = offsetY*pixel_height; _y < view.height(); _y += pixel_height ) linesY.append(QLineF(0, _y, view.width(), _y)); @@ -2910,27 +2911,25 @@ void DefaultViewPort::drawImgRegion(QPainter *painter) int original_font_size = f.pointSize(); //change font size //f.setPointSize(4+(param_matrixWorld.m11()-threshold_zoom_img_region)/5); - f.setPixelSize(10+(param_matrixWorld.m11()-threshold_zoom_img_region)/5); + f.setPixelSize(10+(pixel_height-threshold_zoom_img_region)/5); painter->setFont(f); - QString val; - QRgb rgbValue; - QPointF point1;//sorry, I do not know how to name it - QPointF point2;//idem - for (int j=-1;j= 0 && point2.y() >= 0) - rgbValue = image2Draw_qt_resized.pixel(QPoint(point2.x(),point2.y())); + QRgb rgbValue; + if (image2Draw_qt.valid(point_in_image)) + rgbValue = image2Draw_qt.pixel(point_in_image); else rgbValue = qRgb(0,0,0); @@ -2943,29 +2942,29 @@ void DefaultViewPort::drawImgRegion(QPainter *painter) painter->drawText(QRect(point1.x(),point1.y(),param_matrixWorld.m11(),param_matrixWorld.m11()/2), Qt::AlignCenter, val); */ + QString val; val = tr("%1").arg(qRed(rgbValue)); painter->setPen(QPen(Qt::red, 1)); - painter->drawText(QRect(point1.x(),point1.y(),param_matrixWorld.m11(),param_matrixWorld.m11()/3), + painter->drawText(QRect(pos_in_view.x(),pos_in_view.y(),pixel_width,pixel_height/3), Qt::AlignCenter, val); val = tr("%1").arg(qGreen(rgbValue)); painter->setPen(QPen(Qt::green, 1)); - painter->drawText(QRect(point1.x(),point1.y()+param_matrixWorld.m11()/3,param_matrixWorld.m11(),param_matrixWorld.m11()/3), + painter->drawText(QRect(pos_in_view.x(),pos_in_view.y()+pixel_height/3,pixel_width,pixel_height/3), Qt::AlignCenter, val); val = tr("%1").arg(qBlue(rgbValue)); painter->setPen(QPen(Qt::blue, 1)); - painter->drawText(QRect(point1.x(),point1.y()+2*param_matrixWorld.m11()/3,param_matrixWorld.m11(),param_matrixWorld.m11()/3), + painter->drawText(QRect(pos_in_view.x(),pos_in_view.y()+2*pixel_height/3,pixel_width,pixel_height/3), Qt::AlignCenter, val); } if (nbChannelOriginImage==CV_8UC1) { - - val = tr("%1").arg(qRed(rgbValue)); - painter->drawText(QRect(point1.x(),point1.y(),param_matrixWorld.m11(),param_matrixWorld.m11()), + QString val = tr("%1").arg(qRed(rgbValue)); + painter->drawText(QRect(pos_in_view.x(),pos_in_view.y(),pixel_width,pixel_height), Qt::AlignCenter, val); } } From 7d0f6b4d68b37234acdb0a399e2e95b9a7d39143 Mon Sep 17 00:00:00 2001 From: Leszek Swirski Date: Tue, 21 May 2013 17:54:58 +0100 Subject: [PATCH 081/178] Fix image saving from QT toolbar --- modules/highgui/src/window_QT.cpp | 18 ++++++++---------- modules/highgui/src/window_QT.h | 1 - 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/modules/highgui/src/window_QT.cpp b/modules/highgui/src/window_QT.cpp index 438c356f73..0c50c7070c 100644 --- a/modules/highgui/src/window_QT.cpp +++ b/modules/highgui/src/window_QT.cpp @@ -2473,35 +2473,33 @@ void DefaultViewPort::saveView() if (!fileName.isEmpty()) //save the picture { QString extension = fileName.right(3); - - // (no need anymore) create the image resized to receive the 'screenshot' - // image2Draw_qt_resized = QImage(viewport()->width(), viewport()->height(),QImage::Format_RGB888); - - QPainter saveimage(&image2Draw_qt_resized); - this->render(&saveimage); + + // Create a new pixmap to render the viewport into + QPixmap viewportPixmap(viewport()->size()); + viewport()->render(&viewportPixmap); // Save it.. if (QString::compare(extension, "png", Qt::CaseInsensitive) == 0) { - image2Draw_qt_resized.save(fileName, "PNG"); + viewportPixmap.save(fileName, "PNG"); return; } if (QString::compare(extension, "jpg", Qt::CaseInsensitive) == 0) { - image2Draw_qt_resized.save(fileName, "JPG"); + viewportPixmap.save(fileName, "JPG"); return; } if (QString::compare(extension, "bmp", Qt::CaseInsensitive) == 0) { - image2Draw_qt_resized.save(fileName, "BMP"); + viewportPixmap.save(fileName, "BMP"); return; } if (QString::compare(extension, "jpeg", Qt::CaseInsensitive) == 0) { - image2Draw_qt_resized.save(fileName, "JPEG"); + viewportPixmap.save(fileName, "JPEG"); return; } diff --git a/modules/highgui/src/window_QT.h b/modules/highgui/src/window_QT.h index 089997f514..a96a8c6e69 100644 --- a/modules/highgui/src/window_QT.h +++ b/modules/highgui/src/window_QT.h @@ -522,7 +522,6 @@ private: CvMat* image2Draw_mat; QImage image2Draw_qt; - QImage image2Draw_qt_resized; int nbChannelOriginImage; //for mouse callback From ab6be9b7b7691967e42297aa6d3a67fb07597fd8 Mon Sep 17 00:00:00 2001 From: Peter Minin Date: Thu, 6 Jun 2013 19:00:55 +0400 Subject: [PATCH 082/178] Add a variant of detectMultiScale with an argument 'weights' that receives the number of neighbors joined into each detected object --- .../objdetect/doc/cascade_classification.rst | 3 +++ .../include/opencv2/objdetect/objdetect.hpp | 11 ++++++++- modules/objdetect/src/cascadedetect.cpp | 24 ++++++++++++++++++- 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/modules/objdetect/doc/cascade_classification.rst b/modules/objdetect/doc/cascade_classification.rst index eb07a6c8f7..a00bdc933a 100644 --- a/modules/objdetect/doc/cascade_classification.rst +++ b/modules/objdetect/doc/cascade_classification.rst @@ -189,6 +189,7 @@ CascadeClassifier::detectMultiScale Detects objects of different sizes in the input image. The detected objects are returned as a list of rectangles. .. ocv:function:: void CascadeClassifier::detectMultiScale( const Mat& image, vector& objects, double scaleFactor=1.1, int minNeighbors=3, int flags=0, Size minSize=Size(), Size maxSize=Size()) +.. ocv:function:: void CascadeClassifier::detectMultiScale( const Mat& image, vector& objects, vector& weights, double scaleFactor=1.1, int minNeighbors=3, int flags=0, Size minSize=Size(), Size maxSize=Size()) .. ocv:pyfunction:: cv2.CascadeClassifier.detectMultiScale(image[, scaleFactor[, minNeighbors[, flags[, minSize[, maxSize]]]]]) -> objects .. ocv:pyfunction:: cv2.CascadeClassifier.detectMultiScale(image, rejectLevels, levelWeights[, scaleFactor[, minNeighbors[, flags[, minSize[, maxSize[, outputRejectLevels]]]]]]) -> objects @@ -203,6 +204,8 @@ Detects objects of different sizes in the input image. The detected objects are :param objects: Vector of rectangles where each rectangle contains the detected object. + :param weights: Vector of weights of the corresponding objects. Weight is the number of neighboring positively classified rectangles that were joined into one object. + :param scaleFactor: Parameter specifying how much the image size is reduced at each image scale. :param minNeighbors: Parameter specifying how many neighbors each candidate rectangle should have to retain it. diff --git a/modules/objdetect/include/opencv2/objdetect/objdetect.hpp b/modules/objdetect/include/opencv2/objdetect/objdetect.hpp index 8d7efb0ba4..7924b67e5d 100644 --- a/modules/objdetect/include/opencv2/objdetect/objdetect.hpp +++ b/modules/objdetect/include/opencv2/objdetect/objdetect.hpp @@ -382,6 +382,14 @@ public: Size minSize=Size(), Size maxSize=Size() ); + CV_WRAP virtual void detectMultiScale( const Mat& image, + CV_OUT vector& objects, + vector& weights, + double scaleFactor=1.1, + int minNeighbors=3, int flags=0, + Size minSize=Size(), + Size maxSize=Size() ); + CV_WRAP virtual void detectMultiScale( const Mat& image, CV_OUT vector& objects, vector& rejectLevels, @@ -390,7 +398,8 @@ public: int minNeighbors=3, int flags=0, Size minSize=Size(), Size maxSize=Size(), - bool outputRejectLevels=false ); + bool outputRejectLevels=false, + bool outputWeights=false ); bool isOldFormatCascade() const; diff --git a/modules/objdetect/src/cascadedetect.cpp b/modules/objdetect/src/cascadedetect.cpp index 9e78dce243..341ef2a0d6 100644 --- a/modules/objdetect/src/cascadedetect.cpp +++ b/modules/objdetect/src/cascadedetect.cpp @@ -1023,6 +1023,7 @@ public: }; struct getRect { Rect operator ()(const CvAvgComp& e) const { return e.rect; } }; +struct getNeighbors { int operator ()(const CvAvgComp& e) const { return e.neighbors; } }; bool CascadeClassifier::detectSingleScale( const Mat& image, int stripCount, Size processingRectSize, @@ -1092,11 +1093,12 @@ void CascadeClassifier::detectMultiScale( const Mat& image, vector& object vector& levelWeights, double scaleFactor, int minNeighbors, int flags, Size minObjectSize, Size maxObjectSize, - bool outputRejectLevels ) + bool outputRejectLevels, bool outputWeights ) { const double GROUP_EPS = 0.2; CV_Assert( scaleFactor > 1 && image.depth() == CV_8U ); + CV_Assert( !( outputRejectLevels && outputWeights ) ); if( empty() ) return; @@ -1111,6 +1113,12 @@ void CascadeClassifier::detectMultiScale( const Mat& image, vector& object Seq(_objects).copyTo(vecAvgComp); objects.resize(vecAvgComp.size()); std::transform(vecAvgComp.begin(), vecAvgComp.end(), objects.begin(), getRect()); + if( outputWeights ) + { + rejectLevels.resize(vecAvgComp.size()); + std::transform(vecAvgComp.begin(), vecAvgComp.end(), rejectLevels.begin(), + getNeighbors()); + } return; } @@ -1183,6 +1191,10 @@ void CascadeClassifier::detectMultiScale( const Mat& image, vector& object { groupRectangles( objects, rejectLevels, levelWeights, minNeighbors, GROUP_EPS ); } + else if( outputWeights ) + { + groupRectangles( objects, rejectLevels, minNeighbors, GROUP_EPS ); + } else { groupRectangles( objects, minNeighbors, GROUP_EPS ); @@ -1199,6 +1211,16 @@ void CascadeClassifier::detectMultiScale( const Mat& image, vector& object minNeighbors, flags, minObjectSize, maxObjectSize, false ); } +void CascadeClassifier::detectMultiScale( const Mat& image, CV_OUT vector& objects, + vector& weights, double scaleFactor, + int minNeighbors, int flags, Size minObjectSize, + Size maxObjectSize ) +{ + vector fakeLevelWeights; + detectMultiScale( image, objects, weights, fakeLevelWeights, scaleFactor, + minNeighbors, flags, minObjectSize, maxObjectSize, false, true ); +} + bool CascadeClassifier::Data::read(const FileNode &root) { static const float THRESHOLD_EPS = 1e-5f; From 33d1f675015b22b7f4a938a934c0448b21c731ee Mon Sep 17 00:00:00 2001 From: Roman Donchenko Date: Fri, 7 Jun 2013 19:07:00 +0400 Subject: [PATCH 083/178] Include the OpenCV config headers into every module. This has no bearing on compilation, but it makes them show up in IDEs. --- cmake/OpenCVModule.cmake | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmake/OpenCVModule.cmake b/cmake/OpenCVModule.cmake index e8619ad7dd..81340bd0eb 100644 --- a/cmake/OpenCVModule.cmake +++ b/cmake/OpenCVModule.cmake @@ -470,7 +470,8 @@ endmacro() # ocv_create_module() # ocv_create_module(SKIP_LINK) macro(ocv_create_module) - add_library(${the_module} ${OPENCV_MODULE_TYPE} ${OPENCV_MODULE_${the_module}_HEADERS} ${OPENCV_MODULE_${the_module}_SOURCES}) + add_library(${the_module} ${OPENCV_MODULE_TYPE} ${OPENCV_MODULE_${the_module}_HEADERS} ${OPENCV_MODULE_${the_module}_SOURCES} + "${OPENCV_CONFIG_FILE_INCLUDE_DIR}/cvconfig.h" "${OPENCV_CONFIG_FILE_INCLUDE_DIR}/opencv2/opencv_modules.hpp") if(NOT "${ARGN}" STREQUAL "SKIP_LINK") target_link_libraries(${the_module} ${OPENCV_MODULE_${the_module}_DEPS} ${OPENCV_MODULE_${the_module}_DEPS_EXT} ${OPENCV_LINKER_LIBS} ${IPP_LIBS} ${ARGN}) From 264d26e6718ed55df0f3f187908e3a28481bbe2b Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Sat, 8 Jun 2013 12:41:57 +0400 Subject: [PATCH 084/178] fixing empty Mat case --- modules/java/generator/src/java/core+MatOfByte.java | 4 ++-- modules/java/generator/src/java/core+MatOfDouble.java | 4 ++-- modules/java/generator/src/java/core+MatOfFloat.java | 4 ++-- modules/java/generator/src/java/core+MatOfFloat4.java | 4 ++-- modules/java/generator/src/java/core+MatOfFloat6.java | 4 ++-- modules/java/generator/src/java/core+MatOfInt.java | 4 ++-- modules/java/generator/src/java/core+MatOfInt4.java | 4 ++-- modules/java/generator/src/java/core+MatOfKeyPoint.java | 4 ++-- modules/java/generator/src/java/core+MatOfPoint.java | 4 ++-- modules/java/generator/src/java/core+MatOfPoint2f.java | 4 ++-- modules/java/generator/src/java/core+MatOfPoint3.java | 4 ++-- modules/java/generator/src/java/core+MatOfPoint3f.java | 4 ++-- modules/java/generator/src/java/core+MatOfRect.java | 4 ++-- 13 files changed, 26 insertions(+), 26 deletions(-) diff --git a/modules/java/generator/src/java/core+MatOfByte.java b/modules/java/generator/src/java/core+MatOfByte.java index 0ebdb66733..b3fe5691ee 100644 --- a/modules/java/generator/src/java/core+MatOfByte.java +++ b/modules/java/generator/src/java/core+MatOfByte.java @@ -14,7 +14,7 @@ public class MatOfByte extends Mat { protected MatOfByte(long addr) { super(addr); - if(checkVector(_channels, _depth) < 0 ) + if( !empty() && checkVector(_channels, _depth) < 0 ) throw new IllegalArgumentException("Incomatible Mat"); //FIXME: do we need release() here? } @@ -25,7 +25,7 @@ public class MatOfByte extends Mat { public MatOfByte(Mat m) { super(m, Range.all()); - if(checkVector(_channels, _depth) < 0 ) + if( !empty() && checkVector(_channels, _depth) < 0 ) throw new IllegalArgumentException("Incomatible Mat"); //FIXME: do we need release() here? } diff --git a/modules/java/generator/src/java/core+MatOfDouble.java b/modules/java/generator/src/java/core+MatOfDouble.java index cca5251105..4eb7cbc280 100644 --- a/modules/java/generator/src/java/core+MatOfDouble.java +++ b/modules/java/generator/src/java/core+MatOfDouble.java @@ -14,7 +14,7 @@ public class MatOfDouble extends Mat { protected MatOfDouble(long addr) { super(addr); - if(checkVector(_channels, _depth) < 0 ) + if( !empty() && checkVector(_channels, _depth) < 0 ) throw new IllegalArgumentException("Incomatible Mat"); //FIXME: do we need release() here? } @@ -25,7 +25,7 @@ public class MatOfDouble extends Mat { public MatOfDouble(Mat m) { super(m, Range.all()); - if(checkVector(_channels, _depth) < 0 ) + if( !empty() && checkVector(_channels, _depth) < 0 ) throw new IllegalArgumentException("Incomatible Mat"); //FIXME: do we need release() here? } diff --git a/modules/java/generator/src/java/core+MatOfFloat.java b/modules/java/generator/src/java/core+MatOfFloat.java index ce73b6f638..96bbeab9fb 100644 --- a/modules/java/generator/src/java/core+MatOfFloat.java +++ b/modules/java/generator/src/java/core+MatOfFloat.java @@ -14,7 +14,7 @@ public class MatOfFloat extends Mat { protected MatOfFloat(long addr) { super(addr); - if(checkVector(_channels, _depth) < 0 ) + if( !empty() && checkVector(_channels, _depth) < 0 ) throw new IllegalArgumentException("Incomatible Mat"); //FIXME: do we need release() here? } @@ -25,7 +25,7 @@ public class MatOfFloat extends Mat { public MatOfFloat(Mat m) { super(m, Range.all()); - if(checkVector(_channels, _depth) < 0 ) + if( !empty() && checkVector(_channels, _depth) < 0 ) throw new IllegalArgumentException("Incomatible Mat"); //FIXME: do we need release() here? } diff --git a/modules/java/generator/src/java/core+MatOfFloat4.java b/modules/java/generator/src/java/core+MatOfFloat4.java index 8a3e51014f..aaa97b7990 100644 --- a/modules/java/generator/src/java/core+MatOfFloat4.java +++ b/modules/java/generator/src/java/core+MatOfFloat4.java @@ -14,7 +14,7 @@ public class MatOfFloat4 extends Mat { protected MatOfFloat4(long addr) { super(addr); - if(checkVector(_channels, _depth) < 0 ) + if( !empty() && checkVector(_channels, _depth) < 0 ) throw new IllegalArgumentException("Incomatible Mat"); //FIXME: do we need release() here? } @@ -25,7 +25,7 @@ public class MatOfFloat4 extends Mat { public MatOfFloat4(Mat m) { super(m, Range.all()); - if(checkVector(_channels, _depth) < 0 ) + if( !empty() && checkVector(_channels, _depth) < 0 ) throw new IllegalArgumentException("Incomatible Mat"); //FIXME: do we need release() here? } diff --git a/modules/java/generator/src/java/core+MatOfFloat6.java b/modules/java/generator/src/java/core+MatOfFloat6.java index 1e23101a72..68e6249b6d 100644 --- a/modules/java/generator/src/java/core+MatOfFloat6.java +++ b/modules/java/generator/src/java/core+MatOfFloat6.java @@ -14,7 +14,7 @@ public class MatOfFloat6 extends Mat { protected MatOfFloat6(long addr) { super(addr); - if(checkVector(_channels, _depth) < 0 ) + if( !empty() && checkVector(_channels, _depth) < 0 ) throw new IllegalArgumentException("Incomatible Mat"); //FIXME: do we need release() here? } @@ -25,7 +25,7 @@ public class MatOfFloat6 extends Mat { public MatOfFloat6(Mat m) { super(m, Range.all()); - if(checkVector(_channels, _depth) < 0 ) + if( !empty() && checkVector(_channels, _depth) < 0 ) throw new IllegalArgumentException("Incomatible Mat"); //FIXME: do we need release() here? } diff --git a/modules/java/generator/src/java/core+MatOfInt.java b/modules/java/generator/src/java/core+MatOfInt.java index 80c5b3a5c2..33e5124e4f 100644 --- a/modules/java/generator/src/java/core+MatOfInt.java +++ b/modules/java/generator/src/java/core+MatOfInt.java @@ -15,7 +15,7 @@ public class MatOfInt extends Mat { protected MatOfInt(long addr) { super(addr); - if(checkVector(_channels, _depth) < 0 ) + if( !empty() && checkVector(_channels, _depth) < 0 ) throw new IllegalArgumentException("Incomatible Mat"); //FIXME: do we need release() here? } @@ -26,7 +26,7 @@ public class MatOfInt extends Mat { public MatOfInt(Mat m) { super(m, Range.all()); - if(checkVector(_channels, _depth) < 0 ) + if( !empty() && checkVector(_channels, _depth) < 0 ) throw new IllegalArgumentException("Incomatible Mat"); //FIXME: do we need release() here? } diff --git a/modules/java/generator/src/java/core+MatOfInt4.java b/modules/java/generator/src/java/core+MatOfInt4.java index 60277103cc..c924233a6c 100644 --- a/modules/java/generator/src/java/core+MatOfInt4.java +++ b/modules/java/generator/src/java/core+MatOfInt4.java @@ -15,7 +15,7 @@ public class MatOfInt4 extends Mat { protected MatOfInt4(long addr) { super(addr); - if(checkVector(_channels, _depth) < 0 ) + if( !empty() && checkVector(_channels, _depth) < 0 ) throw new IllegalArgumentException("Incomatible Mat"); //FIXME: do we need release() here? } @@ -26,7 +26,7 @@ public class MatOfInt4 extends Mat { public MatOfInt4(Mat m) { super(m, Range.all()); - if(checkVector(_channels, _depth) < 0 ) + if( !empty() && checkVector(_channels, _depth) < 0 ) throw new IllegalArgumentException("Incomatible Mat"); //FIXME: do we need release() here? } diff --git a/modules/java/generator/src/java/core+MatOfKeyPoint.java b/modules/java/generator/src/java/core+MatOfKeyPoint.java index b91fedcee8..b402fe1245 100644 --- a/modules/java/generator/src/java/core+MatOfKeyPoint.java +++ b/modules/java/generator/src/java/core+MatOfKeyPoint.java @@ -16,7 +16,7 @@ public class MatOfKeyPoint extends Mat { protected MatOfKeyPoint(long addr) { super(addr); - if(checkVector(_channels, _depth) < 0 ) + if( !empty() && checkVector(_channels, _depth) < 0 ) throw new IllegalArgumentException("Incomatible Mat"); //FIXME: do we need release() here? } @@ -27,7 +27,7 @@ public class MatOfKeyPoint extends Mat { public MatOfKeyPoint(Mat m) { super(m, Range.all()); - if(checkVector(_channels, _depth) < 0 ) + if( !empty() && checkVector(_channels, _depth) < 0 ) throw new IllegalArgumentException("Incomatible Mat"); //FIXME: do we need release() here? } diff --git a/modules/java/generator/src/java/core+MatOfPoint.java b/modules/java/generator/src/java/core+MatOfPoint.java index 23eeed0ebb..6d23ed1162 100644 --- a/modules/java/generator/src/java/core+MatOfPoint.java +++ b/modules/java/generator/src/java/core+MatOfPoint.java @@ -14,7 +14,7 @@ public class MatOfPoint extends Mat { protected MatOfPoint(long addr) { super(addr); - if(checkVector(_channels, _depth) < 0 ) + if( !empty() && checkVector(_channels, _depth) < 0 ) throw new IllegalArgumentException("Incomatible Mat"); //FIXME: do we need release() here? } @@ -25,7 +25,7 @@ public class MatOfPoint extends Mat { public MatOfPoint(Mat m) { super(m, Range.all()); - if(checkVector(_channels, _depth) < 0 ) + if( !empty() && checkVector(_channels, _depth) < 0 ) throw new IllegalArgumentException("Incomatible Mat"); //FIXME: do we need release() here? } diff --git a/modules/java/generator/src/java/core+MatOfPoint2f.java b/modules/java/generator/src/java/core+MatOfPoint2f.java index ba4be4ac5e..0c6960730b 100644 --- a/modules/java/generator/src/java/core+MatOfPoint2f.java +++ b/modules/java/generator/src/java/core+MatOfPoint2f.java @@ -14,7 +14,7 @@ public class MatOfPoint2f extends Mat { protected MatOfPoint2f(long addr) { super(addr); - if(checkVector(_channels, _depth) < 0 ) + if( !empty() && checkVector(_channels, _depth) < 0 ) throw new IllegalArgumentException("Incomatible Mat"); //FIXME: do we need release() here? } @@ -25,7 +25,7 @@ public class MatOfPoint2f extends Mat { public MatOfPoint2f(Mat m) { super(m, Range.all()); - if(checkVector(_channels, _depth) < 0 ) + if( !empty() && checkVector(_channels, _depth) < 0 ) throw new IllegalArgumentException("Incomatible Mat"); //FIXME: do we need release() here? } diff --git a/modules/java/generator/src/java/core+MatOfPoint3.java b/modules/java/generator/src/java/core+MatOfPoint3.java index 16e21301ef..0c8374f250 100644 --- a/modules/java/generator/src/java/core+MatOfPoint3.java +++ b/modules/java/generator/src/java/core+MatOfPoint3.java @@ -14,7 +14,7 @@ public class MatOfPoint3 extends Mat { protected MatOfPoint3(long addr) { super(addr); - if(checkVector(_channels, _depth) < 0 ) + if( !empty() && checkVector(_channels, _depth) < 0 ) throw new IllegalArgumentException("Incomatible Mat"); //FIXME: do we need release() here? } @@ -25,7 +25,7 @@ public class MatOfPoint3 extends Mat { public MatOfPoint3(Mat m) { super(m, Range.all()); - if(checkVector(_channels, _depth) < 0 ) + if( !empty() && checkVector(_channels, _depth) < 0 ) throw new IllegalArgumentException("Incomatible Mat"); //FIXME: do we need release() here? } diff --git a/modules/java/generator/src/java/core+MatOfPoint3f.java b/modules/java/generator/src/java/core+MatOfPoint3f.java index 97e2a95702..b0d50d4500 100644 --- a/modules/java/generator/src/java/core+MatOfPoint3f.java +++ b/modules/java/generator/src/java/core+MatOfPoint3f.java @@ -14,7 +14,7 @@ public class MatOfPoint3f extends Mat { protected MatOfPoint3f(long addr) { super(addr); - if(checkVector(_channels, _depth) < 0 ) + if( !empty() && checkVector(_channels, _depth) < 0 ) throw new IllegalArgumentException("Incomatible Mat"); //FIXME: do we need release() here? } @@ -25,7 +25,7 @@ public class MatOfPoint3f extends Mat { public MatOfPoint3f(Mat m) { super(m, Range.all()); - if(checkVector(_channels, _depth) < 0 ) + if( !empty() && checkVector(_channels, _depth) < 0 ) throw new IllegalArgumentException("Incomatible Mat"); //FIXME: do we need release() here? } diff --git a/modules/java/generator/src/java/core+MatOfRect.java b/modules/java/generator/src/java/core+MatOfRect.java index 2e58bfe897..3844d9dfbf 100644 --- a/modules/java/generator/src/java/core+MatOfRect.java +++ b/modules/java/generator/src/java/core+MatOfRect.java @@ -15,7 +15,7 @@ public class MatOfRect extends Mat { protected MatOfRect(long addr) { super(addr); - if(checkVector(_channels, _depth) < 0 ) + if( !empty() && checkVector(_channels, _depth) < 0 ) throw new IllegalArgumentException("Incomatible Mat"); //FIXME: do we need release() here? } @@ -26,7 +26,7 @@ public class MatOfRect extends Mat { public MatOfRect(Mat m) { super(m, Range.all()); - if(checkVector(_channels, _depth) < 0 ) + if( !empty() && checkVector(_channels, _depth) < 0 ) throw new IllegalArgumentException("Incomatible Mat"); //FIXME: do we need release() here? } From a39a9f677f39e5301a533d39ebf7c9569b1d9126 Mon Sep 17 00:00:00 2001 From: Alexander Smorkalov Date: Mon, 10 Jun 2013 11:06:28 +0400 Subject: [PATCH 085/178] NativeActivity sample build with Eclipse CDT fixed. --- samples/android/native-activity/.cproject | 136 ++++++++---------- samples/android/native-activity/.project | 68 +++++++++ .../android/native-activity/jni/native.cpp | 14 +- 3 files changed, 136 insertions(+), 82 deletions(-) diff --git a/samples/android/native-activity/.cproject b/samples/android/native-activity/.cproject index 09687f3ac0..44aadfe9af 100644 --- a/samples/android/native-activity/.cproject +++ b/samples/android/native-activity/.cproject @@ -1,75 +1,61 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/android/native-activity/.project b/samples/android/native-activity/.project index cf0823c0b3..c20be83f60 100644 --- a/samples/android/native-activity/.project +++ b/samples/android/native-activity/.project @@ -5,6 +5,64 @@ + + org.eclipse.cdt.managedbuilder.core.genmakebuilder + auto,full,incremental, + + + ?name? + + + + org.eclipse.cdt.make.core.append_environment + true + + + org.eclipse.cdt.make.core.autoBuildTarget + + + + org.eclipse.cdt.make.core.buildArguments + + + + org.eclipse.cdt.make.core.buildCommand + "${NDKROOT}/ndk-build.cmd" + + + org.eclipse.cdt.make.core.cleanBuildTarget + clean + + + org.eclipse.cdt.make.core.contents + org.eclipse.cdt.make.core.activeConfigSettings + + + org.eclipse.cdt.make.core.enableAutoBuild + true + + + org.eclipse.cdt.make.core.enableCleanBuild + false + + + org.eclipse.cdt.make.core.enableFullBuild + true + + + org.eclipse.cdt.make.core.fullBuildTarget + + + + org.eclipse.cdt.make.core.stopOnError + true + + + org.eclipse.cdt.make.core.useDefaultBuildCmd + false + + + com.android.ide.eclipse.adt.ResourceManagerBuilder @@ -25,9 +83,19 @@ + + org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder + full,incremental, + + + com.android.ide.eclipse.adt.AndroidNature org.eclipse.jdt.core.javanature + org.eclipse.cdt.core.cnature + org.eclipse.cdt.core.ccnature + org.eclipse.cdt.managedbuilder.core.managedBuildNature + org.eclipse.cdt.managedbuilder.core.ScannerConfigNature diff --git a/samples/android/native-activity/jni/native.cpp b/samples/android/native-activity/jni/native.cpp index 66bc006db1..5cfb3a9611 100644 --- a/samples/android/native-activity/jni/native.cpp +++ b/samples/android/native-activity/jni/native.cpp @@ -9,7 +9,6 @@ #include #include #include -#include #include #include @@ -60,7 +59,7 @@ static cv::Size calc_optimal_camera_resolution(const char* supported, int width, } } - idx++; // to skip coma symbol + idx++; // to skip comma symbol } while(supported[idx-1] != '\0'); @@ -86,9 +85,9 @@ static void engine_draw_frame(Engine* engine, const cv::Mat& frame) for (int yy = top_indent; yy < std::min(frame.rows+top_indent, buffer.height); yy++) { - unsigned char* line = (unsigned char*)pixels; - memcpy(line+left_indent*4*sizeof(unsigned char), frame.ptr(yy), - std::min(frame.cols, buffer.width)*4*sizeof(unsigned char)); + unsigned char* line = (unsigned char*)pixels + left_indent*4*sizeof(unsigned char); + size_t line_size = std::min(frame.cols, buffer.width)*4*sizeof(unsigned char); + memcpy(line, frame.ptr(yy), line_size); // go to next line pixels = (int32_t*)pixels + buffer.stride; } @@ -139,7 +138,7 @@ static void engine_handle_cmd(android_app* app, int32_t cmd) return; } - LOGI("Camera initialized at resoution %dx%d", camera_resolution.width, camera_resolution.height); + LOGI("Camera initialized at resolution %dx%d", camera_resolution.width, camera_resolution.height); } break; case APP_CMD_TERM_WINDOW: @@ -157,7 +156,8 @@ void android_main(android_app* app) // Make sure glue isn't stripped. app_dummy(); - memset(&engine, 0, sizeof(engine)); + size_t engine_size = sizeof(engine); // for Eclipse CDT parser + memset((void*)&engine, 0, engine_size); app->userData = &engine; app->onAppCmd = engine_handle_cmd; engine.app = app; From e77abeef16a4214e3240dc5fb91cd2b0459b7a10 Mon Sep 17 00:00:00 2001 From: Peng Xiao Date: Mon, 10 Jun 2013 16:38:22 +0800 Subject: [PATCH 086/178] Add a new global function to control ocl binary storage Previously the feature is controlled by setBinpath implicitly. We add the function to cope with setBinpath and setBinpath is only useful when setBinaryDiskCache is set. Refer to the header to see more info. --- modules/ocl/include/opencv2/ocl/ocl.hpp | 20 ++++++-- modules/ocl/src/initialization.cpp | 62 ++++++++++++++++++------- 2 files changed, 62 insertions(+), 20 deletions(-) diff --git a/modules/ocl/include/opencv2/ocl/ocl.hpp b/modules/ocl/include/opencv2/ocl/ocl.hpp index 01b0f72d2a..730c2e6b8f 100644 --- a/modules/ocl/include/opencv2/ocl/ocl.hpp +++ b/modules/ocl/include/opencv2/ocl/ocl.hpp @@ -118,9 +118,6 @@ namespace cv //the devnum is the index of the selected device in DeviceName vector of INfo CV_EXPORTS void setDevice(Info &oclinfo, int devnum = 0); - //optional function, if you want save opencl binary kernel to the file, set its path - CV_EXPORTS void setBinpath(const char *path); - //The two functions below enable other opencl program to use ocl module's cl_context and cl_command_queue //returns cl_context * CV_EXPORTS void* getoclContext(); @@ -181,6 +178,23 @@ namespace cv bool finish = true, bool measureKernelTime = false, bool cleanUp = true); + //! Enable or disable OpenCL program binary caching onto local disk + // After a program (*.cl files in opencl/ folder) is built at runtime, we allow the compiled program to be + // cached onto local disk automatically, which may accelerate subsequent runs. + // Caching mode is controlled by the following enum + // Note, the feature is by default enabled when OpenCV is built in release mode. + // enum BinaryDiskCacheMode + const int CACHE_NONE = 0; + const int CACHE_DEBUG = 0x1 << 0; + const int CACHE_RELEASE = 0x1 << 1; + const int CACHE_ALL = CACHE_DEBUG | CACHE_RELEASE; + const int CACHE_UPDATE = 0x1 << 2; // if the binary cache file with the same name is already on the disk, it will be updated. + + CV_EXPORTS void setBinaryDiskCache(int mode = CACHE_RELEASE, cv::String path = "./"); + + //! set where binary cache to be saved to + CV_EXPORTS void setBinpath(const char *path); + class CV_EXPORTS oclMatExpr; //////////////////////////////// oclMat //////////////////////////////// class CV_EXPORTS oclMat diff --git a/modules/ocl/src/initialization.cpp b/modules/ocl/src/initialization.cpp index a9cd08b9f4..9a0915ce55 100644 --- a/modules/ocl/src/initialization.cpp +++ b/modules/ocl/src/initialization.cpp @@ -124,7 +124,8 @@ namespace cv cacheSize = 0; } - + // not to be exported to dynamic lib + void setBinaryDiskCacheImpl(int mode, String path, Info::Impl * impl); struct Info::Impl { cl_platform_id oclplatform; @@ -142,22 +143,12 @@ namespace cv char extra_options[512]; int double_support; int unified_memory; //1 means integrated GPU, otherwise this value is 0 + bool enable_disk_cache; + bool update_disk_cache; string binpath; int refcounter; - Impl() - { - refcounter = 1; - oclplatform = 0; - oclcontext = 0; - clCmdQueue = 0; - devnum = -1; - maxComputeUnits = 0; - maxWorkGroupSize = 0; - memset(extra_options, 0, 512); - double_support = 0; - unified_memory = 0; - } + Impl(); void setDevice(void *ctx, void *q, int devnum); @@ -182,6 +173,25 @@ namespace cv void releaseResources(); }; + Info::Impl::Impl() + :oclplatform(0), + oclcontext(0), + clCmdQueue(0), + devnum(-1), + maxWorkGroupSize(0), + maxDimensions(0), + maxComputeUnits(0), + double_support(0), + unified_memory(0), + enable_disk_cache(false), + update_disk_cache(false), + binpath("./"), + refcounter(1) + { + memset(extra_options, 0, 512); + setBinaryDiskCacheImpl(CACHE_RELEASE, String("./"), this); + } + void Info::Impl::releaseResources() { devnum = -1; @@ -494,6 +504,24 @@ namespace cv return openCLGetKernelFromSource(clCxt, source, kernelName, NULL); } + void setBinaryDiskCacheImpl(int mode, String path, Info::Impl * impl) + { + impl->update_disk_cache = (mode & CACHE_UPDATE) == CACHE_UPDATE; + impl->enable_disk_cache = +#if !defined(NDEBUG) || defined(_DEBUG) + (mode & CACHE_DEBUG) == CACHE_DEBUG; +#else + (mode & CACHE_RELEASE) == CACHE_RELEASE; +#endif + if(impl->enable_disk_cache && !path.empty()) + { + impl->binpath = path; + } + } + void setBinaryDiskCache(int mode, cv::String path) + { + setBinaryDiskCacheImpl(mode, path, Context::getContext()->impl); + } void setBinpath(const char *path) { @@ -573,8 +601,8 @@ namespace cv filename = clCxt->impl->binpath + kernelName + "_" + clCxt->impl->devName[clCxt->impl->devnum] + ".clb"; } - FILE *fp = fopen(filename.c_str(), "rb"); - if(fp == NULL || clCxt->impl->binpath.size() == 0) //we should generate a binary file for the first time. + FILE *fp = clCxt->impl->enable_disk_cache ? fopen(filename.c_str(), "rb") : NULL; + if(fp == NULL || clCxt->impl->update_disk_cache) { if(fp != NULL) fclose(fp); @@ -583,7 +611,7 @@ namespace cv clCxt->impl->oclcontext, 1, source, NULL, &status); openCLVerifyCall(status); status = clBuildProgram(program, 1, &(clCxt->impl->devices[clCxt->impl->devnum]), all_build_options, NULL, NULL); - if(status == CL_SUCCESS && clCxt->impl->binpath.size()) + if(status == CL_SUCCESS && clCxt->impl->enable_disk_cache) savetofile(clCxt, program, filename.c_str()); } else From 41482fe56ca1eb98461bbd202c869faf4442c19c Mon Sep 17 00:00:00 2001 From: Roman Donchenko Date: Mon, 10 Jun 2013 13:30:23 +0400 Subject: [PATCH 087/178] Erase MatchPairsBody's copy constructor. It's the same as the implicitly defined one, and it causes a -Wextra warning (not initializing the base class in a copy constructor). --- modules/stitching/src/matchers.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/modules/stitching/src/matchers.cpp b/modules/stitching/src/matchers.cpp index b5bd8ad4d1..d918cfff29 100644 --- a/modules/stitching/src/matchers.cpp +++ b/modules/stitching/src/matchers.cpp @@ -68,10 +68,6 @@ struct DistIdxPair struct MatchPairsBody : ParallelLoopBody { - MatchPairsBody(const MatchPairsBody& other) - : matcher(other.matcher), features(other.features), - pairwise_matches(other.pairwise_matches), near_pairs(other.near_pairs) {} - MatchPairsBody(FeaturesMatcher &_matcher, const vector &_features, vector &_pairwise_matches, vector > &_near_pairs) : matcher(_matcher), features(_features), From 99a5b3417aabfb52d1c75e4999e2e0ada5983a50 Mon Sep 17 00:00:00 2001 From: Vladislav Vinogradov Date: Mon, 10 Jun 2013 13:41:46 +0400 Subject: [PATCH 088/178] added missing BackgroundSubtractorMOG2 parameters --- modules/video/src/video_init.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/modules/video/src/video_init.cpp b/modules/video/src/video_init.cpp index 0f3cec144c..7ec860fbd3 100644 --- a/modules/video/src/video_init.cpp +++ b/modules/video/src/video_init.cpp @@ -60,7 +60,15 @@ CV_INIT_ALGORITHM(BackgroundSubtractorMOG2, "BackgroundSubtractor.MOG2", obj.info()->addParam(obj, "history", obj.history); obj.info()->addParam(obj, "nmixtures", obj.nmixtures); obj.info()->addParam(obj, "varThreshold", obj.varThreshold); - obj.info()->addParam(obj, "detectShadows", obj.bShadowDetection)); + obj.info()->addParam(obj, "detectShadows", obj.bShadowDetection); + obj.info()->addParam(obj, "backgroundRatio", obj.backgroundRatio); + obj.info()->addParam(obj, "varThresholdGen", obj.varThresholdGen); + obj.info()->addParam(obj, "fVarInit", obj.fVarInit); + obj.info()->addParam(obj, "fVarMin", obj.fVarMin); + obj.info()->addParam(obj, "fVarMax", obj.fVarMax); + obj.info()->addParam(obj, "fCT", obj.fCT); + obj.info()->addParam(obj, "nShadowDetection", obj.nShadowDetection); + obj.info()->addParam(obj, "fTau", obj.fTau)); /////////////////////////////////////////////////////////////////////////////////////////////////////////// From 1d8cd3a717160f2097bcb7765097ed6196b3b535 Mon Sep 17 00:00:00 2001 From: Peng Xiao Date: Mon, 10 Jun 2013 18:37:48 +0800 Subject: [PATCH 089/178] Add ocl CLACH implementation. Test cases (accuracy and performance) are provided. --- modules/ocl/include/opencv2/ocl/ocl.hpp | 17 ++ modules/ocl/perf/perf_imgproc.cpp | 49 ++++- modules/ocl/src/imgproc.cpp | 185 ++++++++++++++++ modules/ocl/src/opencl/imgproc_clahe.cl | 275 ++++++++++++++++++++++++ modules/ocl/test/test_imgproc.cpp | 45 ++++ 5 files changed, 570 insertions(+), 1 deletion(-) create mode 100644 modules/ocl/src/opencl/imgproc_clahe.cl diff --git a/modules/ocl/include/opencv2/ocl/ocl.hpp b/modules/ocl/include/opencv2/ocl/ocl.hpp index 01b0f72d2a..4a5debf504 100644 --- a/modules/ocl/include/opencv2/ocl/ocl.hpp +++ b/modules/ocl/include/opencv2/ocl/ocl.hpp @@ -483,6 +483,23 @@ namespace cv CV_EXPORTS void calcHist(const oclMat &mat_src, oclMat &mat_hist); //! only 8UC1 and 256 bins is supported now CV_EXPORTS void equalizeHist(const oclMat &mat_src, oclMat &mat_dst); + + //! only 8UC1 is supported now + class CV_EXPORTS CLAHE + { + public: + virtual void apply(const oclMat &src, oclMat &dst) = 0; + + virtual void setClipLimit(double clipLimit) = 0; + virtual double getClipLimit() const = 0; + + virtual void setTilesGridSize(Size tileGridSize) = 0; + virtual Size getTilesGridSize() const = 0; + + virtual void collectGarbage() = 0; + }; + CV_EXPORTS Ptr createCLAHE(double clipLimit = 40.0, Size tileGridSize = Size(8, 8)); + //! bilateralFilter // supports 8UC1 8UC4 CV_EXPORTS void bilateralFilter(const oclMat& src, oclMat& dst, int d, double sigmaColor, double sigmaSpave, int borderType=BORDER_DEFAULT); diff --git a/modules/ocl/perf/perf_imgproc.cpp b/modules/ocl/perf/perf_imgproc.cpp index 0aef8b27e4..e87e8213de 100644 --- a/modules/ocl/perf/perf_imgproc.cpp +++ b/modules/ocl/perf/perf_imgproc.cpp @@ -921,4 +921,51 @@ PERFTEST(remap) } } -} \ No newline at end of file +} +///////////// CLAHE //////////////////////// +PERFTEST(CLAHE) +{ + Mat src, dst, ocl_dst; + cv::ocl::oclMat d_src, d_dst; + int all_type[] = {CV_8UC1}; + std::string type_name[] = {"CV_8UC1"}; + + double clipLimit = 40.0; + + cv::Ptr clahe = cv::createCLAHE(clipLimit); + cv::Ptr d_clahe = cv::ocl::createCLAHE(clipLimit); + + for (int size = Min_Size; size <= Max_Size; size *= Multiple) + { + for (size_t j = 0; j < sizeof(all_type) / sizeof(int); j++) + { + SUBTEST << size << 'x' << size << "; " << type_name[j] ; + + gen(src, size, size, all_type[j], 0, 256); + + CPU_ON; + clahe->apply(src, dst); + CPU_OFF; + + d_src.upload(src); + + WARMUP_ON; + d_clahe->apply(d_src, d_dst); + WARMUP_OFF; + + ocl_dst = d_dst; + + TestSystem::instance().ExpectedMatNear(dst, ocl_dst, 1.0); + + GPU_ON; + d_clahe->apply(d_src, d_dst); + GPU_OFF; + + GPU_FULL_ON; + d_src.upload(src); + d_clahe->apply(d_src, d_dst); + d_dst.download(dst); + GPU_FULL_OFF; + } + } +} diff --git a/modules/ocl/src/imgproc.cpp b/modules/ocl/src/imgproc.cpp index ef48b8eaff..3dbd68df8a 100644 --- a/modules/ocl/src/imgproc.cpp +++ b/modules/ocl/src/imgproc.cpp @@ -25,6 +25,7 @@ // Xu Pang, pangxu010@163.com // Wu Zailong, bullet@yeah.net // Wenju He, wenju@multicorewareinc.com +// Sen Liu, swjtuls1987@126.com // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: @@ -80,6 +81,7 @@ namespace cv extern const char *imgproc_calcHarris; extern const char *imgproc_calcMinEigenVal; extern const char *imgproc_convolve; + extern const char *imgproc_clahe; ////////////////////////////////////OpenCL call wrappers//////////////////////////// template struct index_and_sizeof; @@ -1511,6 +1513,189 @@ namespace cv openCLExecuteKernel(clCxt, &imgproc_histogram, kernelName, globalThreads, localThreads, args, -1, -1); LUT(mat_src, lut, mat_dst); } + + //////////////////////////////////////////////////////////////////////// + // CLAHE + namespace clahe + { + inline int divUp(int total, int grain) + { + return (total + grain - 1) / grain * grain; + } + + static void calcLut(const oclMat &src, oclMat &dst, + const int tilesX, const int tilesY, const cv::Size tileSize, + const int clipLimit, const float lutScale) + { + cl_int2 tile_size; + tile_size.s[0] = tileSize.width; + tile_size.s[1] = tileSize.height; + + std::vector > args; + args.push_back( std::make_pair( sizeof(cl_mem), (void *)&src.data )); + args.push_back( std::make_pair( sizeof(cl_mem), (void *)&dst.data )); + args.push_back( std::make_pair( sizeof(cl_int), (void *)&src.step )); + args.push_back( std::make_pair( sizeof(cl_int), (void *)&dst.step )); + args.push_back( std::make_pair( sizeof(cl_int2), (void *)&tile_size )); + args.push_back( std::make_pair( sizeof(cl_int), (void *)&tilesX )); + args.push_back( std::make_pair( sizeof(cl_int), (void *)&clipLimit )); + args.push_back( std::make_pair( sizeof(cl_float), (void *)&lutScale )); + + String kernelName = "calcLut"; + size_t localThreads[3] = { 32, 8, 1 }; + size_t globalThreads[3] = { tilesX * localThreads[0], tilesY * localThreads[1], 1 }; + bool is_cpu = queryDeviceInfo(); + if (is_cpu) + { + openCLExecuteKernel(Context::getContext(), &imgproc_clahe, kernelName, globalThreads, localThreads, args, -1, -1, (char*)" -D CPU"); + } + else + { + cl_kernel kernel = openCLGetKernelFromSource(Context::getContext(), &imgproc_clahe, kernelName); + int wave_size = queryDeviceInfo(kernel); + openCLSafeCall(clReleaseKernel(kernel)); + + static char opt[20] = {0}; + sprintf(opt, " -D WAVE_SIZE=%d", wave_size); + openCLExecuteKernel(Context::getContext(), &imgproc_clahe, kernelName, globalThreads, localThreads, args, -1, -1, opt); + } + } + + static void transform(const oclMat &src, oclMat &dst, const oclMat &lut, + const int tilesX, const int tilesY, const cv::Size tileSize) + { + cl_int2 tile_size; + tile_size.s[0] = tileSize.width; + tile_size.s[1] = tileSize.height; + + std::vector > args; + args.push_back( std::make_pair( sizeof(cl_mem), (void *)&src.data )); + args.push_back( std::make_pair( sizeof(cl_mem), (void *)&dst.data )); + args.push_back( std::make_pair( sizeof(cl_mem), (void *)&lut.data )); + args.push_back( std::make_pair( sizeof(cl_int), (void *)&src.step )); + args.push_back( std::make_pair( sizeof(cl_int), (void *)&dst.step )); + args.push_back( std::make_pair( sizeof(cl_int), (void *)&lut.step )); + args.push_back( std::make_pair( sizeof(cl_int), (void *)&src.cols )); + args.push_back( std::make_pair( sizeof(cl_int), (void *)&src.rows )); + args.push_back( std::make_pair( sizeof(cl_int2), (void *)&tile_size )); + args.push_back( std::make_pair( sizeof(cl_int), (void *)&tilesX )); + args.push_back( std::make_pair( sizeof(cl_int), (void *)&tilesY )); + + String kernelName = "transform"; + size_t localThreads[3] = { 32, 8, 1 }; + size_t globalThreads[3] = { divUp(src.cols, localThreads[0]), divUp(src.rows, localThreads[1]), 1 }; + + openCLExecuteKernel(Context::getContext(), &imgproc_clahe, kernelName, globalThreads, localThreads, args, -1, -1); + } + } + + namespace + { + class CLAHE_Impl : public cv::ocl::CLAHE + { + public: + CLAHE_Impl(double clipLimit = 40.0, int tilesX = 8, int tilesY = 8); + + cv::AlgorithmInfo* info() const; + + void apply(const oclMat &src, oclMat &dst); + + void setClipLimit(double clipLimit); + double getClipLimit() const; + + void setTilesGridSize(cv::Size tileGridSize); + cv::Size getTilesGridSize() const; + + void collectGarbage(); + + private: + double clipLimit_; + int tilesX_; + int tilesY_; + + oclMat srcExt_; + oclMat lut_; + }; + + CLAHE_Impl::CLAHE_Impl(double clipLimit, int tilesX, int tilesY) : + clipLimit_(clipLimit), tilesX_(tilesX), tilesY_(tilesY) + { + } + + void CLAHE_Impl::apply(const oclMat &src, oclMat &dst) + { + CV_Assert( src.type() == CV_8UC1 ); + + dst.create( src.size(), src.type() ); + + const int histSize = 256; + + ensureSizeIsEnough(tilesX_ * tilesY_, histSize, CV_8UC1, lut_); + + cv::Size tileSize; + oclMat srcForLut; + + if (src.cols % tilesX_ == 0 && src.rows % tilesY_ == 0) + { + tileSize = cv::Size(src.cols / tilesX_, src.rows / tilesY_); + srcForLut = src; + } + else + { + cv::ocl::copyMakeBorder(src, srcExt_, 0, tilesY_ - (src.rows % tilesY_), 0, tilesX_ - (src.cols % tilesX_), cv::BORDER_REFLECT_101, cv::Scalar()); + + tileSize = cv::Size(srcExt_.cols / tilesX_, srcExt_.rows / tilesY_); + srcForLut = srcExt_; + } + + const int tileSizeTotal = tileSize.area(); + const float lutScale = static_cast(histSize - 1) / tileSizeTotal; + + int clipLimit = 0; + if (clipLimit_ > 0.0) + { + clipLimit = static_cast(clipLimit_ * tileSizeTotal / histSize); + clipLimit = std::max(clipLimit, 1); + } + + clahe::calcLut(srcForLut, lut_, tilesX_, tilesY_, tileSize, clipLimit, lutScale); + //finish(); + clahe::transform(src, dst, lut_, tilesX_, tilesY_, tileSize); + } + + void CLAHE_Impl::setClipLimit(double clipLimit) + { + clipLimit_ = clipLimit; + } + + double CLAHE_Impl::getClipLimit() const + { + return clipLimit_; + } + + void CLAHE_Impl::setTilesGridSize(cv::Size tileGridSize) + { + tilesX_ = tileGridSize.width; + tilesY_ = tileGridSize.height; + } + + cv::Size CLAHE_Impl::getTilesGridSize() const + { + return cv::Size(tilesX_, tilesY_); + } + + void CLAHE_Impl::collectGarbage() + { + srcExt_.release(); + lut_.release(); + } + } + + cv::Ptr createCLAHE(double clipLimit, cv::Size tileGridSize) + { + return new CLAHE_Impl(clipLimit, tileGridSize.width, tileGridSize.height); + } + //////////////////////////////////bilateralFilter//////////////////////////////////////////////////// static void oclbilateralFilter_8u( const oclMat &src, oclMat &dst, int d, diff --git a/modules/ocl/src/opencl/imgproc_clahe.cl b/modules/ocl/src/opencl/imgproc_clahe.cl new file mode 100644 index 0000000000..0d010f7a5b --- /dev/null +++ b/modules/ocl/src/opencl/imgproc_clahe.cl @@ -0,0 +1,275 @@ +/*M/////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// License Agreement +// For Open Source Computer Vision Library +// +// Copyright (C) 2010-2012, Multicoreware, Inc., all rights reserved. +// Copyright (C) 2010-2012, Advanced Micro Devices, Inc., all rights reserved. +// Third party copyrights are property of their respective owners. +// +// @Authors +// Sen Liu, swjtuls1987@126.com +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other oclMaterials provided with the distribution. +// +// * The name of the copyright holders may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors as is and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the Intel Corporation or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +//M*/ + +#ifndef WAVE_SIZE +#define WAVE_SIZE 1 +#endif + +int calc_lut(__local int* smem, int val, int tid) +{ + smem[tid] = val; + barrier(CLK_LOCAL_MEM_FENCE); + + if (tid == 0) + { + for (int i = 1; i < 256; ++i) + { + smem[i] += smem[i - 1]; + } + } + barrier(CLK_LOCAL_MEM_FENCE); + + return smem[tid]; +} + +#ifdef CPU +void reduce(volatile __local int* smem, int val, int tid) +{ + smem[tid] = val; + barrier(CLK_LOCAL_MEM_FENCE); + + if (tid < 128) + { + smem[tid] = val += smem[tid + 128]; + } + barrier(CLK_LOCAL_MEM_FENCE); + + if (tid < 64) + { + smem[tid] = val += smem[tid + 64]; + } + barrier(CLK_LOCAL_MEM_FENCE); + + if (tid < 32) + { + smem[tid] += smem[tid + 32]; + } + barrier(CLK_LOCAL_MEM_FENCE); + + if (tid < 16) + { + smem[tid] += smem[tid + 16]; + } + barrier(CLK_LOCAL_MEM_FENCE); + + if (tid < 8) + { + smem[tid] += smem[tid + 8]; + } + barrier(CLK_LOCAL_MEM_FENCE); + + if (tid < 4) + { + smem[tid] += smem[tid + 4]; + } + barrier(CLK_LOCAL_MEM_FENCE); + + if (tid < 2) + { + smem[tid] += smem[tid + 2]; + } + barrier(CLK_LOCAL_MEM_FENCE); + + if (tid < 1) + { + smem[256] = smem[tid] + smem[tid + 1]; + } + barrier(CLK_LOCAL_MEM_FENCE); +} +#else +void reduce(__local volatile int* smem, int val, int tid) +{ + smem[tid] = val; + barrier(CLK_LOCAL_MEM_FENCE); + + if (tid < 128) + { + smem[tid] = val += smem[tid + 128]; + } + barrier(CLK_LOCAL_MEM_FENCE); + + if (tid < 64) + { + smem[tid] = val += smem[tid + 64]; + } + barrier(CLK_LOCAL_MEM_FENCE); + + if (tid < 32) + { + smem[tid] += smem[tid + 32]; +#if WAVE_SIZE < 32 + } barrier(CLK_LOCAL_MEM_FENCE); + if (tid < 16) { +#endif + smem[tid] += smem[tid + 16]; +#if WAVE_SIZE < 16 + } barrier(CLK_LOCAL_MEM_FENCE); + if (tid < 8) { +#endif + smem[tid] += smem[tid + 8]; + smem[tid] += smem[tid + 4]; + smem[tid] += smem[tid + 2]; + smem[tid] += smem[tid + 1]; + } +} +#endif + +__kernel void calcLut(__global __const uchar * src, __global uchar * lut, + const int srcStep, const int dstStep, + const int2 tileSize, const int tilesX, + const int clipLimit, const float lutScale) +{ + __local int smem[512]; + + const int tx = get_group_id(0); + const int ty = get_group_id(1); + const unsigned int tid = get_local_id(1) * get_local_size(0) + + get_local_id(0); + + smem[tid] = 0; + barrier(CLK_LOCAL_MEM_FENCE); + + for (int i = get_local_id(1); i < tileSize.y; i += get_local_size(1)) + { + __global const uchar* srcPtr = src + mad24( ty * tileSize.y + i, + srcStep, tx * tileSize.x ); + for (int j = get_local_id(0); j < tileSize.x; j += get_local_size(0)) + { + const int data = srcPtr[j]; + atomic_inc(&smem[data]); + } + } + + barrier(CLK_LOCAL_MEM_FENCE); + + int tHistVal = smem[tid]; + + barrier(CLK_LOCAL_MEM_FENCE); + + if (clipLimit > 0) + { + // clip histogram bar + + int clipped = 0; + if (tHistVal > clipLimit) + { + clipped = tHistVal - clipLimit; + tHistVal = clipLimit; + } + + // find number of overall clipped samples + + reduce(smem, clipped, tid); + barrier(CLK_LOCAL_MEM_FENCE); +#ifdef CPU + clipped = smem[256]; +#else + clipped = smem[0]; +#endif + + // broadcast evaluated value + + __local int totalClipped; + + if (tid == 0) + totalClipped = clipped; + barrier(CLK_LOCAL_MEM_FENCE); + + // redistribute clipped samples evenly + + int redistBatch = totalClipped / 256; + tHistVal += redistBatch; + + int residual = totalClipped - redistBatch * 256; + if (tid < residual) + ++tHistVal; + } + + const int lutVal = calc_lut(smem, tHistVal, tid); + uint ires = (uint)convert_int_rte(lutScale * lutVal); + lut[(ty * tilesX + tx) * dstStep + tid] = + convert_uchar(clamp(ires, (uint)0, (uint)255)); +} + +__kernel void transform(__global __const uchar * src, + __global uchar * dst, + __global uchar * lut, + const int srcStep, const int dstStep, const int lutStep, + const int cols, const int rows, + const int2 tileSize, + const int tilesX, const int tilesY) +{ + const int x = get_global_id(0); + const int y = get_global_id(1); + + if (x >= cols || y >= rows) + return; + + const float tyf = (convert_float(y) / tileSize.y) - 0.5f; + int ty1 = convert_int_rtn(tyf); + int ty2 = ty1 + 1; + const float ya = tyf - ty1; + ty1 = max(ty1, 0); + ty2 = min(ty2, tilesY - 1); + + const float txf = (convert_float(x) / tileSize.x) - 0.5f; + int tx1 = convert_int_rtn(txf); + int tx2 = tx1 + 1; + const float xa = txf - tx1; + tx1 = max(tx1, 0); + tx2 = min(tx2, tilesX - 1); + + const int srcVal = src[mad24(y, srcStep, x)]; + + float res = 0; + + res += lut[mad24(ty1 * tilesX + tx1, lutStep, srcVal)] * ((1.0f - xa) * (1.0f - ya)); + res += lut[mad24(ty1 * tilesX + tx2, lutStep, srcVal)] * ((xa) * (1.0f - ya)); + res += lut[mad24(ty2 * tilesX + tx1, lutStep, srcVal)] * ((1.0f - xa) * (ya)); + res += lut[mad24(ty2 * tilesX + tx2, lutStep, srcVal)] * ((xa) * (ya)); + + uint ires = (uint)convert_int_rte(res); + dst[mad24(y, dstStep, x)] = convert_uchar(clamp(ires, (uint)0, (uint)255)); +} diff --git a/modules/ocl/test/test_imgproc.cpp b/modules/ocl/test/test_imgproc.cpp index 664f8a3919..b9f4740b17 100644 --- a/modules/ocl/test/test_imgproc.cpp +++ b/modules/ocl/test/test_imgproc.cpp @@ -23,6 +23,7 @@ // Rock Li, Rock.Li@amd.com // Wu Zailong, bullet@yeah.net // Xu Pang, pangxu010@163.com +// Sen Liu, swjtuls1987@126.com // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: @@ -1393,6 +1394,46 @@ TEST_P(calcHist, Mat) EXPECT_MAT_NEAR(dst_hist, cpu_hist, 0.0); } } +/////////////////////////////////////////////////////////////////////////////////////////////////////// +// CLAHE +namespace +{ + IMPLEMENT_PARAM_CLASS(ClipLimit, double) +} + +PARAM_TEST_CASE(CLAHE, cv::Size, ClipLimit) +{ + cv::Size size; + double clipLimit; + + cv::Mat src; + cv::Mat dst_gold; + + cv::ocl::oclMat g_src; + cv::ocl::oclMat g_dst; + + virtual void SetUp() + { + size = GET_PARAM(0); + clipLimit = GET_PARAM(1); + + cv::RNG &rng = TS::ptr()->get_rng(); + src = randomMat(rng, size, CV_8UC1, 0, 256, false); + g_src.upload(src); + } +}; + +TEST_P(CLAHE, Accuracy) +{ + cv::Ptr clahe = cv::ocl::createCLAHE(clipLimit); + clahe->apply(g_src, g_dst); + cv::Mat dst(g_dst); + + cv::Ptr clahe_gold = cv::createCLAHE(clipLimit); + clahe_gold->apply(src, dst_gold); + + EXPECT_MAT_NEAR(dst_gold, dst, 1.0); +} ///////////////////////////Convolve////////////////////////////////// PARAM_TEST_CASE(ConvolveTestBase, MatType, bool) @@ -1643,6 +1684,10 @@ INSTANTIATE_TEST_CASE_P(histTestBase, calcHist, Combine( ONE_TYPE(CV_32SC1) //no use )); +INSTANTIATE_TEST_CASE_P(ImgProc, CLAHE, Combine( + Values(cv::Size(128, 128), cv::Size(113, 113), cv::Size(1300, 1300)), + Values(0.0, 40.0))); + //INSTANTIATE_TEST_CASE_P(ConvolveTestBase, Convolve, Combine( // Values(CV_32FC1, CV_32FC1), // Values(false))); // Values(false) is the reserved parameter From 956d8027efeccce0b3e595f556e3eae0bb867fd8 Mon Sep 17 00:00:00 2001 From: Alexander Smorkalov Date: Mon, 10 Jun 2013 13:29:45 -0700 Subject: [PATCH 090/178] Bug #3044 cap_dshow.cpp forgotten validity check fixed. --- modules/highgui/src/cap_dshow.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/highgui/src/cap_dshow.cpp b/modules/highgui/src/cap_dshow.cpp index 21fb947b15..b7cfbd94b3 100644 --- a/modules/highgui/src/cap_dshow.cpp +++ b/modules/highgui/src/cap_dshow.cpp @@ -3195,8 +3195,10 @@ IplImage* CvCaptureCAM_DShow::retrieveFrame(int) frame = cvCreateImage( cvSize(w,h), 8, 3 ); } - VI.getPixels( index, (uchar*)frame->imageData, false, true ); - return frame; + if (VI.getPixels( index, (uchar*)frame->imageData, false, true )) + return frame; + else + return NULL; } double CvCaptureCAM_DShow::getProperty( int property_id ) From d583a79869a780521b18459c5bdf4ec29732805d Mon Sep 17 00:00:00 2001 From: Roman Donchenko Date: Mon, 10 Jun 2013 17:06:34 +0400 Subject: [PATCH 091/178] Revert "Add a variant of detectMultiScale with an argument 'weights'" It was merged by mistake. This reverts commit ab6be9b7b7691967e42297aa6d3a67fb07597fd8. --- .../objdetect/doc/cascade_classification.rst | 3 --- .../include/opencv2/objdetect/objdetect.hpp | 11 +-------- modules/objdetect/src/cascadedetect.cpp | 24 +------------------ 3 files changed, 2 insertions(+), 36 deletions(-) diff --git a/modules/objdetect/doc/cascade_classification.rst b/modules/objdetect/doc/cascade_classification.rst index a00bdc933a..eb07a6c8f7 100644 --- a/modules/objdetect/doc/cascade_classification.rst +++ b/modules/objdetect/doc/cascade_classification.rst @@ -189,7 +189,6 @@ CascadeClassifier::detectMultiScale Detects objects of different sizes in the input image. The detected objects are returned as a list of rectangles. .. ocv:function:: void CascadeClassifier::detectMultiScale( const Mat& image, vector& objects, double scaleFactor=1.1, int minNeighbors=3, int flags=0, Size minSize=Size(), Size maxSize=Size()) -.. ocv:function:: void CascadeClassifier::detectMultiScale( const Mat& image, vector& objects, vector& weights, double scaleFactor=1.1, int minNeighbors=3, int flags=0, Size minSize=Size(), Size maxSize=Size()) .. ocv:pyfunction:: cv2.CascadeClassifier.detectMultiScale(image[, scaleFactor[, minNeighbors[, flags[, minSize[, maxSize]]]]]) -> objects .. ocv:pyfunction:: cv2.CascadeClassifier.detectMultiScale(image, rejectLevels, levelWeights[, scaleFactor[, minNeighbors[, flags[, minSize[, maxSize[, outputRejectLevels]]]]]]) -> objects @@ -204,8 +203,6 @@ Detects objects of different sizes in the input image. The detected objects are :param objects: Vector of rectangles where each rectangle contains the detected object. - :param weights: Vector of weights of the corresponding objects. Weight is the number of neighboring positively classified rectangles that were joined into one object. - :param scaleFactor: Parameter specifying how much the image size is reduced at each image scale. :param minNeighbors: Parameter specifying how many neighbors each candidate rectangle should have to retain it. diff --git a/modules/objdetect/include/opencv2/objdetect/objdetect.hpp b/modules/objdetect/include/opencv2/objdetect/objdetect.hpp index 7924b67e5d..8d7efb0ba4 100644 --- a/modules/objdetect/include/opencv2/objdetect/objdetect.hpp +++ b/modules/objdetect/include/opencv2/objdetect/objdetect.hpp @@ -382,14 +382,6 @@ public: Size minSize=Size(), Size maxSize=Size() ); - CV_WRAP virtual void detectMultiScale( const Mat& image, - CV_OUT vector& objects, - vector& weights, - double scaleFactor=1.1, - int minNeighbors=3, int flags=0, - Size minSize=Size(), - Size maxSize=Size() ); - CV_WRAP virtual void detectMultiScale( const Mat& image, CV_OUT vector& objects, vector& rejectLevels, @@ -398,8 +390,7 @@ public: int minNeighbors=3, int flags=0, Size minSize=Size(), Size maxSize=Size(), - bool outputRejectLevels=false, - bool outputWeights=false ); + bool outputRejectLevels=false ); bool isOldFormatCascade() const; diff --git a/modules/objdetect/src/cascadedetect.cpp b/modules/objdetect/src/cascadedetect.cpp index 341ef2a0d6..9e78dce243 100644 --- a/modules/objdetect/src/cascadedetect.cpp +++ b/modules/objdetect/src/cascadedetect.cpp @@ -1023,7 +1023,6 @@ public: }; struct getRect { Rect operator ()(const CvAvgComp& e) const { return e.rect; } }; -struct getNeighbors { int operator ()(const CvAvgComp& e) const { return e.neighbors; } }; bool CascadeClassifier::detectSingleScale( const Mat& image, int stripCount, Size processingRectSize, @@ -1093,12 +1092,11 @@ void CascadeClassifier::detectMultiScale( const Mat& image, vector& object vector& levelWeights, double scaleFactor, int minNeighbors, int flags, Size minObjectSize, Size maxObjectSize, - bool outputRejectLevels, bool outputWeights ) + bool outputRejectLevels ) { const double GROUP_EPS = 0.2; CV_Assert( scaleFactor > 1 && image.depth() == CV_8U ); - CV_Assert( !( outputRejectLevels && outputWeights ) ); if( empty() ) return; @@ -1113,12 +1111,6 @@ void CascadeClassifier::detectMultiScale( const Mat& image, vector& object Seq(_objects).copyTo(vecAvgComp); objects.resize(vecAvgComp.size()); std::transform(vecAvgComp.begin(), vecAvgComp.end(), objects.begin(), getRect()); - if( outputWeights ) - { - rejectLevels.resize(vecAvgComp.size()); - std::transform(vecAvgComp.begin(), vecAvgComp.end(), rejectLevels.begin(), - getNeighbors()); - } return; } @@ -1191,10 +1183,6 @@ void CascadeClassifier::detectMultiScale( const Mat& image, vector& object { groupRectangles( objects, rejectLevels, levelWeights, minNeighbors, GROUP_EPS ); } - else if( outputWeights ) - { - groupRectangles( objects, rejectLevels, minNeighbors, GROUP_EPS ); - } else { groupRectangles( objects, minNeighbors, GROUP_EPS ); @@ -1211,16 +1199,6 @@ void CascadeClassifier::detectMultiScale( const Mat& image, vector& object minNeighbors, flags, minObjectSize, maxObjectSize, false ); } -void CascadeClassifier::detectMultiScale( const Mat& image, CV_OUT vector& objects, - vector& weights, double scaleFactor, - int minNeighbors, int flags, Size minObjectSize, - Size maxObjectSize ) -{ - vector fakeLevelWeights; - detectMultiScale( image, objects, weights, fakeLevelWeights, scaleFactor, - minNeighbors, flags, minObjectSize, maxObjectSize, false, true ); -} - bool CascadeClassifier::Data::read(const FileNode &root) { static const float THRESHOLD_EPS = 1e-5f; From c8398c9fdc6641516d8e195e1ede2efd8a138b3c Mon Sep 17 00:00:00 2001 From: Peng Xiao Date: Tue, 11 Jun 2013 20:32:55 +0800 Subject: [PATCH 092/178] Use anonymous enumerations instead of constants --- modules/ocl/include/opencv2/ocl/ocl.hpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/modules/ocl/include/opencv2/ocl/ocl.hpp b/modules/ocl/include/opencv2/ocl/ocl.hpp index 730c2e6b8f..dc58f6f2e7 100644 --- a/modules/ocl/include/opencv2/ocl/ocl.hpp +++ b/modules/ocl/include/opencv2/ocl/ocl.hpp @@ -183,13 +183,14 @@ namespace cv // cached onto local disk automatically, which may accelerate subsequent runs. // Caching mode is controlled by the following enum // Note, the feature is by default enabled when OpenCV is built in release mode. - // enum BinaryDiskCacheMode - const int CACHE_NONE = 0; - const int CACHE_DEBUG = 0x1 << 0; - const int CACHE_RELEASE = 0x1 << 1; - const int CACHE_ALL = CACHE_DEBUG | CACHE_RELEASE; - const int CACHE_UPDATE = 0x1 << 2; // if the binary cache file with the same name is already on the disk, it will be updated. - + enum + { + CACHE_NONE = 0, + CACHE_DEBUG = 0x1 << 0, + CACHE_RELEASE = 0x1 << 1, + CACHE_ALL = CACHE_DEBUG | CACHE_RELEASE, + CACHE_UPDATE = 0x1 << 2 // if the binary cache file with the same name is already on the disk, it will be updated. + }; CV_EXPORTS void setBinaryDiskCache(int mode = CACHE_RELEASE, cv::String path = "./"); //! set where binary cache to be saved to From 8eb6decb251f74f1a68f982b2057c599fc5545c0 Mon Sep 17 00:00:00 2001 From: Andrew Senin Date: Tue, 11 Jun 2013 21:17:31 +0400 Subject: [PATCH 093/178] Fixed Ximea cameras support --- cmake/OpenCVFindXimea.cmake | 14 ++-- modules/highgui/CMakeLists.txt | 6 +- modules/highgui/src/cap_ximea.cpp | 129 ++++++++++++++++++------------ 3 files changed, 93 insertions(+), 56 deletions(-) diff --git a/cmake/OpenCVFindXimea.cmake b/cmake/OpenCVFindXimea.cmake index 5600275f47..27e2a78ad4 100644 --- a/cmake/OpenCVFindXimea.cmake +++ b/cmake/OpenCVFindXimea.cmake @@ -9,6 +9,7 @@ # # Created: 5 Aug 2011 by Marian Zajko (marian.zajko@ximea.com) # Updated: 25 June 2012 by Igor Kuzmin (parafin@ximea.com) +# Updated: 22 October 2012 by Marian Zajko (marian.zajko@ximea.com) # set(XIMEA_FOUND) @@ -18,11 +19,15 @@ set(XIMEA_LIBRARY_DIR) if(WIN32) # Try to find the XIMEA API path in registry. GET_FILENAME_COMPONENT(XIMEA_PATH "[HKEY_CURRENT_USER\\Software\\XIMEA\\CamSupport\\API;Path]" ABSOLUTE) - - if(EXISTS XIMEA_PATH) + + if(EXISTS ${XIMEA_PATH}) set(XIMEA_FOUND 1) # set LIB folders - set(XIMEA_LIBRARY_DIR "${XIMEA_PATH}/x86") + if(CMAKE_CL_64) + set(XIMEA_LIBRARY_DIR "${XIMEA_PATH}/x64") + else() + set(XIMEA_LIBRARY_DIR "${XIMEA_PATH}/x86") + endif() else() set(XIMEA_FOUND 0) endif() @@ -38,5 +43,4 @@ endif() mark_as_advanced(FORCE XIMEA_FOUND) mark_as_advanced(FORCE XIMEA_PATH) -mark_as_advanced(FORCE XIMEA_LIBRARY_DIR) - +mark_as_advanced(FORCE XIMEA_LIBRARY_DIR) \ No newline at end of file diff --git a/modules/highgui/CMakeLists.txt b/modules/highgui/CMakeLists.txt index fad2562c88..05ab99a78c 100644 --- a/modules/highgui/CMakeLists.txt +++ b/modules/highgui/CMakeLists.txt @@ -179,7 +179,11 @@ if(HAVE_XIMEA) if(XIMEA_LIBRARY_DIR) link_directories(${XIMEA_LIBRARY_DIR}) endif() - list(APPEND HIGHGUI_LIBRARIES m3api) + if(CMAKE_CL_64) + list(APPEND HIGHGUI_LIBRARIES m3apiX64) + else() + list(APPEND HIGHGUI_LIBRARIES m3api) + endif() endif(HAVE_XIMEA) if(HAVE_FFMPEG) diff --git a/modules/highgui/src/cap_ximea.cpp b/modules/highgui/src/cap_ximea.cpp index dbb8f58683..5acf2c09d1 100644 --- a/modules/highgui/src/cap_ximea.cpp +++ b/modules/highgui/src/cap_ximea.cpp @@ -20,25 +20,24 @@ public: virtual IplImage* retrieveFrame(int); virtual int getCaptureDomain() { return CV_CAP_XIAPI; } // Return the type of the capture object: CV_CAP_VFW, etc... -protected: +private: void init(); void errMsg(const char* msg, int errNum); + void resetCvImage(); + int getBpp(); IplImage* frame; HANDLE hmv; DWORD numDevices; - XI_IMG image; - int width; - int height; - int format; int timeout; + XI_IMG image; }; /**********************************************************************************/ CvCapture* cvCreateCameraCapture_XIMEA( int index ) { - CvCaptureCAM_XIMEA* capture = new CvCaptureCAM_XIMEA; + CvCaptureCAM_XIMEA* capture = new CvCaptureCAM_XIMEA; if( capture->open( index )) return capture; @@ -79,18 +78,19 @@ bool CvCaptureCAM_XIMEA::open( int wIndex ) // always use auto white ballance mvret = xiSetParamInt( hmv, XI_PRM_AUTO_WB, 1); if(mvret != XI_OK) goto error; + + // default image format RGB24 + mvret = xiSetParamInt( hmv, XI_PRM_IMAGE_DATA_FORMAT, XI_RGB24); + if(mvret != XI_OK) goto error; + int width = 0; mvret = xiGetParamInt( hmv, XI_PRM_WIDTH, &width); if(mvret != XI_OK) goto error; + int height = 0; mvret = xiGetParamInt( hmv, XI_PRM_HEIGHT, &height); if(mvret != XI_OK) goto error; - // default image format RGB24 - format = XI_RGB24; - mvret = xiSetParamInt( hmv, XI_PRM_IMAGE_DATA_FORMAT, format); - if(mvret != XI_OK) goto error; - // allocate frame buffer for RGB24 image frame = cvCreateImage(cvSize( width, height), IPL_DEPTH_8U, 3); @@ -103,10 +103,10 @@ bool CvCaptureCAM_XIMEA::open( int wIndex ) errMsg("StartAcquisition XI_DEVICE failed", mvret); goto error; } - return true; error: + errMsg("Open XI_DEVICE failed", mvret); xiCloseDevice(hmv); hmv = NULL; return false; @@ -116,18 +116,19 @@ error: void CvCaptureCAM_XIMEA::close() { - if(hmv) - { - xiStopAcquisition(hmv); - xiCloseDevice(hmv); - hmv = NULL; - } + if(frame) + cvReleaseImage(&frame); + + xiStopAcquisition(hmv); + xiCloseDevice(hmv); + hmv = NULL; } /**********************************************************************************/ bool CvCaptureCAM_XIMEA::grabFrame() { + memset(&image, 0, sizeof(XI_IMG)); image.size = sizeof(XI_IMG); int mvret = xiGetImage( hmv, timeout, &image); @@ -151,31 +152,18 @@ bool CvCaptureCAM_XIMEA::grabFrame() IplImage* CvCaptureCAM_XIMEA::retrieveFrame(int) { // update cvImage after format has changed - if( (int)image.width != width || (int)image.height != height || image.frm != (XI_IMG_FORMAT)format) - { - cvReleaseImage(&frame); - switch( image.frm) - { - case XI_MONO8 : frame = cvCreateImage(cvSize( image.width, image.height), IPL_DEPTH_8U, 1); break; - case XI_MONO16 : frame = cvCreateImage(cvSize( image.width, image.height), IPL_DEPTH_16U, 1); break; - case XI_RGB24 : frame = cvCreateImage(cvSize( image.width, image.height), IPL_DEPTH_8U, 3); break; - case XI_RGB32 : frame = cvCreateImage(cvSize( image.width, image.height), IPL_DEPTH_8U, 4); break; - default : - return frame; - } - // update global image format - format = image.frm; - width = image.width; - height = image.height; - } - + resetCvImage(); + // copy pixel data switch( image.frm) { - case XI_MONO8 : memcpy( frame->imageData, image.bp, image.width*image.height); break; - case XI_MONO16 : memcpy( frame->imageData, image.bp, image.width*image.height*sizeof(WORD)); break; - case XI_RGB24 : memcpy( frame->imageData, image.bp, image.width*image.height*3); break; - case XI_RGB32 : memcpy( frame->imageData, image.bp, image.width*image.height*sizeof(DWORD)); break; + case XI_MONO8 : + case XI_RAW8 : memcpy( frame->imageData, image.bp, image.width*image.height); break; + case XI_MONO16 : + case XI_RAW16 : memcpy( frame->imageData, image.bp, image.width*image.height*sizeof(WORD)); break; + case XI_RGB24 : + case XI_RGB_PLANAR : memcpy( frame->imageData, image.bp, image.width*image.height*3); break; + case XI_RGB32 : memcpy( frame->imageData, image.bp, image.width*image.height*4); break; default: break; } return frame; @@ -183,6 +171,35 @@ IplImage* CvCaptureCAM_XIMEA::retrieveFrame(int) /**********************************************************************************/ +void CvCaptureCAM_XIMEA::resetCvImage() +{ + int width = 0, height = 0, format = 0; + xiGetParamInt( hmv, XI_PRM_WIDTH, &width); + xiGetParamInt( hmv, XI_PRM_HEIGHT, &height); + xiGetParamInt( hmv, XI_PRM_IMAGE_DATA_FORMAT, &format); + + if( (int)image.width != width || (int)image.height != height || image.frm != (XI_IMG_FORMAT)format) + { + if(frame) cvReleaseImage(&frame); + frame = NULL; + + switch( image.frm) + { + case XI_MONO8 : + case XI_RAW8 : frame = cvCreateImage(cvSize( image.width, image.height), IPL_DEPTH_8U, 1); break; + case XI_MONO16 : + case XI_RAW16 : frame = cvCreateImage(cvSize( image.width, image.height), IPL_DEPTH_16U, 1); break; + case XI_RGB24 : + case XI_RGB_PLANAR : frame = cvCreateImage(cvSize( image.width, image.height), IPL_DEPTH_8U, 3); break; + case XI_RGB32 : frame = cvCreateImage(cvSize( image.width, image.height), IPL_DEPTH_8U, 4); break; + default : + return; + } + } + cvZero(frame); +} +/**********************************************************************************/ + double CvCaptureCAM_XIMEA::getProperty( int property_id ) { if(hmv == NULL) @@ -238,20 +255,14 @@ bool CvCaptureCAM_XIMEA::setProperty( int property_id, double value ) switch(property_id) { // OCV parameters - case CV_CAP_PROP_FRAME_WIDTH : mvret = xiSetParamInt( hmv, XI_PRM_WIDTH, ival); - if(mvret == XI_OK) width = ival; - break; - case CV_CAP_PROP_FRAME_HEIGHT : mvret = xiSetParamInt( hmv, XI_PRM_HEIGHT, ival); - if(mvret == XI_OK) height = ival; - break; + case CV_CAP_PROP_FRAME_WIDTH : mvret = xiSetParamInt( hmv, XI_PRM_WIDTH, ival); break; + case CV_CAP_PROP_FRAME_HEIGHT : mvret = xiSetParamInt( hmv, XI_PRM_HEIGHT, ival); break; case CV_CAP_PROP_FPS : mvret = xiSetParamFloat( hmv, XI_PRM_FRAMERATE, fval); break; case CV_CAP_PROP_GAIN : mvret = xiSetParamFloat( hmv, XI_PRM_GAIN, fval); break; case CV_CAP_PROP_EXPOSURE : mvret = xiSetParamInt( hmv, XI_PRM_EXPOSURE, ival); break; // XIMEA camera properties case CV_CAP_PROP_XI_DOWNSAMPLING : mvret = xiSetParamInt( hmv, XI_PRM_DOWNSAMPLING, ival); break; - case CV_CAP_PROP_XI_DATA_FORMAT : mvret = xiSetParamInt( hmv, XI_PRM_IMAGE_DATA_FORMAT, ival); - if(mvret == XI_OK) format = ival; - break; + case CV_CAP_PROP_XI_DATA_FORMAT : mvret = xiSetParamInt( hmv, XI_PRM_IMAGE_DATA_FORMAT, ival); break; case CV_CAP_PROP_XI_OFFSET_X : mvret = xiSetParamInt( hmv, XI_PRM_OFFSET_X, ival); break; case CV_CAP_PROP_XI_OFFSET_Y : mvret = xiSetParamInt( hmv, XI_PRM_OFFSET_Y, ival); break; case CV_CAP_PROP_XI_TRG_SOURCE : mvret = xiSetParamInt( hmv, XI_PRM_TRG_SOURCE, ival); break; @@ -288,7 +299,7 @@ bool CvCaptureCAM_XIMEA::setProperty( int property_id, double value ) void CvCaptureCAM_XIMEA::errMsg(const char* msg, int errNum) { #if defined WIN32 || defined _WIN32 - char buf[512]; + char buf[512]=""; sprintf( buf, "%s : %d\n", msg, errNum); OutputDebugString(buf); #else @@ -296,4 +307,22 @@ void CvCaptureCAM_XIMEA::errMsg(const char* msg, int errNum) #endif } +/**********************************************************************************/ + +int CvCaptureCAM_XIMEA::getBpp() +{ + switch( image.frm) + { + case XI_MONO8 : + case XI_RAW8 : return 1; + case XI_MONO16 : + case XI_RAW16 : return 2; + case XI_RGB24 : + case XI_RGB_PLANAR : return 3; + case XI_RGB32 : return 4; + default : + return 0; + } +} + /**********************************************************************************/ \ No newline at end of file From d9ab22e4ed63ca53634c74d7c022d60106879f23 Mon Sep 17 00:00:00 2001 From: Peng Xiao Date: Wed, 12 Jun 2013 13:55:20 +0800 Subject: [PATCH 094/178] Fix two bugs related to opencl context. 1. As getDevice will implicitly call setDevice, in getContext we should not need to call it again. 2. Fix an incorrect type casting. --- modules/ocl/src/initialization.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/modules/ocl/src/initialization.cpp b/modules/ocl/src/initialization.cpp index a9cd08b9f4..71289f6218 100644 --- a/modules/ocl/src/initialization.cpp +++ b/modules/ocl/src/initialization.cpp @@ -930,8 +930,6 @@ namespace cv clCxt.reset(new Context); std::vector oclinfo; CV_Assert(getDevice(oclinfo, CVCL_DEVICE_TYPE_ALL) > 0); - oclinfo[0].impl->setDevice(0, 0, 0); - clCxt.get()->impl = oclinfo[0].impl->copy(); *((volatile int*)&val) = 1; } @@ -1056,7 +1054,7 @@ BOOL WINAPI DllMain( HINSTANCE, DWORD fdwReason, LPVOID ) Context* cv_ctx = Context::getContext(); if(cv_ctx) { - cl_context ctx = (cl_context)&(cv_ctx->impl->oclcontext); + cl_context ctx = cv_ctx->impl->oclcontext; if(ctx) openCLSafeCall(clReleaseContext(ctx)); } From 5fd724b54a1143bed2d3aa0ff8f5a1ec0bc61e30 Mon Sep 17 00:00:00 2001 From: peng xiao Date: Thu, 13 Jun 2013 10:46:12 +0800 Subject: [PATCH 095/178] Add a function to query if global OpenCL context is initialized. --- modules/ocl/include/opencv2/ocl/ocl.hpp | 5 ++++- modules/ocl/src/initialization.cpp | 8 ++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/modules/ocl/include/opencv2/ocl/ocl.hpp b/modules/ocl/include/opencv2/ocl/ocl.hpp index 4a5debf504..29021278fd 100644 --- a/modules/ocl/include/opencv2/ocl/ocl.hpp +++ b/modules/ocl/include/opencv2/ocl/ocl.hpp @@ -134,6 +134,9 @@ namespace cv //getDevice also need to be called before this function CV_EXPORTS void setDeviceEx(Info &oclinfo, void *ctx, void *qu, int devnum = 0); + //returns true when global OpenCL context is initialized + CV_EXPORTS bool initialized(); + //////////////////////////////// Error handling //////////////////////// CV_EXPORTS void error(const char *error_string, const char *file, const int line, const char *func); @@ -144,7 +147,7 @@ namespace cv protected: Context(); friend class auto_ptr; - + friend bool initialized(); private: static auto_ptr clCxt; static int val; diff --git a/modules/ocl/src/initialization.cpp b/modules/ocl/src/initialization.cpp index a9cd08b9f4..78a956719b 100644 --- a/modules/ocl/src/initialization.cpp +++ b/modules/ocl/src/initialization.cpp @@ -917,6 +917,14 @@ namespace cv int Context::val = 0; static Mutex cs; static volatile int context_tear_down = 0; + + bool initialized() + { + return *((volatile int*)&Context::val) != 0 && + Context::clCxt->impl->clCmdQueue != NULL&& + Context::clCxt->impl->oclcontext != NULL; + } + Context* Context::getContext() { if(*((volatile int*)&val) != 1) From e433145b7e15f7ff8056d109fe17de3bf423e916 Mon Sep 17 00:00:00 2001 From: Vladislav Vinogradov Date: Thu, 13 Jun 2013 10:22:56 +0400 Subject: [PATCH 096/178] fix for Bug #3085: weights array is only allocated for (l_count+1) elements, but then weights[l_count+1] element is accessed. --- modules/ml/src/ann_mlp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ml/src/ann_mlp.cpp b/modules/ml/src/ann_mlp.cpp index bf85425b9c..7323ab57a7 100644 --- a/modules/ml/src/ann_mlp.cpp +++ b/modules/ml/src/ann_mlp.cpp @@ -251,7 +251,7 @@ void CvANN_MLP::create( const CvMat* _layer_sizes, int _activ_func, buf_sz += (l_dst[0] + l_dst[l_count-1]*2)*2; CV_CALL( wbuf = cvCreateMat( 1, buf_sz, CV_64F )); - CV_CALL( weights = (double**)cvAlloc( (l_count+1)*sizeof(weights[0]) )); + CV_CALL( weights = (double**)cvAlloc( (l_count+2)*sizeof(weights[0]) )); weights[0] = wbuf->data.db; weights[1] = weights[0] + l_dst[0]*2; From 982ef83f807c7c2e4285f3d24894d2e251a66fa2 Mon Sep 17 00:00:00 2001 From: Sergei Nosov Date: Thu, 13 Jun 2013 11:51:45 +0400 Subject: [PATCH 097/178] Fixes bug #3071. If we have perfect matches (min_dist == 0.0), then strict comparison fails. Making it non-strict results in treating perfect matches as good. --- .../feature_flann_matcher/feature_flann_matcher.rst | 5 +---- samples/cpp/tutorial_code/features2D/SURF_FlannMatcher.cpp | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/doc/tutorials/features2d/feature_flann_matcher/feature_flann_matcher.rst b/doc/tutorials/features2d/feature_flann_matcher/feature_flann_matcher.rst index 47eafedbc7..54d28890ab 100644 --- a/doc/tutorials/features2d/feature_flann_matcher/feature_flann_matcher.rst +++ b/doc/tutorials/features2d/feature_flann_matcher/feature_flann_matcher.rst @@ -85,7 +85,7 @@ This tutorial code's is shown lines below. You can also download it from `here < std::vector< DMatch > good_matches; for( int i = 0; i < descriptors_1.rows; i++ ) - { if( matches[i].distance < 2*min_dist ) + { if( matches[i].distance <= 2*min_dist ) { good_matches.push_back( matches[i]); } } @@ -127,6 +127,3 @@ Result .. image:: images/Feature_FlannMatcher_Keypoints_Result.jpg :align: center :height: 250pt - - - diff --git a/samples/cpp/tutorial_code/features2D/SURF_FlannMatcher.cpp b/samples/cpp/tutorial_code/features2D/SURF_FlannMatcher.cpp index f4cde9b2ee..ead7fd7182 100644 --- a/samples/cpp/tutorial_code/features2D/SURF_FlannMatcher.cpp +++ b/samples/cpp/tutorial_code/features2D/SURF_FlannMatcher.cpp @@ -70,7 +70,7 @@ int main( int argc, char** argv ) std::vector< DMatch > good_matches; for( int i = 0; i < descriptors_1.rows; i++ ) - { if( matches[i].distance < 2*min_dist ) + { if( matches[i].distance <= 2*min_dist ) { good_matches.push_back( matches[i]); } } From 83e9b0a87a41e8f70de0810e5ce769ebb2ebd9c5 Mon Sep 17 00:00:00 2001 From: Alexander Smorkalov Date: Thu, 13 Jun 2013 12:40:14 +0400 Subject: [PATCH 098/178] Javadoc waring fix. --- .../java/generator/src/java/android+CameraBridgeViewBase.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/java/generator/src/java/android+CameraBridgeViewBase.java b/modules/java/generator/src/java/android+CameraBridgeViewBase.java index b15ae2bd8f..c0c9f5bde7 100644 --- a/modules/java/generator/src/java/android+CameraBridgeViewBase.java +++ b/modules/java/generator/src/java/android+CameraBridgeViewBase.java @@ -80,10 +80,10 @@ public abstract class CameraBridgeViewBase extends SurfaceView implements Surfac mMaxHeight = MAX_UNSPECIFIED; styledAttrs.recycle(); } - + /** * Sets the camera index - * @param camera index + * @param cameraIndex new camera index */ public void setCameraIndex(int cameraIndex) { this.mCameraIndex = cameraIndex; From de4c3f01788dbe72bd5a51714d85bfc753b700a5 Mon Sep 17 00:00:00 2001 From: Ivan Korolev Date: Thu, 13 Jun 2013 13:41:43 +0400 Subject: [PATCH 099/178] Fixed a bug related to video stabilization crashes with a blank video (Bug #3023) --- modules/videostab/src/global_motion.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/videostab/src/global_motion.cpp b/modules/videostab/src/global_motion.cpp index 484b598dc1..de93d5c5ac 100644 --- a/modules/videostab/src/global_motion.cpp +++ b/modules/videostab/src/global_motion.cpp @@ -205,6 +205,9 @@ Mat estimateGlobalMotionRobust( estimateGlobMotionLeastSquaresAffine }; const int npoints = static_cast(points0.size()); + if (npoints < params.size) + return Mat::eye(3, 3, CV_32F); + const int niters = static_cast(ceil(log(1 - params.prob) / log(1 - pow(1 - params.eps, params.size)))); @@ -300,6 +303,8 @@ PyrLkRobustMotionEstimator::PyrLkRobustMotionEstimator() Mat PyrLkRobustMotionEstimator::estimate(const Mat &frame0, const Mat &frame1) { detector_->detect(frame0, keypointsPrev_); + if (keypointsPrev_.empty()) + return Mat::eye(3, 3, CV_32F); pointsPrev_.resize(keypointsPrev_.size()); for (size_t i = 0; i < keypointsPrev_.size(); ++i) From 055137582c40a9756f35185d31368b92cb27143d Mon Sep 17 00:00:00 2001 From: Vladislav Vinogradov Date: Thu, 13 Jun 2013 16:13:46 +0400 Subject: [PATCH 100/178] fix for bug #2985: OPENCLAMDBLAS and OPENCLAMDFFT never detected under linux. lib64/import and lib32/import is the path on Windows but not Linux. for CLAMDBLAS library we should use CLAMDBLAS_PATH (not CLAMDFFT_PATH) --- cmake/OpenCVDetectOpenCL.cmake | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/cmake/OpenCVDetectOpenCL.cmake b/cmake/OpenCVDetectOpenCL.cmake index 014066bc7e..a1e8bbac70 100644 --- a/cmake/OpenCVDetectOpenCL.cmake +++ b/cmake/OpenCVDetectOpenCL.cmake @@ -44,12 +44,18 @@ if(OPENCL_FOUND) set(OPENCL_INCLUDE_DIRS ${OPENCL_INCLUDE_DIR}) set(OPENCL_LIBRARIES ${OPENCL_LIBRARY}) - if (X86_64) + if(WIN64) set(CLAMD_POSSIBLE_LIB_SUFFIXES lib64/import) - elseif (X86) + elseif(WIN32) set(CLAMD_POSSIBLE_LIB_SUFFIXES lib32/import) endif() + if(X86_64 AND UNIX) + set(CLAMD_POSSIBLE_LIB_SUFFIXES lib64) + elseif(X86 AND UNIX) + set(CLAMD_POSSIBLE_LIB_SUFFIXES lib32) + endif() + if(WITH_OPENCLAMDFFT) find_path(CLAMDFFT_ROOT_DIR NAMES include/clAmdFft.h @@ -80,7 +86,7 @@ if(OPENCL_FOUND) if(WITH_OPENCLAMDBLAS) find_path(CLAMDBLAS_ROOT_DIR NAMES include/clAmdBlas.h - PATHS ENV CLAMDFFT_PATH ENV ProgramFiles + PATHS ENV CLAMDBLAS_PATH ENV ProgramFiles PATH_SUFFIXES clAmdBlas AMD/clAmdBlas DOC "AMD FFT root directory" NO_DEFAULT_PATH) From 0367a7f992bc111ae93060da39dd769b5cdc71c6 Mon Sep 17 00:00:00 2001 From: Vladislav Vinogradov Date: Thu, 13 Jun 2013 16:46:34 +0400 Subject: [PATCH 101/178] link with nvcuvenc and ffmpeg libraries only if WITH_NVCUVID is enabled --- modules/gpu/CMakeLists.txt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/modules/gpu/CMakeLists.txt b/modules/gpu/CMakeLists.txt index a471da0088..0062944bab 100644 --- a/modules/gpu/CMakeLists.txt +++ b/modules/gpu/CMakeLists.txt @@ -46,15 +46,15 @@ if(HAVE_CUDA) if(WITH_NVCUVID) set(cuda_link_libs ${cuda_link_libs} ${CUDA_CUDA_LIBRARY} ${CUDA_nvcuvid_LIBRARY}) - endif() - if(WIN32) - find_cuda_helper_libs(nvcuvenc) - set(cuda_link_libs ${cuda_link_libs} ${CUDA_nvcuvenc_LIBRARY}) - endif() + if(WIN32) + find_cuda_helper_libs(nvcuvenc) + set(cuda_link_libs ${cuda_link_libs} ${CUDA_nvcuvenc_LIBRARY}) + endif() - if(WITH_FFMPEG) - set(cuda_link_libs ${cuda_link_libs} ${HIGHGUI_LIBRARIES}) + if(WITH_FFMPEG) + set(cuda_link_libs ${cuda_link_libs} ${HIGHGUI_LIBRARIES}) + endif() endif() else() set(lib_cuda "") From 80f6ede2336d1e1b18718e360169b3c2813e557a Mon Sep 17 00:00:00 2001 From: Alexander Smorkalov Date: Thu, 13 Jun 2013 16:51:45 +0400 Subject: [PATCH 102/178] Fix build problems on MIPS and Linaro NDK. Merged android.toolchain.cmake from project https://github.com/taka-no-me/android-cmake commit: fd1f7055f8b7338644d58d9a2015a784dfa3a5ca --- android/android.toolchain.cmake | 237 ++++++++++++++++------ platforms/android/android.toolchain.cmake | 236 +++++++++++++++------ 2 files changed, 349 insertions(+), 124 deletions(-) diff --git a/android/android.toolchain.cmake b/android/android.toolchain.cmake index df365fc2c0..9db174a138 100644 --- a/android/android.toolchain.cmake +++ b/android/android.toolchain.cmake @@ -1,6 +1,7 @@ message(STATUS "Android toolchain was moved to platfroms/android!") message(STATUS "This file is depricated and will be removed!") +# Copyright (c) 2010-2011, Ethan Rublee # Copyright (c) 2011-2013, Andrey Kamaev # All rights reserved. # @@ -291,6 +292,9 @@ message(STATUS "This file is depricated and will be removed!") # - March 2013 # [+] updated for NDK r8e (x86 version) # [+] support x86_64 version of NDK +# - April 2013 +# [+] support non-release NDK layouts (from Linaro git and Android git) +# [~] automatically detect if explicit link to crtbegin_*.o is needed # ------------------------------------------------------------------------------ cmake_minimum_required( VERSION 2.6.3 ) @@ -518,24 +522,19 @@ if( NOT ANDROID_NDK ) endif( ANDROID_NDK ) endif( NOT ANDROID_STANDALONE_TOOLCHAIN ) endif( NOT ANDROID_NDK ) + # remember found paths if( ANDROID_NDK ) get_filename_component( ANDROID_NDK "${ANDROID_NDK}" ABSOLUTE ) - # try to detect change - if( CMAKE_AR ) - string( LENGTH "${ANDROID_NDK}" __length ) - string( SUBSTRING "${CMAKE_AR}" 0 ${__length} __androidNdkPreviousPath ) - if( NOT __androidNdkPreviousPath STREQUAL ANDROID_NDK ) - message( FATAL_ERROR "It is not possible to change the path to the NDK on subsequent CMake run. You must remove all generated files from your build folder first. - " ) - endif() - unset( __androidNdkPreviousPath ) - unset( __length ) - endif() set( ANDROID_NDK "${ANDROID_NDK}" CACHE INTERNAL "Path of the Android NDK" FORCE ) set( BUILD_WITH_ANDROID_NDK True ) - file( STRINGS "${ANDROID_NDK}/RELEASE.TXT" ANDROID_NDK_RELEASE_FULL LIMIT_COUNT 1 REGEX r[0-9]+[a-z]? ) - string( REGEX MATCH r[0-9]+[a-z]? ANDROID_NDK_RELEASE "${ANDROID_NDK_RELEASE_FULL}" ) + if( EXISTS "${ANDROID_NDK}/RELEASE.TXT" ) + file( STRINGS "${ANDROID_NDK}/RELEASE.TXT" ANDROID_NDK_RELEASE_FULL LIMIT_COUNT 1 REGEX r[0-9]+[a-z]? ) + string( REGEX MATCH r[0-9]+[a-z]? ANDROID_NDK_RELEASE "${ANDROID_NDK_RELEASE_FULL}" ) + else() + set( ANDROID_NDK_RELEASE "r1x" ) + set( ANDROID_NDK_RELEASE_FULL "unreleased" ) + endif() elseif( ANDROID_STANDALONE_TOOLCHAIN ) get_filename_component( ANDROID_STANDALONE_TOOLCHAIN "${ANDROID_STANDALONE_TOOLCHAIN}" ABSOLUTE ) # try to detect change @@ -562,6 +561,51 @@ else() sudo ln -s ~/my-android-toolchain ${ANDROID_STANDALONE_TOOLCHAIN_SEARCH_PATH}" ) endif() +# android NDK layout +if( BUILD_WITH_ANDROID_NDK ) + if( NOT DEFINED ANDROID_NDK_LAYOUT ) + # try to automatically detect the layout + if( EXISTS "${ANDROID_NDK}/RELEASE.TXT") + set( ANDROID_NDK_LAYOUT "RELEASE" ) + elseif( EXISTS "${ANDROID_NDK}/../../linux-x86/toolchain/" ) + set( ANDROID_NDK_LAYOUT "LINARO" ) + elseif( EXISTS "${ANDROID_NDK}/../../gcc/" ) + set( ANDROID_NDK_LAYOUT "ANDROID" ) + endif() + endif() + set( ANDROID_NDK_LAYOUT "${ANDROID_NDK_LAYOUT}" CACHE STRING "The inner layout of NDK" ) + mark_as_advanced( ANDROID_NDK_LAYOUT ) + if( ANDROID_NDK_LAYOUT STREQUAL "LINARO" ) + set( ANDROID_NDK_HOST_SYSTEM_NAME ${ANDROID_NDK_HOST_SYSTEM_NAME2} ) # only 32-bit at the moment + set( ANDROID_NDK_TOOLCHAINS_PATH "${ANDROID_NDK}/../../${ANDROID_NDK_HOST_SYSTEM_NAME}/toolchain" ) + set( ANDROID_NDK_TOOLCHAINS_SUBPATH "" ) + set( ANDROID_NDK_TOOLCHAINS_SUBPATH2 "" ) + elseif( ANDROID_NDK_LAYOUT STREQUAL "ANDROID" ) + set( ANDROID_NDK_HOST_SYSTEM_NAME ${ANDROID_NDK_HOST_SYSTEM_NAME2} ) # only 32-bit at the moment + set( ANDROID_NDK_TOOLCHAINS_PATH "${ANDROID_NDK}/../../gcc/${ANDROID_NDK_HOST_SYSTEM_NAME}/arm" ) + set( ANDROID_NDK_TOOLCHAINS_SUBPATH "" ) + set( ANDROID_NDK_TOOLCHAINS_SUBPATH2 "" ) + else() # ANDROID_NDK_LAYOUT STREQUAL "RELEASE" + set( ANDROID_NDK_TOOLCHAINS_PATH "${ANDROID_NDK}/toolchains" ) + set( ANDROID_NDK_TOOLCHAINS_SUBPATH "/prebuilt/${ANDROID_NDK_HOST_SYSTEM_NAME}" ) + set( ANDROID_NDK_TOOLCHAINS_SUBPATH2 "/prebuilt/${ANDROID_NDK_HOST_SYSTEM_NAME2}" ) + endif() + get_filename_component( ANDROID_NDK_TOOLCHAINS_PATH "${ANDROID_NDK_TOOLCHAINS_PATH}" ABSOLUTE ) + + # try to detect change of NDK + if( CMAKE_AR ) + string( LENGTH "${ANDROID_NDK_TOOLCHAINS_PATH}" __length ) + string( SUBSTRING "${CMAKE_AR}" 0 ${__length} __androidNdkPreviousPath ) + if( NOT __androidNdkPreviousPath STREQUAL ANDROID_NDK_TOOLCHAINS_PATH ) + message( FATAL_ERROR "It is not possible to change the path to the NDK on subsequent CMake run. You must remove all generated files from your build folder first. + " ) + endif() + unset( __androidNdkPreviousPath ) + unset( __length ) + endif() +endif() + + # get all the details about standalone toolchain if( BUILD_WITH_STANDALONE_TOOLCHAIN ) __DETECT_NATIVE_API_LEVEL( ANDROID_SUPPORTED_NATIVE_API_LEVELS "${ANDROID_STANDALONE_TOOLCHAIN}/sysroot/usr/include/android/api-level.h" ) @@ -589,17 +633,23 @@ if( BUILD_WITH_STANDALONE_TOOLCHAIN ) endif() endif() -macro( __GLOB_NDK_TOOLCHAINS __availableToolchainsVar __availableToolchainsLst __host_system_name ) +macro( __GLOB_NDK_TOOLCHAINS __availableToolchainsVar __availableToolchainsLst __toolchain_subpath ) foreach( __toolchain ${${__availableToolchainsLst}} ) - if( "${__toolchain}" MATCHES "-clang3[.][0-9]$" AND NOT EXISTS "${ANDROID_NDK}/toolchains/${__toolchain}/prebuilt/" ) + if( "${__toolchain}" MATCHES "-clang3[.][0-9]$" AND NOT EXISTS "${ANDROID_NDK_TOOLCHAINS_PATH}/${__toolchain}${__toolchain_subpath}" ) string( REGEX REPLACE "-clang3[.][0-9]$" "-4.6" __gcc_toolchain "${__toolchain}" ) else() set( __gcc_toolchain "${__toolchain}" ) endif() - __DETECT_TOOLCHAIN_MACHINE_NAME( __machine "${ANDROID_NDK}/toolchains/${__gcc_toolchain}/prebuilt/${__host_system_name}" ) + __DETECT_TOOLCHAIN_MACHINE_NAME( __machine "${ANDROID_NDK_TOOLCHAINS_PATH}/${__gcc_toolchain}${__toolchain_subpath}" ) if( __machine ) - string( REGEX MATCH "[0-9]+[.][0-9]+([.][0-9]+)?$" __version "${__gcc_toolchain}" ) - string( REGEX MATCH "^[^-]+" __arch "${__gcc_toolchain}" ) + string( REGEX MATCH "[0-9]+[.][0-9]+([.][0-9x]+)?$" __version "${__gcc_toolchain}" ) + if( __machine MATCHES i686 ) + set( __arch "x86" ) + elseif( __machine MATCHES arm ) + set( __arch "arm" ) + elseif( __machine MATCHES mipsel ) + set( __arch "mipsel" ) + endif() list( APPEND __availableToolchainMachines "${__machine}" ) list( APPEND __availableToolchainArchs "${__arch}" ) list( APPEND __availableToolchainCompilerVersions "${__version}" ) @@ -617,29 +667,29 @@ if( BUILD_WITH_ANDROID_NDK ) set( __availableToolchainMachines "" ) set( __availableToolchainArchs "" ) set( __availableToolchainCompilerVersions "" ) - if( ANDROID_TOOLCHAIN_NAME AND EXISTS "${ANDROID_NDK}/toolchains/${ANDROID_TOOLCHAIN_NAME}/" ) + if( ANDROID_TOOLCHAIN_NAME AND EXISTS "${ANDROID_NDK_TOOLCHAINS_PATH}/${ANDROID_TOOLCHAIN_NAME}/" ) # do not go through all toolchains if we know the name set( __availableToolchainsLst "${ANDROID_TOOLCHAIN_NAME}" ) - __GLOB_NDK_TOOLCHAINS( __availableToolchains __availableToolchainsLst ${ANDROID_NDK_HOST_SYSTEM_NAME} ) - if( NOT __availableToolchains AND NOT ANDROID_NDK_HOST_SYSTEM_NAME STREQUAL ANDROID_NDK_HOST_SYSTEM_NAME2 ) - __GLOB_NDK_TOOLCHAINS( __availableToolchains __availableToolchainsLst ${ANDROID_NDK_HOST_SYSTEM_NAME2} ) + __GLOB_NDK_TOOLCHAINS( __availableToolchains __availableToolchainsLst "${ANDROID_NDK_TOOLCHAINS_SUBPATH}" ) + if( NOT __availableToolchains AND NOT ANDROID_NDK_TOOLCHAINS_SUBPATH STREQUAL ANDROID_NDK_TOOLCHAINS_SUBPATH2 ) + __GLOB_NDK_TOOLCHAINS( __availableToolchains __availableToolchainsLst "${ANDROID_NDK_TOOLCHAINS_SUBPATH2}" ) if( __availableToolchains ) - set( ANDROID_NDK_HOST_SYSTEM_NAME ${ANDROID_NDK_HOST_SYSTEM_NAME2} ) + set( ANDROID_NDK_TOOLCHAINS_SUBPATH ${ANDROID_NDK_TOOLCHAINS_SUBPATH2} ) endif() endif() endif() if( NOT __availableToolchains ) - file( GLOB __availableToolchainsLst RELATIVE "${ANDROID_NDK}/toolchains" "${ANDROID_NDK}/toolchains/*" ) + file( GLOB __availableToolchainsLst RELATIVE "${ANDROID_NDK_TOOLCHAINS_PATH}" "${ANDROID_NDK_TOOLCHAINS_PATH}/*" ) if( __availableToolchains ) list(SORT __availableToolchainsLst) # we need clang to go after gcc endif() __LIST_FILTER( __availableToolchainsLst "^[.]" ) __LIST_FILTER( __availableToolchainsLst "llvm" ) - __GLOB_NDK_TOOLCHAINS( __availableToolchains __availableToolchainsLst ${ANDROID_NDK_HOST_SYSTEM_NAME} ) - if( NOT __availableToolchains AND NOT ANDROID_NDK_HOST_SYSTEM_NAME STREQUAL ANDROID_NDK_HOST_SYSTEM_NAME2 ) - __GLOB_NDK_TOOLCHAINS( __availableToolchains __availableToolchainsLst ${ANDROID_NDK_HOST_SYSTEM_NAME2} ) + __GLOB_NDK_TOOLCHAINS( __availableToolchains __availableToolchainsLst "${ANDROID_NDK_TOOLCHAINS_SUBPATH}" ) + if( NOT __availableToolchains AND NOT ANDROID_NDK_TOOLCHAINS_SUBPATH STREQUAL ANDROID_NDK_TOOLCHAINS_SUBPATH2 ) + __GLOB_NDK_TOOLCHAINS( __availableToolchains __availableToolchainsLst "${ANDROID_NDK_TOOLCHAINS_SUBPATH2}" ) if( __availableToolchains ) - set( ANDROID_NDK_HOST_SYSTEM_NAME ${ANDROID_NDK_HOST_SYSTEM_NAME2} ) + set( ANDROID_NDK_TOOLCHAINS_SUBPATH ${ANDROID_NDK_TOOLCHAINS_SUBPATH2} ) endif() endif() endif() @@ -770,6 +820,7 @@ else() list( GET __availableToolchainArchs ${__idx} __toolchainArch ) if( __toolchainArch STREQUAL ANDROID_ARCH_FULLNAME ) list( GET __availableToolchainCompilerVersions ${__idx} __toolchainVersion ) + string( REPLACE "x" "99" __toolchainVersion "${__toolchainVersion}") if( __toolchainVersion VERSION_GREATER __toolchainMaxVersion ) set( __toolchainMaxVersion "${__toolchainVersion}" ) set( __toolchainIdx ${__idx} ) @@ -973,11 +1024,11 @@ if( "${ANDROID_TOOLCHAIN_NAME}" STREQUAL "standalone-clang" ) elseif( "${ANDROID_TOOLCHAIN_NAME}" MATCHES "-clang3[.][0-9]?$" ) string( REGEX MATCH "3[.][0-9]$" ANDROID_CLANG_VERSION "${ANDROID_TOOLCHAIN_NAME}") string( REGEX REPLACE "-clang${ANDROID_CLANG_VERSION}$" "-4.6" ANDROID_GCC_TOOLCHAIN_NAME "${ANDROID_TOOLCHAIN_NAME}" ) - if( NOT EXISTS "${ANDROID_NDK}/toolchains/llvm-${ANDROID_CLANG_VERSION}/prebuilt/${ANDROID_NDK_HOST_SYSTEM_NAME}/bin/clang${TOOL_OS_SUFFIX}" ) + if( NOT EXISTS "${ANDROID_NDK_TOOLCHAINS_PATH}/llvm-${ANDROID_CLANG_VERSION}${ANDROID_NDK_TOOLCHAINS_SUBPATH}/bin/clang${TOOL_OS_SUFFIX}" ) message( FATAL_ERROR "Could not find the Clang compiler driver" ) endif() set( ANDROID_COMPILER_IS_CLANG 1 ) - set( ANDROID_CLANG_TOOLCHAIN_ROOT "${ANDROID_NDK}/toolchains/llvm-${ANDROID_CLANG_VERSION}/prebuilt/${ANDROID_NDK_HOST_SYSTEM_NAME}" ) + set( ANDROID_CLANG_TOOLCHAIN_ROOT "${ANDROID_NDK_TOOLCHAINS_PATH}/llvm-${ANDROID_CLANG_VERSION}${ANDROID_NDK_TOOLCHAINS_SUBPATH}" ) else() set( ANDROID_GCC_TOOLCHAIN_NAME "${ANDROID_TOOLCHAIN_NAME}" ) unset( ANDROID_COMPILER_IS_CLANG CACHE ) @@ -991,7 +1042,7 @@ endif() # setup paths and STL for NDK if( BUILD_WITH_ANDROID_NDK ) - set( ANDROID_TOOLCHAIN_ROOT "${ANDROID_NDK}/toolchains/${ANDROID_GCC_TOOLCHAIN_NAME}/prebuilt/${ANDROID_NDK_HOST_SYSTEM_NAME}" ) + set( ANDROID_TOOLCHAIN_ROOT "${ANDROID_NDK_TOOLCHAINS_PATH}/${ANDROID_GCC_TOOLCHAIN_NAME}${ANDROID_NDK_TOOLCHAINS_SUBPATH}" ) set( ANDROID_SYSROOT "${ANDROID_NDK}/platforms/android-${ANDROID_NATIVE_API_LEVEL}/arch-${ANDROID_ARCH_NAME}" ) if( ANDROID_STL STREQUAL "none" ) @@ -1050,11 +1101,11 @@ if( BUILD_WITH_ANDROID_NDK ) endif() # find libsupc++.a - rtti & exceptions if( ANDROID_STL STREQUAL "system_re" OR ANDROID_STL MATCHES "gnustl" ) - if( ANDROID_NDK_RELEASE STRGREATER "r8" ) # r8b - set( __libsupcxx "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/${ANDROID_COMPILER_VERSION}/libs/${ANDROID_NDK_ABI_NAME}/libsupc++.a" ) - elseif( NOT ANDROID_NDK_RELEASE STRLESS "r7" AND ANDROID_NDK_RELEASE STRLESS "r8b") - set( __libsupcxx "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/libs/${ANDROID_NDK_ABI_NAME}/libsupc++.a" ) - else( ANDROID_NDK_RELEASE STRLESS "r7" ) + set( __libsupcxx "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/${ANDROID_COMPILER_VERSION}/libs/${ANDROID_NDK_ABI_NAME}/libsupc++.a" ) # r8b or newer + if( NOT EXISTS "${__libsupcxx}" ) + set( __libsupcxx "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/libs/${ANDROID_NDK_ABI_NAME}/libsupc++.a" ) # r7-r8 + endif() + if( NOT EXISTS "${__libsupcxx}" ) # before r7 if( ARMEABI_V7A ) if( ANDROID_FORCE_ARM_BUILD ) set( __libsupcxx "${ANDROID_TOOLCHAIN_ROOT}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/libsupc++.a" ) @@ -1104,7 +1155,7 @@ unset( _ndk_ccache ) # setup the cross-compiler if( NOT CMAKE_C_COMPILER ) - if( NDK_CCACHE ) + if( NDK_CCACHE AND NOT ANDROID_SYSROOT MATCHES "[ ;\"]" ) set( CMAKE_C_COMPILER "${NDK_CCACHE}" CACHE PATH "ccache as C compiler" ) set( CMAKE_CXX_COMPILER "${NDK_CCACHE}" CACHE PATH "ccache as C++ compiler" ) if( ANDROID_COMPILER_IS_CLANG ) @@ -1176,11 +1227,25 @@ set( CMAKE_ASM_SOURCE_FILE_EXTENSIONS s S asm ) remove_definitions( -DANDROID ) add_definitions( -DANDROID ) -if(ANDROID_SYSROOT MATCHES "[ ;\"]") - set( ANDROID_CXX_FLAGS "--sysroot=\"${ANDROID_SYSROOT}\"" ) +if( ANDROID_SYSROOT MATCHES "[ ;\"]" ) + if( CMAKE_HOST_WIN32 ) + # try to convert path to 8.3 form + file( WRITE "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/cvt83.cmd" "@echo %~s1" ) + execute_process( COMMAND "$ENV{ComSpec}" /c "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/cvt83.cmd" "${ANDROID_SYSROOT}" + OUTPUT_VARIABLE __path OUTPUT_STRIP_TRAILING_WHITESPACE + RESULT_VARIABLE __result ERROR_QUIET ) + if( __result EQUAL 0 ) + file( TO_CMAKE_PATH "${__path}" ANDROID_SYSROOT ) + set( ANDROID_CXX_FLAGS "--sysroot=${ANDROID_SYSROOT}" ) + else() + set( ANDROID_CXX_FLAGS "--sysroot=\"${ANDROID_SYSROOT}\"" ) + endif() + else() + set( ANDROID_CXX_FLAGS "'--sysroot=${ANDROID_SYSROOT}'" ) + endif() if( NOT _CMAKE_IN_TRY_COMPILE ) - # quotes will break try_compile and compiler identification - message(WARNING "Your Android system root has non-alphanumeric symbols. It can break compiler features detection and the whole build.") + # quotes can break try_compile and compiler identification + message(WARNING "Path to your Android NDK (or toolchain) has non-alphanumeric symbols.\nThe build might be broken.\n") endif() else() set( ANDROID_CXX_FLAGS "--sysroot=${ANDROID_SYSROOT}" ) @@ -1251,22 +1316,18 @@ elseif( ARMEABI ) set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -march=armv5te -mtune=xscale -msoft-float" ) endif() +if( ANDROID_STL MATCHES "gnustl" AND (EXISTS "${__libstl}" OR EXISTS "${__libsupcxx}") ) + set( CMAKE_CXX_CREATE_SHARED_LIBRARY " -o " ) + set( CMAKE_CXX_CREATE_SHARED_MODULE " -o " ) + set( CMAKE_CXX_LINK_EXECUTABLE " -o " ) +else() + set( CMAKE_CXX_CREATE_SHARED_LIBRARY " -o " ) + set( CMAKE_CXX_CREATE_SHARED_MODULE " -o " ) + set( CMAKE_CXX_LINK_EXECUTABLE " -o " ) +endif() + # STL if( EXISTS "${__libstl}" OR EXISTS "${__libsupcxx}" ) - if( ANDROID_STL MATCHES "gnustl" ) - set( CMAKE_CXX_CREATE_SHARED_LIBRARY " -o " ) - set( CMAKE_CXX_CREATE_SHARED_MODULE " -o " ) - set( CMAKE_CXX_LINK_EXECUTABLE " -o " ) - else() - set( CMAKE_CXX_CREATE_SHARED_LIBRARY " -o " ) - set( CMAKE_CXX_CREATE_SHARED_MODULE " -o " ) - set( CMAKE_CXX_LINK_EXECUTABLE " -o " ) - endif() - if ( X86 AND ANDROID_STL MATCHES "gnustl" AND ANDROID_NDK_RELEASE STREQUAL "r6" ) - # workaround "undefined reference to `__dso_handle'" problem - set( CMAKE_CXX_CREATE_SHARED_LIBRARY "${CMAKE_CXX_CREATE_SHARED_LIBRARY} \"${ANDROID_SYSROOT}/usr/lib/crtbegin_so.o\"" ) - set( CMAKE_CXX_CREATE_SHARED_MODULE "${CMAKE_CXX_CREATE_SHARED_MODULE} \"${ANDROID_SYSROOT}/usr/lib/crtbegin_so.o\"" ) - endif() if( EXISTS "${__libstl}" ) set( CMAKE_CXX_CREATE_SHARED_LIBRARY "${CMAKE_CXX_CREATE_SHARED_LIBRARY} \"${__libstl}\"" ) set( CMAKE_CXX_CREATE_SHARED_MODULE "${CMAKE_CXX_CREATE_SHARED_MODULE} \"${__libstl}\"" ) @@ -1285,9 +1346,12 @@ if( EXISTS "${__libstl}" OR EXISTS "${__libsupcxx}" ) set( CMAKE_C_LINK_EXECUTABLE "${CMAKE_C_LINK_EXECUTABLE} \"${__libsupcxx}\"" ) endif() if( ANDROID_STL MATCHES "gnustl" ) - set( CMAKE_CXX_CREATE_SHARED_LIBRARY "${CMAKE_CXX_CREATE_SHARED_LIBRARY} -lm" ) - set( CMAKE_CXX_CREATE_SHARED_MODULE "${CMAKE_CXX_CREATE_SHARED_MODULE} -lm" ) - set( CMAKE_CXX_LINK_EXECUTABLE "${CMAKE_CXX_LINK_EXECUTABLE} -lm" ) + if( NOT EXISTS "${ANDROID_LIBM_PATH}" ) + set( ANDROID_LIBM_PATH -lm ) + endif() + set( CMAKE_CXX_CREATE_SHARED_LIBRARY "${CMAKE_CXX_CREATE_SHARED_LIBRARY} ${ANDROID_LIBM_PATH}" ) + set( CMAKE_CXX_CREATE_SHARED_MODULE "${CMAKE_CXX_CREATE_SHARED_MODULE} ${ANDROID_LIBM_PATH}" ) + set( CMAKE_CXX_LINK_EXECUTABLE "${CMAKE_CXX_LINK_EXECUTABLE} ${ANDROID_LIBM_PATH}" ) endif() endif() @@ -1323,7 +1387,14 @@ if( ARMEABI_V7A ) endif() if( ANDROID_NO_UNDEFINED ) - set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,--no-undefined" ) + if( MIPS ) + # there is some sysroot-related problem in mips linker... + if( NOT ANDROID_SYSROOT MATCHES "[ ;\"]" ) + set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,--no-undefined -Wl,-rpath-link,${ANDROID_SYSROOT}/usr/lib" ) + endif() + else() + set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,--no-undefined" ) + endif() endif() if( ANDROID_SO_UNDEFINED ) @@ -1403,9 +1474,9 @@ set( CMAKE_MODULE_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} ${CMAKE_MODULE_LINKER_FL set( CMAKE_EXE_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} ${CMAKE_EXE_LINKER_FLAGS}" ) if( MIPS AND BUILD_WITH_ANDROID_NDK AND ANDROID_NDK_RELEASE STREQUAL "r8" ) - set( CMAKE_SHARED_LINKER_FLAGS "-Wl,-T,${ANDROID_NDK}/toolchains/${ANDROID_GCC_TOOLCHAIN_NAME}/mipself.xsc ${CMAKE_SHARED_LINKER_FLAGS}" ) - set( CMAKE_MODULE_LINKER_FLAGS "-Wl,-T,${ANDROID_NDK}/toolchains/${ANDROID_GCC_TOOLCHAIN_NAME}/mipself.xsc ${CMAKE_MODULE_LINKER_FLAGS}" ) - set( CMAKE_EXE_LINKER_FLAGS "-Wl,-T,${ANDROID_NDK}/toolchains/${ANDROID_GCC_TOOLCHAIN_NAME}/mipself.x ${CMAKE_EXE_LINKER_FLAGS}" ) + set( CMAKE_SHARED_LINKER_FLAGS "-Wl,-T,${ANDROID_NDK_TOOLCHAINS_PATH}/${ANDROID_GCC_TOOLCHAIN_NAME}/mipself.xsc ${CMAKE_SHARED_LINKER_FLAGS}" ) + set( CMAKE_MODULE_LINKER_FLAGS "-Wl,-T,${ANDROID_NDK_TOOLCHAINS_PATH}/${ANDROID_GCC_TOOLCHAIN_NAME}/mipself.xsc ${CMAKE_MODULE_LINKER_FLAGS}" ) + set( CMAKE_EXE_LINKER_FLAGS "-Wl,-T,${ANDROID_NDK_TOOLCHAINS_PATH}/${ANDROID_GCC_TOOLCHAIN_NAME}/mipself.x ${CMAKE_EXE_LINKER_FLAGS}" ) endif() # configure rtti @@ -1432,6 +1503,43 @@ endif() include_directories( SYSTEM "${ANDROID_SYSROOT}/usr/include" ${ANDROID_STL_INCLUDE_DIRS} ) link_directories( "${CMAKE_INSTALL_PREFIX}/libs/${ANDROID_NDK_ABI_NAME}" ) +# detect if need link crtbegin_so.o explicitly +if( NOT DEFINED ANDROID_EXPLICIT_CRT_LINK ) + set( __cmd "${CMAKE_CXX_CREATE_SHARED_LIBRARY}" ) + string( REPLACE "" "${CMAKE_CXX_COMPILER} ${CMAKE_CXX_COMPILER_ARG1}" __cmd "${__cmd}" ) + string( REPLACE "" "${CMAKE_C_COMPILER} ${CMAKE_C_COMPILER_ARG1}" __cmd "${__cmd}" ) + string( REPLACE "" "${CMAKE_CXX_FLAGS}" __cmd "${__cmd}" ) + string( REPLACE "" "" __cmd "${__cmd}" ) + string( REPLACE "" "${CMAKE_SHARED_LINKER_FLAGS}" __cmd "${__cmd}" ) + string( REPLACE "" "-shared" __cmd "${__cmd}" ) + string( REPLACE "" "" __cmd "${__cmd}" ) + string( REPLACE "" "" __cmd "${__cmd}" ) + string( REPLACE "" "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/toolchain_crtlink_test.so" __cmd "${__cmd}" ) + string( REPLACE "" "\"${ANDROID_SYSROOT}/usr/lib/crtbegin_so.o\"" __cmd "${__cmd}" ) + string( REPLACE "" "" __cmd "${__cmd}" ) + separate_arguments( __cmd ) + foreach( __var ANDROID_NDK ANDROID_NDK_TOOLCHAINS_PATH ANDROID_STANDALONE_TOOLCHAIN ) + if( ${__var} ) + set( __tmp "${${__var}}" ) + separate_arguments( __tmp ) + string( REPLACE "${__tmp}" "${${__var}}" __cmd "${__cmd}") + endif() + endforeach() + string( REPLACE "'" "" __cmd "${__cmd}" ) + string( REPLACE "\"" "" __cmd "${__cmd}" ) + execute_process( COMMAND ${__cmd} RESULT_VARIABLE __cmd_result OUTPUT_QUIET ERROR_QUIET ) + if( __cmd_result EQUAL 0 ) + set( ANDROID_EXPLICIT_CRT_LINK ON ) + else() + set( ANDROID_EXPLICIT_CRT_LINK OFF ) + endif() +endif() + +if( ANDROID_EXPLICIT_CRT_LINK ) + set( CMAKE_CXX_CREATE_SHARED_LIBRARY "${CMAKE_CXX_CREATE_SHARED_LIBRARY} \"${ANDROID_SYSROOT}/usr/lib/crtbegin_so.o\"" ) + set( CMAKE_CXX_CREATE_SHARED_MODULE "${CMAKE_CXX_CREATE_SHARED_MODULE} \"${ANDROID_SYSROOT}/usr/lib/crtbegin_so.o\"" ) +endif() + # setup output directories set( LIBRARY_OUTPUT_PATH_ROOT ${CMAKE_SOURCE_DIR} CACHE PATH "root for library output, set this to change where android libs are installed to" ) set( CMAKE_INSTALL_PREFIX "${ANDROID_TOOLCHAIN_ROOT}/user" CACHE STRING "path for installing" ) @@ -1523,6 +1631,7 @@ if( NOT PROJECT_NAME STREQUAL "CMAKE_TRY_COMPILE" ) foreach( __var NDK_CCACHE LIBRARY_OUTPUT_PATH_ROOT ANDROID_FORBID_SYGWIN ANDROID_SET_OBSOLETE_VARIABLES ANDROID_NDK_HOST_X64 ANDROID_NDK + ANDROID_NDK_LAYOUT ANDROID_STANDALONE_TOOLCHAIN ANDROID_TOOLCHAIN_NAME ANDROID_ABI @@ -1536,6 +1645,8 @@ if( NOT PROJECT_NAME STREQUAL "CMAKE_TRY_COMPILE" ) ANDROID_GOLD_LINKER ANDROID_NOEXECSTACK ANDROID_RELRO + ANDROID_LIBM_PATH + ANDROID_EXPLICIT_CRT_LINK ) if( DEFINED ${__var} ) if( "${__var}" MATCHES " ") @@ -1579,6 +1690,7 @@ endif() # ANDROID_STANDALONE_TOOLCHAIN # ANDROID_TOOLCHAIN_NAME : the NDK name of compiler toolchain # ANDROID_NDK_HOST_X64 : try to use x86_64 toolchain (default for x64 host systems) +# ANDROID_NDK_LAYOUT : the inner NDK structure (RELEASE, LINARO, ANDROID) # LIBRARY_OUTPUT_PATH_ROOT : # NDK_CCACHE : # Obsolete: @@ -1624,6 +1736,7 @@ endif() # ANDROID_EXCEPTIONS : if exceptions are enabled by the runtime # ANDROID_GCC_TOOLCHAIN_NAME : read-only, differs from ANDROID_TOOLCHAIN_NAME only if clang is used # ANDROID_CLANG_VERSION : version of clang compiler if clang is used +# ANDROID_LIBM_PATH : path to libm.so (set to something like $(TOP)/out/target/product//obj/lib/libm.so) to workaround unresolved `sincos` # # Defaults: # ANDROID_DEFAULT_NDK_API_LEVEL diff --git a/platforms/android/android.toolchain.cmake b/platforms/android/android.toolchain.cmake index 0f7e340678..d7f09c7888 100644 --- a/platforms/android/android.toolchain.cmake +++ b/platforms/android/android.toolchain.cmake @@ -289,6 +289,9 @@ # - March 2013 # [+] updated for NDK r8e (x86 version) # [+] support x86_64 version of NDK +# - April 2013 +# [+] support non-release NDK layouts (from Linaro git and Android git) +# [~] automatically detect if explicit link to crtbegin_*.o is needed # ------------------------------------------------------------------------------ cmake_minimum_required( VERSION 2.6.3 ) @@ -516,24 +519,19 @@ if( NOT ANDROID_NDK ) endif( ANDROID_NDK ) endif( NOT ANDROID_STANDALONE_TOOLCHAIN ) endif( NOT ANDROID_NDK ) + # remember found paths if( ANDROID_NDK ) get_filename_component( ANDROID_NDK "${ANDROID_NDK}" ABSOLUTE ) - # try to detect change - if( CMAKE_AR ) - string( LENGTH "${ANDROID_NDK}" __length ) - string( SUBSTRING "${CMAKE_AR}" 0 ${__length} __androidNdkPreviousPath ) - if( NOT __androidNdkPreviousPath STREQUAL ANDROID_NDK ) - message( FATAL_ERROR "It is not possible to change the path to the NDK on subsequent CMake run. You must remove all generated files from your build folder first. - " ) - endif() - unset( __androidNdkPreviousPath ) - unset( __length ) - endif() set( ANDROID_NDK "${ANDROID_NDK}" CACHE INTERNAL "Path of the Android NDK" FORCE ) set( BUILD_WITH_ANDROID_NDK True ) - file( STRINGS "${ANDROID_NDK}/RELEASE.TXT" ANDROID_NDK_RELEASE_FULL LIMIT_COUNT 1 REGEX r[0-9]+[a-z]? ) - string( REGEX MATCH r[0-9]+[a-z]? ANDROID_NDK_RELEASE "${ANDROID_NDK_RELEASE_FULL}" ) + if( EXISTS "${ANDROID_NDK}/RELEASE.TXT" ) + file( STRINGS "${ANDROID_NDK}/RELEASE.TXT" ANDROID_NDK_RELEASE_FULL LIMIT_COUNT 1 REGEX r[0-9]+[a-z]? ) + string( REGEX MATCH r[0-9]+[a-z]? ANDROID_NDK_RELEASE "${ANDROID_NDK_RELEASE_FULL}" ) + else() + set( ANDROID_NDK_RELEASE "r1x" ) + set( ANDROID_NDK_RELEASE_FULL "unreleased" ) + endif() elseif( ANDROID_STANDALONE_TOOLCHAIN ) get_filename_component( ANDROID_STANDALONE_TOOLCHAIN "${ANDROID_STANDALONE_TOOLCHAIN}" ABSOLUTE ) # try to detect change @@ -560,6 +558,51 @@ else() sudo ln -s ~/my-android-toolchain ${ANDROID_STANDALONE_TOOLCHAIN_SEARCH_PATH}" ) endif() +# android NDK layout +if( BUILD_WITH_ANDROID_NDK ) + if( NOT DEFINED ANDROID_NDK_LAYOUT ) + # try to automatically detect the layout + if( EXISTS "${ANDROID_NDK}/RELEASE.TXT") + set( ANDROID_NDK_LAYOUT "RELEASE" ) + elseif( EXISTS "${ANDROID_NDK}/../../linux-x86/toolchain/" ) + set( ANDROID_NDK_LAYOUT "LINARO" ) + elseif( EXISTS "${ANDROID_NDK}/../../gcc/" ) + set( ANDROID_NDK_LAYOUT "ANDROID" ) + endif() + endif() + set( ANDROID_NDK_LAYOUT "${ANDROID_NDK_LAYOUT}" CACHE STRING "The inner layout of NDK" ) + mark_as_advanced( ANDROID_NDK_LAYOUT ) + if( ANDROID_NDK_LAYOUT STREQUAL "LINARO" ) + set( ANDROID_NDK_HOST_SYSTEM_NAME ${ANDROID_NDK_HOST_SYSTEM_NAME2} ) # only 32-bit at the moment + set( ANDROID_NDK_TOOLCHAINS_PATH "${ANDROID_NDK}/../../${ANDROID_NDK_HOST_SYSTEM_NAME}/toolchain" ) + set( ANDROID_NDK_TOOLCHAINS_SUBPATH "" ) + set( ANDROID_NDK_TOOLCHAINS_SUBPATH2 "" ) + elseif( ANDROID_NDK_LAYOUT STREQUAL "ANDROID" ) + set( ANDROID_NDK_HOST_SYSTEM_NAME ${ANDROID_NDK_HOST_SYSTEM_NAME2} ) # only 32-bit at the moment + set( ANDROID_NDK_TOOLCHAINS_PATH "${ANDROID_NDK}/../../gcc/${ANDROID_NDK_HOST_SYSTEM_NAME}/arm" ) + set( ANDROID_NDK_TOOLCHAINS_SUBPATH "" ) + set( ANDROID_NDK_TOOLCHAINS_SUBPATH2 "" ) + else() # ANDROID_NDK_LAYOUT STREQUAL "RELEASE" + set( ANDROID_NDK_TOOLCHAINS_PATH "${ANDROID_NDK}/toolchains" ) + set( ANDROID_NDK_TOOLCHAINS_SUBPATH "/prebuilt/${ANDROID_NDK_HOST_SYSTEM_NAME}" ) + set( ANDROID_NDK_TOOLCHAINS_SUBPATH2 "/prebuilt/${ANDROID_NDK_HOST_SYSTEM_NAME2}" ) + endif() + get_filename_component( ANDROID_NDK_TOOLCHAINS_PATH "${ANDROID_NDK_TOOLCHAINS_PATH}" ABSOLUTE ) + + # try to detect change of NDK + if( CMAKE_AR ) + string( LENGTH "${ANDROID_NDK_TOOLCHAINS_PATH}" __length ) + string( SUBSTRING "${CMAKE_AR}" 0 ${__length} __androidNdkPreviousPath ) + if( NOT __androidNdkPreviousPath STREQUAL ANDROID_NDK_TOOLCHAINS_PATH ) + message( FATAL_ERROR "It is not possible to change the path to the NDK on subsequent CMake run. You must remove all generated files from your build folder first. + " ) + endif() + unset( __androidNdkPreviousPath ) + unset( __length ) + endif() +endif() + + # get all the details about standalone toolchain if( BUILD_WITH_STANDALONE_TOOLCHAIN ) __DETECT_NATIVE_API_LEVEL( ANDROID_SUPPORTED_NATIVE_API_LEVELS "${ANDROID_STANDALONE_TOOLCHAIN}/sysroot/usr/include/android/api-level.h" ) @@ -587,17 +630,23 @@ if( BUILD_WITH_STANDALONE_TOOLCHAIN ) endif() endif() -macro( __GLOB_NDK_TOOLCHAINS __availableToolchainsVar __availableToolchainsLst __host_system_name ) +macro( __GLOB_NDK_TOOLCHAINS __availableToolchainsVar __availableToolchainsLst __toolchain_subpath ) foreach( __toolchain ${${__availableToolchainsLst}} ) - if( "${__toolchain}" MATCHES "-clang3[.][0-9]$" AND NOT EXISTS "${ANDROID_NDK}/toolchains/${__toolchain}/prebuilt/" ) + if( "${__toolchain}" MATCHES "-clang3[.][0-9]$" AND NOT EXISTS "${ANDROID_NDK_TOOLCHAINS_PATH}/${__toolchain}${__toolchain_subpath}" ) string( REGEX REPLACE "-clang3[.][0-9]$" "-4.6" __gcc_toolchain "${__toolchain}" ) else() set( __gcc_toolchain "${__toolchain}" ) endif() - __DETECT_TOOLCHAIN_MACHINE_NAME( __machine "${ANDROID_NDK}/toolchains/${__gcc_toolchain}/prebuilt/${__host_system_name}" ) + __DETECT_TOOLCHAIN_MACHINE_NAME( __machine "${ANDROID_NDK_TOOLCHAINS_PATH}/${__gcc_toolchain}${__toolchain_subpath}" ) if( __machine ) - string( REGEX MATCH "[0-9]+[.][0-9]+([.][0-9]+)?$" __version "${__gcc_toolchain}" ) - string( REGEX MATCH "^[^-]+" __arch "${__gcc_toolchain}" ) + string( REGEX MATCH "[0-9]+[.][0-9]+([.][0-9x]+)?$" __version "${__gcc_toolchain}" ) + if( __machine MATCHES i686 ) + set( __arch "x86" ) + elseif( __machine MATCHES arm ) + set( __arch "arm" ) + elseif( __machine MATCHES mipsel ) + set( __arch "mipsel" ) + endif() list( APPEND __availableToolchainMachines "${__machine}" ) list( APPEND __availableToolchainArchs "${__arch}" ) list( APPEND __availableToolchainCompilerVersions "${__version}" ) @@ -615,29 +664,29 @@ if( BUILD_WITH_ANDROID_NDK ) set( __availableToolchainMachines "" ) set( __availableToolchainArchs "" ) set( __availableToolchainCompilerVersions "" ) - if( ANDROID_TOOLCHAIN_NAME AND EXISTS "${ANDROID_NDK}/toolchains/${ANDROID_TOOLCHAIN_NAME}/" ) + if( ANDROID_TOOLCHAIN_NAME AND EXISTS "${ANDROID_NDK_TOOLCHAINS_PATH}/${ANDROID_TOOLCHAIN_NAME}/" ) # do not go through all toolchains if we know the name set( __availableToolchainsLst "${ANDROID_TOOLCHAIN_NAME}" ) - __GLOB_NDK_TOOLCHAINS( __availableToolchains __availableToolchainsLst ${ANDROID_NDK_HOST_SYSTEM_NAME} ) - if( NOT __availableToolchains AND NOT ANDROID_NDK_HOST_SYSTEM_NAME STREQUAL ANDROID_NDK_HOST_SYSTEM_NAME2 ) - __GLOB_NDK_TOOLCHAINS( __availableToolchains __availableToolchainsLst ${ANDROID_NDK_HOST_SYSTEM_NAME2} ) + __GLOB_NDK_TOOLCHAINS( __availableToolchains __availableToolchainsLst "${ANDROID_NDK_TOOLCHAINS_SUBPATH}" ) + if( NOT __availableToolchains AND NOT ANDROID_NDK_TOOLCHAINS_SUBPATH STREQUAL ANDROID_NDK_TOOLCHAINS_SUBPATH2 ) + __GLOB_NDK_TOOLCHAINS( __availableToolchains __availableToolchainsLst "${ANDROID_NDK_TOOLCHAINS_SUBPATH2}" ) if( __availableToolchains ) - set( ANDROID_NDK_HOST_SYSTEM_NAME ${ANDROID_NDK_HOST_SYSTEM_NAME2} ) + set( ANDROID_NDK_TOOLCHAINS_SUBPATH ${ANDROID_NDK_TOOLCHAINS_SUBPATH2} ) endif() endif() endif() if( NOT __availableToolchains ) - file( GLOB __availableToolchainsLst RELATIVE "${ANDROID_NDK}/toolchains" "${ANDROID_NDK}/toolchains/*" ) + file( GLOB __availableToolchainsLst RELATIVE "${ANDROID_NDK_TOOLCHAINS_PATH}" "${ANDROID_NDK_TOOLCHAINS_PATH}/*" ) if( __availableToolchains ) list(SORT __availableToolchainsLst) # we need clang to go after gcc endif() __LIST_FILTER( __availableToolchainsLst "^[.]" ) __LIST_FILTER( __availableToolchainsLst "llvm" ) - __GLOB_NDK_TOOLCHAINS( __availableToolchains __availableToolchainsLst ${ANDROID_NDK_HOST_SYSTEM_NAME} ) - if( NOT __availableToolchains AND NOT ANDROID_NDK_HOST_SYSTEM_NAME STREQUAL ANDROID_NDK_HOST_SYSTEM_NAME2 ) - __GLOB_NDK_TOOLCHAINS( __availableToolchains __availableToolchainsLst ${ANDROID_NDK_HOST_SYSTEM_NAME2} ) + __GLOB_NDK_TOOLCHAINS( __availableToolchains __availableToolchainsLst "${ANDROID_NDK_TOOLCHAINS_SUBPATH}" ) + if( NOT __availableToolchains AND NOT ANDROID_NDK_TOOLCHAINS_SUBPATH STREQUAL ANDROID_NDK_TOOLCHAINS_SUBPATH2 ) + __GLOB_NDK_TOOLCHAINS( __availableToolchains __availableToolchainsLst "${ANDROID_NDK_TOOLCHAINS_SUBPATH2}" ) if( __availableToolchains ) - set( ANDROID_NDK_HOST_SYSTEM_NAME ${ANDROID_NDK_HOST_SYSTEM_NAME2} ) + set( ANDROID_NDK_TOOLCHAINS_SUBPATH ${ANDROID_NDK_TOOLCHAINS_SUBPATH2} ) endif() endif() endif() @@ -768,6 +817,7 @@ else() list( GET __availableToolchainArchs ${__idx} __toolchainArch ) if( __toolchainArch STREQUAL ANDROID_ARCH_FULLNAME ) list( GET __availableToolchainCompilerVersions ${__idx} __toolchainVersion ) + string( REPLACE "x" "99" __toolchainVersion "${__toolchainVersion}") if( __toolchainVersion VERSION_GREATER __toolchainMaxVersion ) set( __toolchainMaxVersion "${__toolchainVersion}" ) set( __toolchainIdx ${__idx} ) @@ -971,11 +1021,11 @@ if( "${ANDROID_TOOLCHAIN_NAME}" STREQUAL "standalone-clang" ) elseif( "${ANDROID_TOOLCHAIN_NAME}" MATCHES "-clang3[.][0-9]?$" ) string( REGEX MATCH "3[.][0-9]$" ANDROID_CLANG_VERSION "${ANDROID_TOOLCHAIN_NAME}") string( REGEX REPLACE "-clang${ANDROID_CLANG_VERSION}$" "-4.6" ANDROID_GCC_TOOLCHAIN_NAME "${ANDROID_TOOLCHAIN_NAME}" ) - if( NOT EXISTS "${ANDROID_NDK}/toolchains/llvm-${ANDROID_CLANG_VERSION}/prebuilt/${ANDROID_NDK_HOST_SYSTEM_NAME}/bin/clang${TOOL_OS_SUFFIX}" ) + if( NOT EXISTS "${ANDROID_NDK_TOOLCHAINS_PATH}/llvm-${ANDROID_CLANG_VERSION}${ANDROID_NDK_TOOLCHAINS_SUBPATH}/bin/clang${TOOL_OS_SUFFIX}" ) message( FATAL_ERROR "Could not find the Clang compiler driver" ) endif() set( ANDROID_COMPILER_IS_CLANG 1 ) - set( ANDROID_CLANG_TOOLCHAIN_ROOT "${ANDROID_NDK}/toolchains/llvm-${ANDROID_CLANG_VERSION}/prebuilt/${ANDROID_NDK_HOST_SYSTEM_NAME}" ) + set( ANDROID_CLANG_TOOLCHAIN_ROOT "${ANDROID_NDK_TOOLCHAINS_PATH}/llvm-${ANDROID_CLANG_VERSION}${ANDROID_NDK_TOOLCHAINS_SUBPATH}" ) else() set( ANDROID_GCC_TOOLCHAIN_NAME "${ANDROID_TOOLCHAIN_NAME}" ) unset( ANDROID_COMPILER_IS_CLANG CACHE ) @@ -989,7 +1039,7 @@ endif() # setup paths and STL for NDK if( BUILD_WITH_ANDROID_NDK ) - set( ANDROID_TOOLCHAIN_ROOT "${ANDROID_NDK}/toolchains/${ANDROID_GCC_TOOLCHAIN_NAME}/prebuilt/${ANDROID_NDK_HOST_SYSTEM_NAME}" ) + set( ANDROID_TOOLCHAIN_ROOT "${ANDROID_NDK_TOOLCHAINS_PATH}/${ANDROID_GCC_TOOLCHAIN_NAME}${ANDROID_NDK_TOOLCHAINS_SUBPATH}" ) set( ANDROID_SYSROOT "${ANDROID_NDK}/platforms/android-${ANDROID_NATIVE_API_LEVEL}/arch-${ANDROID_ARCH_NAME}" ) if( ANDROID_STL STREQUAL "none" ) @@ -1048,11 +1098,11 @@ if( BUILD_WITH_ANDROID_NDK ) endif() # find libsupc++.a - rtti & exceptions if( ANDROID_STL STREQUAL "system_re" OR ANDROID_STL MATCHES "gnustl" ) - if( ANDROID_NDK_RELEASE STRGREATER "r8" ) # r8b - set( __libsupcxx "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/${ANDROID_COMPILER_VERSION}/libs/${ANDROID_NDK_ABI_NAME}/libsupc++.a" ) - elseif( NOT ANDROID_NDK_RELEASE STRLESS "r7" AND ANDROID_NDK_RELEASE STRLESS "r8b") - set( __libsupcxx "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/libs/${ANDROID_NDK_ABI_NAME}/libsupc++.a" ) - else( ANDROID_NDK_RELEASE STRLESS "r7" ) + set( __libsupcxx "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/${ANDROID_COMPILER_VERSION}/libs/${ANDROID_NDK_ABI_NAME}/libsupc++.a" ) # r8b or newer + if( NOT EXISTS "${__libsupcxx}" ) + set( __libsupcxx "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/libs/${ANDROID_NDK_ABI_NAME}/libsupc++.a" ) # r7-r8 + endif() + if( NOT EXISTS "${__libsupcxx}" ) # before r7 if( ARMEABI_V7A ) if( ANDROID_FORCE_ARM_BUILD ) set( __libsupcxx "${ANDROID_TOOLCHAIN_ROOT}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/libsupc++.a" ) @@ -1102,7 +1152,7 @@ unset( _ndk_ccache ) # setup the cross-compiler if( NOT CMAKE_C_COMPILER ) - if( NDK_CCACHE ) + if( NDK_CCACHE AND NOT ANDROID_SYSROOT MATCHES "[ ;\"]" ) set( CMAKE_C_COMPILER "${NDK_CCACHE}" CACHE PATH "ccache as C compiler" ) set( CMAKE_CXX_COMPILER "${NDK_CCACHE}" CACHE PATH "ccache as C++ compiler" ) if( ANDROID_COMPILER_IS_CLANG ) @@ -1174,11 +1224,25 @@ set( CMAKE_ASM_SOURCE_FILE_EXTENSIONS s S asm ) remove_definitions( -DANDROID ) add_definitions( -DANDROID ) -if(ANDROID_SYSROOT MATCHES "[ ;\"]") - set( ANDROID_CXX_FLAGS "--sysroot=\"${ANDROID_SYSROOT}\"" ) +if( ANDROID_SYSROOT MATCHES "[ ;\"]" ) + if( CMAKE_HOST_WIN32 ) + # try to convert path to 8.3 form + file( WRITE "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/cvt83.cmd" "@echo %~s1" ) + execute_process( COMMAND "$ENV{ComSpec}" /c "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/cvt83.cmd" "${ANDROID_SYSROOT}" + OUTPUT_VARIABLE __path OUTPUT_STRIP_TRAILING_WHITESPACE + RESULT_VARIABLE __result ERROR_QUIET ) + if( __result EQUAL 0 ) + file( TO_CMAKE_PATH "${__path}" ANDROID_SYSROOT ) + set( ANDROID_CXX_FLAGS "--sysroot=${ANDROID_SYSROOT}" ) + else() + set( ANDROID_CXX_FLAGS "--sysroot=\"${ANDROID_SYSROOT}\"" ) + endif() + else() + set( ANDROID_CXX_FLAGS "'--sysroot=${ANDROID_SYSROOT}'" ) + endif() if( NOT _CMAKE_IN_TRY_COMPILE ) - # quotes will break try_compile and compiler identification - message(WARNING "Your Android system root has non-alphanumeric symbols. It can break compiler features detection and the whole build.") + # quotes can break try_compile and compiler identification + message(WARNING "Path to your Android NDK (or toolchain) has non-alphanumeric symbols.\nThe build might be broken.\n") endif() else() set( ANDROID_CXX_FLAGS "--sysroot=${ANDROID_SYSROOT}" ) @@ -1249,22 +1313,18 @@ elseif( ARMEABI ) set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -march=armv5te -mtune=xscale -msoft-float" ) endif() +if( ANDROID_STL MATCHES "gnustl" AND (EXISTS "${__libstl}" OR EXISTS "${__libsupcxx}") ) + set( CMAKE_CXX_CREATE_SHARED_LIBRARY " -o " ) + set( CMAKE_CXX_CREATE_SHARED_MODULE " -o " ) + set( CMAKE_CXX_LINK_EXECUTABLE " -o " ) +else() + set( CMAKE_CXX_CREATE_SHARED_LIBRARY " -o " ) + set( CMAKE_CXX_CREATE_SHARED_MODULE " -o " ) + set( CMAKE_CXX_LINK_EXECUTABLE " -o " ) +endif() + # STL if( EXISTS "${__libstl}" OR EXISTS "${__libsupcxx}" ) - if( ANDROID_STL MATCHES "gnustl" ) - set( CMAKE_CXX_CREATE_SHARED_LIBRARY " -o " ) - set( CMAKE_CXX_CREATE_SHARED_MODULE " -o " ) - set( CMAKE_CXX_LINK_EXECUTABLE " -o " ) - else() - set( CMAKE_CXX_CREATE_SHARED_LIBRARY " -o " ) - set( CMAKE_CXX_CREATE_SHARED_MODULE " -o " ) - set( CMAKE_CXX_LINK_EXECUTABLE " -o " ) - endif() - if ( X86 AND ANDROID_STL MATCHES "gnustl" AND ANDROID_NDK_RELEASE STREQUAL "r6" ) - # workaround "undefined reference to `__dso_handle'" problem - set( CMAKE_CXX_CREATE_SHARED_LIBRARY "${CMAKE_CXX_CREATE_SHARED_LIBRARY} \"${ANDROID_SYSROOT}/usr/lib/crtbegin_so.o\"" ) - set( CMAKE_CXX_CREATE_SHARED_MODULE "${CMAKE_CXX_CREATE_SHARED_MODULE} \"${ANDROID_SYSROOT}/usr/lib/crtbegin_so.o\"" ) - endif() if( EXISTS "${__libstl}" ) set( CMAKE_CXX_CREATE_SHARED_LIBRARY "${CMAKE_CXX_CREATE_SHARED_LIBRARY} \"${__libstl}\"" ) set( CMAKE_CXX_CREATE_SHARED_MODULE "${CMAKE_CXX_CREATE_SHARED_MODULE} \"${__libstl}\"" ) @@ -1283,9 +1343,12 @@ if( EXISTS "${__libstl}" OR EXISTS "${__libsupcxx}" ) set( CMAKE_C_LINK_EXECUTABLE "${CMAKE_C_LINK_EXECUTABLE} \"${__libsupcxx}\"" ) endif() if( ANDROID_STL MATCHES "gnustl" ) - set( CMAKE_CXX_CREATE_SHARED_LIBRARY "${CMAKE_CXX_CREATE_SHARED_LIBRARY} -lm" ) - set( CMAKE_CXX_CREATE_SHARED_MODULE "${CMAKE_CXX_CREATE_SHARED_MODULE} -lm" ) - set( CMAKE_CXX_LINK_EXECUTABLE "${CMAKE_CXX_LINK_EXECUTABLE} -lm" ) + if( NOT EXISTS "${ANDROID_LIBM_PATH}" ) + set( ANDROID_LIBM_PATH -lm ) + endif() + set( CMAKE_CXX_CREATE_SHARED_LIBRARY "${CMAKE_CXX_CREATE_SHARED_LIBRARY} ${ANDROID_LIBM_PATH}" ) + set( CMAKE_CXX_CREATE_SHARED_MODULE "${CMAKE_CXX_CREATE_SHARED_MODULE} ${ANDROID_LIBM_PATH}" ) + set( CMAKE_CXX_LINK_EXECUTABLE "${CMAKE_CXX_LINK_EXECUTABLE} ${ANDROID_LIBM_PATH}" ) endif() endif() @@ -1321,7 +1384,14 @@ if( ARMEABI_V7A ) endif() if( ANDROID_NO_UNDEFINED ) - set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,--no-undefined" ) + if( MIPS ) + # there is some sysroot-related problem in mips linker... + if( NOT ANDROID_SYSROOT MATCHES "[ ;\"]" ) + set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,--no-undefined -Wl,-rpath-link,${ANDROID_SYSROOT}/usr/lib" ) + endif() + else() + set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,--no-undefined" ) + endif() endif() if( ANDROID_SO_UNDEFINED ) @@ -1401,9 +1471,9 @@ set( CMAKE_MODULE_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} ${CMAKE_MODULE_LINKER_FL set( CMAKE_EXE_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} ${CMAKE_EXE_LINKER_FLAGS}" ) if( MIPS AND BUILD_WITH_ANDROID_NDK AND ANDROID_NDK_RELEASE STREQUAL "r8" ) - set( CMAKE_SHARED_LINKER_FLAGS "-Wl,-T,${ANDROID_NDK}/toolchains/${ANDROID_GCC_TOOLCHAIN_NAME}/mipself.xsc ${CMAKE_SHARED_LINKER_FLAGS}" ) - set( CMAKE_MODULE_LINKER_FLAGS "-Wl,-T,${ANDROID_NDK}/toolchains/${ANDROID_GCC_TOOLCHAIN_NAME}/mipself.xsc ${CMAKE_MODULE_LINKER_FLAGS}" ) - set( CMAKE_EXE_LINKER_FLAGS "-Wl,-T,${ANDROID_NDK}/toolchains/${ANDROID_GCC_TOOLCHAIN_NAME}/mipself.x ${CMAKE_EXE_LINKER_FLAGS}" ) + set( CMAKE_SHARED_LINKER_FLAGS "-Wl,-T,${ANDROID_NDK_TOOLCHAINS_PATH}/${ANDROID_GCC_TOOLCHAIN_NAME}/mipself.xsc ${CMAKE_SHARED_LINKER_FLAGS}" ) + set( CMAKE_MODULE_LINKER_FLAGS "-Wl,-T,${ANDROID_NDK_TOOLCHAINS_PATH}/${ANDROID_GCC_TOOLCHAIN_NAME}/mipself.xsc ${CMAKE_MODULE_LINKER_FLAGS}" ) + set( CMAKE_EXE_LINKER_FLAGS "-Wl,-T,${ANDROID_NDK_TOOLCHAINS_PATH}/${ANDROID_GCC_TOOLCHAIN_NAME}/mipself.x ${CMAKE_EXE_LINKER_FLAGS}" ) endif() # configure rtti @@ -1430,6 +1500,43 @@ endif() include_directories( SYSTEM "${ANDROID_SYSROOT}/usr/include" ${ANDROID_STL_INCLUDE_DIRS} ) link_directories( "${CMAKE_INSTALL_PREFIX}/libs/${ANDROID_NDK_ABI_NAME}" ) +# detect if need link crtbegin_so.o explicitly +if( NOT DEFINED ANDROID_EXPLICIT_CRT_LINK ) + set( __cmd "${CMAKE_CXX_CREATE_SHARED_LIBRARY}" ) + string( REPLACE "" "${CMAKE_CXX_COMPILER} ${CMAKE_CXX_COMPILER_ARG1}" __cmd "${__cmd}" ) + string( REPLACE "" "${CMAKE_C_COMPILER} ${CMAKE_C_COMPILER_ARG1}" __cmd "${__cmd}" ) + string( REPLACE "" "${CMAKE_CXX_FLAGS}" __cmd "${__cmd}" ) + string( REPLACE "" "" __cmd "${__cmd}" ) + string( REPLACE "" "${CMAKE_SHARED_LINKER_FLAGS}" __cmd "${__cmd}" ) + string( REPLACE "" "-shared" __cmd "${__cmd}" ) + string( REPLACE "" "" __cmd "${__cmd}" ) + string( REPLACE "" "" __cmd "${__cmd}" ) + string( REPLACE "" "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/toolchain_crtlink_test.so" __cmd "${__cmd}" ) + string( REPLACE "" "\"${ANDROID_SYSROOT}/usr/lib/crtbegin_so.o\"" __cmd "${__cmd}" ) + string( REPLACE "" "" __cmd "${__cmd}" ) + separate_arguments( __cmd ) + foreach( __var ANDROID_NDK ANDROID_NDK_TOOLCHAINS_PATH ANDROID_STANDALONE_TOOLCHAIN ) + if( ${__var} ) + set( __tmp "${${__var}}" ) + separate_arguments( __tmp ) + string( REPLACE "${__tmp}" "${${__var}}" __cmd "${__cmd}") + endif() + endforeach() + string( REPLACE "'" "" __cmd "${__cmd}" ) + string( REPLACE "\"" "" __cmd "${__cmd}" ) + execute_process( COMMAND ${__cmd} RESULT_VARIABLE __cmd_result OUTPUT_QUIET ERROR_QUIET ) + if( __cmd_result EQUAL 0 ) + set( ANDROID_EXPLICIT_CRT_LINK ON ) + else() + set( ANDROID_EXPLICIT_CRT_LINK OFF ) + endif() +endif() + +if( ANDROID_EXPLICIT_CRT_LINK ) + set( CMAKE_CXX_CREATE_SHARED_LIBRARY "${CMAKE_CXX_CREATE_SHARED_LIBRARY} \"${ANDROID_SYSROOT}/usr/lib/crtbegin_so.o\"" ) + set( CMAKE_CXX_CREATE_SHARED_MODULE "${CMAKE_CXX_CREATE_SHARED_MODULE} \"${ANDROID_SYSROOT}/usr/lib/crtbegin_so.o\"" ) +endif() + # setup output directories set( LIBRARY_OUTPUT_PATH_ROOT ${CMAKE_SOURCE_DIR} CACHE PATH "root for library output, set this to change where android libs are installed to" ) set( CMAKE_INSTALL_PREFIX "${ANDROID_TOOLCHAIN_ROOT}/user" CACHE STRING "path for installing" ) @@ -1521,6 +1628,7 @@ if( NOT PROJECT_NAME STREQUAL "CMAKE_TRY_COMPILE" ) foreach( __var NDK_CCACHE LIBRARY_OUTPUT_PATH_ROOT ANDROID_FORBID_SYGWIN ANDROID_SET_OBSOLETE_VARIABLES ANDROID_NDK_HOST_X64 ANDROID_NDK + ANDROID_NDK_LAYOUT ANDROID_STANDALONE_TOOLCHAIN ANDROID_TOOLCHAIN_NAME ANDROID_ABI @@ -1534,6 +1642,8 @@ if( NOT PROJECT_NAME STREQUAL "CMAKE_TRY_COMPILE" ) ANDROID_GOLD_LINKER ANDROID_NOEXECSTACK ANDROID_RELRO + ANDROID_LIBM_PATH + ANDROID_EXPLICIT_CRT_LINK ) if( DEFINED ${__var} ) if( "${__var}" MATCHES " ") @@ -1577,6 +1687,7 @@ endif() # ANDROID_STANDALONE_TOOLCHAIN # ANDROID_TOOLCHAIN_NAME : the NDK name of compiler toolchain # ANDROID_NDK_HOST_X64 : try to use x86_64 toolchain (default for x64 host systems) +# ANDROID_NDK_LAYOUT : the inner NDK structure (RELEASE, LINARO, ANDROID) # LIBRARY_OUTPUT_PATH_ROOT : # NDK_CCACHE : # Obsolete: @@ -1622,6 +1733,7 @@ endif() # ANDROID_EXCEPTIONS : if exceptions are enabled by the runtime # ANDROID_GCC_TOOLCHAIN_NAME : read-only, differs from ANDROID_TOOLCHAIN_NAME only if clang is used # ANDROID_CLANG_VERSION : version of clang compiler if clang is used +# ANDROID_LIBM_PATH : path to libm.so (set to something like $(TOP)/out/target/product//obj/lib/libm.so) to workaround unresolved `sincos` # # Defaults: # ANDROID_DEFAULT_NDK_API_LEVEL From c90abb6a037eb838099322721860ef5c732ca5a2 Mon Sep 17 00:00:00 2001 From: Sergei Nosov Date: Thu, 13 Jun 2013 21:14:42 +0400 Subject: [PATCH 103/178] add multiruns to fix "unreliable results" error --- modules/core/perf/perf_reduce.cpp | 4 ++-- modules/imgproc/perf/perf_cvt_color.cpp | 6 ++++-- modules/imgproc/perf/perf_morph.cpp | 3 ++- modules/imgproc/perf/perf_remap.cpp | 3 ++- modules/imgproc/perf/perf_threshold.cpp | 2 +- modules/video/perf/perf_optflowpyrlk.cpp | 5 +++-- 6 files changed, 14 insertions(+), 9 deletions(-) diff --git a/modules/core/perf/perf_reduce.cpp b/modules/core/perf/perf_reduce.cpp index 93d3a14166..7b74b0e7e3 100644 --- a/modules/core/perf/perf_reduce.cpp +++ b/modules/core/perf/perf_reduce.cpp @@ -34,7 +34,8 @@ PERF_TEST_P(Size_MatType_ROp, reduceR, declare.in(src, WARMUP_RNG).out(vec); declare.time(100); - TEST_CYCLE() reduce(src, vec, 0, reduceOp, ddepth); + int runs = 15; + TEST_CYCLE_MULTIRUN(runs) reduce(src, vec, 0, reduceOp, ddepth); SANITY_CHECK(vec, 1); } @@ -65,4 +66,3 @@ PERF_TEST_P(Size_MatType_ROp, reduceC, SANITY_CHECK(vec, 1); } - diff --git a/modules/imgproc/perf/perf_cvt_color.cpp b/modules/imgproc/perf/perf_cvt_color.cpp index 9b87afe99c..89c7c69160 100644 --- a/modules/imgproc/perf/perf_cvt_color.cpp +++ b/modules/imgproc/perf/perf_cvt_color.cpp @@ -258,7 +258,8 @@ PERF_TEST_P(Size_CvtMode, cvtColor8u, declare.time(100); declare.in(src, WARMUP_RNG).out(dst); - TEST_CYCLE() cvtColor(src, dst, mode, ch.dcn); + int runs = sz.width <= 320 ? 70 : 1; + TEST_CYCLE_MULTIRUN(runs) cvtColor(src, dst, mode, ch.dcn); SANITY_CHECK(dst, 1); } @@ -334,7 +335,8 @@ PERF_TEST_P(Size_CvtMode3, cvtColorRGB2YUV420p, declare.time(100); declare.in(src, WARMUP_RNG).out(dst); - TEST_CYCLE() cvtColor(src, dst, mode, ch.dcn); + int runs = (sz.width <= 640) ? 10 : 1; + TEST_CYCLE_MULTIRUN(runs) cvtColor(src, dst, mode, ch.dcn); SANITY_CHECK(dst, 1); } diff --git a/modules/imgproc/perf/perf_morph.cpp b/modules/imgproc/perf/perf_morph.cpp index 9aadeaff52..d3dbba38fb 100644 --- a/modules/imgproc/perf/perf_morph.cpp +++ b/modules/imgproc/perf/perf_morph.cpp @@ -19,7 +19,8 @@ PERF_TEST_P(Size_MatType, erode, TYPICAL_MATS_MORPH) declare.in(src, WARMUP_RNG).out(dst); - TEST_CYCLE() erode(src, dst, noArray()); + int runs = (sz.width <= 320) ? 15 : 1; + TEST_CYCLE_MULTIRUN(runs) erode(src, dst, noArray()); SANITY_CHECK(dst); } diff --git a/modules/imgproc/perf/perf_remap.cpp b/modules/imgproc/perf/perf_remap.cpp index 334c5ff960..92c6007a2b 100644 --- a/modules/imgproc/perf/perf_remap.cpp +++ b/modules/imgproc/perf/perf_remap.cpp @@ -63,7 +63,8 @@ PERF_TEST_P( TestRemap, Remap, declare.in(src, WARMUP_RNG).out(dst).time(20); - TEST_CYCLE() remap(src, dst, map1, map2, inter_type); + int runs = (sz.width <= 640) ? 3 : 1; + TEST_CYCLE_MULTIRUN(runs) remap(src, dst, map1, map2, inter_type); SANITY_CHECK(dst); } diff --git a/modules/imgproc/perf/perf_threshold.cpp b/modules/imgproc/perf/perf_threshold.cpp index 61255e2283..01fff2e8cc 100644 --- a/modules/imgproc/perf/perf_threshold.cpp +++ b/modules/imgproc/perf/perf_threshold.cpp @@ -32,7 +32,7 @@ PERF_TEST_P(Size_MatType_ThreshType, threshold, declare.in(src, WARMUP_RNG).out(dst); - int runs = (sz.width <= 640) ? 8 : 1; + int runs = (sz.width <= 640) ? 40 : 1; TEST_CYCLE_MULTIRUN(runs) threshold(src, dst, thresh, maxval, threshType); SANITY_CHECK(dst); diff --git a/modules/video/perf/perf_optflowpyrlk.cpp b/modules/video/perf/perf_optflowpyrlk.cpp index 12005f8ffa..8c53db03ae 100644 --- a/modules/video/perf/perf_optflowpyrlk.cpp +++ b/modules/video/perf/perf_optflowpyrlk.cpp @@ -165,7 +165,8 @@ PERF_TEST_P(Path_Idx_Cn_NPoints_WSize_Deriv, OpticalFlowPyrLK_self, testing::Com declare.in(pyramid1, pyramid2, inPoints).out(outPoints); declare.time(400); - TEST_CYCLE() + int runs = 3; + TEST_CYCLE_MULTIRUN(runs) { calcOpticalFlowPyrLK(pyramid1, pyramid2, inPoints, outPoints, status, err, Size(winSize, winSize), maxLevel, criteria, @@ -217,4 +218,4 @@ PERF_TEST_P(Path_Win_Deriv_Border_Reuse, OpticalFlowPyrLK_pyr, testing::Combine( } SANITY_CHECK(pyramid); -} \ No newline at end of file +} From fc82150edc219dabf680561f26ae5c2bb8d3c040 Mon Sep 17 00:00:00 2001 From: Ivan Korolev Date: Fri, 14 Jun 2013 08:21:42 +0400 Subject: [PATCH 104/178] Fixed a bug #2892 --- modules/nonfree/src/sift.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/nonfree/src/sift.cpp b/modules/nonfree/src/sift.cpp index 58ebd31016..5a7fd89407 100644 --- a/modules/nonfree/src/sift.cpp +++ b/modules/nonfree/src/sift.cpp @@ -774,9 +774,6 @@ void SIFT::operator()(InputArray _image, InputArray _mask, findScaleSpaceExtrema(gpyr, dogpyr, keypoints); KeyPointsFilter::removeDuplicated( keypoints ); - if( !mask.empty() ) - KeyPointsFilter::runByPixelsMask( keypoints, mask ); - if( nfeatures > 0 ) KeyPointsFilter::retainBest(keypoints, nfeatures); //t = (double)getTickCount() - t; @@ -791,6 +788,9 @@ void SIFT::operator()(InputArray _image, InputArray _mask, kpt.pt *= scale; kpt.size *= scale; } + + if( !mask.empty() ) + KeyPointsFilter::runByPixelsMask( keypoints, mask ); } else { From 58fa401b4d0cefe763e4b307802f34c96a942a44 Mon Sep 17 00:00:00 2001 From: Ivan Korolev Date: Fri, 14 Jun 2013 10:43:20 +0400 Subject: [PATCH 105/178] Fixed a bug #2405 --- modules/stitching/src/motion_estimators.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/stitching/src/motion_estimators.cpp b/modules/stitching/src/motion_estimators.cpp index ab27a46a2a..c873bc721a 100644 --- a/modules/stitching/src/motion_estimators.cpp +++ b/modules/stitching/src/motion_estimators.cpp @@ -69,13 +69,13 @@ struct CalcRotation K_from(0,0) = cameras[edge.from].focal; K_from(1,1) = cameras[edge.from].focal * cameras[edge.from].aspect; K_from(0,2) = cameras[edge.from].ppx; - K_from(0,2) = cameras[edge.from].ppy; + K_from(1,2) = cameras[edge.from].ppy; Mat_ K_to = Mat::eye(3, 3, CV_64F); K_to(0,0) = cameras[edge.to].focal; K_to(1,1) = cameras[edge.to].focal * cameras[edge.to].aspect; K_to(0,2) = cameras[edge.to].ppx; - K_to(0,2) = cameras[edge.to].ppy; + K_to(1,2) = cameras[edge.to].ppy; Mat R = K_from.inv() * pairwise_matches[pair_idx].H.inv() * K_to; cameras[edge.to].R = cameras[edge.from].R * R; From e6b18fc492e9115043b375c8b005687b24b84746 Mon Sep 17 00:00:00 2001 From: peng xiao Date: Fri, 14 Jun 2013 16:37:00 +0800 Subject: [PATCH 106/178] Fix a bug caused by NDEBUG macro; it is now removed. Revise some descriptions of the enums. --- modules/ocl/include/opencv2/ocl/ocl.hpp | 21 +++++++++++++-------- modules/ocl/src/initialization.cpp | 2 +- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/modules/ocl/include/opencv2/ocl/ocl.hpp b/modules/ocl/include/opencv2/ocl/ocl.hpp index dc58f6f2e7..308383b61a 100644 --- a/modules/ocl/include/opencv2/ocl/ocl.hpp +++ b/modules/ocl/include/opencv2/ocl/ocl.hpp @@ -179,16 +179,21 @@ namespace cv bool cleanUp = true); //! Enable or disable OpenCL program binary caching onto local disk - // After a program (*.cl files in opencl/ folder) is built at runtime, we allow the compiled program to be - // cached onto local disk automatically, which may accelerate subsequent runs. - // Caching mode is controlled by the following enum - // Note, the feature is by default enabled when OpenCV is built in release mode. + // After a program (*.cl files in opencl/ folder) is built at runtime, we allow the + // compiled OpenCL program to be cached to the path automatically as "path/*.clb" + // binary file, which will be reused when the OpenCV executable is started again. + // + // Caching mode is controlled by the following enums + // Notes + // 1. the feature is by default enabled when OpenCV is built in release mode. + // 2. the CACHE_DEBUG / CACHE_RELEASE flags only effectively work with MSVC compiler; + // for GNU compilers, the function always treats the build as release mode (enabled by default). enum { - CACHE_NONE = 0, - CACHE_DEBUG = 0x1 << 0, - CACHE_RELEASE = 0x1 << 1, - CACHE_ALL = CACHE_DEBUG | CACHE_RELEASE, + CACHE_NONE = 0, // do not cache OpenCL binary + CACHE_DEBUG = 0x1 << 0, // cache OpenCL binary when built in debug mode (only work with MSVC) + CACHE_RELEASE = 0x1 << 1, // default behavior, only cache when built in release mode (only work with MSVC) + CACHE_ALL = CACHE_DEBUG | CACHE_RELEASE, // always cache opencl binary CACHE_UPDATE = 0x1 << 2 // if the binary cache file with the same name is already on the disk, it will be updated. }; CV_EXPORTS void setBinaryDiskCache(int mode = CACHE_RELEASE, cv::String path = "./"); diff --git a/modules/ocl/src/initialization.cpp b/modules/ocl/src/initialization.cpp index 9a0915ce55..bdae7059ec 100644 --- a/modules/ocl/src/initialization.cpp +++ b/modules/ocl/src/initialization.cpp @@ -508,7 +508,7 @@ namespace cv { impl->update_disk_cache = (mode & CACHE_UPDATE) == CACHE_UPDATE; impl->enable_disk_cache = -#if !defined(NDEBUG) || defined(_DEBUG) +#ifdef _DEBUG (mode & CACHE_DEBUG) == CACHE_DEBUG; #else (mode & CACHE_RELEASE) == CACHE_RELEASE; From a4750f49c62f6b3f97715f1b430ac97b7d88b3a7 Mon Sep 17 00:00:00 2001 From: Vladislav Vinogradov Date: Fri, 14 Jun 2013 12:53:44 +0400 Subject: [PATCH 107/178] fix for bug #3068 (PCA::computeVar for double input): The matrix g can have CV_32F or CV_64F type, but g.at uses only float template. This fix adds specialization for double type. --- modules/core/src/matmul.cpp | 57 +++++++++++++++++++++++-------------- 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/modules/core/src/matmul.cpp b/modules/core/src/matmul.cpp index 5988363d3c..05a0c55524 100644 --- a/modules/core/src/matmul.cpp +++ b/modules/core/src/matmul.cpp @@ -2855,9 +2855,9 @@ PCA& PCA::operator()(InputArray _data, InputArray __mean, int flags, int maxComp if( _mean.data ) { - CV_Assert( _mean.size() == mean_sz ); + CV_Assert( _mean.size() == mean_sz ); _mean.convertTo(mean, ctype); - covar_flags |= CV_COVAR_USE_AVG; + covar_flags |= CV_COVAR_USE_AVG; } calcCovarMatrix( data, covar, mean, covar_flags, ctype ); @@ -2901,6 +2901,36 @@ PCA& PCA::operator()(InputArray _data, InputArray __mean, int flags, int maxComp return *this; } +template +int computeCumulativeEnergy(const Mat& eigenvalues, double retainedVariance) +{ + CV_DbgAssert( eigenvalues.type() == DataType::type ); + + Mat g(eigenvalues.size(), DataType::type); + + for(int ig = 0; ig < g.rows; ig++) + { + g.at(ig, 0) = 0; + for(int im = 0; im <= ig; im++) + { + g.at(ig,0) += eigenvalues.at(im,0); + } + } + + int L; + + for(L = 0; L < eigenvalues.rows; L++) + { + double energy = g.at(L, 0) / g.at(g.rows - 1, 0); + if(energy > retainedVariance) + break; + } + + L = std::max(2, L); + + return L; +} + PCA& PCA::computeVar(InputArray _data, InputArray __mean, int flags, double retainedVariance) { Mat data = _data.getMat(), _mean = __mean.getMat(); @@ -2977,26 +3007,11 @@ PCA& PCA::computeVar(InputArray _data, InputArray __mean, int flags, double reta } // compute the cumulative energy content for each eigenvector - Mat g(eigenvalues.size(), ctype); - - for(int ig = 0; ig < g.rows; ig++) - { - g.at(ig,0) = 0; - for(int im = 0; im <= ig; im++) - { - g.at(ig,0) += eigenvalues.at(im,0); - } - } - int L; - for(L = 0; L < eigenvalues.rows; L++) - { - double energy = g.at(L, 0) / g.at(g.rows - 1, 0); - if(energy > retainedVariance) - break; - } - - L = std::max(2, L); + if (ctype == CV_32F) + L = computeCumulativeEnergy(eigenvalues, retainedVariance); + else + L = computeCumulativeEnergy(eigenvalues, retainedVariance); // use clone() to physically copy the data and thus deallocate the original matrices eigenvalues = eigenvalues.rowRange(0,L).clone(); From 93200922fd75921ee74fbc8a176d3d9a3fdd0bd8 Mon Sep 17 00:00:00 2001 From: Alexander Shishkov Date: Fri, 14 Jun 2013 13:12:35 +0400 Subject: [PATCH 108/178] Fix bug with indices --- .../calib3d/camera_calibration/camera_calibration.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/tutorials/calib3d/camera_calibration/camera_calibration.rst b/doc/tutorials/calib3d/camera_calibration/camera_calibration.rst index 9196c87d6a..6637e2590c 100644 --- a/doc/tutorials/calib3d/camera_calibration/camera_calibration.rst +++ b/doc/tutorials/calib3d/camera_calibration/camera_calibration.rst @@ -12,8 +12,8 @@ For the distortion OpenCV takes into account the radial and tangential factors. .. math:: - x_{corrected} = x( 1 + k_1 r^2 + k_2 r^4 + k^3 r^6) \\ - y_{corrected} = y( 1 + k_1 r^2 + k_2 r^4 + k^3 r^6) + x_{corrected} = x( 1 + k_1 r^2 + k_2 r^4 + k_3 r^6) \\ + y_{corrected} = y( 1 + k_1 r^2 + k_2 r^4 + k_3 r^6) So for an old pixel point at :math:`(x,y)` coordinate in the input image, for a corrected output image its position will be :math:`(x_{corrected} y_{corrected})` . The presence of the radial distortion manifests in form of the "barrel" or "fish-eye" effect. From 0cee15eb7f8e10361e008b0428f70e9a781a75d6 Mon Sep 17 00:00:00 2001 From: Alexander Shishkov Date: Fri, 14 Jun 2013 15:10:25 +0400 Subject: [PATCH 109/178] Updated iOS camera. Added rotation flag. Added functions to lock/unlock focus, white balance and exposure. --- .../highgui/include/opencv2/highgui/cap_ios.h | 12 ++- .../highgui/src/cap_ios_abstract_camera.mm | 85 +++++++++++++++++++ modules/highgui/src/cap_ios_photo_camera.mm | 2 +- modules/highgui/src/cap_ios_video_camera.mm | 53 +++++++++--- 4 files changed, 138 insertions(+), 14 deletions(-) diff --git a/modules/highgui/include/opencv2/highgui/cap_ios.h b/modules/highgui/include/opencv2/highgui/cap_ios.h index 5bd5fe3c67..db3928f13b 100644 --- a/modules/highgui/include/opencv2/highgui/cap_ios.h +++ b/modules/highgui/include/opencv2/highgui/cap_ios.h @@ -1,6 +1,4 @@ -/* - * cap_ios.h - * For iOS video I/O +/* For iOS video I/O * by Eduard Feicho on 29/07/12 * Copyright 2012. All rights reserved. * @@ -90,6 +88,12 @@ - (void)createVideoPreviewLayer; - (void)updateOrientation; +- (void)lockFocus; +- (void)unlockFocus; +- (void)lockExposure; +- (void)unlockExposure; +- (void)lockBalance; +- (void)unlockBalance; @end @@ -116,6 +120,7 @@ BOOL grayscaleMode; BOOL recordVideo; + BOOL rotateVideo; AVAssetWriterInput* recordAssetWriterInput; AVAssetWriterInputPixelBufferAdaptor* recordPixelBufferAdaptor; AVAssetWriter* recordAssetWriter; @@ -128,6 +133,7 @@ @property (nonatomic, assign) BOOL grayscaleMode; @property (nonatomic, assign) BOOL recordVideo; +@property (nonatomic, assign) BOOL rotateVideo; @property (nonatomic, retain) AVAssetWriterInput* recordAssetWriterInput; @property (nonatomic, retain) AVAssetWriterInputPixelBufferAdaptor* recordPixelBufferAdaptor; @property (nonatomic, retain) AVAssetWriter* recordAssetWriter; diff --git a/modules/highgui/src/cap_ios_abstract_camera.mm b/modules/highgui/src/cap_ios_abstract_camera.mm index b6a7d944fa..a0e8f3e8b5 100644 --- a/modules/highgui/src/cap_ios_abstract_camera.mm +++ b/modules/highgui/src/cap_ios_abstract_camera.mm @@ -405,4 +405,89 @@ } } +- (void)lockFocus; +{ + AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; + if ([device isFocusModeSupported:AVCaptureFocusModeLocked]) { + NSError *error = nil; + if ([device lockForConfiguration:&error]) { + device.focusMode = AVCaptureFocusModeLocked; + [device unlockForConfiguration]; + } else { + NSLog(@"unable to lock device for locked focus configuration %@", [error localizedDescription]); + } + } +} + +- (void) unlockFocus; +{ + AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; + if ([device isFocusModeSupported:AVCaptureFocusModeContinuousAutoFocus]) { + NSError *error = nil; + if ([device lockForConfiguration:&error]) { + device.focusMode = AVCaptureFocusModeContinuousAutoFocus; + [device unlockForConfiguration]; + } else { + NSLog(@"unable to lock device for autofocus configuration %@", [error localizedDescription]); + } + } +} + +- (void)lockExposure; +{ + AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; + if ([device isExposureModeSupported:AVCaptureExposureModeLocked]) { + NSError *error = nil; + if ([device lockForConfiguration:&error]) { + device.exposureMode = AVCaptureExposureModeLocked; + [device unlockForConfiguration]; + } else { + NSLog(@"unable to lock device for locked exposure configuration %@", [error localizedDescription]); + } + } +} + +- (void) unlockExposure; +{ + AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; + if ([device isExposureModeSupported:AVCaptureExposureModeContinuousAutoExposure]) { + NSError *error = nil; + if ([device lockForConfiguration:&error]) { + device.exposureMode = AVCaptureExposureModeContinuousAutoExposure; + [device unlockForConfiguration]; + } else { + NSLog(@"unable to lock device for autoexposure configuration %@", [error localizedDescription]); + } + } +} + +- (void)lockBalance; +{ + AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; + if ([device isWhiteBalanceModeSupported:AVCaptureWhiteBalanceModeLocked]) { + NSError *error = nil; + if ([device lockForConfiguration:&error]) { + device.whiteBalanceMode = AVCaptureWhiteBalanceModeLocked; + [device unlockForConfiguration]; + } else { + NSLog(@"unable to lock device for locked exposure configuration %@", [error localizedDescription]); + } + } +} + +- (void) unlockBalance; +{ + AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; + if ([device isWhiteBalanceModeSupported:AVCaptureWhiteBalanceModeContinuousAutoWhiteBalance]) { + NSError *error = nil; + if ([device lockForConfiguration:&error]) { + device.whiteBalanceMode = AVCaptureWhiteBalanceModeContinuousAutoWhiteBalance; + [device unlockForConfiguration]; + } else { + NSLog(@"unable to lock device for autoexposure configuration %@", [error localizedDescription]); + } + } +} + @end + diff --git a/modules/highgui/src/cap_ios_photo_camera.mm b/modules/highgui/src/cap_ios_photo_camera.mm index f05cfa5f87..f8891f2277 100644 --- a/modules/highgui/src/cap_ios_photo_camera.mm +++ b/modules/highgui/src/cap_ios_photo_camera.mm @@ -32,7 +32,7 @@ #import "opencv2/highgui/cap_ios.h" #include "precomp.hpp" -#pragma mark - Private Interface +#pragma mark - Private Interface mark - Private Interface @interface CvPhotoCamera () diff --git a/modules/highgui/src/cap_ios_video_camera.mm b/modules/highgui/src/cap_ios_video_camera.mm index 1f9ea14bf8..588adfc9cc 100644 --- a/modules/highgui/src/cap_ios_video_camera.mm +++ b/modules/highgui/src/cap_ios_video_camera.mm @@ -30,7 +30,6 @@ #import "opencv2/highgui/cap_ios.h" #include "precomp.hpp" - #import @@ -70,6 +69,7 @@ static CGFloat DegreesToRadians(CGFloat degrees) {return degrees * M_PI / 180;}; @synthesize videoDataOutput; @synthesize recordVideo; +@synthesize rotateVideo; //@synthesize videoFileOutput; @synthesize recordAssetWriterInput; @synthesize recordPixelBufferAdaptor; @@ -85,6 +85,7 @@ static CGFloat DegreesToRadians(CGFloat degrees) {return degrees * M_PI / 180;}; if (self) { self.useAVCaptureVideoPreviewLayer = NO; self.recordVideo = NO; + self.rotateVideo = NO; } return self; } @@ -269,13 +270,8 @@ static CGFloat DegreesToRadians(CGFloat degrees) {return degrees * M_PI / 180;}; } - - - #pragma mark - Private Interface - - - (void)createVideoDataOutput; { // Make a video data output @@ -389,6 +385,38 @@ static CGFloat DegreesToRadians(CGFloat degrees) {return degrees * M_PI / 180;}; [self.parentView.layer addSublayer:self.customPreviewLayer]; } +- (CVPixelBufferRef) pixelBufferFromCGImage: (CGImageRef) image +{ + + CGSize frameSize = CGSizeMake(CGImageGetWidth(image), CGImageGetHeight(image)); + NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithBool:NO], kCVPixelBufferCGImageCompatibilityKey, + [NSNumber numberWithBool:NO], kCVPixelBufferCGBitmapContextCompatibilityKey, + nil]; + CVPixelBufferRef pxbuffer = NULL; + CVReturn status = CVPixelBufferCreate(kCFAllocatorDefault, frameSize.width, + frameSize.height, kCVPixelFormatType_32ARGB, (CFDictionaryRef) CFBridgingRetain(options), + &pxbuffer); + NSParameterAssert(status == kCVReturnSuccess && pxbuffer != NULL); + + CVPixelBufferLockBaseAddress(pxbuffer, 0); + void *pxdata = CVPixelBufferGetBaseAddress(pxbuffer); + + + CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB(); + CGContextRef context = CGBitmapContextCreate(pxdata, frameSize.width, + frameSize.height, 8, 4*frameSize.width, rgbColorSpace, + kCGImageAlphaPremultipliedFirst); + + CGContextDrawImage(context, CGRectMake(0, 0, CGImageGetWidth(image), + CGImageGetHeight(image)), image); + CGColorSpaceRelease(rgbColorSpace); + CGContextRelease(context); + + CVPixelBufferUnlockBaseAddress(pxbuffer, 0); + + return pxbuffer; +} #pragma mark - Protocol AVCaptureVideoDataOutputSampleBufferDelegate @@ -522,7 +550,8 @@ static CGFloat DegreesToRadians(CGFloat degrees) {return degrees * M_PI / 180;}; } if (self.recordAssetWriterInput.readyForMoreMediaData) { - if (! [self.recordPixelBufferAdaptor appendPixelBuffer:imageBuffer + CVImageBufferRef pixelBuffer = [self pixelBufferFromCGImage:dstImage]; + if (! [self.recordPixelBufferAdaptor appendPixelBuffer:pixelBuffer withPresentationTime:lastSampleTime] ) { NSLog(@"Video Writing Error"); } @@ -543,9 +572,12 @@ static CGFloat DegreesToRadians(CGFloat degrees) {return degrees * M_PI / 180;}; - (void)updateOrientation; { - NSLog(@"rotate.."); - self.customPreviewLayer.bounds = CGRectMake(0, 0, self.parentView.frame.size.width, self.parentView.frame.size.height); - [self layoutPreviewLayer]; + if (self.rotateVideo == YES) + { + NSLog(@"rotate.."); + self.customPreviewLayer.bounds = CGRectMake(0, 0, self.parentView.frame.size.width, self.parentView.frame.size.height); + [self layoutPreviewLayer]; + } } @@ -583,3 +615,4 @@ static CGFloat DegreesToRadians(CGFloat degrees) {return degrees * M_PI / 180;}; } @end + From fee81210405ce01bccc810be59c957b8f9d227dc Mon Sep 17 00:00:00 2001 From: Ivan Korolev Date: Fri, 14 Jun 2013 17:03:15 +0400 Subject: [PATCH 110/178] Added regression tests for SURF/SIFT (related to #2892) --- modules/nonfree/test/test_features2d.cpp | 73 ++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/modules/nonfree/test/test_features2d.cpp b/modules/nonfree/test/test_features2d.cpp index 001d628aaa..4cce77b9d5 100644 --- a/modules/nonfree/test/test_features2d.cpp +++ b/modules/nonfree/test/test_features2d.cpp @@ -1146,3 +1146,76 @@ protected: TEST(Features2d_SIFTHomographyTest, regression) { CV_DetectPlanarTest test("SIFT", 80); test.safe_run(); } TEST(Features2d_SURFHomographyTest, regression) { CV_DetectPlanarTest test("SURF", 80); test.safe_run(); } +class FeatureDetectorUsingMaskTest : public cvtest::BaseTest +{ +public: + FeatureDetectorUsingMaskTest(const Ptr& featureDetector) : + featureDetector_(featureDetector) + { + CV_Assert(!featureDetector_.empty()); + } + +protected: + + void run(int) + { + const int nStepX = 2; + const int nStepY = 2; + + const string imageFilename = string(ts->get_data_path()) + "/features2d/tsukuba.png"; + + Mat image = imread(imageFilename); + if(image.empty()) + { + ts->printf(cvtest::TS::LOG, "Image %s can not be read.\n", imageFilename.c_str()); + ts->set_failed_test_info(cvtest::TS::FAIL_INVALID_TEST_DATA); + return; + } + + Mat mask(image.size(), CV_8U); + + const int stepX = image.size().width / nStepX; + const int stepY = image.size().height / nStepY; + + vector keyPoints; + vector points; + for(int i=0; idetect(image, keyPoints, mask); + KeyPoint::convert(keyPoints, points); + + for(size_t k=0; kprintf(cvtest::TS::LOG, "The feature point is outside of the mask."); + ts->set_failed_test_info(cvtest::TS::FAIL_INVALID_OUTPUT); + return; + } + } + } + + ts->set_failed_test_info( cvtest::TS::OK ); + } + + Ptr featureDetector_; +}; + +TEST(Features2d_SIFT_using_mask, regression) +{ + FeatureDetectorUsingMaskTest test(Algorithm::create("Feature2D.SIFT")); + test.safe_run(); +} + +TEST(DISABLED_Features2d_SURF_using_mask, regression) +{ + FeatureDetectorUsingMaskTest test(Algorithm::create("Feature2D.SURF")); + test.safe_run(); +} + From 5db08961cec08f309c3165fa086a0eb8e8e5d6ee Mon Sep 17 00:00:00 2001 From: Alexander Shishkov Date: Tue, 18 Jun 2013 06:59:52 +0400 Subject: [PATCH 111/178] fixed Kirill's comments --- modules/highgui/src/cap_ios_abstract_camera.mm | 4 ++-- modules/highgui/src/cap_ios_photo_camera.mm | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/highgui/src/cap_ios_abstract_camera.mm b/modules/highgui/src/cap_ios_abstract_camera.mm index a0e8f3e8b5..dc4faaaeff 100644 --- a/modules/highgui/src/cap_ios_abstract_camera.mm +++ b/modules/highgui/src/cap_ios_abstract_camera.mm @@ -470,7 +470,7 @@ device.whiteBalanceMode = AVCaptureWhiteBalanceModeLocked; [device unlockForConfiguration]; } else { - NSLog(@"unable to lock device for locked exposure configuration %@", [error localizedDescription]); + NSLog(@"unable to lock device for locked white balance configuration %@", [error localizedDescription]); } } } @@ -484,7 +484,7 @@ device.whiteBalanceMode = AVCaptureWhiteBalanceModeContinuousAutoWhiteBalance; [device unlockForConfiguration]; } else { - NSLog(@"unable to lock device for autoexposure configuration %@", [error localizedDescription]); + NSLog(@"unable to lock device for auto white balance configuration %@", [error localizedDescription]); } } } diff --git a/modules/highgui/src/cap_ios_photo_camera.mm b/modules/highgui/src/cap_ios_photo_camera.mm index f8891f2277..f05cfa5f87 100644 --- a/modules/highgui/src/cap_ios_photo_camera.mm +++ b/modules/highgui/src/cap_ios_photo_camera.mm @@ -32,7 +32,7 @@ #import "opencv2/highgui/cap_ios.h" #include "precomp.hpp" -#pragma mark - Private Interface mark - Private Interface +#pragma mark - Private Interface @interface CvPhotoCamera () From 24fd2cc326db17a511eda02670dd64209b7b689a Mon Sep 17 00:00:00 2001 From: Alexander Shishkov Date: Tue, 18 Jun 2013 07:02:09 +0400 Subject: [PATCH 112/178] updated licenses --- modules/highgui/src/cap_ios_abstract_camera.mm | 1 + modules/highgui/src/cap_ios_video_camera.mm | 1 + 2 files changed, 2 insertions(+) diff --git a/modules/highgui/src/cap_ios_abstract_camera.mm b/modules/highgui/src/cap_ios_abstract_camera.mm index dc4faaaeff..38e1c12e68 100644 --- a/modules/highgui/src/cap_ios_abstract_camera.mm +++ b/modules/highgui/src/cap_ios_abstract_camera.mm @@ -2,6 +2,7 @@ * cap_ios_abstract_camera.mm * For iOS video I/O * by Eduard Feicho on 29/07/12 + * by Alexander Shishkov on 17/07/13 * Copyright 2012. All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/modules/highgui/src/cap_ios_video_camera.mm b/modules/highgui/src/cap_ios_video_camera.mm index 588adfc9cc..ac85f79ee5 100644 --- a/modules/highgui/src/cap_ios_video_camera.mm +++ b/modules/highgui/src/cap_ios_video_camera.mm @@ -2,6 +2,7 @@ * cap_ios_video_camera.mm * For iOS video I/O * by Eduard Feicho on 29/07/12 + * by Alexander Shishkov on 17/07/13 * Copyright 2012. All rights reserved. * * Redistribution and use in source and binary forms, with or without From f003e29dc0e10fa7d28dd5c717fbec134b2bf67e Mon Sep 17 00:00:00 2001 From: Roman Donchenko Date: Thu, 13 Jun 2013 12:22:12 +0400 Subject: [PATCH 113/178] Updated testlog_parser.py to the latest version from the private repo. --- modules/ts/misc/testlog_parser.py | 39 +++++++++++++++++-------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/modules/ts/misc/testlog_parser.py b/modules/ts/misc/testlog_parser.py index 7ae6aa5980..8ab21417ca 100755 --- a/modules/ts/misc/testlog_parser.py +++ b/modules/ts/misc/testlog_parser.py @@ -100,34 +100,39 @@ class TestInfo(object): def dump(self, units="ms"): print "%s ->\t\033[1;31m%s\033[0m = \t%.2f%s" % (str(self), self.status, self.get("gmean", units), units) - def shortName(self): + + def getName(self): pos = self.name.find("/") if pos > 0: - name = self.name[:pos] - else: - name = self.name - if self.fixture.endswith(name): - fixture = self.fixture[:-len(name)] + return self.name[:pos] + return self.name + + + def getFixture(self): + if self.fixture.endswith(self.getName()): + fixture = self.fixture[:-len(self.getName())] else: fixture = self.fixture if fixture.endswith("_"): fixture = fixture[:-1] + return fixture + + + def param(self): + return '::'.join(filter(None, [self.type_param, self.value_param])) + + def shortName(self): + name = self.getName() + fixture = self.getFixture() return '::'.join(filter(None, [name, fixture])) + def __str__(self): - pos = self.name.find("/") - if pos > 0: - name = self.name[:pos] - else: - name = self.name - if self.fixture.endswith(name): - fixture = self.fixture[:-len(name)] - else: - fixture = self.fixture - if fixture.endswith("_"): - fixture = fixture[:-1] + name = self.getName() + fixture = self.getFixture() return '::'.join(filter(None, [name, fixture, self.type_param, self.value_param])) + def __cmp__(self, other): r = cmp(self.fixture, other.fixture); if r != 0: From 6ff207b53a6379933018c88167ee11b5b1a62e2d Mon Sep 17 00:00:00 2001 From: Roman Donchenko Date: Fri, 14 Jun 2013 14:53:02 +0400 Subject: [PATCH 114/178] Added a new and improved version of the XLS report generator. --- modules/ts/misc/xls-report.py | 171 ++++++++++++++++++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100755 modules/ts/misc/xls-report.py diff --git a/modules/ts/misc/xls-report.py b/modules/ts/misc/xls-report.py new file mode 100755 index 0000000000..fb6cfd0960 --- /dev/null +++ b/modules/ts/misc/xls-report.py @@ -0,0 +1,171 @@ +#!/usr/bin/env python + +from __future__ import division + +import ast +import logging +import os, os.path +import re + +from argparse import ArgumentParser +from glob import glob +from itertools import ifilter + +import xlwt + +from testlog_parser import parseLogFile + +# To build XLS report you neet to put your xmls (OpenCV tests output) in the +# following way: +# +# "root" --- folder, representing the whole XLS document. It contains several +# subfolders --- sheet-paths of the XLS document. Each sheet-path contains it's +# subfolders --- config-paths. Config-paths are columns of the sheet and +# they contains xmls files --- output of OpenCV modules testing. +# Config-path means OpenCV build configuration, including different +# options such as NEON, TBB, GPU enabling/disabling. +# +# root +# root\sheet_path +# root\sheet_path\configuration1 (column 1) +# root\sheet_path\configuration2 (column 2) + +re_image_size = re.compile(r'^ \d+ x \d+$', re.VERBOSE) +re_data_type = re.compile(r'^ (?: 8 | 16 | 32 | 64 ) [USF] C [1234] $', re.VERBOSE) + +time_style = xlwt.easyxf(num_format_str='#0.00') +no_time_style = xlwt.easyxf('pattern: pattern solid, fore_color gray25') + +speedup_style = time_style +good_speedup_style = xlwt.easyxf('font: color green', num_format_str='#0.00') +bad_speedup_style = xlwt.easyxf('font: color red', num_format_str='#0.00') +no_speedup_style = no_time_style +error_speedup_style = xlwt.easyxf('pattern: pattern solid, fore_color orange') +header_style = xlwt.easyxf('font: bold true; alignment: horizontal centre, vertical top') + +def collect_xml(collection, configuration, xml_fullname): + xml_fname = os.path.split(xml_fullname)[1] + module = xml_fname[:xml_fname.index('_')] + + if module not in collection: + collection[module] = {} + + for test in sorted(parseLogFile(xml_fullname)): + if test.shortName() not in collection[module]: + collection[module][test.shortName()] = {} + if test.param() not in collection[module][test.shortName()]: + collection[module][test.shortName()][test.param()] = {} + collection[module][test.shortName()][test.param()][configuration] = \ + test.get("gmean") + +def main(): + arg_parser = ArgumentParser(description='Build an XLS performance report.') + arg_parser.add_argument('sheet_dirs', nargs='+', metavar='DIR', help='directory containing perf test logs') + arg_parser.add_argument('-o', '--output', metavar='XLS', default='report.xls', help='name of output file') + arg_parser.add_argument('-c', '--config', metavar='CONF', help='global configuration file') + + args = arg_parser.parse_args() + + logging.basicConfig(format='%(levelname)s: %(message)s', level=logging.DEBUG) + + if args.config is not None: + with open(args.config) as global_conf_file: + global_conf = ast.literal_eval(global_conf_file.read()) + else: + global_conf = {} + + wb = xlwt.Workbook() + + for sheet_path in args.sheet_dirs: + try: + with open(os.path.join(sheet_path, 'sheet.conf')) as sheet_conf_file: + sheet_conf = ast.literal_eval(sheet_conf_file.read()) + except Exception: + sheet_conf = {} + logging.debug('no sheet.conf for {}'.format(sheet_path)) + + sheet_conf = dict(global_conf.items() + sheet_conf.items()) + + if 'configurations' in sheet_conf: + config_names = sheet_conf['configurations'] + else: + try: + config_names = [p for p in os.listdir(sheet_path) + if os.path.isdir(os.path.join(sheet_path, p))] + except Exception as e: + logging.warning(e) + continue + + collection = {} + + for configuration, configuration_path in \ + [(c, os.path.join(sheet_path, c)) for c in config_names]: + logging.info('processing {}'.format(configuration_path)) + for xml_fullname in glob(os.path.join(configuration_path, '*.xml')): + collect_xml(collection, configuration, xml_fullname) + + sheet = wb.add_sheet(sheet_conf.get('sheet_name', os.path.basename(os.path.abspath(sheet_path)))) + + sheet.row(0).height = 800 + sheet.panes_frozen = True + sheet.remove_splits = True + sheet.horz_split_pos = 1 + sheet.horz_split_first_visible = 1 + + sheet_comparisons = sheet_conf.get('comparisons', []) + + for i, w in enumerate([2000, 15000, 2500, 2000, 15000] + + (len(config_names) + 1 + len(sheet_comparisons)) * [3000]): + sheet.col(i).width = w + + for i, caption in enumerate(['Module', 'Test', 'Image\nsize', 'Data\ntype', 'Parameters'] + + config_names + [None] + + [comp['from'] + '\nvs\n' + comp['to'] for comp in sheet_comparisons]): + sheet.row(0).write(i, caption, header_style) + + row = 1 + + module_colors = sheet_conf.get('module_colors', {}) + module_styles = {module: xlwt.easyxf('pattern: pattern solid, fore_color {}'.format(color)) + for module, color in module_colors.iteritems()} + + for module, tests in collection.iteritems(): + for test, params in tests.iteritems(): + for param, configs in params.iteritems(): + sheet.write(row, 0, module, module_styles.get(module, xlwt.Style.default_style)) + sheet.write(row, 1, test) + + param_list = param[1:-1].split(", ") + sheet.write(row, 2, next(ifilter(re_image_size.match, param_list), None)) + sheet.write(row, 3, next(ifilter(re_data_type.match, param_list), None)) + + sheet.row(row).write(4, param) + for i, c in enumerate(config_names): + if c in configs: + sheet.write(row, 5 + i, configs[c], time_style) + else: + sheet.write(row, 5 + i, None, no_time_style) + + for i, comp in enumerate(sheet_comparisons): + left = configs.get(comp["from"]) + right = configs.get(comp["to"]) + col = 5 + len(config_names) + 1 + i + + if left is not None and right is not None: + try: + speedup = left / right + sheet.write(row, col, speedup, good_speedup_style if speedup > 1.1 else + bad_speedup_style if speedup < 0.9 else + speedup_style) + except ArithmeticError as e: + sheet.write(row, col, None, error_speedup_style) + else: + sheet.write(row, col, None, no_speedup_style) + + row += 1 + if row % 1000 == 0: sheet.flush_row_data() + + wb.save(args.output) + +if __name__ == '__main__': + main() From 4d7b1b5eded9cfbb456b0238a2f55c6f6ae491ee Mon Sep 17 00:00:00 2001 From: Roman Donchenko Date: Mon, 17 Jun 2013 21:06:02 +0400 Subject: [PATCH 115/178] In the XLS report, enabled word wrapping for header cells. Otherwise, Excel ignores line breaks in them. --- modules/ts/misc/xls-report.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ts/misc/xls-report.py b/modules/ts/misc/xls-report.py index fb6cfd0960..f8288e16da 100755 --- a/modules/ts/misc/xls-report.py +++ b/modules/ts/misc/xls-report.py @@ -41,7 +41,7 @@ good_speedup_style = xlwt.easyxf('font: color green', num_format_str='#0.00') bad_speedup_style = xlwt.easyxf('font: color red', num_format_str='#0.00') no_speedup_style = no_time_style error_speedup_style = xlwt.easyxf('pattern: pattern solid, fore_color orange') -header_style = xlwt.easyxf('font: bold true; alignment: horizontal centre, vertical top') +header_style = xlwt.easyxf('font: bold true; alignment: horizontal centre, vertical top, wrap True') def collect_xml(collection, configuration, xml_fullname): xml_fname = os.path.split(xml_fullname)[1] From 0f1156bbb61efa0ec7d7b48e8a0cd02ec72378ba Mon Sep 17 00:00:00 2001 From: Roman Donchenko Date: Tue, 18 Jun 2013 13:36:20 +0400 Subject: [PATCH 116/178] Made the order of tests in XLS reports deterministic. --- modules/ts/misc/xls-report.py | 71 ++++++++++++++++------------------- 1 file changed, 33 insertions(+), 38 deletions(-) diff --git a/modules/ts/misc/xls-report.py b/modules/ts/misc/xls-report.py index f8288e16da..7e63b6737c 100755 --- a/modules/ts/misc/xls-report.py +++ b/modules/ts/misc/xls-report.py @@ -8,6 +8,7 @@ import os, os.path import re from argparse import ArgumentParser +from collections import OrderedDict from glob import glob from itertools import ifilter @@ -47,16 +48,11 @@ def collect_xml(collection, configuration, xml_fullname): xml_fname = os.path.split(xml_fullname)[1] module = xml_fname[:xml_fname.index('_')] - if module not in collection: - collection[module] = {} + module_tests = collection.setdefault(module, OrderedDict()) for test in sorted(parseLogFile(xml_fullname)): - if test.shortName() not in collection[module]: - collection[module][test.shortName()] = {} - if test.param() not in collection[module][test.shortName()]: - collection[module][test.shortName()][test.param()] = {} - collection[module][test.shortName()][test.param()][configuration] = \ - test.get("gmean") + test_results = module_tests.setdefault((test.shortName(), test.param()), {}) + test_results[configuration] = test.get("gmean") def main(): arg_parser = ArgumentParser(description='Build an XLS performance report.') @@ -129,41 +125,40 @@ def main(): module_styles = {module: xlwt.easyxf('pattern: pattern solid, fore_color {}'.format(color)) for module, color in module_colors.iteritems()} - for module, tests in collection.iteritems(): - for test, params in tests.iteritems(): - for param, configs in params.iteritems(): - sheet.write(row, 0, module, module_styles.get(module, xlwt.Style.default_style)) - sheet.write(row, 1, test) + for module, tests in sorted(collection.iteritems()): + for ((test, param), configs) in tests.iteritems(): + sheet.write(row, 0, module, module_styles.get(module, xlwt.Style.default_style)) + sheet.write(row, 1, test) - param_list = param[1:-1].split(", ") - sheet.write(row, 2, next(ifilter(re_image_size.match, param_list), None)) - sheet.write(row, 3, next(ifilter(re_data_type.match, param_list), None)) + param_list = param[1:-1].split(", ") + sheet.write(row, 2, next(ifilter(re_image_size.match, param_list), None)) + sheet.write(row, 3, next(ifilter(re_data_type.match, param_list), None)) - sheet.row(row).write(4, param) - for i, c in enumerate(config_names): - if c in configs: - sheet.write(row, 5 + i, configs[c], time_style) - else: - sheet.write(row, 5 + i, None, no_time_style) + sheet.row(row).write(4, param) + for i, c in enumerate(config_names): + if c in configs: + sheet.write(row, 5 + i, configs[c], time_style) + else: + sheet.write(row, 5 + i, None, no_time_style) - for i, comp in enumerate(sheet_comparisons): - left = configs.get(comp["from"]) - right = configs.get(comp["to"]) - col = 5 + len(config_names) + 1 + i + for i, comp in enumerate(sheet_comparisons): + left = configs.get(comp["from"]) + right = configs.get(comp["to"]) + col = 5 + len(config_names) + 1 + i - if left is not None and right is not None: - try: - speedup = left / right - sheet.write(row, col, speedup, good_speedup_style if speedup > 1.1 else - bad_speedup_style if speedup < 0.9 else - speedup_style) - except ArithmeticError as e: - sheet.write(row, col, None, error_speedup_style) - else: - sheet.write(row, col, None, no_speedup_style) + if left is not None and right is not None: + try: + speedup = left / right + sheet.write(row, col, speedup, good_speedup_style if speedup > 1.1 else + bad_speedup_style if speedup < 0.9 else + speedup_style) + except ArithmeticError as e: + sheet.write(row, col, None, error_speedup_style) + else: + sheet.write(row, col, None, no_speedup_style) - row += 1 - if row % 1000 == 0: sheet.flush_row_data() + row += 1 + if row % 1000 == 0: sheet.flush_row_data() wb.save(args.output) From 584f0745d0f917c993629c6e77bf898c6d243bf0 Mon Sep 17 00:00:00 2001 From: Roman Donchenko Date: Tue, 18 Jun 2013 12:30:05 +0400 Subject: [PATCH 117/178] Made xls-report.py ignore tests that were not successful. --- modules/ts/misc/xls-report.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/ts/misc/xls-report.py b/modules/ts/misc/xls-report.py index 7e63b6737c..f6278bae00 100755 --- a/modules/ts/misc/xls-report.py +++ b/modules/ts/misc/xls-report.py @@ -52,7 +52,8 @@ def collect_xml(collection, configuration, xml_fullname): for test in sorted(parseLogFile(xml_fullname)): test_results = module_tests.setdefault((test.shortName(), test.param()), {}) - test_results[configuration] = test.get("gmean") + if test.status == 'run': + test_results[configuration] = test.get("gmean") def main(): arg_parser = ArgumentParser(description='Build an XLS performance report.') From 16c4aad36de4e42624e70baf677dc67d0c17fefa Mon Sep 17 00:00:00 2001 From: Alexander Smorkalov Date: Thu, 13 Jun 2013 15:38:21 +0400 Subject: [PATCH 118/178] Java/Python bindings for computeCorrespondEpilines added. Simle Java test for computeCorrespondEpilines added. --- .../calib3d/include/opencv2/calib3d/calib3d.hpp | 6 +++--- .../src/org/opencv/test/calib3d/Calib3dTest.java | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/modules/calib3d/include/opencv2/calib3d/calib3d.hpp b/modules/calib3d/include/opencv2/calib3d/calib3d.hpp index 0d1cc46915..f213a114f4 100644 --- a/modules/calib3d/include/opencv2/calib3d/calib3d.hpp +++ b/modules/calib3d/include/opencv2/calib3d/calib3d.hpp @@ -639,9 +639,9 @@ CV_EXPORTS Mat findFundamentalMat( InputArray points1, InputArray points2, double param1=3., double param2=0.99); //! finds coordinates of epipolar lines corresponding the specified points -CV_EXPORTS void computeCorrespondEpilines( InputArray points, - int whichImage, InputArray F, - OutputArray lines ); +CV_EXPORTS_W void computeCorrespondEpilines( InputArray points, + int whichImage, InputArray F, + OutputArray lines ); CV_EXPORTS_W void triangulatePoints( InputArray projMatr1, InputArray projMatr2, InputArray projPoints1, InputArray projPoints2, diff --git a/modules/java/android_test/src/org/opencv/test/calib3d/Calib3dTest.java b/modules/java/android_test/src/org/opencv/test/calib3d/Calib3dTest.java index 8bcaf58a05..db806b6fc9 100644 --- a/modules/java/android_test/src/org/opencv/test/calib3d/Calib3dTest.java +++ b/modules/java/android_test/src/org/opencv/test/calib3d/Calib3dTest.java @@ -585,4 +585,18 @@ public class Calib3dTest extends OpenCVTestCase { public void testValidateDisparityMatMatIntIntInt() { fail("Not yet implemented"); } + + public void testComputeCorrespondEpilines() + { + Mat fundamental = new Mat(3, 3, CvType.CV_64F); + fundamental.put(0, 0, 0, -0.577, 0.288, 0.577, 0, 0.288, -0.288, -0.288, 0); + MatOfPoint2f left = new MatOfPoint2f(); + left.alloc(1); + left.put(0, 0, 2, 3); //add(new Point(x, y)); + Mat lines = new Mat(); + Mat truth = new Mat(1, 1, CvType.CV_32FC3); + truth.put(0, 0, -0.70735186, 0.70686162, -0.70588124); + Calib3d.computeCorrespondEpilines(left, 1, fundamental, lines); + assertMatEqual(truth, lines, EPS); + } } From 8f7ba03ed29284d86c68831996ec826692ba7bd6 Mon Sep 17 00:00:00 2001 From: Alexander Smorkalov Date: Fri, 14 Jun 2013 11:53:54 +0400 Subject: [PATCH 119/178] Some fixes for incorrectly documented parameters identified by rst_parser.py (Bug #1205) --- modules/core/doc/basic_structures.rst | 31 +++++++++++++++++-- modules/core/doc/clustering.rst | 8 +++++ modules/core/doc/drawing_functions.rst | 8 +++++ modules/core/doc/operations_on_arrays.rst | 2 ++ ...tility_and_system_functions_and_macros.rst | 9 ++++++ modules/core/doc/xml_yaml_persistence.rst | 11 +++++++ modules/core/include/opencv2/core/core.hpp | 2 -- 7 files changed, 67 insertions(+), 4 deletions(-) diff --git a/modules/core/doc/basic_structures.rst b/modules/core/doc/basic_structures.rst index acfbb911d2..3705879228 100644 --- a/modules/core/doc/basic_structures.rst +++ b/modules/core/doc/basic_structures.rst @@ -489,6 +489,9 @@ Various Ptr constructors. .. ocv:function:: Ptr::Ptr(_Tp* _obj) .. ocv:function:: Ptr::Ptr(const Ptr& ptr) + :param _obj: Object for copy. + :param ptr: Object for copy. + Ptr::~Ptr --------- The Ptr destructor. @@ -501,6 +504,8 @@ Assignment operator. .. ocv:function:: Ptr& Ptr::operator = (const Ptr& ptr) + :param ptr: Object for assignment. + Decrements own reference counter (with ``release()``) and increments ptr's reference counter. Ptr::addref @@ -1465,6 +1470,7 @@ Adds elements to the bottom of the matrix. .. ocv:function:: void Mat::push_back( const Mat& m ) :param elem: Added element(s). + :param m: Added line(s). The methods add one or more elements to the bottom of the matrix. They emulate the corresponding method of the STL vector class. When ``elem`` is ``Mat`` , its type and the number of columns must be the same as in the container matrix. @@ -2160,7 +2166,6 @@ Various SparseMat constructors. :param dims: Array dimensionality. :param _sizes: Sparce matrix size on all dementions. :param _type: Sparse matrix data type. - :param try1d: if try1d is true and matrix is a single-column matrix (Nx1), then the sparse matrix will be 1-dimensional. SparseMat::~SparseMat --------------------- @@ -2175,6 +2180,8 @@ Provides sparse matrix assignment operators. .. ocv:function:: SparseMat& SparseMat::operator = (const SparseMat& m) .. ocv:function:: SparseMat& SparseMat::operator = (const Mat& m) + :param m: Matrix for assignment. + The last variant is equivalent to the corresponding constructor with try1d=false. @@ -2202,6 +2209,10 @@ Convert sparse matrix with possible type change and scaling. .. ocv:function:: void SparseMat::convertTo( SparseMat& m, int rtype, double alpha=1 ) const .. ocv:function:: void SparseMat::convertTo( Mat& m, int rtype, double alpha=1, double beta=0 ) const + :param m: Destination matrix. + :param rtype: Destination matrix type. + :param alpha: Conversion multiplier. + The first version converts arbitrary sparse matrix to dense matrix and multiplies all the matrix elements by the specified scalar. The second versiob converts sparse matrix to dense matrix with optional type conversion and scaling. When rtype=-1, the destination element type will be the same as the sparse matrix element type. @@ -2294,7 +2305,7 @@ The method returns the number of matrix channels. SparseMat::size --------------- -Returns the array of sizes or matrix size by i dimention and 0 if the matrix is not allocated. +Returns the array of sizes or matrix size by i dimension and 0 if the matrix is not allocated. .. ocv:function:: const int* SparseMat::size() const .. ocv:function:: int SparseMat::size(int i) const @@ -2322,6 +2333,11 @@ Compute element hash value from the element indices. .. ocv:function:: size_t SparseMat::hash(int i0, int i1, int i2) const .. ocv:function:: size_t SparseMat::hash(const int* idx) const + :param i0: The first dimension index. + :param i1: The second dimension index. + :param i2: The third dimension index. + :param idx: Array of element indices for multidimensional matices. + SparseMat::ptr -------------- Low-level element-access functions, special variants for 1D, 2D, 3D cases, and the generic one for n-D case. @@ -2331,6 +2347,12 @@ Low-level element-access functions, special variants for 1D, 2D, 3D cases, and t .. ocv:function:: uchar* SparseMat::ptr(int i0, int i1, int i2, bool createMissing, size_t* hashval=0) .. ocv:function:: uchar* SparseMat::ptr(const int* idx, bool createMissing, size_t* hashval=0) + :param i0: The first dimension index. + :param i1: The second dimension index. + :param i2: The third dimension index. + :param idx: Array of element indices for multidimensional matices. + :param createMissing: Create new element with 0 value if it does not exist in SparseMat. + Return pointer to the matrix element. If the element is there (it is non-zero), the pointer to it is returned. If it is not there and ``createMissing=false``, NULL pointer is returned. If it is not there and ``createMissing=true``, the new elementis created and initialized with 0. Pointer to it is returned. If the optional hashval pointer is not ``NULL``, @@ -2344,6 +2366,11 @@ Erase the specified matrix element. When there is no such an element, the method .. ocv:function:: void SparseMat::erase(int i0, int i1, int i2, size_t* hashval=0) .. ocv:function:: void SparseMat::erase(const int* idx, size_t* hashval=0) + :param i0: The first dimension index. + :param i1: The second dimension index. + :param i2: The third dimension index. + :param idx: Array of element indices for multidimensional matices. + SparseMat\_ ----------- .. ocv:class:: SparseMat_ diff --git a/modules/core/doc/clustering.rst b/modules/core/doc/clustering.rst index 46130bc8fd..f58e99ce2c 100644 --- a/modules/core/doc/clustering.rst +++ b/modules/core/doc/clustering.rst @@ -17,12 +17,18 @@ Finds centers of clusters and groups input samples around the clusters. :param samples: Floating-point matrix of input samples, one row per sample. + :param data: Data for clustering. + :param cluster_count: Number of clusters to split the set by. + :param K: Number of clusters to split the set by. + :param labels: Input/output integer array that stores the cluster indices for every sample. :param criteria: The algorithm termination criteria, that is, the maximum number of iterations and/or the desired accuracy. The accuracy is specified as ``criteria.epsilon``. As soon as each of the cluster centers moves by less than ``criteria.epsilon`` on some iteration, the algorithm stops. + :param termcrit: The algorithm termination criteria, that is, the maximum number of iterations and/or the desired accuracy. + :param attempts: Flag to specify the number of times the algorithm is executed using different initial labellings. The algorithm returns the labels that yield the best compactness (see the last function parameter). :param rng: CvRNG state initialized by RNG(). @@ -37,6 +43,8 @@ Finds centers of clusters and groups input samples around the clusters. :param centers: Output matrix of the cluster centers, one row per each cluster center. + :param _centers: Output matrix of the cluster centers, one row per each cluster center. + :param compactness: The returned value that is described below. The function ``kmeans`` implements a k-means algorithm that finds the diff --git a/modules/core/doc/drawing_functions.rst b/modules/core/doc/drawing_functions.rst index 24328f9a54..342301db97 100644 --- a/modules/core/doc/drawing_functions.rst +++ b/modules/core/doc/drawing_functions.rst @@ -234,6 +234,8 @@ Calculates the width and height of a text string. :param text: Input text string. + :param text_string: Input text string in C format. + :param fontFace: Font to use. See the :ocv:func:`putText` for details. :param fontScale: Font scale. See the :ocv:func:`putText` for details. @@ -242,6 +244,12 @@ Calculates the width and height of a text string. :param baseLine: Output parameter - y-coordinate of the baseline relative to the bottom-most text point. + :param baseline: Output parameter - y-coordinate of the baseline relative to the bottom-most text point. + + :param font: Font description in terms of old C API. + + :param text_size: Output parameter - The size of a box that contains the specified text. + The function ``getTextSize`` calculates and returns the size of a box that contains the specified text. That is, the following code renders some text, the tight box surrounding it, and the baseline: :: diff --git a/modules/core/doc/operations_on_arrays.rst b/modules/core/doc/operations_on_arrays.rst index d338444760..bd55993afe 100644 --- a/modules/core/doc/operations_on_arrays.rst +++ b/modules/core/doc/operations_on_arrays.rst @@ -1062,6 +1062,8 @@ Returns the determinant of a square floating-point matrix. :param mtx: input matrix that must have ``CV_32FC1`` or ``CV_64FC1`` type and square size. + :param mat: input matrix that must have ``CV_32FC1`` or ``CV_64FC1`` type and square size. + The function ``determinant`` calculates and returns the determinant of the specified matrix. For small matrices ( ``mtx.cols=mtx.rows<=3`` ), the direct method is used. For larger matrices, the function uses LU factorization with partial pivoting. diff --git a/modules/core/doc/utility_and_system_functions_and_macros.rst b/modules/core/doc/utility_and_system_functions_and_macros.rst index 54198b058a..41cf7e1b72 100644 --- a/modules/core/doc/utility_and_system_functions_and_macros.rst +++ b/modules/core/doc/utility_and_system_functions_and_macros.rst @@ -173,6 +173,8 @@ Checks a condition at runtime and throws exception if it fails .. ocv:function:: CV_Assert(expr) + :param expr: Expression for check. + The macros ``CV_Assert`` (and ``CV_DbgAssert``) evaluate the specified expression. If it is 0, the macros raise an error (see :ocv:func:`error` ). The macro ``CV_Assert`` checks the condition in both Debug and Release configurations while ``CV_DbgAssert`` is only retained in the Debug configuration. @@ -188,8 +190,14 @@ Signals an error and raises an exception. :param status: Error code. Normally, it is a negative value. The list of pre-defined error codes can be found in ``cxerror.h`` . + :param func_name: The function name where error occurs. + :param err_msg: Text of the error message. + :param file_name: The file name where error occurs. + + :param line: The line number where error occurs. + :param args: ``printf`` -like formatted error message in parentheses. The function and the helper macros ``CV_Error`` and ``CV_Error_``: :: @@ -249,6 +257,7 @@ Allocates an aligned memory buffer. .. ocv:cfunction:: void* cvAlloc( size_t size ) :param size: Allocated buffer size. + :param bufSize: Allocated buffer size. The function allocates the buffer of the specified size and returns it. When the buffer size is 16 bytes or more, the returned buffer is aligned to 16 bytes. diff --git a/modules/core/doc/xml_yaml_persistence.rst b/modules/core/doc/xml_yaml_persistence.rst index c7d55d01f5..28bae24508 100644 --- a/modules/core/doc/xml_yaml_persistence.rst +++ b/modules/core/doc/xml_yaml_persistence.rst @@ -181,6 +181,17 @@ Opens a file. .. ocv:function:: bool FileStorage::open(const string& filename, int flags, const string& encoding=string()) + :param filename: Name of the file to open or the text string to read the data from. + Extension of the file (``.xml`` or ``.yml``/``.yaml``) determines its format (XML or YAML respectively). + Also you can append ``.gz`` to work with compressed files, for example ``myHugeMatrix.xml.gz``. + If both ``FileStorage::WRITE`` and ``FileStorage::MEMORY`` flags are specified, ``source`` + is used just to specify the output file format (e.g. ``mydata.xml``, ``.yml`` etc.). + + :param flags: Mode of operation. See FileStorage constructor for more details. + + :param encoding: Encoding of the file. Note that UTF-16 XML encoding is not supported currently and you should use 8-bit encoding instead of it. + + See description of parameters in :ocv:func:`FileStorage::FileStorage`. The method calls :ocv:func:`FileStorage::release` before opening the file. diff --git a/modules/core/include/opencv2/core/core.hpp b/modules/core/include/opencv2/core/core.hpp index 2b7791958f..10210c511b 100644 --- a/modules/core/include/opencv2/core/core.hpp +++ b/modules/core/include/opencv2/core/core.hpp @@ -3409,8 +3409,6 @@ public: //! converts dense 2d matrix to the sparse form /*! \param m the input matrix - \param try1d if true and m is a single-column matrix (Nx1), - then the sparse matrix will be 1-dimensional. */ explicit SparseMat(const Mat& m); //! converts old-style sparse matrix to the new-style. All the data is copied From 1492b204727066daae2967f1bb2831acde42eb92 Mon Sep 17 00:00:00 2001 From: Vladislav Vinogradov Date: Tue, 18 Jun 2013 13:17:33 +0400 Subject: [PATCH 120/178] fix gpu warnings with signed/unsigned char --- .../gpu/include/opencv2/gpu/device/limits.hpp | 231 +++++------------- modules/gpu/src/nvidia/core/NCV.hpp | 2 +- .../src/nvidia/core/NCVPixelOperations.hpp | 4 +- 3 files changed, 62 insertions(+), 175 deletions(-) diff --git a/modules/gpu/include/opencv2/gpu/device/limits.hpp b/modules/gpu/include/opencv2/gpu/device/limits.hpp index b040f199d6..595978006c 100644 --- a/modules/gpu/include/opencv2/gpu/device/limits.hpp +++ b/modules/gpu/include/opencv2/gpu/device/limits.hpp @@ -43,193 +43,80 @@ #ifndef __OPENCV_GPU_LIMITS_GPU_HPP__ #define __OPENCV_GPU_LIMITS_GPU_HPP__ -#include +#include +#include #include "common.hpp" namespace cv { namespace gpu { namespace device { - template struct numeric_limits - { - typedef T type; - __device__ __forceinline__ static type min() { return type(); }; - __device__ __forceinline__ static type max() { return type(); }; - __device__ __forceinline__ static type epsilon() { return type(); } - __device__ __forceinline__ static type round_error() { return type(); } - __device__ __forceinline__ static type denorm_min() { return type(); } - __device__ __forceinline__ static type infinity() { return type(); } - __device__ __forceinline__ static type quiet_NaN() { return type(); } - __device__ __forceinline__ static type signaling_NaN() { return T(); } - static const bool is_signed; - }; - template<> struct numeric_limits - { - typedef bool type; - __device__ __forceinline__ static type min() { return false; }; - __device__ __forceinline__ static type max() { return true; }; - __device__ __forceinline__ static type epsilon(); - __device__ __forceinline__ static type round_error(); - __device__ __forceinline__ static type denorm_min(); - __device__ __forceinline__ static type infinity(); - __device__ __forceinline__ static type quiet_NaN(); - __device__ __forceinline__ static type signaling_NaN(); - static const bool is_signed = false; - }; +template struct numeric_limits; - template<> struct numeric_limits - { - typedef char type; - __device__ __forceinline__ static type min() { return CHAR_MIN; }; - __device__ __forceinline__ static type max() { return CHAR_MAX; }; - __device__ __forceinline__ static type epsilon(); - __device__ __forceinline__ static type round_error(); - __device__ __forceinline__ static type denorm_min(); - __device__ __forceinline__ static type infinity(); - __device__ __forceinline__ static type quiet_NaN(); - __device__ __forceinline__ static type signaling_NaN(); - static const bool is_signed = (char)-1 == -1; - }; +template <> struct numeric_limits +{ + __device__ __forceinline__ static bool min() { return false; } + __device__ __forceinline__ static bool max() { return true; } + static const bool is_signed = false; +}; - template<> struct numeric_limits - { - typedef char type; - __device__ __forceinline__ static type min() { return SCHAR_MIN; }; - __device__ __forceinline__ static type max() { return SCHAR_MAX; }; - __device__ __forceinline__ static type epsilon(); - __device__ __forceinline__ static type round_error(); - __device__ __forceinline__ static type denorm_min(); - __device__ __forceinline__ static type infinity(); - __device__ __forceinline__ static type quiet_NaN(); - __device__ __forceinline__ static type signaling_NaN(); - static const bool is_signed = (signed char)-1 == -1; - }; +template <> struct numeric_limits +{ + __device__ __forceinline__ static signed char min() { return SCHAR_MIN; } + __device__ __forceinline__ static signed char max() { return SCHAR_MAX; } + static const bool is_signed = true; +}; - template<> struct numeric_limits - { - typedef unsigned char type; - __device__ __forceinline__ static type min() { return 0; }; - __device__ __forceinline__ static type max() { return UCHAR_MAX; }; - __device__ __forceinline__ static type epsilon(); - __device__ __forceinline__ static type round_error(); - __device__ __forceinline__ static type denorm_min(); - __device__ __forceinline__ static type infinity(); - __device__ __forceinline__ static type quiet_NaN(); - __device__ __forceinline__ static type signaling_NaN(); - static const bool is_signed = false; - }; +template <> struct numeric_limits +{ + __device__ __forceinline__ static unsigned char min() { return 0; } + __device__ __forceinline__ static unsigned char max() { return UCHAR_MAX; } + static const bool is_signed = false; +}; - template<> struct numeric_limits - { - typedef short type; - __device__ __forceinline__ static type min() { return SHRT_MIN; }; - __device__ __forceinline__ static type max() { return SHRT_MAX; }; - __device__ __forceinline__ static type epsilon(); - __device__ __forceinline__ static type round_error(); - __device__ __forceinline__ static type denorm_min(); - __device__ __forceinline__ static type infinity(); - __device__ __forceinline__ static type quiet_NaN(); - __device__ __forceinline__ static type signaling_NaN(); - static const bool is_signed = true; - }; +template <> struct numeric_limits +{ + __device__ __forceinline__ static short min() { return SHRT_MIN; } + __device__ __forceinline__ static short max() { return SHRT_MAX; } + static const bool is_signed = true; +}; - template<> struct numeric_limits - { - typedef unsigned short type; - __device__ __forceinline__ static type min() { return 0; }; - __device__ __forceinline__ static type max() { return USHRT_MAX; }; - __device__ __forceinline__ static type epsilon(); - __device__ __forceinline__ static type round_error(); - __device__ __forceinline__ static type denorm_min(); - __device__ __forceinline__ static type infinity(); - __device__ __forceinline__ static type quiet_NaN(); - __device__ __forceinline__ static type signaling_NaN(); - static const bool is_signed = false; - }; +template <> struct numeric_limits +{ + __device__ __forceinline__ static unsigned short min() { return 0; } + __device__ __forceinline__ static unsigned short max() { return USHRT_MAX; } + static const bool is_signed = false; +}; - template<> struct numeric_limits - { - typedef int type; - __device__ __forceinline__ static type min() { return INT_MIN; }; - __device__ __forceinline__ static type max() { return INT_MAX; }; - __device__ __forceinline__ static type epsilon(); - __device__ __forceinline__ static type round_error(); - __device__ __forceinline__ static type denorm_min(); - __device__ __forceinline__ static type infinity(); - __device__ __forceinline__ static type quiet_NaN(); - __device__ __forceinline__ static type signaling_NaN(); - static const bool is_signed = true; - }; +template <> struct numeric_limits +{ + __device__ __forceinline__ static int min() { return INT_MIN; } + __device__ __forceinline__ static int max() { return INT_MAX; } + static const bool is_signed = true; +}; +template <> struct numeric_limits +{ + __device__ __forceinline__ static unsigned int min() { return 0; } + __device__ __forceinline__ static unsigned int max() { return UINT_MAX; } + static const bool is_signed = false; +}; - template<> struct numeric_limits - { - typedef unsigned int type; - __device__ __forceinline__ static type min() { return 0; }; - __device__ __forceinline__ static type max() { return UINT_MAX; }; - __device__ __forceinline__ static type epsilon(); - __device__ __forceinline__ static type round_error(); - __device__ __forceinline__ static type denorm_min(); - __device__ __forceinline__ static type infinity(); - __device__ __forceinline__ static type quiet_NaN(); - __device__ __forceinline__ static type signaling_NaN(); - static const bool is_signed = false; - }; +template <> struct numeric_limits +{ + __device__ __forceinline__ static float min() { return FLT_MIN; } + __device__ __forceinline__ static float max() { return FLT_MAX; } + __device__ __forceinline__ static float epsilon() { return FLT_EPSILON; } + static const bool is_signed = true; +}; - template<> struct numeric_limits - { - typedef long type; - __device__ __forceinline__ static type min() { return LONG_MIN; }; - __device__ __forceinline__ static type max() { return LONG_MAX; }; - __device__ __forceinline__ static type epsilon(); - __device__ __forceinline__ static type round_error(); - __device__ __forceinline__ static type denorm_min(); - __device__ __forceinline__ static type infinity(); - __device__ __forceinline__ static type quiet_NaN(); - __device__ __forceinline__ static type signaling_NaN(); - static const bool is_signed = true; - }; +template <> struct numeric_limits +{ + __device__ __forceinline__ static double min() { return DBL_MIN; } + __device__ __forceinline__ static double max() { return DBL_MAX; } + __device__ __forceinline__ static double epsilon() { return DBL_EPSILON; } + static const bool is_signed = true; +}; - template<> struct numeric_limits - { - typedef unsigned long type; - __device__ __forceinline__ static type min() { return 0; }; - __device__ __forceinline__ static type max() { return ULONG_MAX; }; - __device__ __forceinline__ static type epsilon(); - __device__ __forceinline__ static type round_error(); - __device__ __forceinline__ static type denorm_min(); - __device__ __forceinline__ static type infinity(); - __device__ __forceinline__ static type quiet_NaN(); - __device__ __forceinline__ static type signaling_NaN(); - static const bool is_signed = false; - }; - - template<> struct numeric_limits - { - typedef float type; - __device__ __forceinline__ static type min() { return 1.175494351e-38f/*FLT_MIN*/; }; - __device__ __forceinline__ static type max() { return 3.402823466e+38f/*FLT_MAX*/; }; - __device__ __forceinline__ static type epsilon() { return 1.192092896e-07f/*FLT_EPSILON*/; }; - __device__ __forceinline__ static type round_error(); - __device__ __forceinline__ static type denorm_min(); - __device__ __forceinline__ static type infinity(); - __device__ __forceinline__ static type quiet_NaN(); - __device__ __forceinline__ static type signaling_NaN(); - static const bool is_signed = true; - }; - - template<> struct numeric_limits - { - typedef double type; - __device__ __forceinline__ static type min() { return 2.2250738585072014e-308/*DBL_MIN*/; }; - __device__ __forceinline__ static type max() { return 1.7976931348623158e+308/*DBL_MAX*/; }; - __device__ __forceinline__ static type epsilon(); - __device__ __forceinline__ static type round_error(); - __device__ __forceinline__ static type denorm_min(); - __device__ __forceinline__ static type infinity(); - __device__ __forceinline__ static type quiet_NaN(); - __device__ __forceinline__ static type signaling_NaN(); - static const bool is_signed = true; - }; }}} // namespace cv { namespace gpu { namespace device { #endif // __OPENCV_GPU_LIMITS_GPU_HPP__ diff --git a/modules/gpu/src/nvidia/core/NCV.hpp b/modules/gpu/src/nvidia/core/NCV.hpp index 0394dba186..80e1da7953 100644 --- a/modules/gpu/src/nvidia/core/NCV.hpp +++ b/modules/gpu/src/nvidia/core/NCV.hpp @@ -130,7 +130,7 @@ typedef int Ncv32s; typedef unsigned int Ncv32u; typedef short Ncv16s; typedef unsigned short Ncv16u; -typedef char Ncv8s; +typedef signed char Ncv8s; typedef unsigned char Ncv8u; typedef float Ncv32f; typedef double Ncv64f; diff --git a/modules/gpu/src/nvidia/core/NCVPixelOperations.hpp b/modules/gpu/src/nvidia/core/NCVPixelOperations.hpp index ec2f16ebb7..c1e06b434e 100644 --- a/modules/gpu/src/nvidia/core/NCVPixelOperations.hpp +++ b/modules/gpu/src/nvidia/core/NCVPixelOperations.hpp @@ -51,7 +51,7 @@ template inline __host__ __device__ TBase _pixMaxVal(); template<> static inline __host__ __device__ Ncv8u _pixMaxVal() {return UCHAR_MAX;} template<> static inline __host__ __device__ Ncv16u _pixMaxVal() {return USHRT_MAX;} template<> static inline __host__ __device__ Ncv32u _pixMaxVal() {return UINT_MAX;} -template<> static inline __host__ __device__ Ncv8s _pixMaxVal() {return CHAR_MAX;} +template<> static inline __host__ __device__ Ncv8s _pixMaxVal() {return SCHAR_MAX;} template<> static inline __host__ __device__ Ncv16s _pixMaxVal() {return SHRT_MAX;} template<> static inline __host__ __device__ Ncv32s _pixMaxVal() {return INT_MAX;} template<> static inline __host__ __device__ Ncv32f _pixMaxVal() {return FLT_MAX;} @@ -61,7 +61,7 @@ template inline __host__ __device__ TBase _pixMinVal(); template<> static inline __host__ __device__ Ncv8u _pixMinVal() {return 0;} template<> static inline __host__ __device__ Ncv16u _pixMinVal() {return 0;} template<> static inline __host__ __device__ Ncv32u _pixMinVal() {return 0;} -template<> static inline __host__ __device__ Ncv8s _pixMinVal() {return CHAR_MIN;} +template<> static inline __host__ __device__ Ncv8s _pixMinVal() {return SCHAR_MIN;} template<> static inline __host__ __device__ Ncv16s _pixMinVal() {return SHRT_MIN;} template<> static inline __host__ __device__ Ncv32s _pixMinVal() {return INT_MIN;} template<> static inline __host__ __device__ Ncv32f _pixMinVal() {return FLT_MIN;} From 24d84a45b19dd3d2016bacf943a3811c67e804d4 Mon Sep 17 00:00:00 2001 From: Roman Donchenko Date: Mon, 17 Jun 2013 21:06:15 +0400 Subject: [PATCH 121/178] Made tests record in the XML output which parallel framework was used. --- .../core/include/opencv2/core/internal.hpp | 26 ++++++++++++++++++ modules/core/src/parallel.cpp | 27 +++++-------------- modules/ts/src/precomp.hpp | 2 ++ modules/ts/src/ts_func.cpp | 8 ++++++ 4 files changed, 42 insertions(+), 21 deletions(-) diff --git a/modules/core/include/opencv2/core/internal.hpp b/modules/core/include/opencv2/core/internal.hpp index 5335fa01f8..10cd2caf93 100644 --- a/modules/core/include/opencv2/core/internal.hpp +++ b/modules/core/include/opencv2/core/internal.hpp @@ -50,6 +50,8 @@ #include +#include "cvconfig.h" + #if defined WIN32 || defined _WIN32 # ifndef WIN32 # define WIN32 @@ -184,6 +186,30 @@ CV_INLINE IppiSize ippiSize(int width, int height) # include "opencv2/core/eigen.hpp" #endif +#ifdef _OPENMP +# define HAVE_OPENMP +#endif + +#ifdef __APPLE__ +# define HAVE_GCD +#endif + +#if defined _MSC_VER && _MSC_VER >= 1600 +# define HAVE_CONCURRENCY +#endif + +#if defined HAVE_TBB && TBB_VERSION_MAJOR*100 + TBB_VERSION_MINOR >= 202 +# define CV_PARALLEL_FRAMEWORK "tbb" +#elif defined HAVE_CSTRIPES +# define CV_PARALLEL_FRAMEWORK "cstripes" +#elif defined HAVE_OPENMP +# define CV_PARALLEL_FRAMEWORK "openmp" +#elif defined HAVE_GCD +# define CV_PARALLEL_FRAMEWORK "gcd" +#elif defined HAVE_CONCURRENCY +# define CV_PARALLEL_FRAMEWORK "ms-concurrency" +#endif + #ifdef __cplusplus namespace cv diff --git a/modules/core/src/parallel.cpp b/modules/core/src/parallel.cpp index 0b2a845ac1..51b165275f 100644 --- a/modules/core/src/parallel.cpp +++ b/modules/core/src/parallel.cpp @@ -61,17 +61,6 @@ #endif #endif -#ifdef _OPENMP - #define HAVE_OPENMP -#endif - -#ifdef __APPLE__ - #define HAVE_GCD -#endif - -#if defined _MSC_VER && _MSC_VER >= 1600 - #define HAVE_CONCURRENCY -#endif /* IMPORTANT: always use the same order of defines 1. HAVE_TBB - 3rdparty library, should be explicitly enabled @@ -110,10 +99,6 @@ #endif #endif -#if defined HAVE_TBB || defined HAVE_CSTRIPES || defined HAVE_OPENMP || defined HAVE_GCD || defined HAVE_CONCURRENCY - #define HAVE_PARALLEL_FRAMEWORK -#endif - namespace cv { ParallelLoopBody::~ParallelLoopBody() {} @@ -121,7 +106,7 @@ namespace cv namespace { -#ifdef HAVE_PARALLEL_FRAMEWORK +#ifdef CV_PARALLEL_FRAMEWORK class ParallelLoopBodyWrapper { public: @@ -218,7 +203,7 @@ public: static SchedPtr pplScheduler; #endif -#endif // HAVE_PARALLEL_FRAMEWORK +#endif // CV_PARALLEL_FRAMEWORK } //namespace @@ -226,7 +211,7 @@ static SchedPtr pplScheduler; void cv::parallel_for_(const cv::Range& range, const cv::ParallelLoopBody& body, double nstripes) { -#ifdef HAVE_PARALLEL_FRAMEWORK +#ifdef CV_PARALLEL_FRAMEWORK if(numThreads != 0) { @@ -281,7 +266,7 @@ void cv::parallel_for_(const cv::Range& range, const cv::ParallelLoopBody& body, } else -#endif // HAVE_PARALLEL_FRAMEWORK +#endif // CV_PARALLEL_FRAMEWORK { (void)nstripes; body(range); @@ -290,7 +275,7 @@ void cv::parallel_for_(const cv::Range& range, const cv::ParallelLoopBody& body, int cv::getNumThreads(void) { -#ifdef HAVE_PARALLEL_FRAMEWORK +#ifdef CV_PARALLEL_FRAMEWORK if(numThreads == 0) return 1; @@ -333,7 +318,7 @@ int cv::getNumThreads(void) void cv::setNumThreads( int threads ) { (void)threads; -#ifdef HAVE_PARALLEL_FRAMEWORK +#ifdef CV_PARALLEL_FRAMEWORK numThreads = threads; #endif diff --git a/modules/ts/src/precomp.hpp b/modules/ts/src/precomp.hpp index 10acd7ad8f..0b2adacc4d 100644 --- a/modules/ts/src/precomp.hpp +++ b/modules/ts/src/precomp.hpp @@ -1,4 +1,6 @@ +#include "opencv2/core/core.hpp" #include "opencv2/core/core_c.h" +#include "opencv2/core/internal.hpp" #include "opencv2/ts/ts.hpp" #ifdef GTEST_LINKED_AS_SHARED_LIBRARY diff --git a/modules/ts/src/ts_func.cpp b/modules/ts/src/ts_func.cpp index 1d636e6746..7a292d71cf 100644 --- a/modules/ts/src/ts_func.cpp +++ b/modules/ts/src/ts_func.cpp @@ -2958,6 +2958,14 @@ void printVersionInfo(bool useStdOut) ::testing::Test::RecordProperty("inner_version", ver); if(useStdOut) std::cout << ver << std::endl; } + +#ifdef CV_PARALLEL_FRAMEWORK + ::testing::Test::RecordProperty("cv_parallel_framework", CV_PARALLEL_FRAMEWORK); + if (useStdOut) + { + std::cout << "Parallel framework: " << CV_PARALLEL_FRAMEWORK << std::endl; + } +#endif } } //namespace cvtest From 4af7d65224f23739176c49341d8bcf795a8ab5ea Mon Sep 17 00:00:00 2001 From: Roman Donchenko Date: Tue, 18 Jun 2013 18:08:38 +0400 Subject: [PATCH 122/178] Made tests record information about CPU features and Tegra optimization status. --- modules/ts/src/ts_func.cpp | 42 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/modules/ts/src/ts_func.cpp b/modules/ts/src/ts_func.cpp index 7a292d71cf..e2998149d5 100644 --- a/modules/ts/src/ts_func.cpp +++ b/modules/ts/src/ts_func.cpp @@ -2,6 +2,10 @@ #include #include +#ifdef HAVE_TEGRA_OPTIMIZATION +#include "tegra.hpp" +#endif + using namespace cv; namespace cvtest @@ -2966,6 +2970,44 @@ void printVersionInfo(bool useStdOut) std::cout << "Parallel framework: " << CV_PARALLEL_FRAMEWORK << std::endl; } #endif + + std::string cpu_features; + +#if CV_SSE + if (checkHardwareSupport(CV_CPU_SSE)) cpu_features += " sse"; +#endif +#if CV_SSE2 + if (checkHardwareSupport(CV_CPU_SSE2)) cpu_features += " sse2"; +#endif +#if CV_SSE3 + if (checkHardwareSupport(CV_CPU_SSE3)) cpu_features += " sse3"; +#endif +#if CV_SSSE3 + if (checkHardwareSupport(CV_CPU_SSSE3)) cpu_features += " ssse3"; +#endif +#if CV_SSE4_1 + if (checkHardwareSupport(CV_CPU_SSE4_1)) cpu_features += " sse4.1"; +#endif +#if CV_SSE4_2 + if (checkHardwareSupport(CV_CPU_SSE4_2)) cpu_features += " sse4.2"; +#endif +#if CV_AVX + if (checkHardwareSupport(CV_CPU_AVX)) cpu_features += " avx"; +#endif +#if CV_NEON + cpu_features += " neon"; // NEON is currently not checked at runtime +#endif + + cpu_features.erase(0, 1); // erase initial space + + ::testing::Test::RecordProperty("cv_cpu_features", cpu_features); + if (useStdOut) std::cout << "CPU features: " << cpu_features << std::endl; + +#ifdef HAVE_TEGRA_OPTIMIZATION + const char * tegra_optimization = tegra::isDeviceSupported() ? "enabled" : "disabled"; + ::testing::Test::RecordProperty("cv_tegra_optimization", tegra_optimization); + if (useStdOut) std::cout << "Tegra optimization: " << tegra_optimization << std::endl; +#endif } } //namespace cvtest From 68741bf8a010913991fc43252c052d2519bdf301 Mon Sep 17 00:00:00 2001 From: Alexander Shishkov Date: Wed, 19 Jun 2013 00:20:21 +0400 Subject: [PATCH 123/178] moved iOS part to platforms folder --- .../introduction/ios_install/ios_install.rst | 2 +- ios/configure-device_xcode.sh | 1 - ios/configure-simulator_xcode.sh | 1 - ios/readme.txt | 15 --------------- {ios => platforms/ios}/Info.plist.in | 2 +- {ios => platforms/ios}/build_framework.py | 9 +++------ .../ios}/cmake/Modules/Platform/iOS.cmake | 0 .../Toolchains/Toolchain-iPhoneOS_Xcode.cmake | 8 ++++---- .../Toolchain-iPhoneSimulator_Xcode.cmake | 8 ++++---- platforms/ios/readme.txt | 7 +++++++ 10 files changed, 20 insertions(+), 33 deletions(-) delete mode 100755 ios/configure-device_xcode.sh delete mode 100755 ios/configure-simulator_xcode.sh delete mode 100644 ios/readme.txt rename {ios => platforms/ios}/Info.plist.in (93%) rename {ios => platforms/ios}/build_framework.py (95%) rename {ios => platforms/ios}/cmake/Modules/Platform/iOS.cmake (100%) rename {ios => platforms/ios}/cmake/Toolchains/Toolchain-iPhoneOS_Xcode.cmake (80%) rename {ios => platforms/ios}/cmake/Toolchains/Toolchain-iPhoneSimulator_Xcode.cmake (80%) create mode 100644 platforms/ios/readme.txt diff --git a/doc/tutorials/introduction/ios_install/ios_install.rst b/doc/tutorials/introduction/ios_install/ios_install.rst index ace657b21c..8d117a0b42 100644 --- a/doc/tutorials/introduction/ios_install/ios_install.rst +++ b/doc/tutorials/introduction/ios_install/ios_install.rst @@ -37,7 +37,7 @@ Building OpenCV from Source, using CMake and Command Line .. code-block:: bash cd ~/ - python opencv/ios/build_framework.py ios + python opencv/platforms/ios/build_framework.py ios If everything's fine, a few minutes later you will get ~//ios/opencv2.framework. You can add this framework to your Xcode projects. diff --git a/ios/configure-device_xcode.sh b/ios/configure-device_xcode.sh deleted file mode 100755 index 8c28a3e909..0000000000 --- a/ios/configure-device_xcode.sh +++ /dev/null @@ -1 +0,0 @@ -cmake -GXcode -DCMAKE_TOOLCHAIN_FILE=../opencv/ios/cmake/Toolchains/Toolchain-iPhoneOS_Xcode.cmake -DCMAKE_INSTALL_PREFIX=../OpenCV_iPhoneOS ../opencv diff --git a/ios/configure-simulator_xcode.sh b/ios/configure-simulator_xcode.sh deleted file mode 100755 index 50e00261db..0000000000 --- a/ios/configure-simulator_xcode.sh +++ /dev/null @@ -1 +0,0 @@ -cmake -GXcode -DCMAKE_TOOLCHAIN_FILE=../opencv/ios/cmake/Toolchains/Toolchain-iPhoneSimulator_Xcode.cmake -DCMAKE_INSTALL_PREFIX=../OpenCV_iPhoneSimulator ../opencv diff --git a/ios/readme.txt b/ios/readme.txt deleted file mode 100644 index 1441b241b7..0000000000 --- a/ios/readme.txt +++ /dev/null @@ -1,15 +0,0 @@ -Assuming that your build directory is on the same level that opencv source, -From the build directory run - ../opencv/ios/configure-device_xcode.sh -or - ../opencv/ios/configure-simulator_xcode.sh - -Then from the same folder invoke - -xcodebuild -sdk iphoneos -configuration Release -target ALL_BUILD -xcodebuild -sdk iphoneos -configuration Release -target install install - -or - -xcodebuild -sdk iphonesimulator -configuration Release -target ALL_BUILD -xcodebuild -sdk iphonesimulator -configuration Release -target install install \ No newline at end of file diff --git a/ios/Info.plist.in b/platforms/ios/Info.plist.in similarity index 93% rename from ios/Info.plist.in rename to platforms/ios/Info.plist.in index 89ef38625d..012de88568 100644 --- a/ios/Info.plist.in +++ b/platforms/ios/Info.plist.in @@ -5,7 +5,7 @@ CFBundleName OpenCV CFBundleIdentifier - com.itseez.opencv + opencv.org CFBundleVersion ${VERSION} CFBundleShortVersionString diff --git a/ios/build_framework.py b/platforms/ios/build_framework.py similarity index 95% rename from ios/build_framework.py rename to platforms/ios/build_framework.py index ceef4b71d7..bc385bb1bb 100755 --- a/ios/build_framework.py +++ b/platforms/ios/build_framework.py @@ -38,7 +38,7 @@ def build_opencv(srcroot, buildroot, target, arch): # for some reason, if you do not specify CMAKE_BUILD_TYPE, it puts libs to "RELEASE" rather than "Release" cmakeargs = ("-GXcode " + "-DCMAKE_BUILD_TYPE=Release " + - "-DCMAKE_TOOLCHAIN_FILE=%s/ios/cmake/Toolchains/Toolchain-%s_Xcode.cmake " + + "-DCMAKE_TOOLCHAIN_FILE=%s/platforms/ios/cmake/Toolchains/Toolchain-%s_Xcode.cmake " + "-DBUILD_opencv_world=ON " + "-DCMAKE_INSTALL_PREFIX=install") % (srcroot, target) # if cmake cache exists, just rerun cmake to update OpenCV.xproj if necessary @@ -92,16 +92,13 @@ def put_framework_together(srcroot, dstroot): os.system("lipo -create " + wlist + " -o " + dstdir + "/opencv2") # form Info.plist - srcfile = open(srcroot + "/ios/Info.plist.in", "rt") + srcfile = open(srcroot + "/platforms/ios/Info.plist.in", "rt") dstfile = open(dstdir + "/Resources/Info.plist", "wt") for l in srcfile.readlines(): dstfile.write(l.replace("${VERSION}", opencv_version)) srcfile.close() dstfile.close() - # copy cascades - # TODO ... - # make symbolic links os.symlink("A", "Versions/Current") os.symlink("Versions/Current/Headers", "Headers") @@ -125,4 +122,4 @@ if __name__ == "__main__": print "Usage:\n\t./build_framework.py \n\n" sys.exit(0) - build_framework(os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), "..")), os.path.abspath(sys.argv[1])) \ No newline at end of file + build_framework(os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), "../..")), os.path.abspath(sys.argv[1])) \ No newline at end of file diff --git a/ios/cmake/Modules/Platform/iOS.cmake b/platforms/ios/cmake/Modules/Platform/iOS.cmake similarity index 100% rename from ios/cmake/Modules/Platform/iOS.cmake rename to platforms/ios/cmake/Modules/Platform/iOS.cmake diff --git a/ios/cmake/Toolchains/Toolchain-iPhoneOS_Xcode.cmake b/platforms/ios/cmake/Toolchains/Toolchain-iPhoneOS_Xcode.cmake similarity index 80% rename from ios/cmake/Toolchains/Toolchain-iPhoneOS_Xcode.cmake rename to platforms/ios/cmake/Toolchains/Toolchain-iPhoneOS_Xcode.cmake index 67343253bd..6493deb459 100644 --- a/ios/cmake/Toolchains/Toolchain-iPhoneOS_Xcode.cmake +++ b/platforms/ios/cmake/Toolchains/Toolchain-iPhoneOS_Xcode.cmake @@ -4,12 +4,12 @@ set (IPHONEOS TRUE) # Standard settings set (CMAKE_SYSTEM_NAME iOS) # Include extra modules for the iOS platform files -set (CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/ios/cmake/Modules") +set (CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/cmake/Modules") -# Force the compilers to gcc for iOS +# Force the compilers to clang for iOS include (CMakeForceCompiler) -#CMAKE_FORCE_C_COMPILER (gcc gcc) -#CMAKE_FORCE_CXX_COMPILER (g++ g++) +#CMAKE_FORCE_C_COMPILER (clang GNU) +#CMAKE_FORCE_CXX_COMPILER (clang++ GNU) set (CMAKE_C_SIZEOF_DATA_PTR 4) set (CMAKE_C_HAS_ISYSROOT 1) diff --git a/ios/cmake/Toolchains/Toolchain-iPhoneSimulator_Xcode.cmake b/platforms/ios/cmake/Toolchains/Toolchain-iPhoneSimulator_Xcode.cmake similarity index 80% rename from ios/cmake/Toolchains/Toolchain-iPhoneSimulator_Xcode.cmake rename to platforms/ios/cmake/Toolchains/Toolchain-iPhoneSimulator_Xcode.cmake index 7ef8113edb..0056c8dbd4 100644 --- a/ios/cmake/Toolchains/Toolchain-iPhoneSimulator_Xcode.cmake +++ b/platforms/ios/cmake/Toolchains/Toolchain-iPhoneSimulator_Xcode.cmake @@ -4,12 +4,12 @@ set (IPHONESIMULATOR TRUE) # Standard settings set (CMAKE_SYSTEM_NAME iOS) # Include extra modules for the iOS platform files -set (CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/ios/cmake/Modules") +set (CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/cmake/Modules") -# Force the compilers to gcc for iOS +# Force the compilers to clang for iOS include (CMakeForceCompiler) -#CMAKE_FORCE_C_COMPILER (gcc gcc) -#CMAKE_FORCE_CXX_COMPILER (g++ g++) +#CMAKE_FORCE_C_COMPILER (clang GNU) +#CMAKE_FORCE_CXX_COMPILER (clang++ GNU) set (CMAKE_C_SIZEOF_DATA_PTR 4) set (CMAKE_C_HAS_ISYSROOT 1) diff --git a/platforms/ios/readme.txt b/platforms/ios/readme.txt new file mode 100644 index 0000000000..8f1f206b03 --- /dev/null +++ b/platforms/ios/readme.txt @@ -0,0 +1,7 @@ +Building OpenCV from Source, using CMake and Command Line +========================================================= + +cd ~/ +python opencv/platforms/ios/build_framework.py ios + +If everything's fine, a few minutes later you will get ~//ios/opencv2.framework. You can add this framework to your Xcode projects. \ No newline at end of file From 26c246140a31556fd116bb53044575a0f9b02b84 Mon Sep 17 00:00:00 2001 From: yao Date: Wed, 19 Jun 2013 11:20:45 +0800 Subject: [PATCH 124/178] optimize hog --- modules/ocl/src/hog.cpp | 508 +++++++++++++++-------- modules/ocl/src/opencl/objdetect_hog.cl | 528 +++++++++++++++++------- 2 files changed, 711 insertions(+), 325 deletions(-) diff --git a/modules/ocl/src/hog.cpp b/modules/ocl/src/hog.cpp index a3514586fa..3533cce69a 100644 --- a/modules/ocl/src/hog.cpp +++ b/modules/ocl/src/hog.cpp @@ -15,7 +15,7 @@ // Third party copyrights are property of their respective owners. // // @Authors -// Wenju He, wenju@multicorewareinc.com +// Wenju He, wenju@multicorewareinc.com // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: @@ -48,13 +48,107 @@ using namespace cv; using namespace cv::ocl; using namespace std; - #define CELL_WIDTH 8 #define CELL_HEIGHT 8 #define CELLS_PER_BLOCK_X 2 #define CELLS_PER_BLOCK_Y 2 #define NTHREADS 256 +static oclMat gauss_w_lut; +static bool hog_device_cpu; +/* pre-compute gaussian and interp_weight lookup tables if sigma is 4.0f */ +static const float gaussian_interp_lut[] = +{ + /* gaussian lut */ + 0.01831564f, 0.02926831f, 0.04393693f, 0.06196101f, 0.08208500f, 0.10215643f, + 0.11943297f, 0.13117145f, 0.13533528f, 0.13117145f, 0.11943297f, 0.10215643f, + 0.08208500f, 0.06196101f, 0.04393693f, 0.02926831f, 0.02926831f, 0.04677062f, + 0.07021102f, 0.09901341f, 0.13117145f, 0.16324551f, 0.19085334f, 0.20961139f, + 0.21626517f, 0.20961139f, 0.19085334f, 0.16324551f, 0.13117145f, 0.09901341f, + 0.07021102f, 0.04677062f, 0.04393693f, 0.07021102f, 0.10539922f, 0.14863673f, + 0.19691168f, 0.24506053f, 0.28650481f, 0.31466395f, 0.32465246f, 0.31466395f, + 0.28650481f, 0.24506053f, 0.19691168f, 0.14863673f, 0.10539922f, 0.07021102f, + 0.06196101f, 0.09901341f, 0.14863673f, 0.20961139f, 0.27768996f, 0.34559074f, + 0.40403652f, 0.44374731f, 0.45783335f, 0.44374731f, 0.40403652f, 0.34559074f, + 0.27768996f, 0.20961139f, 0.14863673f, 0.09901341f, 0.08208500f, 0.13117145f, + 0.19691168f, 0.27768996f, 0.36787945f, 0.45783335f, 0.53526145f, 0.58786964f, + 0.60653067f, 0.58786964f, 0.53526145f, 0.45783335f, 0.36787945f, 0.27768996f, + 0.19691168f, 0.13117145f, 0.10215643f, 0.16324551f, 0.24506053f, 0.34559074f, + 0.45783335f, 0.56978285f, 0.66614360f, 0.73161560f, 0.75483960f, 0.73161560f, + 0.66614360f, 0.56978285f, 0.45783335f, 0.34559074f, 0.24506053f, 0.16324551f, + 0.11943297f, 0.19085334f, 0.28650481f, 0.40403652f, 0.53526145f, 0.66614360f, + 0.77880079f, 0.85534531f, 0.88249689f, 0.85534531f, 0.77880079f, 0.66614360f, + 0.53526145f, 0.40403652f, 0.28650481f, 0.19085334f, 0.13117145f, 0.20961139f, + 0.31466395f, 0.44374731f, 0.58786964f, 0.73161560f, 0.85534531f, 0.93941307f, + 0.96923321f, 0.93941307f, 0.85534531f, 0.73161560f, 0.58786964f, 0.44374731f, + 0.31466395f, 0.20961139f, 0.13533528f, 0.21626517f, 0.32465246f, 0.45783335f, + 0.60653067f, 0.75483960f, 0.88249689f, 0.96923321f, 1.00000000f, 0.96923321f, + 0.88249689f, 0.75483960f, 0.60653067f, 0.45783335f, 0.32465246f, 0.21626517f, + 0.13117145f, 0.20961139f, 0.31466395f, 0.44374731f, 0.58786964f, 0.73161560f, + 0.85534531f, 0.93941307f, 0.96923321f, 0.93941307f, 0.85534531f, 0.73161560f, + 0.58786964f, 0.44374731f, 0.31466395f, 0.20961139f, 0.11943297f, 0.19085334f, + 0.28650481f, 0.40403652f, 0.53526145f, 0.66614360f, 0.77880079f, 0.85534531f, + 0.88249689f, 0.85534531f, 0.77880079f, 0.66614360f, 0.53526145f, 0.40403652f, + 0.28650481f, 0.19085334f, 0.10215643f, 0.16324551f, 0.24506053f, 0.34559074f, + 0.45783335f, 0.56978285f, 0.66614360f, 0.73161560f, 0.75483960f, 0.73161560f, + 0.66614360f, 0.56978285f, 0.45783335f, 0.34559074f, 0.24506053f, 0.16324551f, + 0.08208500f, 0.13117145f, 0.19691168f, 0.27768996f, 0.36787945f, 0.45783335f, + 0.53526145f, 0.58786964f, 0.60653067f, 0.58786964f, 0.53526145f, 0.45783335f, + 0.36787945f, 0.27768996f, 0.19691168f, 0.13117145f, 0.06196101f, 0.09901341f, + 0.14863673f, 0.20961139f, 0.27768996f, 0.34559074f, 0.40403652f, 0.44374731f, + 0.45783335f, 0.44374731f, 0.40403652f, 0.34559074f, 0.27768996f, 0.20961139f, + 0.14863673f, 0.09901341f, 0.04393693f, 0.07021102f, 0.10539922f, 0.14863673f, + 0.19691168f, 0.24506053f, 0.28650481f, 0.31466395f, 0.32465246f, 0.31466395f, + 0.28650481f, 0.24506053f, 0.19691168f, 0.14863673f, 0.10539922f, 0.07021102f, + 0.02926831f, 0.04677062f, 0.07021102f, 0.09901341f, 0.13117145f, 0.16324551f, + 0.19085334f, 0.20961139f, 0.21626517f, 0.20961139f, 0.19085334f, 0.16324551f, + 0.13117145f, 0.09901341f, 0.07021102f, 0.04677062f, + /* interp_weight lut */ + 0.00390625f, 0.01171875f, 0.01953125f, 0.02734375f, 0.03515625f, 0.04296875f, + 0.05078125f, 0.05859375f, 0.05859375f, 0.05078125f, 0.04296875f, 0.03515625f, + 0.02734375f, 0.01953125f, 0.01171875f, 0.00390625f, 0.01171875f, 0.03515625f, + 0.05859375f, 0.08203125f, 0.10546875f, 0.12890625f, 0.15234375f, 0.17578125f, + 0.17578125f, 0.15234375f, 0.12890625f, 0.10546875f, 0.08203125f, 0.05859375f, + 0.03515625f, 0.01171875f, 0.01953125f, 0.05859375f, 0.09765625f, 0.13671875f, + 0.17578125f, 0.21484375f, 0.25390625f, 0.29296875f, 0.29296875f, 0.25390625f, + 0.21484375f, 0.17578125f, 0.13671875f, 0.09765625f, 0.05859375f, 0.01953125f, + 0.02734375f, 0.08203125f, 0.13671875f, 0.19140625f, 0.24609375f, 0.30078125f, + 0.35546875f, 0.41015625f, 0.41015625f, 0.35546875f, 0.30078125f, 0.24609375f, + 0.19140625f, 0.13671875f, 0.08203125f, 0.02734375f, 0.03515625f, 0.10546875f, + 0.17578125f, 0.24609375f, 0.31640625f, 0.38671875f, 0.45703125f, 0.52734375f, + 0.52734375f, 0.45703125f, 0.38671875f, 0.31640625f, 0.24609375f, 0.17578125f, + 0.10546875f, 0.03515625f, 0.04296875f, 0.12890625f, 0.21484375f, 0.30078125f, + 0.38671875f, 0.47265625f, 0.55859375f, 0.64453125f, 0.64453125f, 0.55859375f, + 0.47265625f, 0.38671875f, 0.30078125f, 0.21484375f, 0.12890625f, 0.04296875f, + 0.05078125f, 0.15234375f, 0.25390625f, 0.35546875f, 0.45703125f, 0.55859375f, + 0.66015625f, 0.76171875f, 0.76171875f, 0.66015625f, 0.55859375f, 0.45703125f, + 0.35546875f, 0.25390625f, 0.15234375f, 0.05078125f, 0.05859375f, 0.17578125f, + 0.29296875f, 0.41015625f, 0.52734375f, 0.64453125f, 0.76171875f, 0.87890625f, + 0.87890625f, 0.76171875f, 0.64453125f, 0.52734375f, 0.41015625f, 0.29296875f, + 0.17578125f, 0.05859375f, 0.05859375f, 0.17578125f, 0.29296875f, 0.41015625f, + 0.52734375f, 0.64453125f, 0.76171875f, 0.87890625f, 0.87890625f, 0.76171875f, + 0.64453125f, 0.52734375f, 0.41015625f, 0.29296875f, 0.17578125f, 0.05859375f, + 0.05078125f, 0.15234375f, 0.25390625f, 0.35546875f, 0.45703125f, 0.55859375f, + 0.66015625f, 0.76171875f, 0.76171875f, 0.66015625f, 0.55859375f, 0.45703125f, + 0.35546875f, 0.25390625f, 0.15234375f, 0.05078125f, 0.04296875f, 0.12890625f, + 0.21484375f, 0.30078125f, 0.38671875f, 0.47265625f, 0.55859375f, 0.64453125f, + 0.64453125f, 0.55859375f, 0.47265625f, 0.38671875f, 0.30078125f, 0.21484375f, + 0.12890625f, 0.04296875f, 0.03515625f, 0.10546875f, 0.17578125f, 0.24609375f, + 0.31640625f, 0.38671875f, 0.45703125f, 0.52734375f, 0.52734375f, 0.45703125f, + 0.38671875f, 0.31640625f, 0.24609375f, 0.17578125f, 0.10546875f, 0.03515625f, + 0.02734375f, 0.08203125f, 0.13671875f, 0.19140625f, 0.24609375f, 0.30078125f, + 0.35546875f, 0.41015625f, 0.41015625f, 0.35546875f, 0.30078125f, 0.24609375f, + 0.19140625f, 0.13671875f, 0.08203125f, 0.02734375f, 0.01953125f, 0.05859375f, + 0.09765625f, 0.13671875f, 0.17578125f, 0.21484375f, 0.25390625f, 0.29296875f, + 0.29296875f, 0.25390625f, 0.21484375f, 0.17578125f, 0.13671875f, 0.09765625f, + 0.05859375f, 0.01953125f, 0.01171875f, 0.03515625f, 0.05859375f, 0.08203125f, + 0.10546875f, 0.12890625f, 0.15234375f, 0.17578125f, 0.17578125f, 0.15234375f, + 0.12890625f, 0.10546875f, 0.08203125f, 0.05859375f, 0.03515625f, 0.01171875f, + 0.00390625f, 0.01171875f, 0.01953125f, 0.02734375f, 0.03515625f, 0.04296875f, + 0.05078125f, 0.05859375f, 0.05859375f, 0.05078125f, 0.04296875f, 0.03515625f, + 0.02734375f, 0.01953125f, 0.01171875f, 0.00390625f +}; + namespace cv { namespace ocl @@ -78,38 +172,43 @@ namespace cv int cnblocks_win_x; int cnblocks_win_y; int cblock_hist_size; - int cblock_hist_size_2up; int cdescr_size; int cdescr_width; + int cdescr_height; void set_up_constants(int nbins, int block_stride_x, int block_stride_y, int nblocks_win_x, int nblocks_win_y); void compute_hists(int nbins, int block_stride_x, int blovck_stride_y, - int height, int width, const cv::ocl::oclMat &grad, - const cv::ocl::oclMat &qangle, float sigma, cv::ocl::oclMat &block_hists); + int height, int width, float sigma, const cv::ocl::oclMat &grad, + const cv::ocl::oclMat &qangle, + const cv::ocl::oclMat &gauss_w_lut, cv::ocl::oclMat &block_hists); void normalize_hists(int nbins, int block_stride_x, int block_stride_y, - int height, int width, cv::ocl::oclMat &block_hists, float threshold); + int height, int width, cv::ocl::oclMat &block_hists, + float threshold); void classify_hists(int win_height, int win_width, int block_stride_y, - int block_stride_x, int win_stride_y, int win_stride_x, int height, - int width, const cv::ocl::oclMat &block_hists, const cv::ocl::oclMat &coefs, float free_coef, + int block_stride_x, int win_stride_y, int win_stride_x, + int height, int width, const cv::ocl::oclMat &block_hists, + const cv::ocl::oclMat &coefs, float free_coef, float threshold, cv::ocl::oclMat &labels); - void extract_descrs_by_rows(int win_height, int win_width, int block_stride_y, int block_stride_x, - int win_stride_y, int win_stride_x, int height, int width, const cv::ocl::oclMat &block_hists, + void extract_descrs_by_rows(int win_height, int win_width, int block_stride_y, + int block_stride_x, int win_stride_y, int win_stride_x, + int height, int width, const cv::ocl::oclMat &block_hists, cv::ocl::oclMat &descriptors); - void extract_descrs_by_cols(int win_height, int win_width, int block_stride_y, int block_stride_x, - int win_stride_y, int win_stride_x, int height, int width, const cv::ocl::oclMat &block_hists, + void extract_descrs_by_cols(int win_height, int win_width, int block_stride_y, + int block_stride_x, int win_stride_y, int win_stride_x, + int height, int width, const cv::ocl::oclMat &block_hists, cv::ocl::oclMat &descriptors); void compute_gradients_8UC1(int height, int width, const cv::ocl::oclMat &img, - float angle_scale, cv::ocl::oclMat &grad, cv::ocl::oclMat &qangle, bool correct_gamma); + float angle_scale, cv::ocl::oclMat &grad, + cv::ocl::oclMat &qangle, bool correct_gamma); void compute_gradients_8UC4(int height, int width, const cv::ocl::oclMat &img, - float angle_scale, cv::ocl::oclMat &grad, cv::ocl::oclMat &qangle, bool correct_gamma); - - void resize( const oclMat &src, oclMat &dst, const Size sz); + float angle_scale, cv::ocl::oclMat &grad, + cv::ocl::oclMat &qangle, bool correct_gamma); } } } @@ -117,8 +216,14 @@ namespace cv using namespace ::cv::ocl::device; -cv::ocl::HOGDescriptor::HOGDescriptor(Size win_size_, Size block_size_, Size block_stride_, Size cell_size_, - int nbins_, double win_sigma_, double threshold_L2hys_, bool gamma_correction_, int nlevels_) +static inline int divUp(int total, int grain) +{ + return (total + grain - 1) / grain; +} + +cv::ocl::HOGDescriptor::HOGDescriptor(Size win_size_, Size block_size_, Size block_stride_, + Size cell_size_, int nbins_, double win_sigma_, + double threshold_L2hys_, bool gamma_correction_, int nlevels_) : win_size(win_size_), block_size(block_size_), block_stride(block_stride_), @@ -132,19 +237,27 @@ cv::ocl::HOGDescriptor::HOGDescriptor(Size win_size_, Size block_size_, Size blo CV_Assert((win_size.width - block_size.width ) % block_stride.width == 0 && (win_size.height - block_size.height) % block_stride.height == 0); - CV_Assert(block_size.width % cell_size.width == 0 && block_size.height % cell_size.height == 0); + CV_Assert(block_size.width % cell_size.width == 0 && + block_size.height % cell_size.height == 0); CV_Assert(block_stride == cell_size); CV_Assert(cell_size == Size(8, 8)); - Size cells_per_block = Size(block_size.width / cell_size.width, block_size.height / cell_size.height); + Size cells_per_block(block_size.width / cell_size.width, + block_size.height / cell_size.height); CV_Assert(cells_per_block == Size(2, 2)); cv::Size blocks_per_win = numPartsWithin(win_size, block_size, block_stride); - hog::set_up_constants(nbins, block_stride.width, block_stride.height, blocks_per_win.width, blocks_per_win.height); + hog::set_up_constants(nbins, block_stride.width, block_stride.height, + blocks_per_win.width, blocks_per_win.height); effect_size = Size(0, 0); + + if (queryDeviceInfo()) + hog_device_cpu = true; + else + hog_device_cpu = false; } size_t cv::ocl::HOGDescriptor::getDescriptorSize() const @@ -154,7 +267,8 @@ size_t cv::ocl::HOGDescriptor::getDescriptorSize() const size_t cv::ocl::HOGDescriptor::getBlockHistogramSize() const { - Size cells_per_block = Size(block_size.width / cell_size.width, block_size.height / cell_size.height); + Size cells_per_block = Size(block_size.width / cell_size.width, + block_size.height / cell_size.height); return (size_t)(nbins * cells_per_block.area()); } @@ -167,7 +281,8 @@ bool cv::ocl::HOGDescriptor::checkDetectorSize() const { size_t detector_size = detector.rows * detector.cols; size_t descriptor_size = getDescriptorSize(); - return detector_size == 0 || detector_size == descriptor_size || detector_size == descriptor_size + 1; + return detector_size == 0 || detector_size == descriptor_size || + detector_size == descriptor_size + 1; } void cv::ocl::HOGDescriptor::setSVMDetector(const vector &_detector) @@ -207,10 +322,16 @@ void cv::ocl::HOGDescriptor::init_buffer(const oclMat &img, Size win_stride) const size_t block_hist_size = getBlockHistogramSize(); const Size blocks_per_img = numPartsWithin(img.size(), block_size, block_stride); - block_hists.create(1, static_cast(block_hist_size * blocks_per_img.area()), CV_32F); + block_hists.create(1, + static_cast(block_hist_size * blocks_per_img.area()) + 256, CV_32F); Size wins_per_img = numPartsWithin(img.size(), win_size, win_stride); labels.create(1, wins_per_img.area(), CV_8U); + + vector v_lut = vector(gaussian_interp_lut, gaussian_interp_lut + + sizeof(gaussian_interp_lut) / sizeof(gaussian_interp_lut[0])); + Mat m_lut(v_lut); + gauss_w_lut.upload(m_lut.reshape(1,1)); } void cv::ocl::HOGDescriptor::computeGradient(const oclMat &img, oclMat &grad, oclMat &qangle) @@ -221,29 +342,34 @@ void cv::ocl::HOGDescriptor::computeGradient(const oclMat &img, oclMat &grad, oc switch (img.type()) { case CV_8UC1: - hog::compute_gradients_8UC1(effect_size.height, effect_size.width, img, angleScale, grad, qangle, gamma_correction); + hog::compute_gradients_8UC1(effect_size.height, effect_size.width, img, + angleScale, grad, qangle, gamma_correction); break; case CV_8UC4: - hog::compute_gradients_8UC4(effect_size.height, effect_size.width, img, angleScale, grad, qangle, gamma_correction); + hog::compute_gradients_8UC4(effect_size.height, effect_size.width, img, + angleScale, grad, qangle, gamma_correction); break; } } + void cv::ocl::HOGDescriptor::computeBlockHistograms(const oclMat &img) { - computeGradient(img, grad, qangle); + computeGradient(img, this->grad, this->qangle); - hog::compute_hists(nbins, block_stride.width, block_stride.height, effect_size.height, effect_size.width, - grad, qangle, (float)getWinSigma(), block_hists); + hog::compute_hists(nbins, block_stride.width, block_stride.height, effect_size.height, + effect_size.width, (float)getWinSigma(), grad, qangle, gauss_w_lut, block_hists); - hog::normalize_hists(nbins, block_stride.width, block_stride.height, effect_size.height, effect_size.width, - block_hists, (float)threshold_L2hys); + hog::normalize_hists(nbins, block_stride.width, block_stride.height, effect_size.height, + effect_size.width, block_hists, (float)threshold_L2hys); } -void cv::ocl::HOGDescriptor::getDescriptors(const oclMat &img, Size win_stride, oclMat &descriptors, int descr_format) +void cv::ocl::HOGDescriptor::getDescriptors(const oclMat &img, Size win_stride, + oclMat &descriptors, int descr_format) { - CV_Assert(win_stride.width % block_stride.width == 0 && win_stride.height % block_stride.height == 0); + CV_Assert(win_stride.width % block_stride.width == 0 && + win_stride.height % block_stride.height == 0); init_buffer(img, win_stride); @@ -253,17 +379,20 @@ void cv::ocl::HOGDescriptor::getDescriptors(const oclMat &img, Size win_stride, Size blocks_per_win = numPartsWithin(win_size, block_size, block_stride); Size wins_per_img = numPartsWithin(effect_size, win_size, win_stride); - descriptors.create(wins_per_img.area(), static_cast(blocks_per_win.area() * block_hist_size), CV_32F); + descriptors.create(wins_per_img.area(), + static_cast(blocks_per_win.area() * block_hist_size), CV_32F); switch (descr_format) { case DESCR_FORMAT_ROW_BY_ROW: - hog::extract_descrs_by_rows(win_size.height, win_size.width, block_stride.height, block_stride.width, - win_stride.height, win_stride.width, effect_size.height, effect_size.width, block_hists, descriptors); + hog::extract_descrs_by_rows(win_size.height, win_size.width, + block_stride.height, block_stride.width, win_stride.height, win_stride.width, + effect_size.height, effect_size.width, block_hists, descriptors); break; case DESCR_FORMAT_COL_BY_COL: - hog::extract_descrs_by_cols(win_size.height, win_size.width, block_stride.height, block_stride.width, - win_stride.height, win_stride.width, effect_size.height, effect_size.width, block_hists, descriptors); + hog::extract_descrs_by_cols(win_size.height, win_size.width, + block_stride.height, block_stride.width, win_stride.height, win_stride.width, + effect_size.height, effect_size.width, block_hists, descriptors); break; default: CV_Error(CV_StsBadArg, "Unknown descriptor format"); @@ -271,7 +400,8 @@ void cv::ocl::HOGDescriptor::getDescriptors(const oclMat &img, Size win_stride, } -void cv::ocl::HOGDescriptor::detect(const oclMat &img, vector &hits, double hit_threshold, Size win_stride, Size padding) +void cv::ocl::HOGDescriptor::detect(const oclMat &img, vector &hits, + double hit_threshold, Size win_stride, Size padding) { CV_Assert(img.type() == CV_8UC1 || img.type() == CV_8UC4); CV_Assert(padding == Size(0, 0)); @@ -283,14 +413,16 @@ void cv::ocl::HOGDescriptor::detect(const oclMat &img, vector &hits, doub if (win_stride == Size()) win_stride = block_stride; else - CV_Assert(win_stride.width % block_stride.width == 0 && win_stride.height % block_stride.height == 0); + CV_Assert(win_stride.width % block_stride.width == 0 && + win_stride.height % block_stride.height == 0); init_buffer(img, win_stride); computeBlockHistograms(img); - hog::classify_hists(win_size.height, win_size.width, block_stride.height, block_stride.width, - win_stride.height, win_stride.width, effect_size.height, effect_size.width, block_hists, - detector, (float)free_coef, (float)hit_threshold, labels); + hog::classify_hists(win_size.height, win_size.width, block_stride.height, + block_stride.width, win_stride.height, win_stride.width, + effect_size.height, effect_size.width, block_hists, detector, + (float)free_coef, (float)hit_threshold, labels); labels.download(labels_host); unsigned char *vec = labels_host.ptr(); @@ -306,8 +438,9 @@ void cv::ocl::HOGDescriptor::detect(const oclMat &img, vector &hits, doub -void cv::ocl::HOGDescriptor::detectMultiScale(const oclMat &img, vector &found_locations, double hit_threshold, - Size win_stride, Size padding, double scale0, int group_threshold) +void cv::ocl::HOGDescriptor::detectMultiScale(const oclMat &img, vector &found_locations, + double hit_threshold, Size win_stride, Size padding, + double scale0, int group_threshold) { CV_Assert(img.type() == CV_8UC1 || img.type() == CV_8UC4); CV_Assert(scale0 > 1); @@ -333,7 +466,8 @@ void cv::ocl::HOGDescriptor::detectMultiScale(const oclMat &img, vector &f if (win_stride == Size()) win_stride = block_stride; else - CV_Assert(win_stride.width % block_stride.width == 0 && win_stride.height % block_stride.height == 0); + CV_Assert(win_stride.width % block_stride.width == 0 && + win_stride.height % block_stride.height == 0); init_buffer(img, win_stride); image_scale.create(img.size(), img.type()); @@ -347,16 +481,18 @@ void cv::ocl::HOGDescriptor::detectMultiScale(const oclMat &img, vector &f } else { - hog::resize( img, image_scale, effect_size); + resize(img, image_scale, effect_size); detect(image_scale, locations, hit_threshold, win_stride, padding); } - Size scaled_win_size(cvRound(win_size.width * scale), cvRound(win_size.height * scale)); + Size scaled_win_size(cvRound(win_size.width * scale), + cvRound(win_size.height * scale)); for (size_t j = 0; j < locations.size(); j++) - all_candidates.push_back(Rect(Point2d((CvPoint)locations[j]) * scale, scaled_win_size)); + all_candidates.push_back(Rect(Point2d((CvPoint)locations[j]) * scale, + scaled_win_size)); } found_locations.assign(all_candidates.begin(), all_candidates.end()); - groupRectangles(found_locations, group_threshold, 0.2/*magic number copied from CPU version*/); + groupRectangles(found_locations, group_threshold, 0.2); } int cv::ocl::HOGDescriptor::numPartsWithin(int size, int part_size, int stride) @@ -364,9 +500,11 @@ int cv::ocl::HOGDescriptor::numPartsWithin(int size, int part_size, int stride) return (size - part_size + stride) / stride; } -cv::Size cv::ocl::HOGDescriptor::numPartsWithin(cv::Size size, cv::Size part_size, cv::Size stride) +cv::Size cv::ocl::HOGDescriptor::numPartsWithin(cv::Size size, cv::Size part_size, + cv::Size stride) { - return Size(numPartsWithin(size.width, part_size.width, stride.width), numPartsWithin(size.height, part_size.height, stride.height)); + return Size(numPartsWithin(size.width, part_size.width, stride.width), + numPartsWithin(size.height, part_size.height, stride.height)); } std::vector cv::ocl::HOGDescriptor::getDefaultPeopleDetector() @@ -1547,8 +1685,9 @@ static int power_2up(unsigned int n) return -1; // Input is too big } -void cv::ocl::device::hog::set_up_constants(int nbins, int block_stride_x, int block_stride_y, - int nblocks_win_x, int nblocks_win_y) +void cv::ocl::device::hog::set_up_constants(int nbins, + int block_stride_x, int block_stride_y, + int nblocks_win_x, int nblocks_win_y) { cnbins = nbins; cblock_stride_x = block_stride_x; @@ -1559,53 +1698,32 @@ void cv::ocl::device::hog::set_up_constants(int nbins, int block_stride_x, int b int block_hist_size = nbins * CELLS_PER_BLOCK_X * CELLS_PER_BLOCK_Y; cblock_hist_size = block_hist_size; - int block_hist_size_2up = power_2up(block_hist_size); - cblock_hist_size_2up = block_hist_size_2up; - int descr_width = nblocks_win_x * block_hist_size; cdescr_width = descr_width; + cdescr_height = nblocks_win_y; int descr_size = descr_width * nblocks_win_y; cdescr_size = descr_size; } -static inline int divUp(int total, int grain) -{ - return (total + grain - 1) / grain; -} - -static void openCLExecuteKernel_hog(Context *clCxt , const char **source, string kernelName, - size_t globalThreads[3], size_t localThreads[3], - vector< pair > &args) -{ - cl_kernel kernel = openCLGetKernelFromSource(clCxt, source, kernelName); - size_t wave_size = queryDeviceInfo(kernel); - openCLSafeCall(clReleaseKernel(kernel)); - if (wave_size <= 16) - { - char build_options[64]; - sprintf(build_options, (wave_size == 16) ? "-D WAVE_SIZE_16" : "-D WAVE_SIZE_1"); - openCLExecuteKernel(clCxt, source, kernelName, globalThreads, localThreads, args, -1, -1, build_options); - } - else - openCLExecuteKernel(clCxt, source, kernelName, globalThreads, localThreads, args, -1, -1); -} - -void cv::ocl::device::hog::compute_hists(int nbins, int block_stride_x, int block_stride_y, - int height, int width, const cv::ocl::oclMat &grad, - const cv::ocl::oclMat &qangle, float sigma, cv::ocl::oclMat &block_hists) +void cv::ocl::device::hog::compute_hists(int nbins, + int block_stride_x, int block_stride_y, + int height, int width, float sigma, + const cv::ocl::oclMat &grad, + const cv::ocl::oclMat &qangle, + const cv::ocl::oclMat &gauss_w_lut, + cv::ocl::oclMat &block_hists) { Context *clCxt = Context::getContext(); - string kernelName = "compute_hists_kernel"; vector< pair > args; + string kernelName = (sigma == 4.0f) ? "compute_hists_lut_kernel" : + "compute_hists_kernel"; - int img_block_width = (width - CELLS_PER_BLOCK_X * CELL_WIDTH + block_stride_x) / block_stride_x; - int img_block_height = (height - CELLS_PER_BLOCK_Y * CELL_HEIGHT + block_stride_y) / block_stride_y; - + int img_block_width = (width - CELLS_PER_BLOCK_X * CELL_WIDTH + block_stride_x) + / block_stride_x; + int img_block_height = (height - CELLS_PER_BLOCK_Y * CELL_HEIGHT + block_stride_y) + / block_stride_y; int blocks_total = img_block_width * img_block_height; - int blocks_in_group = 4; - size_t localThreads[3] = { blocks_in_group * 24, 2, 1 }; - size_t globalThreads[3] = { divUp(blocks_total, blocks_in_group) * localThreads[0], 2, 1 }; int grad_quadstep = grad.step >> 2; int qangle_step = qangle.step; @@ -1613,6 +1731,11 @@ void cv::ocl::device::hog::compute_hists(int nbins, int block_stride_x, int bloc // Precompute gaussian spatial window parameter float scale = 1.f / (2.f * sigma * sigma); + int blocks_in_group = 4; + size_t localThreads[3] = { blocks_in_group * 24, 2, 1 }; + size_t globalThreads[3] = { + divUp(img_block_width * img_block_height, blocks_in_group) * localThreads[0], 2, 1 }; + int hists_size = (nbins * CELLS_PER_BLOCK_X * CELLS_PER_BLOCK_Y * 12) * sizeof(float); int final_hists_size = (nbins * CELLS_PER_BLOCK_X * CELLS_PER_BLOCK_Y) * sizeof(float); int smem = (hists_size + final_hists_size) * blocks_in_group; @@ -1628,62 +1751,120 @@ void cv::ocl::device::hog::compute_hists(int nbins, int block_stride_x, int bloc args.push_back( make_pair( sizeof(cl_int), (void *)&qangle_step)); args.push_back( make_pair( sizeof(cl_mem), (void *)&grad.data)); args.push_back( make_pair( sizeof(cl_mem), (void *)&qangle.data)); - args.push_back( make_pair( sizeof(cl_float), (void *)&scale)); + if (kernelName.compare("compute_hists_lut_kernel") == 0) + args.push_back( make_pair( sizeof(cl_mem), (void *)&gauss_w_lut.data)); + else + args.push_back( make_pair( sizeof(cl_float), (void *)&scale)); args.push_back( make_pair( sizeof(cl_mem), (void *)&block_hists.data)); args.push_back( make_pair( smem, (void *)NULL)); - openCLExecuteKernel_hog(clCxt, &objdetect_hog, kernelName, globalThreads, localThreads, args); + openCLExecuteKernel(clCxt, &objdetect_hog, kernelName, globalThreads, + localThreads, args, -1, -1); } -void cv::ocl::device::hog::normalize_hists(int nbins, int block_stride_x, int block_stride_y, - int height, int width, cv::ocl::oclMat &block_hists, float threshold) +void cv::ocl::device::hog::normalize_hists(int nbins, + int block_stride_x, int block_stride_y, + int height, int width, + cv::ocl::oclMat &block_hists, + float threshold) { Context *clCxt = Context::getContext(); - string kernelName = "normalize_hists_kernel"; vector< pair > args; + string kernelName; int block_hist_size = nbins * CELLS_PER_BLOCK_X * CELLS_PER_BLOCK_Y; - int nthreads = power_2up(block_hist_size); + int img_block_width = (width - CELLS_PER_BLOCK_X * CELL_WIDTH + block_stride_x) + / block_stride_x; + int img_block_height = (height - CELLS_PER_BLOCK_Y * CELL_HEIGHT + block_stride_y) + / block_stride_y; + int nthreads; + size_t globalThreads[3] = { 1, 1, 1 }; + size_t localThreads[3] = { 1, 1, 1 }; + + if ( nbins == 9 ) + { + /* optimized for the case of 9 bins */ + kernelName = "normalize_hists_36_kernel"; + int blocks_in_group = NTHREADS / block_hist_size; + nthreads = blocks_in_group * block_hist_size; + int num_groups = divUp( img_block_width * img_block_height, blocks_in_group); + globalThreads[0] = nthreads * num_groups; + localThreads[0] = nthreads; + } + else + { + kernelName = "normalize_hists_kernel"; + nthreads = power_2up(block_hist_size); + globalThreads[0] = img_block_width * nthreads; + globalThreads[1] = img_block_height; + localThreads[0] = nthreads; - int img_block_width = (width - CELLS_PER_BLOCK_X * CELL_WIDTH + block_stride_x) / block_stride_x; - int img_block_height = (height - CELLS_PER_BLOCK_Y * CELL_HEIGHT + block_stride_y) / block_stride_y; - size_t globalThreads[3] = { img_block_width * nthreads, img_block_height, 1 }; - size_t localThreads[3] = { nthreads, 1, 1 }; + if ((nthreads < 32) || (nthreads > 512) ) + cv::ocl::error("normalize_hists: histogram's size is too small or too big", + __FILE__, __LINE__, "normalize_hists"); - if ((nthreads < 32) || (nthreads > 512) ) - cv::ocl::error("normalize_hists: histogram's size is too small or too big", __FILE__, __LINE__, "normalize_hists"); + args.push_back( make_pair( sizeof(cl_int), (void *)&nthreads)); + args.push_back( make_pair( sizeof(cl_int), (void *)&block_hist_size)); + args.push_back( make_pair( sizeof(cl_int), (void *)&img_block_width)); + } - args.push_back( make_pair( sizeof(cl_int), (void *)&nthreads)); - args.push_back( make_pair( sizeof(cl_int), (void *)&block_hist_size)); - args.push_back( make_pair( sizeof(cl_int), (void *)&img_block_width)); args.push_back( make_pair( sizeof(cl_mem), (void *)&block_hists.data)); args.push_back( make_pair( sizeof(cl_float), (void *)&threshold)); args.push_back( make_pair( nthreads * sizeof(float), (void *)NULL)); - openCLExecuteKernel_hog(clCxt, &objdetect_hog, kernelName, globalThreads, localThreads, args); + if(hog_device_cpu) + openCLExecuteKernel(clCxt, &objdetect_hog, kernelName, globalThreads, + localThreads, args, -1, -1, "-D CPU"); + else + openCLExecuteKernel(clCxt, &objdetect_hog, kernelName, globalThreads, + localThreads, args, -1, -1); } -void cv::ocl::device::hog::classify_hists(int win_height, int win_width, int block_stride_y, - int block_stride_x, int win_stride_y, int win_stride_x, int height, - int width, const cv::ocl::oclMat &block_hists, const cv::ocl::oclMat &coefs, float free_coef, - float threshold, cv::ocl::oclMat &labels) +void cv::ocl::device::hog::classify_hists(int win_height, int win_width, + int block_stride_y, int block_stride_x, + int win_stride_y, int win_stride_x, + int height, int width, + const cv::ocl::oclMat &block_hists, + const cv::ocl::oclMat &coefs, + float free_coef, float threshold, + cv::ocl::oclMat &labels) { Context *clCxt = Context::getContext(); - string kernelName = "classify_hists_kernel"; vector< pair > args; + int nthreads; + string kernelName; + switch (cdescr_width) + { + case 180: + nthreads = 180; + kernelName = "classify_hists_180_kernel"; + args.push_back( make_pair( sizeof(cl_int), (void *)&cdescr_width)); + args.push_back( make_pair( sizeof(cl_int), (void *)&cdescr_height)); + break; + case 252: + nthreads = 256; + kernelName = "classify_hists_252_kernel"; + args.push_back( make_pair( sizeof(cl_int), (void *)&cdescr_width)); + args.push_back( make_pair( sizeof(cl_int), (void *)&cdescr_height)); + break; + default: + nthreads = 256; + kernelName = "classify_hists_kernel"; + args.push_back( make_pair( sizeof(cl_int), (void *)&cdescr_size)); + args.push_back( make_pair( sizeof(cl_int), (void *)&cdescr_width)); + } + int win_block_stride_x = win_stride_x / block_stride_x; int win_block_stride_y = win_stride_y / block_stride_y; int img_win_width = (width - win_width + win_stride_x) / win_stride_x; int img_win_height = (height - win_height + win_stride_y) / win_stride_y; - int img_block_width = (width - CELLS_PER_BLOCK_X * CELL_WIDTH + block_stride_x) / block_stride_x; - - size_t globalThreads[3] = { img_win_width * NTHREADS, img_win_height, 1 }; - size_t localThreads[3] = { NTHREADS, 1, 1 }; + int img_block_width = (width - CELLS_PER_BLOCK_X * CELL_WIDTH + block_stride_x) / + block_stride_x; + size_t globalThreads[3] = { img_win_width * nthreads, img_win_height, 1 }; + size_t localThreads[3] = { nthreads, 1, 1 }; args.push_back( make_pair( sizeof(cl_int), (void *)&cblock_hist_size)); - args.push_back( make_pair( sizeof(cl_int), (void *)&cdescr_size)); - args.push_back( make_pair( sizeof(cl_int), (void *)&cdescr_width)); args.push_back( make_pair( sizeof(cl_int), (void *)&img_win_width)); args.push_back( make_pair( sizeof(cl_int), (void *)&img_block_width)); args.push_back( make_pair( sizeof(cl_int), (void *)&win_block_stride_x)); @@ -1694,12 +1875,20 @@ void cv::ocl::device::hog::classify_hists(int win_height, int win_width, int blo args.push_back( make_pair( sizeof(cl_float), (void *)&threshold)); args.push_back( make_pair( sizeof(cl_mem), (void *)&labels.data)); - openCLExecuteKernel_hog(clCxt, &objdetect_hog, kernelName, globalThreads, localThreads, args); + if(hog_device_cpu) + openCLExecuteKernel(clCxt, &objdetect_hog, kernelName, globalThreads, + localThreads, args, -1, -1, "-D CPU"); + else + openCLExecuteKernel(clCxt, &objdetect_hog, kernelName, globalThreads, + localThreads, args, -1, -1); } -void cv::ocl::device::hog::extract_descrs_by_rows(int win_height, int win_width, int block_stride_y, int block_stride_x, - int win_stride_y, int win_stride_x, int height, int width, - const cv::ocl::oclMat &block_hists, cv::ocl::oclMat &descriptors) +void cv::ocl::device::hog::extract_descrs_by_rows(int win_height, int win_width, + int block_stride_y, int block_stride_x, + int win_stride_y, int win_stride_x, + int height, int width, + const cv::ocl::oclMat &block_hists, + cv::ocl::oclMat &descriptors) { Context *clCxt = Context::getContext(); string kernelName = "extract_descrs_by_rows_kernel"; @@ -1709,7 +1898,8 @@ void cv::ocl::device::hog::extract_descrs_by_rows(int win_height, int win_width, int win_block_stride_y = win_stride_y / block_stride_y; int img_win_width = (width - win_width + win_stride_x) / win_stride_x; int img_win_height = (height - win_height + win_stride_y) / win_stride_y; - int img_block_width = (width - CELLS_PER_BLOCK_X * CELL_WIDTH + block_stride_x) / block_stride_x; + int img_block_width = (width - CELLS_PER_BLOCK_X * CELL_WIDTH + block_stride_x) / + block_stride_x; int descriptors_quadstep = descriptors.step >> 2; size_t globalThreads[3] = { img_win_width * NTHREADS, img_win_height, 1 }; @@ -1725,12 +1915,16 @@ void cv::ocl::device::hog::extract_descrs_by_rows(int win_height, int win_width, args.push_back( make_pair( sizeof(cl_mem), (void *)&block_hists.data)); args.push_back( make_pair( sizeof(cl_mem), (void *)&descriptors.data)); - openCLExecuteKernel(clCxt, &objdetect_hog, kernelName, globalThreads, localThreads, args, -1, -1); + openCLExecuteKernel(clCxt, &objdetect_hog, kernelName, globalThreads, + localThreads, args, -1, -1); } -void cv::ocl::device::hog::extract_descrs_by_cols(int win_height, int win_width, int block_stride_y, int block_stride_x, - int win_stride_y, int win_stride_x, int height, int width, - const cv::ocl::oclMat &block_hists, cv::ocl::oclMat &descriptors) +void cv::ocl::device::hog::extract_descrs_by_cols(int win_height, int win_width, + int block_stride_y, int block_stride_x, + int win_stride_y, int win_stride_x, + int height, int width, + const cv::ocl::oclMat &block_hists, + cv::ocl::oclMat &descriptors) { Context *clCxt = Context::getContext(); string kernelName = "extract_descrs_by_cols_kernel"; @@ -1740,7 +1934,8 @@ void cv::ocl::device::hog::extract_descrs_by_cols(int win_height, int win_width, int win_block_stride_y = win_stride_y / block_stride_y; int img_win_width = (width - win_width + win_stride_x) / win_stride_x; int img_win_height = (height - win_height + win_stride_y) / win_stride_y; - int img_block_width = (width - CELLS_PER_BLOCK_X * CELL_WIDTH + block_stride_x) / block_stride_x; + int img_block_width = (width - CELLS_PER_BLOCK_X * CELL_WIDTH + block_stride_x) / + block_stride_x; int descriptors_quadstep = descriptors.step >> 2; size_t globalThreads[3] = { img_win_width * NTHREADS, img_win_height, 1 }; @@ -1757,11 +1952,16 @@ void cv::ocl::device::hog::extract_descrs_by_cols(int win_height, int win_width, args.push_back( make_pair( sizeof(cl_mem), (void *)&block_hists.data)); args.push_back( make_pair( sizeof(cl_mem), (void *)&descriptors.data)); - openCLExecuteKernel(clCxt, &objdetect_hog, kernelName, globalThreads, localThreads, args, -1, -1); + openCLExecuteKernel(clCxt, &objdetect_hog, kernelName, globalThreads, + localThreads, args, -1, -1); } -void cv::ocl::device::hog::compute_gradients_8UC1(int height, int width, const cv::ocl::oclMat &img, - float angle_scale, cv::ocl::oclMat &grad, cv::ocl::oclMat &qangle, bool correct_gamma) +void cv::ocl::device::hog::compute_gradients_8UC1(int height, int width, + const cv::ocl::oclMat &img, + float angle_scale, + cv::ocl::oclMat &grad, + cv::ocl::oclMat &qangle, + bool correct_gamma) { Context *clCxt = Context::getContext(); string kernelName = "compute_gradients_8UC1_kernel"; @@ -1786,11 +1986,16 @@ void cv::ocl::device::hog::compute_gradients_8UC1(int height, int width, const c args.push_back( make_pair( sizeof(cl_char), (void *)&correctGamma)); args.push_back( make_pair( sizeof(cl_int), (void *)&cnbins)); - openCLExecuteKernel(clCxt, &objdetect_hog, kernelName, globalThreads, localThreads, args, -1, -1); + openCLExecuteKernel(clCxt, &objdetect_hog, kernelName, globalThreads, + localThreads, args, -1, -1); } -void cv::ocl::device::hog::compute_gradients_8UC4(int height, int width, const cv::ocl::oclMat &img, - float angle_scale, cv::ocl::oclMat &grad, cv::ocl::oclMat &qangle, bool correct_gamma) +void cv::ocl::device::hog::compute_gradients_8UC4(int height, int width, + const cv::ocl::oclMat &img, + float angle_scale, + cv::ocl::oclMat &grad, + cv::ocl::oclMat &qangle, + bool correct_gamma) { Context *clCxt = Context::getContext(); string kernelName = "compute_gradients_8UC4_kernel"; @@ -1816,39 +2021,6 @@ void cv::ocl::device::hog::compute_gradients_8UC4(int height, int width, const c args.push_back( make_pair( sizeof(cl_char), (void *)&correctGamma)); args.push_back( make_pair( sizeof(cl_int), (void *)&cnbins)); - openCLExecuteKernel(clCxt, &objdetect_hog, kernelName, globalThreads, localThreads, args, -1, -1); -} - -void cv::ocl::device::hog::resize( const oclMat &src, oclMat &dst, const Size sz) -{ - CV_Assert( (src.channels() == dst.channels()) ); - Context *clCxt = Context::getContext(); - - string kernelName = (src.type() == CV_8UC1) ? "resize_8UC1_kernel" : "resize_8UC4_kernel"; - size_t blkSizeX = 16, blkSizeY = 16; - size_t glbSizeX = sz.width % blkSizeX == 0 ? sz.width : (sz.width / blkSizeX + 1) * blkSizeX; - size_t glbSizeY = sz.height % blkSizeY == 0 ? sz.height : (sz.height / blkSizeY + 1) * blkSizeY; - size_t globalThreads[3] = {glbSizeX, glbSizeY, 1}; - size_t localThreads[3] = {blkSizeX, blkSizeY, 1}; - - float ifx = (float)src.cols / sz.width; - float ify = (float)src.rows / sz.height; - int src_step = static_cast(src.step); - int dst_step = static_cast(dst.step); - - vector< pair > args; - args.push_back( make_pair(sizeof(cl_mem), (void *)&dst.data)); - args.push_back( make_pair(sizeof(cl_mem), (void *)&src.data)); - args.push_back( make_pair(sizeof(cl_int), (void *)&dst.offset)); - args.push_back( make_pair(sizeof(cl_int), (void *)&src.offset)); - args.push_back( make_pair(sizeof(cl_int), (void *)&dst_step)); - args.push_back( make_pair(sizeof(cl_int), (void *)&src_step)); - args.push_back( make_pair(sizeof(cl_int), (void *)&src.cols)); - args.push_back( make_pair(sizeof(cl_int), (void *)&src.rows)); - args.push_back( make_pair(sizeof(cl_int), (void *)&sz.width)); - args.push_back( make_pair(sizeof(cl_int), (void *)&sz.height)); - args.push_back( make_pair(sizeof(cl_float), (void *)&ifx)); - args.push_back( make_pair(sizeof(cl_float), (void *)&ify)); - - openCLExecuteKernel(clCxt, &objdetect_hog, kernelName, globalThreads, localThreads, args, -1, -1); -} + openCLExecuteKernel(clCxt, &objdetect_hog, kernelName, globalThreads, + localThreads, args, -1, -1); +} \ No newline at end of file diff --git a/modules/ocl/src/opencl/objdetect_hog.cl b/modules/ocl/src/opencl/objdetect_hog.cl index 8852facae8..05d538330f 100644 --- a/modules/ocl/src/opencl/objdetect_hog.cl +++ b/modules/ocl/src/opencl/objdetect_hog.cl @@ -43,7 +43,6 @@ // //M*/ - #define CELL_WIDTH 8 #define CELL_HEIGHT 8 #define CELLS_PER_BLOCK_X 2 @@ -51,6 +50,100 @@ #define NTHREADS 256 #define CV_PI_F 3.1415926535897932384626433832795f +//---------------------------------------------------------------------------- +// Histogram computation +// 12 threads for a cell, 12x4 threads per block +// Use pre-computed gaussian and interp_weight lookup tables if sigma is 4.0f +__kernel void compute_hists_lut_kernel( + const int cblock_stride_x, const int cblock_stride_y, + const int cnbins, const int cblock_hist_size, const int img_block_width, + const int blocks_in_group, const int blocks_total, + const int grad_quadstep, const int qangle_step, + __global const float* grad, __global const uchar* qangle, + __global const float* gauss_w_lut, + __global float* block_hists, __local float* smem) +{ + const int lx = get_local_id(0); + const int lp = lx / 24; /* local group id */ + const int gid = get_group_id(0) * blocks_in_group + lp;/* global group id */ + const int gidY = gid / img_block_width; + const int gidX = gid - gidY * img_block_width; + + const int lidX = lx - lp * 24; + const int lidY = get_local_id(1); + + const int cell_x = lidX / 12; + const int cell_y = lidY; + const int cell_thread_x = lidX - cell_x * 12; + + __local float* hists = smem + lp * cnbins * (CELLS_PER_BLOCK_X * + CELLS_PER_BLOCK_Y * 12 + CELLS_PER_BLOCK_X * CELLS_PER_BLOCK_Y); + __local float* final_hist = hists + cnbins * + (CELLS_PER_BLOCK_X * CELLS_PER_BLOCK_Y * 12); + + const int offset_x = gidX * cblock_stride_x + (cell_x << 2) + cell_thread_x; + const int offset_y = gidY * cblock_stride_y + (cell_y << 2); + + __global const float* grad_ptr = (gid < blocks_total) ? + grad + offset_y * grad_quadstep + (offset_x << 1) : grad; + __global const uchar* qangle_ptr = (gid < blocks_total) ? + qangle + offset_y * qangle_step + (offset_x << 1) : qangle; + + __local float* hist = hists + 12 * (cell_y * CELLS_PER_BLOCK_Y + cell_x) + + cell_thread_x; + for (int bin_id = 0; bin_id < cnbins; ++bin_id) + hist[bin_id * 48] = 0.f; + + const int dist_x = -4 + cell_thread_x - 4 * cell_x; + const int dist_center_x = dist_x - 4 * (1 - 2 * cell_x); + + const int dist_y_begin = -4 - 4 * lidY; + for (int dist_y = dist_y_begin; dist_y < dist_y_begin + 12; ++dist_y) + { + float2 vote = (float2) (grad_ptr[0], grad_ptr[1]); + uchar2 bin = (uchar2) (qangle_ptr[0], qangle_ptr[1]); + + grad_ptr += grad_quadstep; + qangle_ptr += qangle_step; + + int dist_center_y = dist_y - 4 * (1 - 2 * cell_y); + + int idx = (dist_center_y + 8) * 16 + (dist_center_x + 8); + float gaussian = gauss_w_lut[idx]; + idx = (dist_y + 8) * 16 + (dist_x + 8); + float interp_weight = gauss_w_lut[256+idx]; + + hist[bin.x * 48] += gaussian * interp_weight * vote.x; + hist[bin.y * 48] += gaussian * interp_weight * vote.y; + } + barrier(CLK_LOCAL_MEM_FENCE); + + volatile __local float* hist_ = hist; + for (int bin_id = 0; bin_id < cnbins; ++bin_id, hist_ += 48) + { + if (cell_thread_x < 6) + hist_[0] += hist_[6]; + barrier(CLK_LOCAL_MEM_FENCE); + if (cell_thread_x < 3) + hist_[0] += hist_[3]; +#ifdef CPU + barrier(CLK_LOCAL_MEM_FENCE); +#endif + if (cell_thread_x == 0) + final_hist[(cell_x * 2 + cell_y) * cnbins + bin_id] = + hist_[0] + hist_[1] + hist_[2]; + } + barrier(CLK_LOCAL_MEM_FENCE); + + int tid = (cell_y * CELLS_PER_BLOCK_Y + cell_x) * 12 + cell_thread_x; + if ((tid < cblock_hist_size) && (gid < blocks_total)) + { + __global float* block_hist = block_hists + + (gidY * img_block_width + gidX) * cblock_hist_size; + block_hist[tid] = final_hist[tid]; + } +} + //---------------------------------------------------------------------------- // Histogram computation // 12 threads for a cell, 12x4 threads per block @@ -125,16 +218,14 @@ __kernel void compute_hists_kernel( barrier(CLK_LOCAL_MEM_FENCE); if (cell_thread_x < 3) hist_[0] += hist_[3]; -#ifdef WAVE_SIZE_1 +#ifdef CPU barrier(CLK_LOCAL_MEM_FENCE); #endif if (cell_thread_x == 0) final_hist[(cell_x * 2 + cell_y) * cnbins + bin_id] = hist_[0] + hist_[1] + hist_[2]; } -#ifdef WAVE_SIZE_1 barrier(CLK_LOCAL_MEM_FENCE); -#endif int tid = (cell_y * CELLS_PER_BLOCK_Y + cell_x) * 12 + cell_thread_x; if ((tid < cblock_hist_size) && (gid < blocks_total)) @@ -145,6 +236,57 @@ __kernel void compute_hists_kernel( } } +//------------------------------------------------------------- +// Normalization of histograms via L2Hys_norm +// optimized for the case of 9 bins +__kernel void normalize_hists_36_kernel(__global float* block_hists, + const float threshold, __local float *squares) +{ + const int tid = get_local_id(0); + const int gid = get_global_id(0); + const int bid = tid / 36; /* block-hist id, (0 - 6) */ + const int boffset = bid * 36; /* block-hist offset in the work-group */ + const int hid = tid - boffset; /* histogram bin id, (0 - 35) */ + + float elem = block_hists[gid]; + squares[tid] = elem * elem; + barrier(CLK_LOCAL_MEM_FENCE); + + __local float* smem = squares + boffset; + float sum = smem[hid]; + if (hid < 18) + smem[hid] = sum = sum + smem[hid + 18]; + barrier(CLK_LOCAL_MEM_FENCE); + if (hid < 9) + smem[hid] = sum = sum + smem[hid + 9]; + barrier(CLK_LOCAL_MEM_FENCE); + if (hid < 4) + smem[hid] = sum + smem[hid + 4]; + barrier(CLK_LOCAL_MEM_FENCE); + sum = smem[0] + smem[1] + smem[2] + smem[3] + smem[8]; + + elem = elem / (sqrt(sum) + 3.6f); + elem = min(elem, threshold); + + barrier(CLK_LOCAL_MEM_FENCE); + squares[tid] = elem * elem; + barrier(CLK_LOCAL_MEM_FENCE); + + sum = smem[hid]; + if (hid < 18) + smem[hid] = sum = sum + smem[hid + 18]; + barrier(CLK_LOCAL_MEM_FENCE); + if (hid < 9) + smem[hid] = sum = sum + smem[hid + 9]; + barrier(CLK_LOCAL_MEM_FENCE); + if (hid < 4) + smem[hid] = sum + smem[hid + 4]; + barrier(CLK_LOCAL_MEM_FENCE); + sum = smem[0] + smem[1] + smem[2] + smem[3] + smem[8]; + + block_hists[gid] = elem / (sqrt(sum) + 1e-3f); +} + //------------------------------------------------------------- // Normalization of histograms via L2Hys_norm // @@ -153,76 +295,50 @@ float reduce_smem(volatile __local float* smem, int size) unsigned int tid = get_local_id(0); float sum = smem[tid]; - if (size >= 512) - { - if (tid < 256) smem[tid] = sum = sum + smem[tid + 256]; - barrier(CLK_LOCAL_MEM_FENCE); - } - if (size >= 256) - { - if (tid < 128) smem[tid] = sum = sum + smem[tid + 128]; - barrier(CLK_LOCAL_MEM_FENCE); - } - if (size >= 128) - { - if (tid < 64) smem[tid] = sum = sum + smem[tid + 64]; - barrier(CLK_LOCAL_MEM_FENCE); - } - + if (size >= 512) { if (tid < 256) smem[tid] = sum = sum + smem[tid + 256]; + barrier(CLK_LOCAL_MEM_FENCE); } + if (size >= 256) { if (tid < 128) smem[tid] = sum = sum + smem[tid + 128]; + barrier(CLK_LOCAL_MEM_FENCE); } + if (size >= 128) { if (tid < 64) smem[tid] = sum = sum + smem[tid + 64]; + barrier(CLK_LOCAL_MEM_FENCE); } +#ifdef CPU + if (size >= 64) { if (tid < 32) smem[tid] = sum = sum + smem[tid + 32]; + barrier(CLK_LOCAL_MEM_FENCE); } + if (size >= 32) { if (tid < 16) smem[tid] = sum = sum + smem[tid + 16]; + barrier(CLK_LOCAL_MEM_FENCE); } + if (size >= 16) { if (tid < 8) smem[tid] = sum = sum + smem[tid + 8]; + barrier(CLK_LOCAL_MEM_FENCE); } + if (size >= 8) { if (tid < 4) smem[tid] = sum = sum + smem[tid + 4]; + barrier(CLK_LOCAL_MEM_FENCE); } + if (size >= 4) { if (tid < 2) smem[tid] = sum = sum + smem[tid + 2]; + barrier(CLK_LOCAL_MEM_FENCE); } + if (size >= 2) { if (tid < 1) smem[tid] = sum = sum + smem[tid + 1]; + barrier(CLK_LOCAL_MEM_FENCE); } +#else if (tid < 32) { if (size >= 64) smem[tid] = sum = sum + smem[tid + 32]; -#if defined(WAVE_SIZE_16) || defined(WAVE_SIZE_1) - } - barrier(CLK_LOCAL_MEM_FENCE); - if (tid < 16) - { -#endif if (size >= 32) smem[tid] = sum = sum + smem[tid + 16]; -#ifdef WAVE_SIZE_1 - } - barrier(CLK_LOCAL_MEM_FENCE); - if (tid < 8) - { -#endif if (size >= 16) smem[tid] = sum = sum + smem[tid + 8]; -#ifdef WAVE_SIZE_1 - } - barrier(CLK_LOCAL_MEM_FENCE); - if (tid < 4) - { -#endif if (size >= 8) smem[tid] = sum = sum + smem[tid + 4]; -#ifdef WAVE_SIZE_1 - } - barrier(CLK_LOCAL_MEM_FENCE); - if (tid < 2) - { -#endif if (size >= 4) smem[tid] = sum = sum + smem[tid + 2]; -#ifdef WAVE_SIZE_1 - } - barrier(CLK_LOCAL_MEM_FENCE); - if (tid < 1) - { -#endif if (size >= 2) smem[tid] = sum = sum + smem[tid + 1]; } - - barrier(CLK_LOCAL_MEM_FENCE); - sum = smem[0]; +#endif return sum; } -__kernel void normalize_hists_kernel(const int nthreads, const int block_hist_size, const int img_block_width, - __global float* block_hists, const float threshold, __local float *squares) +__kernel void normalize_hists_kernel( + const int nthreads, const int block_hist_size, const int img_block_width, + __global float* block_hists, const float threshold, __local float *squares) { const int tid = get_local_id(0); const int gidX = get_group_id(0); const int gidY = get_group_id(1); - __global float* hist = block_hists + (gidY * img_block_width + gidX) * block_hist_size + tid; + __global float* hist = block_hists + (gidY * img_block_width + gidX) * + block_hist_size + tid; float elem = 0.f; if (tid < block_hist_size) @@ -249,25 +365,98 @@ __kernel void normalize_hists_kernel(const int nthreads, const int block_hist_si //--------------------------------------------------------------------- // Linear SVM based classification -// -__kernel void classify_hists_kernel(const int cblock_hist_size, const int cdescr_size, const int cdescr_width, - const int img_win_width, const int img_block_width, - const int win_block_stride_x, const int win_block_stride_y, - __global const float * block_hists, __global const float* coefs, - float free_coef, float threshold, __global uchar* labels) +// 48x96 window, 9 bins and default parameters +// 180 threads, each thread corresponds to a bin in a row +__kernel void classify_hists_180_kernel( + const int cdescr_width, const int cdescr_height, const int cblock_hist_size, + const int img_win_width, const int img_block_width, + const int win_block_stride_x, const int win_block_stride_y, + __global const float * block_hists, __global const float* coefs, + float free_coef, float threshold, __global uchar* labels) { const int tid = get_local_id(0); const int gidX = get_group_id(0); const int gidY = get_group_id(1); - __global const float* hist = block_hists + (gidY * win_block_stride_y * img_block_width + gidX * win_block_stride_x) * cblock_hist_size; + __global const float* hist = block_hists + (gidY * win_block_stride_y * + img_block_width + gidX * win_block_stride_x) * cblock_hist_size; float product = 0.f; - for (int i = tid; i < cdescr_size; i += NTHREADS) + + for (int i = 0; i < cdescr_height; i++) { - int offset_y = i / cdescr_width; - int offset_x = i - offset_y * cdescr_width; - product += coefs[i] * hist[offset_y * img_block_width * cblock_hist_size + offset_x]; + product += coefs[i * cdescr_width + tid] * + hist[i * img_block_width * cblock_hist_size + tid]; + } + + __local float products[180]; + + products[tid] = product; + + barrier(CLK_LOCAL_MEM_FENCE); + + if (tid < 90) products[tid] = product = product + products[tid + 90]; + barrier(CLK_LOCAL_MEM_FENCE); + + if (tid < 45) products[tid] = product = product + products[tid + 45]; + barrier(CLK_LOCAL_MEM_FENCE); + + volatile __local float* smem = products; +#ifdef CPU + if (tid < 13) smem[tid] = product = product + smem[tid + 32]; + barrier(CLK_LOCAL_MEM_FENCE); + if (tid < 16) smem[tid] = product = product + smem[tid + 16]; + barrier(CLK_LOCAL_MEM_FENCE); + if(tid<8) smem[tid] = product = product + smem[tid + 8]; + barrier(CLK_LOCAL_MEM_FENCE); + if(tid<4) smem[tid] = product = product + smem[tid + 4]; + barrier(CLK_LOCAL_MEM_FENCE); + if(tid<2) smem[tid] = product = product + smem[tid + 2]; + barrier(CLK_LOCAL_MEM_FENCE); +#else + if (tid < 13) + { + smem[tid] = product = product + smem[tid + 32]; + } + if (tid < 16) + { + smem[tid] = product = product + smem[tid + 16]; + smem[tid] = product = product + smem[tid + 8]; + smem[tid] = product = product + smem[tid + 4]; + smem[tid] = product = product + smem[tid + 2]; + } +#endif + + if (tid == 0){ + product = product + smem[tid + 1]; + labels[gidY * img_win_width + gidX] = (product + free_coef >= threshold); + } +} + +//--------------------------------------------------------------------- +// Linear SVM based classification +// 64x128 window, 9 bins and default parameters +// 256 threads, 252 of them are used +__kernel void classify_hists_252_kernel( + const int cdescr_width, const int cdescr_height, const int cblock_hist_size, + const int img_win_width, const int img_block_width, + const int win_block_stride_x, const int win_block_stride_y, + __global const float * block_hists, __global const float* coefs, + float free_coef, float threshold, __global uchar* labels) +{ + const int tid = get_local_id(0); + const int gidX = get_group_id(0); + const int gidY = get_group_id(1); + + __global const float* hist = block_hists + (gidY * win_block_stride_y * + img_block_width + gidX * win_block_stride_x) * cblock_hist_size; + + float product = 0.f; + if (tid < cdescr_width) + { + for (int i = 0; i < cdescr_height; i++) + product += coefs[i * cdescr_width + tid] * + hist[i * img_block_width * cblock_hist_size + tid]; } __local float products[NTHREADS]; @@ -282,67 +471,120 @@ __kernel void classify_hists_kernel(const int cblock_hist_size, const int cdescr if (tid < 64) products[tid] = product = product + products[tid + 64]; barrier(CLK_LOCAL_MEM_FENCE); - volatile __local float* smem = products; + volatile __local float* smem = products; +#ifdef CPU + if(tid<32) smem[tid] = product = product + smem[tid + 32]; + barrier(CLK_LOCAL_MEM_FENCE); + if(tid<16) smem[tid] = product = product + smem[tid + 16]; + barrier(CLK_LOCAL_MEM_FENCE); + if(tid<8) smem[tid] = product = product + smem[tid + 8]; + barrier(CLK_LOCAL_MEM_FENCE); + if(tid<4) smem[tid] = product = product + smem[tid + 4]; + barrier(CLK_LOCAL_MEM_FENCE); + if(tid<2) smem[tid] = product = product + smem[tid + 2]; + barrier(CLK_LOCAL_MEM_FENCE); +#else if (tid < 32) - { + { smem[tid] = product = product + smem[tid + 32]; -#if defined(WAVE_SIZE_16) || defined(WAVE_SIZE_1) - } - barrier(CLK_LOCAL_MEM_FENCE); - if (tid < 16) - { -#endif smem[tid] = product = product + smem[tid + 16]; -#ifdef WAVE_SIZE_1 - } - barrier(CLK_LOCAL_MEM_FENCE); - if (tid < 8) - { -#endif smem[tid] = product = product + smem[tid + 8]; -#ifdef WAVE_SIZE_1 - } - barrier(CLK_LOCAL_MEM_FENCE); - if (tid < 4) - { -#endif smem[tid] = product = product + smem[tid + 4]; -#ifdef WAVE_SIZE_1 - } - barrier(CLK_LOCAL_MEM_FENCE); - if (tid < 2) - { -#endif smem[tid] = product = product + smem[tid + 2]; -#ifdef WAVE_SIZE_1 } - barrier(CLK_LOCAL_MEM_FENCE); - if (tid < 1) - { #endif - smem[tid] = product = product + smem[tid + 1]; + if (tid == 0){ + product = product + smem[tid + 1]; + labels[gidY * img_win_width + gidX] = (product + free_coef >= threshold); + } +} + +//--------------------------------------------------------------------- +// Linear SVM based classification +// 256 threads +__kernel void classify_hists_kernel( + const int cdescr_size, const int cdescr_width, const int cblock_hist_size, + const int img_win_width, const int img_block_width, + const int win_block_stride_x, const int win_block_stride_y, + __global const float * block_hists, __global const float* coefs, + float free_coef, float threshold, __global uchar* labels) +{ + const int tid = get_local_id(0); + const int gidX = get_group_id(0); + const int gidY = get_group_id(1); + + __global const float* hist = block_hists + (gidY * win_block_stride_y * + img_block_width + gidX * win_block_stride_x) * cblock_hist_size; + + float product = 0.f; + for (int i = tid; i < cdescr_size; i += NTHREADS) + { + int offset_y = i / cdescr_width; + int offset_x = i - offset_y * cdescr_width; + product += coefs[i] * + hist[offset_y * img_block_width * cblock_hist_size + offset_x]; } - if (tid == 0) + __local float products[NTHREADS]; + + products[tid] = product; + + barrier(CLK_LOCAL_MEM_FENCE); + + if (tid < 128) products[tid] = product = product + products[tid + 128]; + barrier(CLK_LOCAL_MEM_FENCE); + + if (tid < 64) products[tid] = product = product + products[tid + 64]; + barrier(CLK_LOCAL_MEM_FENCE); + + volatile __local float* smem = products; +#ifdef CPU + if(tid<32) smem[tid] = product = product + smem[tid + 32]; + barrier(CLK_LOCAL_MEM_FENCE); + if(tid<16) smem[tid] = product = product + smem[tid + 16]; + barrier(CLK_LOCAL_MEM_FENCE); + if(tid<8) smem[tid] = product = product + smem[tid + 8]; + barrier(CLK_LOCAL_MEM_FENCE); + if(tid<4) smem[tid] = product = product + smem[tid + 4]; + barrier(CLK_LOCAL_MEM_FENCE); + if(tid<2) smem[tid] = product = product + smem[tid + 2]; + barrier(CLK_LOCAL_MEM_FENCE); +#else + if (tid < 32) + { + smem[tid] = product = product + smem[tid + 32]; + smem[tid] = product = product + smem[tid + 16]; + smem[tid] = product = product + smem[tid + 8]; + smem[tid] = product = product + smem[tid + 4]; + smem[tid] = product = product + smem[tid + 2]; + } +#endif + if (tid == 0){ + smem[tid] = product = product + smem[tid + 1]; labels[gidY * img_win_width + gidX] = (product + free_coef >= threshold); + } } //---------------------------------------------------------------------------- // Extract descriptors -__kernel void extract_descrs_by_rows_kernel(const int cblock_hist_size, const int descriptors_quadstep, const int cdescr_size, const int cdescr_width, - const int img_block_width, const int win_block_stride_x, const int win_block_stride_y, - __global const float* block_hists, __global float* descriptors) +__kernel void extract_descrs_by_rows_kernel( + const int cblock_hist_size, const int descriptors_quadstep, + const int cdescr_size, const int cdescr_width, const int img_block_width, + const int win_block_stride_x, const int win_block_stride_y, + __global const float* block_hists, __global float* descriptors) { int tid = get_local_id(0); int gidX = get_group_id(0); int gidY = get_group_id(1); // Get left top corner of the window in src - __global const float* hist = block_hists + (gidY * win_block_stride_y * img_block_width + gidX * win_block_stride_x) * cblock_hist_size; + __global const float* hist = block_hists + (gidY * win_block_stride_y * + img_block_width + gidX * win_block_stride_x) * cblock_hist_size; // Get left top corner of the window in dst - __global float* descriptor = descriptors + (gidY * get_num_groups(0) + gidX) * descriptors_quadstep; + __global float* descriptor = descriptors + + (gidY * get_num_groups(0) + gidX) * descriptors_quadstep; // Copy elements from src to dst for (int i = tid; i < cdescr_size; i += NTHREADS) @@ -353,19 +595,23 @@ __kernel void extract_descrs_by_rows_kernel(const int cblock_hist_size, const in } } -__kernel void extract_descrs_by_cols_kernel(const int cblock_hist_size, const int descriptors_quadstep, const int cdescr_size, - const int cnblocks_win_x, const int cnblocks_win_y, const int img_block_width, const int win_block_stride_x, - const int win_block_stride_y, __global const float* block_hists, __global float* descriptors) +__kernel void extract_descrs_by_cols_kernel( + const int cblock_hist_size, const int descriptors_quadstep, const int cdescr_size, + const int cnblocks_win_x, const int cnblocks_win_y, const int img_block_width, + const int win_block_stride_x, const int win_block_stride_y, + __global const float* block_hists, __global float* descriptors) { int tid = get_local_id(0); int gidX = get_group_id(0); int gidY = get_group_id(1); // Get left top corner of the window in src - __global const float* hist = block_hists + (gidY * win_block_stride_y * img_block_width + gidX * win_block_stride_x) * cblock_hist_size; + __global const float* hist = block_hists + (gidY * win_block_stride_y * + img_block_width + gidX * win_block_stride_x) * cblock_hist_size; // Get left top corner of the window in dst - __global float* descriptor = descriptors + (gidY * get_num_groups(0) + gidX) * descriptors_quadstep; + __global float* descriptor = descriptors + + (gidY * get_num_groups(0) + gidX) * descriptors_quadstep; // Copy elements from src to dst for (int i = tid; i < cdescr_size; i += NTHREADS) @@ -376,16 +622,19 @@ __kernel void extract_descrs_by_cols_kernel(const int cblock_hist_size, const in int y = block_idx / cnblocks_win_x; int x = block_idx - y * cnblocks_win_x; - descriptor[(x * cnblocks_win_y + y) * cblock_hist_size + idx_in_block] = hist[(y * img_block_width + x) * cblock_hist_size + idx_in_block]; + descriptor[(x * cnblocks_win_y + y) * cblock_hist_size + idx_in_block] = + hist[(y * img_block_width + x) * cblock_hist_size + idx_in_block]; } } //---------------------------------------------------------------------------- // Gradients computation -__kernel void compute_gradients_8UC4_kernel(const int height, const int width, const int img_step, const int grad_quadstep, const int qangle_step, - const __global uchar4 * img, __global float * grad, __global uchar * qangle, - const float angle_scale, const char correct_gamma, const int cnbins) +__kernel void compute_gradients_8UC4_kernel( + const int height, const int width, + const int img_step, const int grad_quadstep, const int qangle_step, + const __global uchar4 * img, __global float * grad, __global uchar * qangle, + const float angle_scale, const char correct_gamma, const int cnbins) { const int x = get_global_id(0); const int tid = get_local_id(0); @@ -426,8 +675,10 @@ __kernel void compute_gradients_8UC4_kernel(const int height, const int width, c barrier(CLK_LOCAL_MEM_FENCE); if (x < width) { - float3 a = (float3) (sh_row[tid], sh_row[tid + (NTHREADS + 2)], sh_row[tid + 2 * (NTHREADS + 2)]); - float3 b = (float3) (sh_row[tid + 2], sh_row[tid + 2 + (NTHREADS + 2)], sh_row[tid + 2 + 2 * (NTHREADS + 2)]); + float3 a = (float3) (sh_row[tid], sh_row[tid + (NTHREADS + 2)], + sh_row[tid + 2 * (NTHREADS + 2)]); + float3 b = (float3) (sh_row[tid + 2], sh_row[tid + 2 + (NTHREADS + 2)], + sh_row[tid + 2 + 2 * (NTHREADS + 2)]); float3 dx; if (correct_gamma == 1) @@ -482,9 +733,11 @@ __kernel void compute_gradients_8UC4_kernel(const int height, const int width, c } } -__kernel void compute_gradients_8UC1_kernel(const int height, const int width, const int img_step, const int grad_quadstep, const int qangle_step, - __global const uchar * img, __global float * grad, __global uchar * qangle, - const float angle_scale, const char correct_gamma, const int cnbins) +__kernel void compute_gradients_8UC1_kernel( + const int height, const int width, + const int img_step, const int grad_quadstep, const int qangle_step, + __global const uchar * img, __global float * grad, __global uchar * qangle, + const float angle_scale, const char correct_gamma, const int cnbins) { const int x = get_global_id(0); const int tid = get_local_id(0); @@ -539,43 +792,4 @@ __kernel void compute_gradients_8UC1_kernel(const int height, const int width, c grad[ (gidY * grad_quadstep + x) << 1 ] = mag * (1.f - ang); grad[ ((gidY * grad_quadstep + x) << 1) + 1 ] = mag * ang; } -} - -//---------------------------------------------------------------------------- -// Resize - -__kernel void resize_8UC4_kernel(__global uchar4 * dst, __global const uchar4 * src, - int dst_offset, int src_offset, int dst_step, int src_step, - int src_cols, int src_rows, int dst_cols, int dst_rows, float ifx, float ify ) -{ - int dx = get_global_id(0); - int dy = get_global_id(1); - - int sx = (int)floor(dx*ifx+0.5f); - int sy = (int)floor(dy*ify+0.5f); - sx = min(sx, src_cols-1); - sy = min(sy, src_rows-1); - int dpos = (dst_offset>>2) + dy * (dst_step>>2) + dx; - int spos = (src_offset>>2) + sy * (src_step>>2) + sx; - - if(dx Date: Wed, 19 Jun 2013 11:31:42 +0800 Subject: [PATCH 125/178] Fix cmake path finding for amd libs. There is no WIN64 defined in the environment. --- cmake/OpenCVDetectOpenCL.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/OpenCVDetectOpenCL.cmake b/cmake/OpenCVDetectOpenCL.cmake index a1e8bbac70..2c96274a8c 100644 --- a/cmake/OpenCVDetectOpenCL.cmake +++ b/cmake/OpenCVDetectOpenCL.cmake @@ -44,7 +44,7 @@ if(OPENCL_FOUND) set(OPENCL_INCLUDE_DIRS ${OPENCL_INCLUDE_DIR}) set(OPENCL_LIBRARIES ${OPENCL_LIBRARY}) - if(WIN64) + if(WIN32 AND X86_64) set(CLAMD_POSSIBLE_LIB_SUFFIXES lib64/import) elseif(WIN32) set(CLAMD_POSSIBLE_LIB_SUFFIXES lib32/import) From 2c198f6cd6802ebfc8d7216f2b06b7c7fb42f6b9 Mon Sep 17 00:00:00 2001 From: yao Date: Wed, 19 Jun 2013 13:03:35 +0800 Subject: [PATCH 126/178] revise accuracy and perf tests --- modules/ocl/perf/main.cpp | 2 + .../perf_calib3d.cpp} | 85 ++++--- modules/ocl/perf/perf_filters.cpp | 16 +- modules/ocl/perf/perf_hog.cpp | 76 +----- modules/ocl/perf/perf_imgproc.cpp | 46 +++- .../{perf_columnsum.cpp => perf_moments.cpp} | 62 ++--- modules/ocl/perf/precomp.cpp | 14 -- modules/ocl/test/test_haar.cpp | 180 -------------- modules/ocl/test/test_imgproc.cpp | 46 +++- .../test/{test_hog.cpp => test_objdetect.cpp} | 231 ++++++++++-------- .../{test_pyrdown.cpp => test_pyramids.cpp} | 44 +++- modules/ocl/test/test_pyrup.cpp | 91 ------- modules/ocl/test/utility.cpp | 102 ++++---- modules/ocl/test/utility.hpp | 11 +- 14 files changed, 392 insertions(+), 614 deletions(-) rename modules/ocl/{test/test_columnsum.cpp => perf/perf_calib3d.cpp} (65%) rename modules/ocl/perf/{perf_columnsum.cpp => perf_moments.cpp} (68%) delete mode 100644 modules/ocl/test/test_haar.cpp rename modules/ocl/test/{test_hog.cpp => test_objdetect.cpp} (51%) rename modules/ocl/test/{test_pyrdown.cpp => test_pyramids.cpp} (75%) delete mode 100644 modules/ocl/test/test_pyrup.cpp diff --git a/modules/ocl/perf/main.cpp b/modules/ocl/perf/main.cpp index 2da17755eb..dfcac20bc0 100644 --- a/modules/ocl/perf/main.cpp +++ b/modules/ocl/perf/main.cpp @@ -52,6 +52,8 @@ int main(int argc, const char *argv[]) cerr << "no device found\n"; return -1; } + // set this to overwrite binary cache every time the test starts + ocl::setBinaryDiskCache(ocl::CACHE_UPDATE); int devidx = 0; diff --git a/modules/ocl/test/test_columnsum.cpp b/modules/ocl/perf/perf_calib3d.cpp similarity index 65% rename from modules/ocl/test/test_columnsum.cpp rename to modules/ocl/perf/perf_calib3d.cpp index 231f0657b0..f998ddf0f3 100644 --- a/modules/ocl/test/test_columnsum.cpp +++ b/modules/ocl/perf/perf_calib3d.cpp @@ -15,8 +15,8 @@ // Third party copyrights are property of their respective owners. // // @Authors -// Chunpeng Zhang chunpeng@multicorewareinc.com -// +// Fangfang Bai, fangfang@multicorewareinc.com +// Jin Ma, jin@multicorewareinc.com // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: @@ -31,7 +31,7 @@ // * The name of the copyright holders may not be used to endorse or promote products // derived from this software without specific prior written permission. // -// This software is provided by the copyright holders and contributors "as is" and +// This software is provided by the copyright holders and contributors as is and // any express or implied warranties, including, but not limited to, the implied // warranties of merchantability and fitness for a particular purpose are disclaimed. // In no event shall the Intel Corporation or contributors be liable for any direct, @@ -45,50 +45,57 @@ //M*/ #include "precomp.hpp" -#include - -#ifdef HAVE_OPENCL - -PARAM_TEST_CASE(ColumnSum, cv::Size) +///////////// StereoMatchBM //////////////////////// +PERFTEST(StereoMatchBM) { - cv::Size size; - cv::Mat src; + Mat left_image = imread(abspath("aloeL.jpg"), cv::IMREAD_GRAYSCALE); + Mat right_image = imread(abspath("aloeR.jpg"), cv::IMREAD_GRAYSCALE); + Mat disp,dst; + ocl::oclMat d_left, d_right,d_disp; + int n_disp= 128; + int winSize =19; - virtual void SetUp() - { - size = GET_PARAM(0); - } -}; + SUBTEST << left_image.cols << 'x' << left_image.rows << "; aloeL.jpg ;"<< right_image.cols << 'x' << right_image.rows << "; aloeR.jpg "; -TEST_P(ColumnSum, Accuracy) -{ - cv::Mat src = randomMat(size, CV_32FC1); - cv::ocl::oclMat d_dst; - cv::ocl::oclMat d_src(src); + StereoBM bm(0, n_disp, winSize); + bm(left_image, right_image, dst); - cv::ocl::columnSum(d_src, d_dst); + CPU_ON; + bm(left_image, right_image, dst); + CPU_OFF; - cv::Mat dst(d_dst); + d_left.upload(left_image); + d_right.upload(right_image); - for (int j = 0; j < src.cols; ++j) - { - float gold = src.at(0, j); - float res = dst.at(0, j); - ASSERT_NEAR(res, gold, 1e-5); - } + ocl::StereoBM_OCL d_bm(0, n_disp, winSize); - for (int i = 1; i < src.rows; ++i) - { - for (int j = 0; j < src.cols; ++j) - { - float gold = src.at(i, j) += src.at(i - 1, j); - float res = dst.at(i, j); - ASSERT_NEAR(res, gold, 1e-5); - } - } + WARMUP_ON; + d_bm(d_left, d_right, d_disp); + WARMUP_OFF; + + cv::Mat ocl_mat; + d_disp.download(ocl_mat); + ocl_mat.convertTo(ocl_mat, dst.type()); + + GPU_ON; + d_bm(d_left, d_right, d_disp); + GPU_OFF; + + GPU_FULL_ON; + d_left.upload(left_image); + d_right.upload(right_image); + d_bm(d_left, d_right, d_disp); + d_disp.download(disp); + GPU_FULL_OFF; + + TestSystem::instance().setAccurate(-1, 0.); } -INSTANTIATE_TEST_CASE_P(OCL_ImgProc, ColumnSum, DIFFERENT_SIZES); -#endif + + + + + + \ No newline at end of file diff --git a/modules/ocl/perf/perf_filters.cpp b/modules/ocl/perf/perf_filters.cpp index a05301b34c..e988ce09d6 100644 --- a/modules/ocl/perf/perf_filters.cpp +++ b/modules/ocl/perf/perf_filters.cpp @@ -284,6 +284,7 @@ PERFTEST(GaussianBlur) Mat src, dst, ocl_dst; int all_type[] = {CV_8UC1, CV_8UC4, CV_32FC1, CV_32FC4}; std::string type_name[] = {"CV_8UC1", "CV_8UC4", "CV_32FC1", "CV_32FC4"}; + const int ksize = 7; for (int size = Min_Size; size <= Max_Size; size *= Multiple) { @@ -291,29 +292,28 @@ PERFTEST(GaussianBlur) { SUBTEST << size << 'x' << size << "; " << type_name[j] ; - gen(src, size, size, all_type[j], 5, 16); + gen(src, size, size, all_type[j], 0, 256); - GaussianBlur(src, dst, Size(9, 9), 0); + GaussianBlur(src, dst, Size(ksize, ksize), 0); CPU_ON; - GaussianBlur(src, dst, Size(9, 9), 0); + GaussianBlur(src, dst, Size(ksize, ksize), 0); CPU_OFF; ocl::oclMat d_src(src); - ocl::oclMat d_dst(src.size(), src.type()); - ocl::oclMat d_buf; + ocl::oclMat d_dst; WARMUP_ON; - ocl::GaussianBlur(d_src, d_dst, Size(9, 9), 0); + ocl::GaussianBlur(d_src, d_dst, Size(ksize, ksize), 0); WARMUP_OFF; GPU_ON; - ocl::GaussianBlur(d_src, d_dst, Size(9, 9), 0); + ocl::GaussianBlur(d_src, d_dst, Size(ksize, ksize), 0); GPU_OFF; GPU_FULL_ON; d_src.upload(src); - ocl::GaussianBlur(d_src, d_dst, Size(9, 9), 0); + ocl::GaussianBlur(d_src, d_dst, Size(ksize, ksize), 0); d_dst.download(ocl_dst); GPU_FULL_OFF; diff --git a/modules/ocl/perf/perf_hog.cpp b/modules/ocl/perf/perf_hog.cpp index 05093811fe..7daa61396c 100644 --- a/modules/ocl/perf/perf_hog.cpp +++ b/modules/ocl/perf/perf_hog.cpp @@ -46,11 +46,6 @@ #include "precomp.hpp" ///////////// HOG//////////////////////// -bool match_rect(cv::Rect r1, cv::Rect r2, int threshold) -{ - return ((abs(r1.x - r2.x) < threshold) && (abs(r1.y - r2.y) < threshold) && - (abs(r1.width - r2.width) < threshold) && (abs(r1.height - r2.height) < threshold)); -} PERFTEST(HOG) { @@ -61,13 +56,12 @@ PERFTEST(HOG) throw runtime_error("can't open road.png"); } - cv::HOGDescriptor hog; hog.setSVMDetector(hog.getDefaultPeopleDetector()); std::vector found_locations; std::vector d_found_locations; - SUBTEST << 768 << 'x' << 576 << "; road.png"; + SUBTEST << src.cols << 'x' << src.rows << "; road.png"; hog.detectMultiScale(src, found_locations); @@ -84,70 +78,10 @@ PERFTEST(HOG) ocl_hog.detectMultiScale(d_src, d_found_locations); WARMUP_OFF; - // Ground-truth rectangular people window - cv::Rect win1_64x128(231, 190, 72, 144); - cv::Rect win2_64x128(621, 156, 97, 194); - cv::Rect win1_48x96(238, 198, 63, 126); - cv::Rect win2_48x96(619, 161, 92, 185); - cv::Rect win3_48x96(488, 136, 56, 112); - - // Compare whether ground-truth windows are detected and compare the number of windows detected. - std::vector d_comp(4); - std::vector comp(4); - for(int i = 0; i < (int)d_comp.size(); i++) - { - d_comp[i] = 0; - comp[i] = 0; - } - - int threshold = 10; - int val = 32; - d_comp[0] = (int)d_found_locations.size(); - comp[0] = (int)found_locations.size(); - - cv::Size winSize = hog.winSize; - - if (winSize == cv::Size(48, 96)) - { - for(int i = 0; i < (int)d_found_locations.size(); i++) - { - if (match_rect(d_found_locations[i], win1_48x96, threshold)) - d_comp[1] = val; - if (match_rect(d_found_locations[i], win2_48x96, threshold)) - d_comp[2] = val; - if (match_rect(d_found_locations[i], win3_48x96, threshold)) - d_comp[3] = val; - } - for(int i = 0; i < (int)found_locations.size(); i++) - { - if (match_rect(found_locations[i], win1_48x96, threshold)) - comp[1] = val; - if (match_rect(found_locations[i], win2_48x96, threshold)) - comp[2] = val; - if (match_rect(found_locations[i], win3_48x96, threshold)) - comp[3] = val; - } - } - else if (winSize == cv::Size(64, 128)) - { - for(int i = 0; i < (int)d_found_locations.size(); i++) - { - if (match_rect(d_found_locations[i], win1_64x128, threshold)) - d_comp[1] = val; - if (match_rect(d_found_locations[i], win2_64x128, threshold)) - d_comp[2] = val; - } - for(int i = 0; i < (int)found_locations.size(); i++) - { - if (match_rect(found_locations[i], win1_64x128, threshold)) - comp[1] = val; - if (match_rect(found_locations[i], win2_64x128, threshold)) - comp[2] = val; - } - } - - cv::Mat gpu_rst(d_comp), cpu_rst(comp); - TestSystem::instance().ExpectedMatNear(gpu_rst, cpu_rst, 3); + if(d_found_locations.size() == found_locations.size()) + TestSystem::instance().setAccurate(1, 0); + else + TestSystem::instance().setAccurate(0, abs((int)found_locations.size() - (int)d_found_locations.size())); GPU_ON; ocl_hog.detectMultiScale(d_src, found_locations); diff --git a/modules/ocl/perf/perf_imgproc.cpp b/modules/ocl/perf/perf_imgproc.cpp index e87e8213de..b330c5ffae 100644 --- a/modules/ocl/perf/perf_imgproc.cpp +++ b/modules/ocl/perf/perf_imgproc.cpp @@ -743,12 +743,12 @@ PERFTEST(meanShiftFiltering) WARMUP_OFF; GPU_ON; - ocl::meanShiftFiltering(d_src, d_dst, sp, sr); + ocl::meanShiftFiltering(d_src, d_dst, sp, sr, crit); GPU_OFF; GPU_FULL_ON; d_src.upload(src); - ocl::meanShiftFiltering(d_src, d_dst, sp, sr); + ocl::meanShiftFiltering(d_src, d_dst, sp, sr, crit); d_dst.download(ocl_dst); GPU_FULL_OFF; @@ -969,3 +969,45 @@ PERFTEST(CLAHE) } } } + +///////////// columnSum//////////////////////// +PERFTEST(columnSum) +{ + Mat src, dst, ocl_dst; + ocl::oclMat d_src, d_dst; + + for (int size = Min_Size; size <= Max_Size; size *= Multiple) + { + SUBTEST << size << 'x' << size << "; CV_32FC1"; + + gen(src, size, size, CV_32FC1, 0, 256); + + CPU_ON; + dst.create(src.size(), src.type()); + for (int j = 0; j < src.cols; j++) + dst.at(0, j) = src.at(0, j); + + for (int i = 1; i < src.rows; ++i) + for (int j = 0; j < src.cols; ++j) + dst.at(i, j) = dst.at(i - 1 , j) + src.at(i , j); + CPU_OFF; + + d_src.upload(src); + + WARMUP_ON; + ocl::columnSum(d_src, d_dst); + WARMUP_OFF; + + GPU_ON; + ocl::columnSum(d_src, d_dst); + GPU_OFF; + + GPU_FULL_ON; + d_src.upload(src); + ocl::columnSum(d_src, d_dst); + d_dst.download(ocl_dst); + GPU_FULL_OFF; + + TestSystem::instance().ExpectedMatNear(dst, ocl_dst, 5e-1); + } +} diff --git a/modules/ocl/perf/perf_columnsum.cpp b/modules/ocl/perf/perf_moments.cpp similarity index 68% rename from modules/ocl/perf/perf_columnsum.cpp rename to modules/ocl/perf/perf_moments.cpp index ff7ebcd1de..7fa3948dec 100644 --- a/modules/ocl/perf/perf_columnsum.cpp +++ b/modules/ocl/perf/perf_moments.cpp @@ -44,45 +44,49 @@ // //M*/ #include "precomp.hpp" - -///////////// columnSum//////////////////////// -PERFTEST(columnSum) +///////////// Moments //////////////////////// +PERFTEST(Moments) { - Mat src, dst, ocl_dst; - ocl::oclMat d_src, d_dst; + Mat src; + bool binaryImage = 0; + + int all_type[] = {CV_8UC1, CV_16SC1, CV_32FC1, CV_64FC1}; + std::string type_name[] = {"CV_8UC1", "CV_16SC1", "CV_32FC1", "CV_64FC1"}; for (int size = Min_Size; size <= Max_Size; size *= Multiple) { - SUBTEST << size << 'x' << size << "; CV_32FC1"; + for (size_t j = 0; j < sizeof(all_type) / sizeof(int); j++) + { + SUBTEST << size << 'x' << size << "; " << type_name[j]; - gen(src, size, size, CV_32FC1, 0, 256); + gen(src, size, size, all_type[j], 0, 256); - CPU_ON; - dst.create(src.size(), src.type()); - for (int j = 0; j < src.cols; j++) - dst.at(0, j) = src.at(0, j); + cv::Moments CvMom = moments(src, binaryImage); - for (int i = 1; i < src.rows; ++i) - for (int j = 0; j < src.cols; ++j) - dst.at(i, j) = dst.at(i - 1 , j) + src.at(i , j); - CPU_OFF; + CPU_ON; + moments(src, binaryImage); + CPU_OFF; - d_src.upload(src); + cv::Moments oclMom; + WARMUP_ON; + oclMom = ocl::ocl_moments(src, binaryImage); + WARMUP_OFF; - WARMUP_ON; - ocl::columnSum(d_src, d_dst); - WARMUP_OFF; + Mat gpu_dst, cpu_dst; + HuMoments(CvMom, cpu_dst); + HuMoments(oclMom, gpu_dst); - GPU_ON; - ocl::columnSum(d_src, d_dst); - GPU_OFF; + GPU_ON; + ocl::ocl_moments(src, binaryImage); + GPU_OFF; - GPU_FULL_ON; - d_src.upload(src); - ocl::columnSum(d_src, d_dst); - d_dst.download(ocl_dst); - GPU_FULL_OFF; + GPU_FULL_ON; + ocl::ocl_moments(src, binaryImage); + GPU_FULL_OFF; + + TestSystem::instance().ExpectedMatNear(gpu_dst, cpu_dst, .5); + + } - TestSystem::instance().ExpectedMatNear(dst, ocl_dst, 5e-1); } -} \ No newline at end of file +} diff --git a/modules/ocl/perf/precomp.cpp b/modules/ocl/perf/precomp.cpp index 71a13a1ee2..9fc634290e 100644 --- a/modules/ocl/perf/precomp.cpp +++ b/modules/ocl/perf/precomp.cpp @@ -331,20 +331,6 @@ void TestSystem::printMetrics(int is_accurate, double cpu_time, double gpu_time, cout << setiosflags(ios_base::left); stringstream stream; -#if 0 - if(is_accurate == 1) - stream << "Pass"; - else if(is_accurate_ == 0) - stream << "Fail"; - else if(is_accurate == -1) - stream << " "; - else - { - std::cout<<"is_accurate errer: "< faces, oclfaces; - - Mat gray, smallImg(cvRound (img.rows / scale), cvRound(img.cols / scale), CV_8UC1 ); - MemStorage storage(cvCreateMemStorage(0)); - cvtColor( img, gray, CV_BGR2GRAY ); - resize( gray, smallImg, smallImg.size(), 0, 0, INTER_LINEAR ); - equalizeHist( smallImg, smallImg ); - - cv::ocl::oclMat image; - CvSeq *_objects; - image.upload(smallImg); - _objects = cascade.oclHaarDetectObjects( image, storage, 1.1, - 3, flags, Size(30, 30), Size(0, 0) ); - vector vecAvgComp; - Seq(_objects).copyTo(vecAvgComp); - oclfaces.resize(vecAvgComp.size()); - std::transform(vecAvgComp.begin(), vecAvgComp.end(), oclfaces.begin(), getRect()); - - cpucascade.detectMultiScale( smallImg, faces, 1.1, 3, - flags, - Size(30, 30), Size(0, 0) ); - EXPECT_EQ(faces.size(), oclfaces.size()); -} - -TEST_P(Haar, FaceDetectUseBuf) -{ - string imgName = workdir + "lena.jpg"; - Mat img = imread( imgName, 1 ); - - if(img.empty()) - { - std::cout << "Couldn't read " << imgName << std::endl; - return ; - } - - vector faces, oclfaces; - - Mat gray, smallImg(cvRound (img.rows / scale), cvRound(img.cols / scale), CV_8UC1 ); - cvtColor( img, gray, CV_BGR2GRAY ); - resize( gray, smallImg, smallImg.size(), 0, 0, INTER_LINEAR ); - equalizeHist( smallImg, smallImg ); - - cv::ocl::oclMat image; - image.upload(smallImg); - - cv::ocl::OclCascadeClassifierBuf cascadebuf; - if( !cascadebuf.load( cascadeName ) ) - { - cout << "ERROR: Could not load classifier cascade for FaceDetectUseBuf!" << endl; - return; - } - cascadebuf.detectMultiScale( image, oclfaces, 1.1, 3, - flags, - Size(30, 30), Size(0, 0) ); - - cpucascade.detectMultiScale( smallImg, faces, 1.1, 3, - flags, - Size(30, 30), Size(0, 0) ); - EXPECT_EQ(faces.size(), oclfaces.size()); - - // intentionally run ocl facedetect again and check if it still works after the first run - cascadebuf.detectMultiScale( image, oclfaces, 1.1, 3, - flags, - Size(30, 30)); - cascadebuf.release(); - EXPECT_EQ(faces.size(), oclfaces.size()); -} - -INSTANTIATE_TEST_CASE_P(FaceDetect, Haar, - Combine(Values(1.0), - Values(CV_HAAR_SCALE_IMAGE, 0), Values(cascade_frontalface_alt, cascade_frontalface_alt2))); - -#endif // HAVE_OPENCL diff --git a/modules/ocl/test/test_imgproc.cpp b/modules/ocl/test/test_imgproc.cpp index b9f4740b17..3a98671d51 100644 --- a/modules/ocl/test/test_imgproc.cpp +++ b/modules/ocl/test/test_imgproc.cpp @@ -1573,6 +1573,47 @@ TEST_P(Convolve, Mat) } } +//////////////////////////////// ColumnSum ////////////////////////////////////// +PARAM_TEST_CASE(ColumnSum, cv::Size) +{ + cv::Size size; + cv::Mat src; + + virtual void SetUp() + { + size = GET_PARAM(0); + } +}; + +TEST_P(ColumnSum, Accuracy) +{ + cv::Mat src = randomMat(size, CV_32FC1); + cv::ocl::oclMat d_dst; + cv::ocl::oclMat d_src(src); + + cv::ocl::columnSum(d_src, d_dst); + + cv::Mat dst(d_dst); + + for (int j = 0; j < src.cols; ++j) + { + float gold = src.at(0, j); + float res = dst.at(0, j); + ASSERT_NEAR(res, gold, 1e-5); + } + + for (int i = 1; i < src.rows; ++i) + { + for (int j = 0; j < src.cols; ++j) + { + float gold = src.at(i, j) += src.at(i - 1, j); + float res = dst.at(i, j); + ASSERT_NEAR(res, gold, 1e-5); + } + } +} +///////////////////////////////////////////////////////////////////////////////////// + INSTANTIATE_TEST_CASE_P(ImgprocTestBase, equalizeHist, Combine( ONE_TYPE(CV_8UC1), NULL_TYPE, @@ -1688,7 +1729,6 @@ INSTANTIATE_TEST_CASE_P(ImgProc, CLAHE, Combine( Values(cv::Size(128, 128), cv::Size(113, 113), cv::Size(1300, 1300)), Values(0.0, 40.0))); -//INSTANTIATE_TEST_CASE_P(ConvolveTestBase, Convolve, Combine( -// Values(CV_32FC1, CV_32FC1), -// Values(false))); // Values(false) is the reserved parameter +INSTANTIATE_TEST_CASE_P(OCL_ImgProc, ColumnSum, DIFFERENT_SIZES); + #endif // HAVE_OPENCL diff --git a/modules/ocl/test/test_hog.cpp b/modules/ocl/test/test_objdetect.cpp similarity index 51% rename from modules/ocl/test/test_hog.cpp rename to modules/ocl/test/test_objdetect.cpp index cfc4e3963f..86590f7981 100644 --- a/modules/ocl/test/test_hog.cpp +++ b/modules/ocl/test/test_objdetect.cpp @@ -15,7 +15,7 @@ // Third party copyrights are property of their respective owners. // // @Authors -// Wenju He, wenju@multicorewareinc.com +// Yao Wang, bitwangyaoyao@gmail.com // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: @@ -45,51 +45,58 @@ #include "precomp.hpp" #include "opencv2/core/core.hpp" -using namespace std; +#include "opencv2/objdetect/objdetect.hpp" + +using namespace cv; +using namespace testing; #ifdef HAVE_OPENCL extern string workdir; -PARAM_TEST_CASE(HOG, cv::Size, int) + +///////////////////// HOG ///////////////////////////// +PARAM_TEST_CASE(HOG, Size, int) { - cv::Size winSize; + Size winSize; int type; + Mat img_rgb; virtual void SetUp() { winSize = GET_PARAM(0); type = GET_PARAM(1); + img_rgb = readImage(workdir + "../gpu/road.png"); + if(img_rgb.empty()) + { + std::cout << "Couldn't read road.png" << std::endl; + } } }; TEST_P(HOG, GetDescriptors) { - // Load image - cv::Mat img_rgb = readImage(workdir + "lena.jpg"); - ASSERT_FALSE(img_rgb.empty()); - // Convert image - cv::Mat img; + Mat img; switch (type) { case CV_8UC1: - cv::cvtColor(img_rgb, img, CV_BGR2GRAY); + cvtColor(img_rgb, img, CV_BGR2GRAY); break; case CV_8UC4: default: - cv::cvtColor(img_rgb, img, CV_BGR2BGRA); + cvtColor(img_rgb, img, CV_BGR2BGRA); break; } - cv::ocl::oclMat d_img(img); + ocl::oclMat d_img(img); // HOGs - cv::ocl::HOGDescriptor ocl_hog; + ocl::HOGDescriptor ocl_hog; ocl_hog.gamma_correction = true; - cv::HOGDescriptor hog; + HOGDescriptor hog; hog.gammaCorrection = true; // Compute descriptor - cv::ocl::oclMat d_descriptors; + ocl::oclMat d_descriptors; ocl_hog.getDescriptors(d_img, ocl_hog.win_size, d_descriptors, ocl_hog.DESCR_FORMAT_COL_BY_COL); - cv::Mat down_descriptors; + Mat down_descriptors; d_descriptors.download(down_descriptors); down_descriptors = down_descriptors.reshape(0, down_descriptors.cols * down_descriptors.rows); @@ -105,45 +112,34 @@ TEST_P(HOG, GetDescriptors) hog.compute(img_rgb, descriptors, ocl_hog.win_size); break; } - cv::Mat cpu_descriptors(descriptors); + Mat cpu_descriptors(descriptors); EXPECT_MAT_SIMILAR(down_descriptors, cpu_descriptors, 1e-2); } - -bool match_rect(cv::Rect r1, cv::Rect r2, int threshold) -{ - return ((abs(r1.x - r2.x) < threshold) && (abs(r1.y - r2.y) < threshold) && - (abs(r1.width - r2.width) < threshold) && (abs(r1.height - r2.height) < threshold)); -} - TEST_P(HOG, Detect) { - // Load image - cv::Mat img_rgb = readImage(workdir + "lena.jpg"); - ASSERT_FALSE(img_rgb.empty()); - // Convert image - cv::Mat img; + Mat img; switch (type) { case CV_8UC1: - cv::cvtColor(img_rgb, img, CV_BGR2GRAY); + cvtColor(img_rgb, img, CV_BGR2GRAY); break; case CV_8UC4: default: - cv::cvtColor(img_rgb, img, CV_BGR2BGRA); + cvtColor(img_rgb, img, CV_BGR2BGRA); break; } - cv::ocl::oclMat d_img(img); + ocl::oclMat d_img(img); // HOGs - if ((winSize != cv::Size(48, 96)) && (winSize != cv::Size(64, 128))) - winSize = cv::Size(64, 128); - cv::ocl::HOGDescriptor ocl_hog(winSize); + if ((winSize != Size(48, 96)) && (winSize != Size(64, 128))) + winSize = Size(64, 128); + ocl::HOGDescriptor ocl_hog(winSize); ocl_hog.gamma_correction = true; - cv::HOGDescriptor hog; + HOGDescriptor hog; hog.winSize = winSize; hog.gammaCorrection = true; @@ -165,88 +161,117 @@ TEST_P(HOG, Detect) } // OpenCL detection - std::vector d_found; - ocl_hog.detectMultiScale(d_img, d_found, 0, cv::Size(8, 8), cv::Size(0, 0), 1.05, 2); + std::vector d_found; + ocl_hog.detectMultiScale(d_img, d_found, 0, Size(8, 8), Size(0, 0), 1.05, 6); // CPU detection - std::vector found; + std::vector found; switch (type) { case CV_8UC1: - hog.detectMultiScale(img, found, 0, cv::Size(8, 8), cv::Size(0, 0), 1.05, 2); + hog.detectMultiScale(img, found, 0, Size(8, 8), Size(0, 0), 1.05, 6); break; case CV_8UC4: default: - hog.detectMultiScale(img_rgb, found, 0, cv::Size(8, 8), cv::Size(0, 0), 1.05, 2); + hog.detectMultiScale(img_rgb, found, 0, Size(8, 8), Size(0, 0), 1.05, 6); break; } - // Ground-truth rectangular people window - cv::Rect win1_64x128(231, 190, 72, 144); - cv::Rect win2_64x128(621, 156, 97, 194); - cv::Rect win1_48x96(238, 198, 63, 126); - cv::Rect win2_48x96(619, 161, 92, 185); - cv::Rect win3_48x96(488, 136, 56, 112); - - // Compare whether ground-truth windows are detected and compare the number of windows detected. - std::vector d_comp(4); - std::vector comp(4); - for(int i = 0; i < (int)d_comp.size(); i++) - { - d_comp[i] = 0; - comp[i] = 0; - } - - int threshold = 10; - int val = 32; - d_comp[0] = (int)d_found.size(); - comp[0] = (int)found.size(); - if (winSize == cv::Size(48, 96)) - { - for(int i = 0; i < (int)d_found.size(); i++) - { - if (match_rect(d_found[i], win1_48x96, threshold)) - d_comp[1] = val; - if (match_rect(d_found[i], win2_48x96, threshold)) - d_comp[2] = val; - if (match_rect(d_found[i], win3_48x96, threshold)) - d_comp[3] = val; - } - for(int i = 0; i < (int)found.size(); i++) - { - if (match_rect(found[i], win1_48x96, threshold)) - comp[1] = val; - if (match_rect(found[i], win2_48x96, threshold)) - comp[2] = val; - if (match_rect(found[i], win3_48x96, threshold)) - comp[3] = val; - } - } - else if (winSize == cv::Size(64, 128)) - { - for(int i = 0; i < (int)d_found.size(); i++) - { - if (match_rect(d_found[i], win1_64x128, threshold)) - d_comp[1] = val; - if (match_rect(d_found[i], win2_64x128, threshold)) - d_comp[2] = val; - } - for(int i = 0; i < (int)found.size(); i++) - { - if (match_rect(found[i], win1_64x128, threshold)) - comp[1] = val; - if (match_rect(found[i], win2_64x128, threshold)) - comp[2] = val; - } - } - - EXPECT_MAT_NEAR(cv::Mat(d_comp), cv::Mat(comp), 3); + EXPECT_LT(checkRectSimilarity(img.size(), found, d_found), 1.0); } INSTANTIATE_TEST_CASE_P(OCL_ObjDetect, HOG, testing::Combine( - testing::Values(cv::Size(64, 128), cv::Size(48, 96)), + testing::Values(Size(64, 128), Size(48, 96)), testing::Values(MatType(CV_8UC1), MatType(CV_8UC4)))); +///////////////////////////// Haar ////////////////////////////// +IMPLEMENT_PARAM_CLASS(CascadeName, std::string); +CascadeName cascade_frontalface_alt(std::string("haarcascade_frontalface_alt.xml")); +CascadeName cascade_frontalface_alt2(std::string("haarcascade_frontalface_alt2.xml")); +struct getRect +{ + Rect operator ()(const CvAvgComp &e) const + { + return e.rect; + } +}; -#endif //HAVE_OPENCL +PARAM_TEST_CASE(Haar, int, CascadeName) +{ + ocl::OclCascadeClassifier cascade, nestedCascade; + CascadeClassifier cpucascade, cpunestedCascade; + + int flags; + std::string cascadeName; + vector faces, oclfaces; + Mat img; + ocl::oclMat d_img; + + virtual void SetUp() + { + flags = GET_PARAM(0); + cascadeName = (workdir + "../../data/haarcascades/").append(GET_PARAM(1)); + if( (!cascade.load( cascadeName )) || (!cpucascade.load(cascadeName)) ) + { + std::cout << "ERROR: Could not load classifier cascade" << std::endl; + return; + } + img = readImage(workdir + "lena.jpg", IMREAD_GRAYSCALE); + if(img.empty()) + { + std::cout << "Couldn't read lena.jpg" << std::endl; + return ; + } + equalizeHist(img, img); + d_img.upload(img); + } +}; + +TEST_P(Haar, FaceDetect) +{ + MemStorage storage(cvCreateMemStorage(0)); + CvSeq *_objects; + _objects = cascade.oclHaarDetectObjects(d_img, storage, 1.1, 3, + flags, Size(30, 30), Size(0, 0)); + vector vecAvgComp; + Seq(_objects).copyTo(vecAvgComp); + oclfaces.resize(vecAvgComp.size()); + std::transform(vecAvgComp.begin(), vecAvgComp.end(), oclfaces.begin(), getRect()); + + cpucascade.detectMultiScale(img, faces, 1.1, 3, + flags, + Size(30, 30), Size(0, 0)); + + EXPECT_LT(checkRectSimilarity(img.size(), faces, oclfaces), 1.0); +} + +TEST_P(Haar, FaceDetectUseBuf) +{ + ocl::OclCascadeClassifierBuf cascadebuf; + if(!cascadebuf.load(cascadeName)) + { + std::cout << "ERROR: Could not load classifier cascade for FaceDetectUseBuf!" << std::endl; + return; + } + cascadebuf.detectMultiScale(d_img, oclfaces, 1.1, 3, + flags, + Size(30, 30), Size(0, 0)); + cpucascade.detectMultiScale(img, faces, 1.1, 3, + flags, + Size(30, 30), Size(0, 0)); + + // intentionally run ocl facedetect again and check if it still works after the first run + cascadebuf.detectMultiScale(d_img, oclfaces, 1.1, 3, + flags, + Size(30, 30)); + cascadebuf.release(); + + EXPECT_LT(checkRectSimilarity(img.size(), faces, oclfaces), 1.0); +} + +INSTANTIATE_TEST_CASE_P(OCL_ObjDetect, Haar, + Combine(Values(CV_HAAR_SCALE_IMAGE, 0), + Values(cascade_frontalface_alt/*, cascade_frontalface_alt2*/))); + +#endif //HAVE_OPENCL \ No newline at end of file diff --git a/modules/ocl/test/test_pyrdown.cpp b/modules/ocl/test/test_pyramids.cpp similarity index 75% rename from modules/ocl/test/test_pyrdown.cpp rename to modules/ocl/test/test_pyramids.cpp index 6d00fb5e45..1bd188dea6 100644 --- a/modules/ocl/test/test_pyrdown.cpp +++ b/modules/ocl/test/test_pyramids.cpp @@ -15,7 +15,6 @@ // Third party copyrights are property of their respective owners. // // @Authors -// Dachuan Zhao, dachuan@multicorewareinc.com // Yao Wang yao@multicorewareinc.com // // Redistribution and use in source and binary forms, with or without modification, @@ -56,11 +55,12 @@ using namespace cvtest; using namespace testing; using namespace std; -PARAM_TEST_CASE(PyrDown, MatType, int) +PARAM_TEST_CASE(PyrBase, MatType, int) { int type; int channels; - + Mat dst_cpu; + oclMat gdst; virtual void SetUp() { type = GET_PARAM(0); @@ -69,19 +69,19 @@ PARAM_TEST_CASE(PyrDown, MatType, int) }; +/////////////////////// PyrDown ////////////////////////// +struct PyrDown : PyrBase {}; TEST_P(PyrDown, Mat) { for(int j = 0; j < LOOP_TIMES; j++) { - cv::Size size(MWIDTH, MHEIGHT); - cv::RNG &rng = TS::ptr()->get_rng(); - cv::Mat src = randomMat(rng, size, CV_MAKETYPE(type, channels), 0, 100, false); - - cv::ocl::oclMat gsrc(src), gdst; - cv::Mat dst_cpu; - cv::pyrDown(src, dst_cpu); - cv::ocl::pyrDown(gsrc, gdst); + Size size(MWIDTH, MHEIGHT); + Mat src = randomMat(size, CV_MAKETYPE(type, channels)); + oclMat gsrc(src); + + pyrDown(src, dst_cpu); + pyrDown(gsrc, gdst); EXPECT_MAT_NEAR(dst_cpu, Mat(gdst), type == CV_32F ? 1e-4f : 1.0f); } @@ -90,5 +90,27 @@ TEST_P(PyrDown, Mat) INSTANTIATE_TEST_CASE_P(OCL_ImgProc, PyrDown, Combine( Values(CV_8U, CV_32F), Values(1, 3, 4))); +/////////////////////// PyrUp ////////////////////////// +struct PyrUp : PyrBase {}; + +TEST_P(PyrUp, Accuracy) +{ + for(int j = 0; j < LOOP_TIMES; j++) + { + Size size(MWIDTH, MHEIGHT); + Mat src = randomMat(size, CV_MAKETYPE(type, channels)); + oclMat gsrc(src); + + pyrUp(src, dst_cpu); + pyrUp(gsrc, gdst); + + EXPECT_MAT_NEAR(dst_cpu, Mat(gdst), (type == CV_32F ? 1e-4f : 1.0)); + } + +} + + +INSTANTIATE_TEST_CASE_P(OCL_ImgProc, PyrUp, testing::Combine( + Values(CV_8U, CV_32F), Values(1, 3, 4))); #endif // HAVE_OPENCL diff --git a/modules/ocl/test/test_pyrup.cpp b/modules/ocl/test/test_pyrup.cpp deleted file mode 100644 index afd3e8b1b8..0000000000 --- a/modules/ocl/test/test_pyrup.cpp +++ /dev/null @@ -1,91 +0,0 @@ -/*M/////////////////////////////////////////////////////////////////////////////////////// -// -// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. -// -// By downloading, copying, installing or using the software you agree to this license. -// If you do not agree to this license, do not download, install, -// copy or use the software. -// -// -// License Agreement -// For Open Source Computer Vision Library -// -// Copyright (C) 2010-2012, Multicoreware, Inc., all rights reserved. -// Copyright (C) 2010-2012, Advanced Micro Devices, Inc., all rights reserved. -// Third party copyrights are property of their respective owners. -// -// @Authors -// Zhang Chunpeng chunpeng@multicorewareinc.com -// Yao Wang yao@multicorewareinc.com -// -// Redistribution and use in source and binary forms, with or without modification, -// are permitted provided that the following conditions are met: -// -// * Redistribution's of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// * Redistribution's in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other oclMaterials provided with the distribution. -// -// * The name of the copyright holders may not be used to endorse or promote products -// derived from this software without specific prior written permission. -// -// This software is provided by the copyright holders and contributors "as is" and -// any express or implied warranties, including, but not limited to, the implied -// warranties of merchantability and fitness for a particular purpose are disclaimed. -// In no event shall the Intel Corporation or contributors be liable for any direct, -// indirect, incidental, special, exemplary, or consequential damages -// (including, but not limited to, procurement of substitute goods or services; -// loss of use, data, or profits; or business interruption) however caused -// and on any theory of liability, whether in contract, strict liability, -// or tort (including negligence or otherwise) arising in any way out of -// the use of this software, even if advised of the possibility of such damage. -// -//M*/ - -#include "precomp.hpp" -#include "opencv2/core/core.hpp" - -#ifdef HAVE_OPENCL - -using namespace cv; -using namespace cvtest; -using namespace testing; -using namespace std; - -PARAM_TEST_CASE(PyrUp, MatType, int) -{ - int type; - int channels; - - virtual void SetUp() - { - type = GET_PARAM(0); - channels = GET_PARAM(1); - } -}; - -TEST_P(PyrUp, Accuracy) -{ - for(int j = 0; j < LOOP_TIMES; j++) - { - Size size(MWIDTH, MHEIGHT); - Mat src = randomMat(size, CV_MAKETYPE(type, channels)); - Mat dst_gold; - pyrUp(src, dst_gold); - ocl::oclMat dst; - ocl::oclMat srcMat(src); - ocl::pyrUp(srcMat, dst); - - EXPECT_MAT_NEAR(dst_gold, Mat(dst), (type == CV_32F ? 1e-4f : 1.0)); - } - -} - - -INSTANTIATE_TEST_CASE_P(OCL_ImgProc, PyrUp, testing::Combine( - Values(CV_8U, CV_32F), Values(1, 3, 4))); - - -#endif // HAVE_OPENCL \ No newline at end of file diff --git a/modules/ocl/test/utility.cpp b/modules/ocl/test/utility.cpp index 4b21081a8b..27f9cec079 100644 --- a/modules/ocl/test/utility.cpp +++ b/modules/ocl/test/utility.cpp @@ -100,12 +100,6 @@ Mat randomMat(Size size, int type, double minVal, double maxVal) return randomMat(TS::ptr()->get_rng(), size, type, minVal, maxVal, false); } - - - - - - /* void showDiff(InputArray gold_, InputArray actual_, double eps) { @@ -137,58 +131,7 @@ void showDiff(InputArray gold_, InputArray actual_, double eps) } */ -/* -bool supportFeature(const DeviceInfo& info, FeatureSet feature) -{ - return TargetArchs::builtWith(feature) && info.supports(feature); -} -const vector& devices() -{ - static vector devs; - static bool first = true; - - if (first) - { - int deviceCount = getCudaEnabledDeviceCount(); - - devs.reserve(deviceCount); - - for (int i = 0; i < deviceCount; ++i) - { - DeviceInfo info(i); - if (info.isCompatible()) - devs.push_back(info); - } - - first = false; - } - - return devs; -} - -vector devices(FeatureSet feature) -{ - const vector& d = devices(); - - vector devs_filtered; - - if (TargetArchs::builtWith(feature)) - { - devs_filtered.reserve(d.size()); - - for (size_t i = 0, size = d.size(); i < size; ++i) - { - const DeviceInfo& info = d[i]; - - if (info.supports(feature)) - devs_filtered.push_back(info); - } - } - - return devs_filtered; -} -*/ vector types(int depth_start, int depth_end, int cn_start, int cn_end) { @@ -264,3 +207,48 @@ void PrintTo(const Inverse &inverse, std::ostream *os) (*os) << "direct"; } +double checkRectSimilarity(Size sz, std::vector& ob1, std::vector& ob2) +{ + double final_test_result = 0.0; + size_t sz1 = ob1.size(); + size_t sz2 = ob2.size(); + + if(sz1 != sz2) + { + return sz1 > sz2 ? (double)(sz1 - sz2) : (double)(sz2 - sz1); + } + else + { + if(sz1==0 && sz2==0) + return 0; + cv::Mat cpu_result(sz, CV_8UC1); + cpu_result.setTo(0); + + for(vector::const_iterator r = ob1.begin(); r != ob1.end(); r++) + { + cv::Mat cpu_result_roi(cpu_result, *r); + cpu_result_roi.setTo(1); + cpu_result.copyTo(cpu_result); + } + int cpu_area = cv::countNonZero(cpu_result > 0); + + cv::Mat gpu_result(sz, CV_8UC1); + gpu_result.setTo(0); + for(vector::const_iterator r2 = ob2.begin(); r2 != ob2.end(); r2++) + { + cv::Mat gpu_result_roi(gpu_result, *r2); + gpu_result_roi.setTo(1); + gpu_result.copyTo(gpu_result); + } + + cv::Mat result_; + multiply(cpu_result, gpu_result, result_); + int result = cv::countNonZero(result_ > 0); + if(cpu_area!=0 && result!=0) + final_test_result = 1.0 - (double)result/(double)cpu_area; + else if(cpu_area==0 && result!=0) + final_test_result = -1; + } + return final_test_result; +} + diff --git a/modules/ocl/test/utility.hpp b/modules/ocl/test/utility.hpp index 42fa69384d..0b101ec50b 100644 --- a/modules/ocl/test/utility.hpp +++ b/modules/ocl/test/utility.hpp @@ -55,13 +55,12 @@ cv::Mat randomMat(cv::Size size, int type, double minVal = 0.0, double maxVal = void showDiff(cv::InputArray gold, cv::InputArray actual, double eps); -//! return true if device supports specified feature and gpu module was built with support the feature. -//bool supportFeature(const cv::gpu::DeviceInfo& info, cv::gpu::FeatureSet feature); +// This function test if gpu_rst matches cpu_rst. +// If the two vectors are not equal, it will return the difference in vector size +// Else it will return (total diff of each cpu and gpu rects covered pixels)/(total cpu rects covered pixels) +// The smaller, the better matched +double checkRectSimilarity(cv::Size sz, std::vector& ob1, std::vector& ob2); -//! return all devices compatible with current gpu module build. -//const std::vector& devices(); -//! return all devices compatible with current gpu module build which support specified feature. -//std::vector devices(cv::gpu::FeatureSet feature); //! read image from testdata folder. cv::Mat readImage(const std::string &fileName, int flags = cv::IMREAD_COLOR); From f1c549fabf2d916df306a889137de49f3ef338d5 Mon Sep 17 00:00:00 2001 From: yao Date: Wed, 19 Jun 2013 16:36:23 +0800 Subject: [PATCH 127/178] revise ocl samples, add tvl1 sample --- samples/ocl/facedetect.cpp | 159 ++++++++------ samples/ocl/hog.cpp | 335 +++++++++++------------------ samples/ocl/pyrlk_optical_flow.cpp | 59 +++-- samples/ocl/squares.cpp | 240 +++++++++++++++++---- samples/ocl/stereo_match.cpp | 306 ++++++++++++-------------- samples/ocl/surf_matcher.cpp | 205 +++++++----------- samples/ocl/tvl1_optical_flow.cpp | 265 +++++++++++++++++++++++ 7 files changed, 924 insertions(+), 645 deletions(-) create mode 100644 samples/ocl/tvl1_optical_flow.cpp diff --git a/samples/ocl/facedetect.cpp b/samples/ocl/facedetect.cpp index 684c2d923b..a49610aeb7 100644 --- a/samples/ocl/facedetect.cpp +++ b/samples/ocl/facedetect.cpp @@ -7,55 +7,67 @@ using namespace std; using namespace cv; -#define LOOP_NUM 10 +#define LOOP_NUM 10 const static Scalar colors[] = { CV_RGB(0,0,255), - CV_RGB(0,128,255), - CV_RGB(0,255,255), - CV_RGB(0,255,0), - CV_RGB(255,128,0), - CV_RGB(255,255,0), - CV_RGB(255,0,0), - CV_RGB(255,0,255)} ; + CV_RGB(0,128,255), + CV_RGB(0,255,255), + CV_RGB(0,255,0), + CV_RGB(255,128,0), + CV_RGB(255,255,0), + CV_RGB(255,0,0), + CV_RGB(255,0,255) + } ; + int64 work_begin = 0; int64 work_end = 0; +string outputName; -static void workBegin() -{ +static void workBegin() +{ work_begin = getTickCount(); } static void workEnd() { work_end += (getTickCount() - work_begin); } -static double getTime(){ +static double getTime() +{ return work_end /((double)cvGetTickFrequency() * 1000.); } -void detect( Mat& img, vector& faces, - cv::ocl::OclCascadeClassifierBuf& cascade, - double scale, bool calTime); -void detectCPU( Mat& img, vector& faces, - CascadeClassifier& cascade, - double scale, bool calTime); +void detect( Mat& img, vector& faces, + ocl::OclCascadeClassifierBuf& cascade, + double scale, bool calTime); + + +void detectCPU( Mat& img, vector& faces, + CascadeClassifier& cascade, + double scale, bool calTime); + void Draw(Mat& img, vector& faces, double scale); + // This function test if gpu_rst matches cpu_rst. // If the two vectors are not equal, it will return the difference in vector size // Else if will return (total diff of each cpu and gpu rects covered pixels)/(total cpu rects covered pixels) -double checkRectSimilarity(Size sz, std::vector& cpu_rst, std::vector& gpu_rst); +double checkRectSimilarity(Size sz, vector& cpu_rst, vector& gpu_rst); + int main( int argc, const char** argv ) { const char* keys = "{ h | help | false | print help message }" "{ i | input | | specify input image }" - "{ t | template | ../../../data/haarcascades/haarcascade_frontalface_alt.xml | specify template file }" + "{ t | template | haarcascade_frontalface_alt.xml |" + " specify template file path }" "{ c | scale | 1.0 | scale image }" - "{ s | use_cpu | false | use cpu or gpu to process the image }"; + "{ s | use_cpu | false | use cpu or gpu to process the image }" + "{ o | output | facedetect_output.jpg |" + " specify output image save path(only works when input is images) }"; CommandLineParser cmd(argc, argv, keys); if (cmd.get("help")) @@ -69,9 +81,10 @@ int main( int argc, const char** argv ) bool useCPU = cmd.get("s"); string inputName = cmd.get("i"); + outputName = cmd.get("o"); string cascadeName = cmd.get("t"); double scale = cmd.get("c"); - cv::ocl::OclCascadeClassifierBuf cascade; + ocl::OclCascadeClassifierBuf cascade; CascadeClassifier cpu_cascade; if( !cascade.load( cascadeName ) || !cpu_cascade.load(cascadeName) ) @@ -83,7 +96,7 @@ int main( int argc, const char** argv ) if( inputName.empty() ) { capture = cvCaptureFromCAM(0); - if(!capture) + if(!capture) cout << "Capture from CAM 0 didn't work" << endl; } else if( inputName.size() ) @@ -92,7 +105,7 @@ int main( int argc, const char** argv ) if( image.empty() ) { capture = cvCaptureFromAVI( inputName.c_str() ); - if(!capture) + if(!capture) cout << "Capture from AVI didn't work" << endl; return -1; } @@ -100,14 +113,15 @@ int main( int argc, const char** argv ) else { image = imread( "lena.jpg", 1 ); - if(image.empty()) + if(image.empty()) cout << "Couldn't read lena.jpg" << endl; return -1; } + cvNamedWindow( "result", 1 ); - std::vector oclinfo; - int devnums = cv::ocl::getDevice(oclinfo); + vector oclinfo; + int devnums = ocl::getDevice(oclinfo); if( devnums < 1 ) { std::cout << "no device found\n"; @@ -130,19 +144,23 @@ int main( int argc, const char** argv ) frame.copyTo( frameCopy ); else flip( frame, frameCopy, 0 ); - if(useCPU){ + if(useCPU) + { detectCPU(frameCopy, faces, cpu_cascade, scale, false); } - else{ - detect(frameCopy, faces, cascade, scale, false); + else + { + detect(frameCopy, faces, cascade, scale, false); } Draw(frameCopy, faces, scale); if( waitKey( 10 ) >= 0 ) goto _cleanup_; } + waitKey(0); + _cleanup_: cvReleaseCapture( &capture ); } @@ -152,18 +170,21 @@ _cleanup_: vector faces; vector ref_rst; double accuracy = 0.; - for(int i = 0; i <= LOOP_NUM;i ++) + for(int i = 0; i <= LOOP_NUM; i ++) { cout << "loop" << i << endl; - if(useCPU){ - detectCPU(image, faces, cpu_cascade, scale, i==0?false:true); + if(useCPU) + { + detectCPU(image, faces, cpu_cascade, scale, i==0?false:true); } - else{ + else + { detect(image, faces, cascade, scale, i==0?false:true); - if(i == 0){ + if(i == 0) + { detectCPU(image, ref_rst, cpu_cascade, scale, false); accuracy = checkRectSimilarity(image.size(), ref_rst, faces); - } + } } if (i == LOOP_NUM) { @@ -180,31 +201,31 @@ _cleanup_: } cvDestroyWindow("result"); - return 0; } -void detect( Mat& img, vector& faces, - cv::ocl::OclCascadeClassifierBuf& cascade, - double scale, bool calTime) +void detect( Mat& img, vector& faces, + ocl::OclCascadeClassifierBuf& cascade, + double scale, bool calTime) { - cv::ocl::oclMat image(img); - cv::ocl::oclMat gray, smallImg( cvRound (img.rows/scale), cvRound(img.cols/scale), CV_8UC1 ); + ocl::oclMat image(img); + ocl::oclMat gray, smallImg( cvRound (img.rows/scale), cvRound(img.cols/scale), CV_8UC1 ); if(calTime) workBegin(); - cv::ocl::cvtColor( image, gray, CV_BGR2GRAY ); - cv::ocl::resize( gray, smallImg, smallImg.size(), 0, 0, INTER_LINEAR ); - cv::ocl::equalizeHist( smallImg, smallImg ); + ocl::cvtColor( image, gray, CV_BGR2GRAY ); + ocl::resize( gray, smallImg, smallImg.size(), 0, 0, INTER_LINEAR ); + ocl::equalizeHist( smallImg, smallImg ); cascade.detectMultiScale( smallImg, faces, 1.1, - 3, 0 - |CV_HAAR_SCALE_IMAGE - , Size(30,30), Size(0, 0) ); + 3, 0 + |CV_HAAR_SCALE_IMAGE + , Size(30,30), Size(0, 0) ); if(calTime) workEnd(); } -void detectCPU( Mat& img, vector& faces, - CascadeClassifier& cascade, - double scale, bool calTime) + +void detectCPU( Mat& img, vector& faces, + CascadeClassifier& cascade, + double scale, bool calTime) { if(calTime) workBegin(); Mat cpu_gray, cpu_smallImg( cvRound (img.rows/scale), cvRound(img.cols/scale), CV_8UC1 ); @@ -212,11 +233,12 @@ void detectCPU( Mat& img, vector& faces, resize(cpu_gray, cpu_smallImg, cpu_smallImg.size(), 0, 0, INTER_LINEAR); equalizeHist(cpu_smallImg, cpu_smallImg); cascade.detectMultiScale(cpu_smallImg, faces, 1.1, - 3, 0 | CV_HAAR_SCALE_IMAGE, - Size(30, 30), Size(0, 0)); - if(calTime) workEnd(); + 3, 0 | CV_HAAR_SCALE_IMAGE, + Size(30, 30), Size(0, 0)); + if(calTime) workEnd(); } + void Draw(Mat& img, vector& faces, double scale) { int i = 0; @@ -230,31 +252,38 @@ void Draw(Mat& img, vector& faces, double scale) radius = cvRound((r->width + r->height)*0.25*scale); circle( img, center, radius, color, 3, 8, 0 ); } - cv::imshow( "result", img ); + imshow( "result", img ); + imwrite( outputName, img ); } -double checkRectSimilarity(Size sz, std::vector& ob1, std::vector& ob2) + +double checkRectSimilarity(Size sz, vector& ob1, vector& ob2) { double final_test_result = 0.0; size_t sz1 = ob1.size(); size_t sz2 = ob2.size(); if(sz1 != sz2) + { return sz1 > sz2 ? (double)(sz1 - sz2) : (double)(sz2 - sz1); + } else { - cv::Mat cpu_result(sz, CV_8UC1); + if(sz1==0 && sz2==0) + return 0; + Mat cpu_result(sz, CV_8UC1); cpu_result.setTo(0); for(vector::const_iterator r = ob1.begin(); r != ob1.end(); r++) - { - cv::Mat cpu_result_roi(cpu_result, *r); + { + Mat cpu_result_roi(cpu_result, *r); cpu_result_roi.setTo(1); cpu_result.copyTo(cpu_result); } - int cpu_area = cv::countNonZero(cpu_result > 0); + int cpu_area = countNonZero(cpu_result > 0); - cv::Mat gpu_result(sz, CV_8UC1); + + Mat gpu_result(sz, CV_8UC1); gpu_result.setTo(0); for(vector::const_iterator r2 = ob2.begin(); r2 != ob2.end(); r2++) { @@ -263,11 +292,13 @@ double checkRectSimilarity(Size sz, std::vector& ob1, std::vector& o gpu_result.copyTo(gpu_result); } - cv::Mat result_; + Mat result_; multiply(cpu_result, gpu_result, result_); - int result = cv::countNonZero(result_ > 0); - - final_test_result = 1.0 - (double)result/(double)cpu_area; + int result = countNonZero(result_ > 0); + if(cpu_area!=0 && result!=0) + final_test_result = 1.0 - (double)result/(double)cpu_area; + else if(cpu_area==0 && result!=0) + final_test_result = -1; } return final_test_result; } diff --git a/samples/ocl/hog.cpp b/samples/ocl/hog.cpp index 28be6fa9af..ff53e010cf 100644 --- a/samples/ocl/hog.cpp +++ b/samples/ocl/hog.cpp @@ -10,75 +10,39 @@ using namespace std; using namespace cv; -bool help_showed = false; - -class Args -{ -public: - Args(); - static Args read(int argc, char** argv); - - string src; - bool src_is_video; - bool src_is_camera; - int camera_id; - - bool write_video; - string dst_video; - double dst_video_fps; - - bool make_gray; - - bool resize_src; - int width, height; - - double scale; - int nlevels; - int gr_threshold; - - double hit_threshold; - bool hit_threshold_auto; - - int win_width; - int win_stride_width, win_stride_height; - - bool gamma_corr; -}; - class App { public: - App(const Args& s); + App(CommandLineParser& cmd); void run(); - void handleKey(char key); - void hogWorkBegin(); void hogWorkEnd(); string hogWorkFps() const; - void workBegin(); void workEnd(); string workFps() const; - string message() const; + // This function test if gpu_rst matches cpu_rst. // If the two vectors are not equal, it will return the difference in vector size -// Else if will return +// Else if will return // (total diff of each cpu and gpu rects covered pixels)/(total cpu rects covered pixels) - double checkRectSimilarity(Size sz, - std::vector& cpu_rst, + double checkRectSimilarity(Size sz, + std::vector& cpu_rst, std::vector& gpu_rst); private: App operator=(App&); - Args args; + //Args args; bool running; - bool use_gpu; bool make_gray; double scale; + double resize_scale; + int win_width; + int win_stride_width, win_stride_height; int gr_threshold; int nlevels; double hit_threshold; @@ -86,119 +50,49 @@ private: int64 hog_work_begin; double hog_work_fps; - int64 work_begin; double work_fps; -}; -static void printHelp() -{ - cout << "Histogram of Oriented Gradients descriptor and detector sample.\n" - << "\nUsage: hog_gpu\n" - << " (|--video |--camera ) # frames source\n" - << " [--make_gray ] # convert image to gray one or not\n" - << " [--resize_src ] # do resize of the source image or not\n" - << " [--width ] # resized image width\n" - << " [--height ] # resized image height\n" - << " [--hit_threshold ] # classifying plane distance threshold (0.0 usually)\n" - << " [--scale ] # HOG window scale factor\n" - << " [--nlevels ] # max number of HOG window scales\n" - << " [--win_width ] # width of the window (48 or 64)\n" - << " [--win_stride_width ] # distance by OX axis between neighbour wins\n" - << " [--win_stride_height ] # distance by OY axis between neighbour wins\n" - << " [--gr_threshold ] # merging similar rects constant\n" - << " [--gamma_correct ] # do gamma correction or not\n" - << " [--write_video ] # write video or not\n" - << " [--dst_video ] # output video path\n" - << " [--dst_video_fps ] # output video fps\n"; - help_showed = true; -} + string img_source; + string vdo_source; + string output; + int camera_id; +}; int main(int argc, char** argv) { + const char* keys = + "{ h | help | false | print help message }" + "{ i | input | | specify input image}" + "{ c | camera | -1 | enable camera capturing }" + "{ v | video | | use video as input }" + "{ g | gray | false | convert image to gray one or not}" + "{ s | scale | 1.0 | resize the image before detect}" + "{ l |larger_win| false | use 64x128 window}" + "{ o | output | | specify output path when input is images}"; + CommandLineParser cmd(argc, argv, keys); + App app(cmd); try { - if (argc < 2) - printHelp(); - Args args = Args::read(argc, argv); - if (help_showed) - return -1; - App app(args); app.run(); } - catch (const Exception& e) { return cout << "error: " << e.what() << endl, 1; } - catch (const exception& e) { return cout << "error: " << e.what() << endl, 1; } - catch(...) { return cout << "unknown exception" << endl, 1; } + catch (const Exception& e) + { + return cout << "error: " << e.what() << endl, 1; + } + catch (const exception& e) + { + return cout << "error: " << e.what() << endl, 1; + } + catch(...) + { + return cout << "unknown exception" << endl, 1; + } return 0; } - -Args::Args() +App::App(CommandLineParser& cmd) { - src_is_video = false; - src_is_camera = false; - camera_id = 0; - - write_video = false; - dst_video_fps = 24.; - - make_gray = false; - - resize_src = false; - width = 640; - height = 480; - - scale = 1.05; - nlevels = 13; - gr_threshold = 8; - hit_threshold = 1.4; - hit_threshold_auto = true; - - win_width = 48; - win_stride_width = 8; - win_stride_height = 8; - - gamma_corr = true; -} - - -Args Args::read(int argc, char** argv) -{ - Args args; - for (int i = 1; i < argc; i++) - { - if (string(argv[i]) == "--make_gray") args.make_gray = (string(argv[++i]) == "true"); - else if (string(argv[i]) == "--resize_src") args.resize_src = (string(argv[++i]) == "true"); - else if (string(argv[i]) == "--width") args.width = atoi(argv[++i]); - else if (string(argv[i]) == "--height") args.height = atoi(argv[++i]); - else if (string(argv[i]) == "--hit_threshold") - { - args.hit_threshold = atof(argv[++i]); - args.hit_threshold_auto = false; - } - else if (string(argv[i]) == "--scale") args.scale = atof(argv[++i]); - else if (string(argv[i]) == "--nlevels") args.nlevels = atoi(argv[++i]); - else if (string(argv[i]) == "--win_width") args.win_width = atoi(argv[++i]); - else if (string(argv[i]) == "--win_stride_width") args.win_stride_width = atoi(argv[++i]); - else if (string(argv[i]) == "--win_stride_height") args.win_stride_height = atoi(argv[++i]); - else if (string(argv[i]) == "--gr_threshold") args.gr_threshold = atoi(argv[++i]); - else if (string(argv[i]) == "--gamma_correct") args.gamma_corr = (string(argv[++i]) == "true"); - else if (string(argv[i]) == "--write_video") args.write_video = (string(argv[++i]) == "true"); - else if (string(argv[i]) == "--dst_video") args.dst_video = argv[++i]; - else if (string(argv[i]) == "--dst_video_fps") args.dst_video_fps = atof(argv[++i]); - else if (string(argv[i]) == "--help") printHelp(); - else if (string(argv[i]) == "--video") { args.src = argv[++i]; args.src_is_video = true; } - else if (string(argv[i]) == "--camera") { args.camera_id = atoi(argv[++i]); args.src_is_camera = true; } - else if (args.src.empty()) args.src = argv[i]; - else throw runtime_error((string("unknown key: ") + argv[i])); - } - return args; -} - - -App::App(const Args& s) -{ - args = s; cout << "\nControls:\n" << "\tESC - exit\n" << "\tm - change mode GPU <-> CPU\n" @@ -209,56 +103,56 @@ App::App(const Args& s) << "\t4/r - increase/decrease hit threshold\n" << endl; + use_gpu = true; - make_gray = args.make_gray; - scale = args.scale; - gr_threshold = args.gr_threshold; - nlevels = args.nlevels; + make_gray = cmd.get("g"); + resize_scale = cmd.get("s"); + win_width = cmd.get("l") == true ? 64 : 48; + vdo_source = cmd.get("v"); + img_source = cmd.get("i"); + output = cmd.get("o"); + camera_id = cmd.get("c"); - if (args.hit_threshold_auto) - args.hit_threshold = args.win_width == 48 ? 1.4 : 0.; - hit_threshold = args.hit_threshold; + win_stride_width = 8; + win_stride_height = 8; + gr_threshold = 8; + nlevels = 13; + hit_threshold = win_width == 48 ? 1.4 : 0.; + scale = 1.05; + gamma_corr = true; - gamma_corr = args.gamma_corr; - - if (args.win_width != 64 && args.win_width != 48) - args.win_width = 64; - - cout << "Scale: " << scale << endl; - if (args.resize_src) - cout << "Resized source: (" << args.width << ", " << args.height << ")\n"; cout << "Group threshold: " << gr_threshold << endl; cout << "Levels number: " << nlevels << endl; - cout << "Win width: " << args.win_width << endl; - cout << "Win stride: (" << args.win_stride_width << ", " << args.win_stride_height << ")\n"; + cout << "Win width: " << win_width << endl; + cout << "Win stride: (" << win_stride_width << ", " << win_stride_height << ")\n"; cout << "Hit threshold: " << hit_threshold << endl; cout << "Gamma correction: " << gamma_corr << endl; cout << endl; } - void App::run() { - std::vector oclinfo; + vector oclinfo; ocl::getDevice(oclinfo); running = true; - cv::VideoWriter video_writer; + VideoWriter video_writer; - Size win_size(args.win_width, args.win_width * 2); //(64, 128) or (48, 96) - Size win_stride(args.win_stride_width, args.win_stride_height); + Size win_size(win_width, win_width * 2); + Size win_stride(win_stride_width, win_stride_height); // Create HOG descriptors and detectors here vector detector; if (win_size == Size(64, 128)) - detector = cv::ocl::HOGDescriptor::getPeopleDetector64x128(); + detector = ocl::HOGDescriptor::getPeopleDetector64x128(); else - detector = cv::ocl::HOGDescriptor::getPeopleDetector48x96(); + detector = ocl::HOGDescriptor::getPeopleDetector48x96(); - cv::ocl::HOGDescriptor gpu_hog(win_size, Size(16, 16), Size(8, 8), Size(8, 8), 9, - cv::ocl::HOGDescriptor::DEFAULT_WIN_SIGMA, 0.2, gamma_corr, - cv::ocl::HOGDescriptor::DEFAULT_NLEVELS); - cv::HOGDescriptor cpu_hog(win_size, Size(16, 16), Size(8, 8), Size(8, 8), 9, 1, -1, - HOGDescriptor::L2Hys, 0.2, gamma_corr, cv::HOGDescriptor::DEFAULT_NLEVELS); + + ocl::HOGDescriptor gpu_hog(win_size, Size(16, 16), Size(8, 8), Size(8, 8), 9, + ocl::HOGDescriptor::DEFAULT_WIN_SIGMA, 0.2, gamma_corr, + ocl::HOGDescriptor::DEFAULT_NLEVELS); + HOGDescriptor cpu_hog(win_size, Size(16, 16), Size(8, 8), Size(8, 8), 9, 1, -1, + HOGDescriptor::L2Hys, 0.2, gamma_corr, cv::HOGDescriptor::DEFAULT_NLEVELS); gpu_hog.setSVMDetector(detector); cpu_hog.setSVMDetector(detector); @@ -267,29 +161,29 @@ void App::run() VideoCapture vc; Mat frame; - if (args.src_is_video) + if (vdo_source!="") { - vc.open(args.src.c_str()); + vc.open(vdo_source.c_str()); if (!vc.isOpened()) - throw runtime_error(string("can't open video file: " + args.src)); + throw runtime_error(string("can't open video file: " + vdo_source)); vc >> frame; } - else if (args.src_is_camera) + else if (camera_id != -1) { - vc.open(args.camera_id); + vc.open(camera_id); if (!vc.isOpened()) { stringstream msg; - msg << "can't open camera: " << args.camera_id; + msg << "can't open camera: " << camera_id; throw runtime_error(msg.str()); } vc >> frame; } else { - frame = imread(args.src); + frame = imread(img_source); if (frame.empty()) - throw runtime_error(string("can't open image file: " + args.src)); + throw runtime_error(string("can't open image file: " + img_source)); } Mat img_aux, img, img_to_show; @@ -307,13 +201,15 @@ void App::run() else frame.copyTo(img_aux); // Resize image - if (args.resize_src) resize(img_aux, img, Size(args.width, args.height)); + if (abs(scale-1.0)>0.001) + { + Size sz((int)((double)img_aux.cols/resize_scale), (int)((double)img_aux.rows/resize_scale)); + resize(img_aux, img, sz); + } else img = img_aux; img_to_show = img; - gpu_hog.nlevels = nlevels; cpu_hog.nlevels = nlevels; - vector found; // Perform HOG classification @@ -330,15 +226,16 @@ void App::run() vector ref_rst; cvtColor(img, img, CV_BGRA2BGR); cpu_hog.detectMultiScale(img, ref_rst, hit_threshold, win_stride, - Size(0, 0), scale, gr_threshold-2); + Size(0, 0), scale, gr_threshold-2); double accuracy = checkRectSimilarity(img.size(), ref_rst, found); - cout << "\naccuracy value: " << accuracy << endl; - } - } + cout << "\naccuracy value: " << accuracy << endl; + } + } else cpu_hog.detectMultiScale(img, found, hit_threshold, win_stride, - Size(0, 0), scale, gr_threshold); + Size(0, 0), scale, gr_threshold); hogWorkEnd(); + // Draw positive classified windows for (size_t i = 0; i < found.size(); i++) { @@ -353,25 +250,31 @@ void App::run() putText(img_to_show, "FPS (HOG only): " + hogWorkFps(), Point(5, 65), FONT_HERSHEY_SIMPLEX, 1., Scalar(255, 100, 0), 2); putText(img_to_show, "FPS (total): " + workFps(), Point(5, 105), FONT_HERSHEY_SIMPLEX, 1., Scalar(255, 100, 0), 2); imshow("opencv_gpu_hog", img_to_show); - - if (args.src_is_video || args.src_is_camera) vc >> frame; + if (vdo_source!="" || camera_id!=-1) vc >> frame; workEnd(); - if (args.write_video) + if (output!="") { - if (!video_writer.isOpened()) + if (img_source!="") // wirte image { - video_writer.open(args.dst_video, CV_FOURCC('x','v','i','d'), args.dst_video_fps, - img_to_show.size(), true); - if (!video_writer.isOpened()) - throw std::runtime_error("can't create video writer"); + imwrite(output, img_to_show); } + else //write video + { + if (!video_writer.isOpened()) + { + video_writer.open(output, CV_FOURCC('x','v','i','d'), 24, + img_to_show.size(), true); + if (!video_writer.isOpened()) + throw std::runtime_error("can't create video writer"); + } - if (make_gray) cvtColor(img_to_show, img, CV_GRAY2BGR); - else cvtColor(img_to_show, img, CV_BGRA2BGR); + if (make_gray) cvtColor(img_to_show, img, CV_GRAY2BGR); + else cvtColor(img_to_show, img, CV_BGRA2BGR); - video_writer << img; + video_writer << img; + } } handleKey((char)waitKey(3)); @@ -379,7 +282,6 @@ void App::run() } } - void App::handleKey(char key) { switch (key) @@ -442,7 +344,10 @@ void App::handleKey(char key) } -inline void App::hogWorkBegin() { hog_work_begin = getTickCount(); } +inline void App::hogWorkBegin() +{ + hog_work_begin = getTickCount(); +} inline void App::hogWorkEnd() { @@ -458,8 +363,10 @@ inline string App::hogWorkFps() const return ss.str(); } - -inline void App::workBegin() { work_begin = getTickCount(); } +inline void App::workBegin() +{ + work_begin = getTickCount(); +} inline void App::workEnd() { @@ -475,8 +382,9 @@ inline string App::workFps() const return ss.str(); } -double App::checkRectSimilarity(Size sz, - std::vector& ob1, + +double App::checkRectSimilarity(Size sz, + std::vector& ob1, std::vector& ob2) { double final_test_result = 0.0; @@ -484,20 +392,26 @@ double App::checkRectSimilarity(Size sz, size_t sz2 = ob2.size(); if(sz1 != sz2) + { return sz1 > sz2 ? (double)(sz1 - sz2) : (double)(sz2 - sz1); + } else { + if(sz1==0 && sz2==0) + return 0; cv::Mat cpu_result(sz, CV_8UC1); cpu_result.setTo(0); + for(vector::const_iterator r = ob1.begin(); r != ob1.end(); r++) - { + { cv::Mat cpu_result_roi(cpu_result, *r); cpu_result_roi.setTo(1); cpu_result.copyTo(cpu_result); } int cpu_area = cv::countNonZero(cpu_result > 0); + cv::Mat gpu_result(sz, CV_8UC1); gpu_result.setTo(0); for(vector::const_iterator r2 = ob2.begin(); r2 != ob2.end(); r2++) @@ -510,10 +424,11 @@ double App::checkRectSimilarity(Size sz, cv::Mat result_; multiply(cpu_result, gpu_result, result_); int result = cv::countNonZero(result_ > 0); - - final_test_result = 1.0 - (double)result/(double)cpu_area; + if(cpu_area!=0 && result!=0) + final_test_result = 1.0 - (double)result/(double)cpu_area; + else if(cpu_area==0 && result!=0) + final_test_result = -1; } return final_test_result; - } diff --git a/samples/ocl/pyrlk_optical_flow.cpp b/samples/ocl/pyrlk_optical_flow.cpp index cc8d886f79..cefa928670 100644 --- a/samples/ocl/pyrlk_optical_flow.cpp +++ b/samples/ocl/pyrlk_optical_flow.cpp @@ -11,19 +11,20 @@ using namespace cv; using namespace cv::ocl; typedef unsigned char uchar; -#define LOOP_NUM 10 +#define LOOP_NUM 10 int64 work_begin = 0; int64 work_end = 0; -static void workBegin() -{ +static void workBegin() +{ work_begin = getTickCount(); } static void workEnd() { work_end += (getTickCount() - work_begin); } -static double getTime(){ +static double getTime() +{ return work_end * 1000. / getTickFrequency(); } @@ -93,14 +94,15 @@ int main(int argc, const char* argv[]) //set this to save kernel compile time from second time you run ocl::setBinpath("./"); const char* keys = - "{ h | help | false | print help message }" - "{ l | left | | specify left image }" - "{ r | right | | specify right image }" - "{ c | camera | 0 | enable camera capturing }" - "{ s | use_cpu | false | use cpu or gpu to process the image }" - "{ v | video | | use video as input }" - "{ points | points | 1000 | specify points count [GoodFeatureToTrack] }" - "{ min_dist | min_dist | 0 | specify minimal distance between points [GoodFeatureToTrack] }"; + "{ h | help | false | print help message }" + "{ l | left | | specify left image }" + "{ r | right | | specify right image }" + "{ c | camera | 0 | specify camera id }" + "{ s | use_cpu | false | use cpu or gpu to process the image }" + "{ v | video | | use video as input }" + "{ o | output | pyrlk_output.jpg| specify output save path when input is images }" + "{ p | points | 1000 | specify points count [GoodFeatureToTrack] }" + "{ m | min_dist | 0 | specify minimal distance between points [GoodFeatureToTrack] }"; CommandLineParser cmd(argc, argv, keys); @@ -113,13 +115,13 @@ int main(int argc, const char* argv[]) } bool defaultPicturesFail = false; - string fname0 = cmd.get("left"); - string fname1 = cmd.get("right"); - string vdofile = cmd.get("video"); - int points = cmd.get("points"); - double minDist = cmd.get("min_dist"); + string fname0 = cmd.get("l"); + string fname1 = cmd.get("r"); + string vdofile = cmd.get("v"); + string outfile = cmd.get("o"); + int points = cmd.get("p"); + double minDist = cmd.get("m"); bool useCPU = cmd.get("s"); - bool useCamera = cmd.get("c"); int inputName = cmd.get("c"); oclMat d_nextPts, d_status; @@ -132,22 +134,9 @@ int main(int argc, const char* argv[]) vector status(points); vector err; - if (frame0.empty() || frame1.empty()) - { - useCamera = true; - defaultPicturesFail = true; - CvCapture* capture = 0; - capture = cvCaptureFromCAM( inputName ); - if (!capture) - { - cout << "Can't load input images" << endl; - return -1; - } - } - cout << "Points count : " << points << endl << endl; - if (useCamera) + if (frame0.empty() || frame1.empty()) { CvCapture* capture = 0; Mat frame, frameCopy; @@ -241,10 +230,10 @@ _cleanup_: else { nocamera: - for(int i = 0; i <= LOOP_NUM;i ++) + for(int i = 0; i <= LOOP_NUM; i ++) { cout << "loop" << i << endl; - if (i > 0) workBegin(); + if (i > 0) workBegin(); if (useCPU) { @@ -274,8 +263,8 @@ nocamera: cout << getTime() / LOOP_NUM << " ms" << endl; drawArrows(frame0, pts, nextPts, status, Scalar(255, 0, 0)); - imshow("PyrLK [Sparse]", frame0); + imwrite(outfile, frame0); } } } diff --git a/samples/ocl/squares.cpp b/samples/ocl/squares.cpp index 6b184161f7..48964ffb2e 100644 --- a/samples/ocl/squares.cpp +++ b/samples/ocl/squares.cpp @@ -6,7 +6,6 @@ #include "opencv2/imgproc/imgproc.hpp" #include "opencv2/highgui/highgui.hpp" #include "opencv2/ocl/ocl.hpp" - #include #include #include @@ -14,23 +13,50 @@ using namespace cv; using namespace std; -static void help() -{ - cout << - "\nA program using OCL module pyramid scaling, Canny, dilate functions, threshold, split; cpu contours, contour simpification and\n" - "memory storage (it's got it all folks) to find\n" - "squares in a list of images pic1-6.png\n" - "Returns sequence of squares detected on the image.\n" - "the sequence is stored in the specified memory storage\n" - "Call:\n" - "./squares\n" - "Using OpenCV version %s\n" << CV_VERSION << "\n" << endl; -} +#define ACCURACY_CHECK 1 +#if ACCURACY_CHECK +// check if two vectors of vector of points are near or not +// prior assumption is that they are in correct order +static bool checkPoints( + vector< vector > set1, + vector< vector > set2, + int maxDiff = 5) +{ + if(set1.size() != set2.size()) + { + return false; + } + + for(vector< vector >::iterator it1 = set1.begin(), it2 = set2.begin(); + it1 < set1.end() && it2 < set2.end(); it1 ++, it2 ++) + { + vector pts1 = *it1; + vector pts2 = *it2; + + + if(pts1.size() != pts2.size()) + { + return false; + } + for(size_t i = 0; i < pts1.size(); i ++) + { + Point pt1 = pts1[i], pt2 = pts2[i]; + if(std::abs(pt1.x - pt2.x) > maxDiff || + std::abs(pt1.y - pt2.y) > maxDiff) + { + return false; + } + } + } + return true; +} +#endif int thresh = 50, N = 11; const char* wndname = "OpenCL Square Detection Demo"; + // helper function: // finds a cosine of angle between vectors // from pt0->pt1 and from pt0->pt2 @@ -43,9 +69,92 @@ static double angle( Point pt1, Point pt2, Point pt0 ) return (dx1*dx2 + dy1*dy2)/sqrt((dx1*dx1 + dy1*dy1)*(dx2*dx2 + dy2*dy2) + 1e-10); } + // returns sequence of squares detected on the image. // the sequence is stored in the specified memory storage static void findSquares( const Mat& image, vector >& squares ) +{ + squares.clear(); + Mat pyr, timg, gray0(image.size(), CV_8U), gray; + + // down-scale and upscale the image to filter out the noise + pyrDown(image, pyr, Size(image.cols/2, image.rows/2)); + pyrUp(pyr, timg, image.size()); + vector > contours; + + // find squares in every color plane of the image + for( int c = 0; c < 3; c++ ) + { + int ch[] = {c, 0}; + mixChannels(&timg, 1, &gray0, 1, ch, 1); + + // try several threshold levels + for( int l = 0; l < N; l++ ) + { + // hack: use Canny instead of zero threshold level. + // Canny helps to catch squares with gradient shading + if( l == 0 ) + { + // apply Canny. Take the upper threshold from slider + // and set the lower to 0 (which forces edges merging) + Canny(gray0, gray, 0, thresh, 5); + // dilate canny output to remove potential + // holes between edge segments + dilate(gray, gray, Mat(), Point(-1,-1)); + } + else + { + // apply threshold if l!=0: + // tgray(x,y) = gray(x,y) < (l+1)*255/N ? 255 : 0 + cv::threshold(gray0, gray, (l+1)*255/N, 255, THRESH_BINARY); + } + + // find contours and store them all as a list + findContours(gray, contours, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE); + + vector approx; + + // test each contour + for( size_t i = 0; i < contours.size(); i++ ) + { + // approximate contour with accuracy proportional + // to the contour perimeter + approxPolyDP(Mat(contours[i]), approx, arcLength(Mat(contours[i]), true)*0.02, true); + + // square contours should have 4 vertices after approximation + // relatively large area (to filter out noisy contours) + // and be convex. + // Note: absolute value of an area is used because + // area may be positive or negative - in accordance with the + // contour orientation + if( approx.size() == 4 && + fabs(contourArea(Mat(approx))) > 1000 && + isContourConvex(Mat(approx)) ) + { + double maxCosine = 0; + + for( int j = 2; j < 5; j++ ) + { + // find the maximum cosine of the angle between joint edges + double cosine = fabs(angle(approx[j%4], approx[j-2], approx[j-1])); + maxCosine = MAX(maxCosine, cosine); + } + + // if cosines of all angles are small + // (all angles are ~90 degree) then write quandrange + // vertices to resultant sequence + if( maxCosine < 0.3 ) + squares.push_back(approx); + } + } + } + } +} + + +// returns sequence of squares detected on the image. +// the sequence is stored in the specified memory storage +static void findSquares_ocl( const Mat& image, vector >& squares ) { squares.clear(); @@ -91,7 +200,6 @@ static void findSquares( const Mat& image, vector >& squares ) findContours(gray, contours, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE); vector approx; - // test each contour for( size_t i = 0; i < contours.size(); i++ ) { @@ -106,11 +214,10 @@ static void findSquares( const Mat& image, vector >& squares ) // area may be positive or negative - in accordance with the // contour orientation if( approx.size() == 4 && - fabs(contourArea(Mat(approx))) > 1000 && - isContourConvex(Mat(approx)) ) + fabs(contourArea(Mat(approx))) > 1000 && + isContourConvex(Mat(approx)) ) { double maxCosine = 0; - for( int j = 2; j < 5; j++ ) { // find the maximum cosine of the angle between joint edges @@ -139,40 +246,93 @@ static void drawSquares( Mat& image, const vector >& squares ) int n = (int)squares[i].size(); polylines(image, &p, &n, 1, true, Scalar(0,255,0), 3, CV_AA); } - - imshow(wndname, image); } -int main(int /*argc*/, char** /*argv*/) +// draw both pure-C++ and ocl square results onto a single image +static Mat drawSquaresBoth( const Mat& image, + const vector >& sqsCPP, + const vector >& sqsOCL +) { + Mat imgToShow(Size(image.cols * 2, image.rows), image.type()); + Mat lImg = imgToShow(Rect(Point(0, 0), image.size())); + Mat rImg = imgToShow(Rect(Point(image.cols, 0), image.size())); + image.copyTo(lImg); + image.copyTo(rImg); + drawSquares(lImg, sqsCPP); + drawSquares(rImg, sqsOCL); + float fontScale = 0.8f; + Scalar white = Scalar::all(255), black = Scalar::all(0); + + putText(lImg, "C++", Point(10, 20), FONT_HERSHEY_COMPLEX_SMALL, fontScale, black, 2); + putText(rImg, "OCL", Point(10, 20), FONT_HERSHEY_COMPLEX_SMALL, fontScale, black, 2); + putText(lImg, "C++", Point(10, 20), FONT_HERSHEY_COMPLEX_SMALL, fontScale, white, 1); + putText(rImg, "OCL", Point(10, 20), FONT_HERSHEY_COMPLEX_SMALL, fontScale, white, 1); + + return imgToShow; +} + + +int main(int argc, char** argv) +{ + const char* keys = + "{ i | input | | specify input image }" + "{ o | output | squares_output.jpg | specify output save path}"; + CommandLineParser cmd(argc, argv, keys); + string inputName = cmd.get("i"); + string outfile = cmd.get("o"); + if(inputName.empty()) + { + cout << "Avaible options:" << endl; + cmd.printParams(); + return 0; + } - //ocl::setBinpath("F:/kernel_bin"); vector info; CV_Assert(ocl::getDevice(info)); - - static const char* names[] = { "pic1.png", "pic2.png", "pic3.png", - "pic4.png", "pic5.png", "pic6.png", 0 }; - help(); + int iterations = 10; namedWindow( wndname, 1 ); - vector > squares; + vector > squares_cpu, squares_ocl; - for( int i = 0; names[i] != 0; i++ ) + Mat image = imread(inputName, 1); + if( image.empty() ) { - Mat image = imread(names[i], 1); - if( image.empty() ) - { - cout << "Couldn't load " << names[i] << endl; - continue; - } - - findSquares(image, squares); - drawSquares(image, squares); - - int c = waitKey(); - if( (char)c == 27 ) - break; + cout << "Couldn't load " << inputName << endl; + return -1; } + int j = iterations; + int64 t_ocl = 0, t_cpp = 0; + //warm-ups + cout << "warming up ..." << endl; + findSquares(image, squares_cpu); + findSquares_ocl(image, squares_ocl); + + +#if ACCURACY_CHECK + cout << "Checking ocl accuracy ... " << endl; + cout << (checkPoints(squares_cpu, squares_ocl) ? "Pass" : "Failed") << endl; +#endif + do + { + int64 t_start = cv::getTickCount(); + findSquares(image, squares_cpu); + t_cpp += cv::getTickCount() - t_start; + + + t_start = cv::getTickCount(); + findSquares_ocl(image, squares_ocl); + t_ocl += cv::getTickCount() - t_start; + cout << "run loop: " << j << endl; + } + while(--j); + cout << "cpp average time: " << 1000.0f * (double)t_cpp / getTickFrequency() / iterations << "ms" << endl; + cout << "ocl average time: " << 1000.0f * (double)t_ocl / getTickFrequency() / iterations << "ms" << endl; + + Mat result = drawSquaresBoth(image, squares_cpu, squares_ocl); + imshow(wndname, result); + imwrite(outfile, result); + cvWaitKey(0); return 0; } diff --git a/samples/ocl/stereo_match.cpp b/samples/ocl/stereo_match.cpp index 7ac2c9a6f3..565744baa6 100644 --- a/samples/ocl/stereo_match.cpp +++ b/samples/ocl/stereo_match.cpp @@ -10,56 +10,45 @@ using namespace cv; using namespace std; using namespace ocl; -bool help_showed = false; - -struct Params -{ - Params(); - static Params read(int argc, char** argv); - - string left; - string right; - - string method_str() const - { - switch (method) - { - case BM: return "BM"; - case BP: return "BP"; - case CSBP: return "CSBP"; - } - return ""; - } - enum {BM, BP, CSBP} method; - int ndisp; // Max disparity + 1 - enum {GPU, CPU} type; -}; - struct App { - App(const Params& p); + App(CommandLineParser& cmd); void run(); void handleKey(char key); void printParams() const; - void workBegin() { work_begin = getTickCount(); } + void workBegin() + { + work_begin = getTickCount(); + } void workEnd() { int64 d = getTickCount() - work_begin; double f = getTickFrequency(); work_fps = f / d; } - + string method_str() const + { + switch (method) + { + case BM: + return "BM"; + case BP: + return "BP"; + case CSBP: + return "CSBP"; + } + return ""; + } string text() const { stringstream ss; - ss << "(" << p.method_str() << ") FPS: " << setiosflags(ios::left) - << setprecision(4) << work_fps; + ss << "(" << method_str() << ") FPS: " << setiosflags(ios::left) + << setprecision(4) << work_fps; return ss.str(); } private: - Params p; bool running; Mat left_src, right_src; @@ -72,42 +61,45 @@ private: int64 work_begin; double work_fps; -}; -static void printHelp() -{ - cout << "Usage: stereo_match_gpu\n" - << "\t--left --right # must be rectified\n" - << "\t--method # BM | BP | CSBP\n" - << "\t--ndisp # number of disparity levels\n" - << "\t--type # cpu | CPU | gpu | GPU\n"; - help_showed = true; -} + string l_img, r_img; + string out_img; + enum {BM, BP, CSBP} method; + int ndisp; // Max disparity + 1 + enum {GPU, CPU} type; +}; int main(int argc, char** argv) { + const char* keys = + "{ h | help | false | print help message }" + "{ l | left | | specify left image }" + "{ r | right | | specify right image }" + "{ m | method | BM | specify match method(BM/BP/CSBP) }" + "{ n | ndisp | 64 | specify number of disparity levels }" + "{ s | cpu_ocl | false | use cpu or gpu as ocl device to process the image }" + "{ o | output | stereo_match_output.jpg | specify output path when input is images}"; + CommandLineParser cmd(argc, argv, keys); + if (cmd.get("help")) + { + cout << "Avaible options:" << endl; + cmd.printParams(); + return 0; + } try { - if (argc < 2) - { - printHelp(); - return 1; - } + App app(cmd); + int flag = CVCL_DEVICE_TYPE_GPU; + if(cmd.get("s") == true) + flag = CVCL_DEVICE_TYPE_CPU; - Params args = Params::read(argc, argv); - if (help_showed) - return -1; - - int flags[2] = { CVCL_DEVICE_TYPE_GPU, CVCL_DEVICE_TYPE_CPU }; vector info; - - if(getDevice(info, flags[args.type]) == 0) + if(getDevice(info, flag) == 0) { throw runtime_error("Error: Did not find a valid OpenCL device!"); } cout << "Device name:" << info[0].DeviceName[0] << endl; - App app(args); app.run(); } catch (const exception& e) @@ -117,77 +109,39 @@ int main(int argc, char** argv) return 0; } - -Params::Params() -{ - method = BM; - ndisp = 64; - type = GPU; -} - - -Params Params::read(int argc, char** argv) -{ - Params p; - - for (int i = 1; i < argc; i++) - { - if (string(argv[i]) == "--left") p.left = argv[++i]; - else if (string(argv[i]) == "--right") p.right = argv[++i]; - else if (string(argv[i]) == "--method") - { - if (string(argv[i + 1]) == "BM") p.method = BM; - else if (string(argv[i + 1]) == "BP") p.method = BP; - else if (string(argv[i + 1]) == "CSBP") p.method = CSBP; - else throw runtime_error("unknown stereo match method: " + string(argv[i + 1])); - i++; - } - else if (string(argv[i]) == "--ndisp") p.ndisp = atoi(argv[++i]); - else if (string(argv[i]) == "--type") - { - string t(argv[++i]); - if (t == "cpu" || t == "CPU") - { - p.type = CPU; - } - else if (t == "gpu" || t == "GPU") - { - p.type = GPU; - } - else throw runtime_error("unknown device type: " + t); - } - else if (string(argv[i]) == "--help") printHelp(); - else throw runtime_error("unknown key: " + string(argv[i])); - } - - return p; -} - - -App::App(const Params& params) - : p(params), running(false) +App::App(CommandLineParser& cmd) + : running(false),method(BM) { cout << "stereo_match_ocl sample\n"; cout << "\nControls:\n" - << "\tesc - exit\n" - << "\tp - print current parameters\n" - << "\tg - convert source images into gray\n" - << "\tm - change stereo match method\n" - << "\ts - change Sobel prefiltering flag (for BM only)\n" - << "\t1/q - increase/decrease maximum disparity\n" - << "\t2/w - increase/decrease window size (for BM only)\n" - << "\t3/e - increase/decrease iteration count (for BP and CSBP only)\n" - << "\t4/r - increase/decrease level count (for BP and CSBP only)\n"; + << "\tesc - exit\n" + << "\tp - print current parameters\n" + << "\tg - convert source images into gray\n" + << "\tm - change stereo match method\n" + << "\ts - change Sobel prefiltering flag (for BM only)\n" + << "\t1/q - increase/decrease maximum disparity\n" + << "\t2/w - increase/decrease window size (for BM only)\n" + << "\t3/e - increase/decrease iteration count (for BP and CSBP only)\n" + << "\t4/r - increase/decrease level count (for BP and CSBP only)\n"; + l_img = cmd.get("l"); + r_img = cmd.get("r"); + string mstr = cmd.get("m"); + if(mstr == "BM") method = BM; + else if(mstr == "BP") method = BP; + else if(mstr == "CSBP") method = CSBP; + else cout << "unknown method!\n"; + ndisp = cmd.get("n"); + out_img = cmd.get("o"); } void App::run() { // Load images - left_src = imread(p.left); - right_src = imread(p.right); - if (left_src.empty()) throw runtime_error("can't open file \"" + p.left + "\""); - if (right_src.empty()) throw runtime_error("can't open file \"" + p.right + "\""); + left_src = imread(l_img); + right_src = imread(r_img); + if (left_src.empty()) throw runtime_error("can't open file \"" + l_img + "\""); + if (right_src.empty()) throw runtime_error("can't open file \"" + r_img + "\""); cvtColor(left_src, left, CV_BGR2GRAY); cvtColor(right_src, right, CV_BGR2GRAY); @@ -199,14 +153,15 @@ void App::run() imshow("right", right); // Set common parameters - bm.ndisp = p.ndisp; - bp.ndisp = p.ndisp; - csbp.ndisp = p.ndisp; + bm.ndisp = ndisp; + bp.ndisp = ndisp; + csbp.ndisp = ndisp; cout << endl; printParams(); running = true; + bool written = false; while (running) { @@ -214,9 +169,9 @@ void App::run() Mat disp; oclMat d_disp; workBegin(); - switch (p.method) + switch (method) { - case Params::BM: + case BM: if (d_left.channels() > 1 || d_right.channels() > 1) { cout << "BM doesn't support color images\n"; @@ -230,25 +185,28 @@ void App::run() } bm(d_left, d_right, d_disp); break; - case Params::BP: + case BP: bp(d_left, d_right, d_disp); break; - case Params::CSBP: + case CSBP: csbp(d_left, d_right, d_disp); break; } - ocl::finish(); workEnd(); // Show results d_disp.download(disp); - if (p.method != Params::BM) + if (method != BM) { disp.convertTo(disp, 0); } putText(disp, text(), Point(5, 25), FONT_HERSHEY_SIMPLEX, 1.0, Scalar::all(255)); imshow("disparity", disp); - + if(!written) + { + imwrite(out_img, disp); + written = true; + } handleKey((char)waitKey(3)); } } @@ -259,19 +217,19 @@ void App::printParams() const cout << "--- Parameters ---\n"; cout << "image_size: (" << left.cols << ", " << left.rows << ")\n"; cout << "image_channels: " << left.channels() << endl; - cout << "method: " << p.method_str() << endl - << "ndisp: " << p.ndisp << endl; - switch (p.method) + cout << "method: " << method_str() << endl + << "ndisp: " << ndisp << endl; + switch (method) { - case Params::BM: + case BM: cout << "win_size: " << bm.winSize << endl; cout << "prefilter_sobel: " << bm.preset << endl; break; - case Params::BP: + case BP: cout << "iter_count: " << bp.iters << endl; cout << "level_count: " << bp.levels << endl; break; - case Params::CSBP: + case CSBP: cout << "iter_count: " << csbp.iters << endl; cout << "level_count: " << csbp.levels << endl; break; @@ -287,11 +245,13 @@ void App::handleKey(char key) case 27: running = false; break; - case 'p': case 'P': + case 'p': + case 'P': printParams(); break; - case 'g': case 'G': - if (left.channels() == 1 && p.method != Params::BM) + case 'g': + case 'G': + if (left.channels() == 1 && method != BM) { left = left_src; right = right_src; @@ -307,23 +267,25 @@ void App::handleKey(char key) imshow("left", left); imshow("right", right); break; - case 'm': case 'M': - switch (p.method) + case 'm': + case 'M': + switch (method) { - case Params::BM: - p.method = Params::BP; + case BM: + method = BP; break; - case Params::BP: - p.method = Params::CSBP; + case BP: + method = CSBP; break; - case Params::CSBP: - p.method = Params::BM; + case CSBP: + method = BM; break; } - cout << "method: " << p.method_str() << endl; + cout << "method: " << method_str() << endl; break; - case 's': case 'S': - if (p.method == Params::BM) + case 's': + case 'S': + if (method == BM) { switch (bm.preset) { @@ -338,76 +300,80 @@ void App::handleKey(char key) } break; case '1': - p.ndisp = p.ndisp == 1 ? 8 : p.ndisp + 8; - cout << "ndisp: " << p.ndisp << endl; - bm.ndisp = p.ndisp; - bp.ndisp = p.ndisp; - csbp.ndisp = p.ndisp; + ndisp == 1 ? ndisp = 8 : ndisp += 8; + cout << "ndisp: " << ndisp << endl; + bm.ndisp = ndisp; + bp.ndisp = ndisp; + csbp.ndisp = ndisp; break; - case 'q': case 'Q': - p.ndisp = max(p.ndisp - 8, 1); - cout << "ndisp: " << p.ndisp << endl; - bm.ndisp = p.ndisp; - bp.ndisp = p.ndisp; - csbp.ndisp = p.ndisp; + case 'q': + case 'Q': + ndisp = max(ndisp - 8, 1); + cout << "ndisp: " << ndisp << endl; + bm.ndisp = ndisp; + bp.ndisp = ndisp; + csbp.ndisp = ndisp; break; case '2': - if (p.method == Params::BM) + if (method == BM) { bm.winSize = min(bm.winSize + 1, 51); cout << "win_size: " << bm.winSize << endl; } break; - case 'w': case 'W': - if (p.method == Params::BM) + case 'w': + case 'W': + if (method == BM) { bm.winSize = max(bm.winSize - 1, 2); cout << "win_size: " << bm.winSize << endl; } break; case '3': - if (p.method == Params::BP) + if (method == BP) { bp.iters += 1; cout << "iter_count: " << bp.iters << endl; } - else if (p.method == Params::CSBP) + else if (method == CSBP) { csbp.iters += 1; cout << "iter_count: " << csbp.iters << endl; } break; - case 'e': case 'E': - if (p.method == Params::BP) + case 'e': + case 'E': + if (method == BP) { bp.iters = max(bp.iters - 1, 1); cout << "iter_count: " << bp.iters << endl; } - else if (p.method == Params::CSBP) + else if (method == CSBP) { csbp.iters = max(csbp.iters - 1, 1); cout << "iter_count: " << csbp.iters << endl; } break; case '4': - if (p.method == Params::BP) + if (method == BP) { bp.levels += 1; cout << "level_count: " << bp.levels << endl; } - else if (p.method == Params::CSBP) + else if (method == CSBP) { csbp.levels += 1; cout << "level_count: " << csbp.levels << endl; } break; - case 'r': case 'R': - if (p.method == Params::BP) + case 'r': + case 'R': + if (method == BP) { bp.levels = max(bp.levels - 1, 1); cout << "level_count: " << bp.levels << endl; } - else if (p.method == Params::CSBP) + else if (method == CSBP) { csbp.levels = max(csbp.levels - 1, 1); cout << "level_count: " << csbp.levels << endl; diff --git a/samples/ocl/surf_matcher.cpp b/samples/ocl/surf_matcher.cpp index 038a8dc5cd..bee517fbca 100644 --- a/samples/ocl/surf_matcher.cpp +++ b/samples/ocl/surf_matcher.cpp @@ -1,48 +1,3 @@ -/*M/////////////////////////////////////////////////////////////////////////////////////// -// -// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. -// -// By downloading, copying, installing or using the software you agree to this license. -// If you do not agree to this license, do not download, install, -// copy or use the software. -// -// -// License Agreement -// For Open Source Computer Vision Library -// -// Copyright (C) 2010-2012, Multicoreware, Inc., all rights reserved. -// Copyright (C) 2010-2012, Advanced Micro Devices, Inc., all rights reserved. -// Third party copyrights are property of their respective owners. -// -// @Authors -// Peng Xiao, pengxiao@multicorewareinc.com -// -// Redistribution and use in source and binary forms, with or without modification, -// are permitted provided that the following conditions are met: -// -// * Redistribution's of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// * Redistribution's in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other oclMaterials provided with the distribution. -// -// * The name of the copyright holders may not be used to endorse or promote products -// derived from this software without specific prior written permission. -// -// This software is provided by the copyright holders and contributors as is and -// any express or implied warranties, including, but not limited to, the implied -// warranties of merchantability and fitness for a particular purpose are disclaimed. -// In no event shall the Intel Corporation or contributors be liable for any direct, -// indirect, incidental, special, exemplary, or consequential damages -// (including, but not limited to, procurement of substitute goods or services; -// loss of use, data, or profits; or business interruption) however caused -// and on any theory of liability, whether in contract, strict liability, -// or tort (including negligence or otherwise) arising in any way out of -// the use of this software, even if advised of the possibility of such damage. -// -//M*/ - #include #include #include "opencv2/core/core.hpp" @@ -61,27 +16,20 @@ const float GOOD_PORTION = 0.15f; namespace { -void help(); - -void help() -{ - std::cout << "\nThis program demonstrates using SURF_OCL features detector and descriptor extractor" << std::endl; - std::cout << "\nUsage:\n\tsurf_matcher --left --right [-c]" << std::endl; - std::cout << "\nExample:\n\tsurf_matcher --left box.png --right box_in_scene.png" << std::endl; -} int64 work_begin = 0; int64 work_end = 0; -void workBegin() -{ +void workBegin() +{ work_begin = getTickCount(); } void workEnd() { work_end = getTickCount() - work_begin; } -double getTime(){ +double getTime() +{ return work_end /((double)cvGetTickFrequency() * 1000.); } @@ -114,17 +62,17 @@ struct SURFMatcher Mat drawGoodMatches( const Mat& cpu_img1, const Mat& cpu_img2, - const vector& keypoints1, - const vector& keypoints2, + const vector& keypoints1, + const vector& keypoints2, vector& matches, vector& scene_corners_ - ) +) { - //-- Sort matches and preserve top 10% matches + //-- Sort matches and preserve top 10% matches std::sort(matches.begin(), matches.end()); std::vector< DMatch > good_matches; double minDist = matches.front().distance, - maxDist = matches.back().distance; + maxDist = matches.back().distance; const int ptsPairs = std::min(GOOD_PTS_MAX, (int)(matches.size() * GOOD_PORTION)); for( int i = 0; i < ptsPairs; i++ ) @@ -139,8 +87,8 @@ Mat drawGoodMatches( // drawing the results Mat img_matches; drawMatches( cpu_img1, keypoints1, cpu_img2, keypoints2, - good_matches, img_matches, Scalar::all(-1), Scalar::all(-1), - vector(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS ); + good_matches, img_matches, Scalar::all(-1), Scalar::all(-1), + vector(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS ); //-- Localize the object std::vector obj; @@ -154,28 +102,30 @@ Mat drawGoodMatches( } //-- Get the corners from the image_1 ( the object to be "detected" ) std::vector obj_corners(4); - obj_corners[0] = cvPoint(0,0); obj_corners[1] = cvPoint( cpu_img1.cols, 0 ); - obj_corners[2] = cvPoint( cpu_img1.cols, cpu_img1.rows ); obj_corners[3] = cvPoint( 0, cpu_img1.rows ); + obj_corners[0] = cvPoint(0,0); + obj_corners[1] = cvPoint( cpu_img1.cols, 0 ); + obj_corners[2] = cvPoint( cpu_img1.cols, cpu_img1.rows ); + obj_corners[3] = cvPoint( 0, cpu_img1.rows ); std::vector scene_corners(4); - + Mat H = findHomography( obj, scene, CV_RANSAC ); perspectiveTransform( obj_corners, scene_corners, H); scene_corners_ = scene_corners; - + //-- Draw lines between the corners (the mapped object in the scene - image_2 ) - line( img_matches, - scene_corners[0] + Point2f( (float)cpu_img1.cols, 0), scene_corners[1] + Point2f( (float)cpu_img1.cols, 0), - Scalar( 0, 255, 0), 2, CV_AA ); - line( img_matches, - scene_corners[1] + Point2f( (float)cpu_img1.cols, 0), scene_corners[2] + Point2f( (float)cpu_img1.cols, 0), - Scalar( 0, 255, 0), 2, CV_AA ); - line( img_matches, - scene_corners[2] + Point2f( (float)cpu_img1.cols, 0), scene_corners[3] + Point2f( (float)cpu_img1.cols, 0), - Scalar( 0, 255, 0), 2, CV_AA ); - line( img_matches, - scene_corners[3] + Point2f( (float)cpu_img1.cols, 0), scene_corners[0] + Point2f( (float)cpu_img1.cols, 0), - Scalar( 0, 255, 0), 2, CV_AA ); + line( img_matches, + scene_corners[0] + Point2f( (float)cpu_img1.cols, 0), scene_corners[1] + Point2f( (float)cpu_img1.cols, 0), + Scalar( 0, 255, 0), 2, CV_AA ); + line( img_matches, + scene_corners[1] + Point2f( (float)cpu_img1.cols, 0), scene_corners[2] + Point2f( (float)cpu_img1.cols, 0), + Scalar( 0, 255, 0), 2, CV_AA ); + line( img_matches, + scene_corners[2] + Point2f( (float)cpu_img1.cols, 0), scene_corners[3] + Point2f( (float)cpu_img1.cols, 0), + Scalar( 0, 255, 0), 2, CV_AA ); + line( img_matches, + scene_corners[3] + Point2f( (float)cpu_img1.cols, 0), scene_corners[0] + Point2f( (float)cpu_img1.cols, 0), + Scalar( 0, 255, 0), 2, CV_AA ); return img_matches; } @@ -185,6 +135,21 @@ Mat drawGoodMatches( // use cpu findHomography interface to calculate the transformation matrix int main(int argc, char* argv[]) { + const char* keys = + "{ h | help | false | print help message }" + "{ l | left | | specify left image }" + "{ r | right | | specify right image }" + "{ o | output | SURF_output.jpg | specify output save path (only works in CPU or GPU only mode) }" + "{ c | use_cpu | false | use CPU algorithms }" + "{ a | use_all | false | use both CPU and GPU algorithms}"; + CommandLineParser cmd(argc, argv, keys); + if (cmd.get("help")) + { + std::cout << "Avaible options:" << std::endl; + cmd.printParams(); + return 0; + } + vector info; if(cv::ocl::getDevice(info) == 0) { @@ -195,54 +160,38 @@ int main(int argc, char* argv[]) Mat cpu_img1, cpu_img2, cpu_img1_grey, cpu_img2_grey; oclMat img1, img2; - bool useCPU = false; + bool useCPU = cmd.get("c"); bool useGPU = false; - bool useALL = false; + bool useALL = cmd.get("a"); - for (int i = 1; i < argc; ++i) + string outpath = cmd.get("o"); + + cpu_img1 = imread(cmd.get("l")); + CV_Assert(!cpu_img1.empty()); + cvtColor(cpu_img1, cpu_img1_grey, CV_BGR2GRAY); + img1 = cpu_img1_grey; + + cpu_img2 = imread(cmd.get("r")); + CV_Assert(!cpu_img2.empty()); + cvtColor(cpu_img2, cpu_img2_grey, CV_BGR2GRAY); + img2 = cpu_img2_grey; + + if(useALL) { - if (string(argv[i]) == "--left") - { - cpu_img1 = imread(argv[++i]); - CV_Assert(!cpu_img1.empty()); - cvtColor(cpu_img1, cpu_img1_grey, CV_BGR2GRAY); - img1 = cpu_img1_grey; - } - else if (string(argv[i]) == "--right") - { - cpu_img2 = imread(argv[++i]); - CV_Assert(!cpu_img2.empty()); - cvtColor(cpu_img2, cpu_img2_grey, CV_BGR2GRAY); - img2 = cpu_img2_grey; - } - else if (string(argv[i]) == "-c") - { - useCPU = true; - useGPU = false; - useALL = false; - }else if(string(argv[i]) == "-g") - { - useGPU = true; - useCPU = false; - useALL = false; - }else if(string(argv[i]) == "-a") - { - useALL = true; - useCPU = false; - useGPU = false; - } - else if (string(argv[i]) == "--help") - { - help(); - return -1; - } + useCPU = false; + useGPU = false; } + else if(useCPU==false && useALL==false) + { + useGPU = true; + } + if(!useCPU) { std::cout - << "Device name:" - << info[0].DeviceName[0] - << std::endl; + << "Device name:" + << info[0].DeviceName[0] + << std::endl; } double surf_time = 0.; @@ -262,12 +211,12 @@ int main(int argc, char* argv[]) //instantiate detectors/matchers SURFDetector cpp_surf; SURFDetector ocl_surf; - + SURFMatcher cpp_matcher; SURFMatcher ocl_matcher; //-- start of timing section - if (useCPU) + if (useCPU) { for (int i = 0; i <= LOOP_NUM; i++) { @@ -298,7 +247,8 @@ int main(int argc, char* argv[]) surf_time = getTime(); std::cout << "SURF run time: " << surf_time / LOOP_NUM << " ms" << std::endl<<"\n"; - }else + } + else { //cpu runs for (int i = 0; i <= LOOP_NUM; i++) @@ -353,14 +303,14 @@ int main(int argc, char* argv[]) for(size_t i = 0; i < cpu_corner.size(); i++) { if((std::abs(cpu_corner[i].x - gpu_corner[i].x) > 10) - ||(std::abs(cpu_corner[i].y - gpu_corner[i].y) > 10)) + ||(std::abs(cpu_corner[i].y - gpu_corner[i].y) > 10)) { std::cout<<"Failed\n"; result = false; break; } result = true; - } + } if(result) std::cout<<"Passed\n"; } @@ -371,12 +321,15 @@ int main(int argc, char* argv[]) { namedWindow("cpu surf matches", 0); imshow("cpu surf matches", img_matches); + imwrite(outpath, img_matches); } else if(useGPU) { namedWindow("ocl surf matches", 0); imshow("ocl surf matches", img_matches); - }else + imwrite(outpath, img_matches); + } + else { namedWindow("cpu surf matches", 0); imshow("cpu surf matches", img_matches); diff --git a/samples/ocl/tvl1_optical_flow.cpp b/samples/ocl/tvl1_optical_flow.cpp new file mode 100644 index 0000000000..cff9692ed6 --- /dev/null +++ b/samples/ocl/tvl1_optical_flow.cpp @@ -0,0 +1,265 @@ +#include +#include +#include + +#include "opencv2/highgui/highgui.hpp" +#include "opencv2/ocl/ocl.hpp" +#include "opencv2/video/video.hpp" + +using namespace std; +using namespace cv; +using namespace cv::ocl; + +typedef unsigned char uchar; +#define LOOP_NUM 10 +int64 work_begin = 0; +int64 work_end = 0; + +static void workBegin() +{ + work_begin = getTickCount(); +} +static void workEnd() +{ + work_end += (getTickCount() - work_begin); +} +static double getTime() +{ + return work_end * 1000. / getTickFrequency(); +} + +template inline T clamp (T x, T a, T b) +{ + return ((x) > (a) ? ((x) < (b) ? (x) : (b)) : (a)); +} + +template inline T mapValue(T x, T a, T b, T c, T d) +{ + x = clamp(x, a, b); + return c + (d - c) * (x - a) / (b - a); +} + +static void getFlowField(const Mat& u, const Mat& v, Mat& flowField) +{ + float maxDisplacement = 1.0f; + + for (int i = 0; i < u.rows; ++i) + { + const float* ptr_u = u.ptr(i); + const float* ptr_v = v.ptr(i); + + for (int j = 0; j < u.cols; ++j) + { + float d = max(fabsf(ptr_u[j]), fabsf(ptr_v[j])); + + if (d > maxDisplacement) + maxDisplacement = d; + } + } + + flowField.create(u.size(), CV_8UC4); + + for (int i = 0; i < flowField.rows; ++i) + { + const float* ptr_u = u.ptr(i); + const float* ptr_v = v.ptr(i); + + + Vec4b* row = flowField.ptr(i); + + for (int j = 0; j < flowField.cols; ++j) + { + row[j][0] = 0; + row[j][1] = static_cast (mapValue (-ptr_v[j], -maxDisplacement, maxDisplacement, 0.0f, 255.0f)); + row[j][2] = static_cast (mapValue ( ptr_u[j], -maxDisplacement, maxDisplacement, 0.0f, 255.0f)); + row[j][3] = 255; + } + } +} + + +int main(int argc, const char* argv[]) +{ + static std::vector ocl_info; + ocl::getDevice(ocl_info); + //if you want to use undefault device, set it here + setDevice(ocl_info[0]); + + //set this to save kernel compile time from second time you run + ocl::setBinpath("./"); + const char* keys = + "{ h | help | false | print help message }" + "{ l | left | | specify left image }" + "{ r | right | | specify right image }" + "{ o | output | tvl1_output.jpg | specify output save path }" + "{ c | camera | 0 | enable camera capturing }" + "{ s | use_cpu | false | use cpu or gpu to process the image }" + "{ v | video | | use video as input }"; + + CommandLineParser cmd(argc, argv, keys); + + if (cmd.get("help")) + { + cout << "Usage: pyrlk_optical_flow [options]" << endl; + cout << "Avaible options:" << endl; + cmd.printParams(); + return 0; + } + + bool defaultPicturesFail = false; + string fname0 = cmd.get("l"); + string fname1 = cmd.get("r"); + string vdofile = cmd.get("v"); + string outpath = cmd.get("o"); + bool useCPU = cmd.get("s"); + bool useCamera = cmd.get("c"); + int inputName = cmd.get("c"); + + Mat frame0 = imread(fname0, cv::IMREAD_GRAYSCALE); + Mat frame1 = imread(fname1, cv::IMREAD_GRAYSCALE); + cv::Ptr alg = cv::createOptFlow_DualTVL1(); + cv::ocl::OpticalFlowDual_TVL1_OCL d_alg; + + + Mat flow, show_flow; + Mat flow_vec[2]; + if (frame0.empty() || frame1.empty()) + { + useCamera = true; + defaultPicturesFail = true; + CvCapture* capture = 0; + capture = cvCaptureFromCAM( inputName ); + if (!capture) + { + cout << "Can't load input images" << endl; + return -1; + } + } + + + if (useCamera) + { + CvCapture* capture = 0; + Mat frame, frameCopy; + Mat frame0Gray, frame1Gray; + Mat ptr0, ptr1; + + if(vdofile == "") + capture = cvCaptureFromCAM( inputName ); + else + capture = cvCreateFileCapture(vdofile.c_str()); + + int c = inputName ; + if(!capture) + { + if(vdofile == "") + cout << "Capture from CAM " << c << " didn't work" << endl; + else + cout << "Capture from file " << vdofile << " failed" <calc(ptr0, ptr1, flow); + split(flow, flow_vec); + } + else + { + oclMat d_flowx, d_flowy; + d_alg(oclMat(ptr0), oclMat(ptr1), d_flowx, d_flowy); + d_flowx.download(flow_vec[0]); + d_flowy.download(flow_vec[1]); + } + if (i%2 == 1) + frame1.copyTo(frameCopy); + else + frame0.copyTo(frameCopy); + getFlowField(flow_vec[0], flow_vec[1], show_flow); + imshow("PyrLK [Sparse]", show_flow); + } + + if( waitKey( 10 ) >= 0 ) + goto _cleanup_; + } + + waitKey(0); + +_cleanup_: + cvReleaseCapture( &capture ); + } + else + { +nocamera: + oclMat d_flowx, d_flowy; + for(int i = 0; i <= LOOP_NUM; i ++) + { + cout << "loop" << i << endl; + + if (i > 0) workBegin(); + if (useCPU) + { + alg->calc(frame0, frame1, flow); + split(flow, flow_vec); + } + else + { + d_alg(oclMat(frame0), oclMat(frame1), d_flowx, d_flowy); + d_flowx.download(flow_vec[0]); + d_flowy.download(flow_vec[1]); + } + if (i > 0 && i <= LOOP_NUM) + workEnd(); + + if (i == LOOP_NUM) + { + if (useCPU) + cout << "average CPU time (noCamera) : "; + else + cout << "average GPU time (noCamera) : "; + cout << getTime() / LOOP_NUM << " ms" << endl; + + getFlowField(flow_vec[0], flow_vec[1], show_flow); + imshow("PyrLK [Sparse]", show_flow); + imwrite(outpath, show_flow); + } + } + } + + waitKey(); + + return 0; +} \ No newline at end of file From d58421c08eb578fe449e6b90cbeb7731fdb1a44b Mon Sep 17 00:00:00 2001 From: Roman Donchenko Date: Wed, 19 Jun 2013 14:45:03 +0400 Subject: [PATCH 128/178] Make version-related test properties more useful. Namely, normalize their names to a common convention and remove useless text from their values. --- modules/ts/src/ts_func.cpp | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/modules/ts/src/ts_func.cpp b/modules/ts/src/ts_func.cpp index e2998149d5..9b6b535816 100644 --- a/modules/ts/src/ts_func.cpp +++ b/modules/ts/src/ts_func.cpp @@ -2940,27 +2940,29 @@ MatComparator::operator()(const char* expr1, const char* expr2, void printVersionInfo(bool useStdOut) { - ::testing::Test::RecordProperty("CV_VERSION", CV_VERSION); + ::testing::Test::RecordProperty("cv_version", CV_VERSION); if(useStdOut) std::cout << "OpenCV version: " << CV_VERSION << std::endl; std::string buildInfo( cv::getBuildInformation() ); size_t pos1 = buildInfo.find("Version control"); - size_t pos2 = buildInfo.find("\n", pos1);\ + size_t pos2 = buildInfo.find('\n', pos1); if(pos1 != std::string::npos && pos2 != std::string::npos) { - std::string ver( buildInfo.substr(pos1, pos2-pos1) ); - ::testing::Test::RecordProperty("Version_control", ver); - if(useStdOut) std::cout << ver << std::endl; + size_t value_start = buildInfo.rfind(' ', pos2) + 1; + std::string ver( buildInfo.substr(value_start, pos2 - value_start) ); + ::testing::Test::RecordProperty("cv_vcs_version", ver); + if (useStdOut) std::cout << "OpenCV VCS version: " << ver << std::endl; } pos1 = buildInfo.find("inner version"); - pos2 = buildInfo.find("\n", pos1);\ + pos2 = buildInfo.find('\n', pos1); if(pos1 != std::string::npos && pos2 != std::string::npos) { - std::string ver( buildInfo.substr(pos1, pos2-pos1) ); - ::testing::Test::RecordProperty("inner_version", ver); - if(useStdOut) std::cout << ver << std::endl; + size_t value_start = buildInfo.rfind(' ', pos2) + 1; + std::string ver( buildInfo.substr(value_start, pos2 - value_start) ); + ::testing::Test::RecordProperty("cv_inner_vcs_version", ver); + if(useStdOut) std::cout << "Inner VCS version: " << ver << std::endl; } #ifdef CV_PARALLEL_FRAMEWORK From 1ed5fb937d34348becbf9fa3c837d1bdfe9c6f95 Mon Sep 17 00:00:00 2001 From: Roman Donchenko Date: Wed, 19 Jun 2013 15:39:11 +0400 Subject: [PATCH 129/178] Give cv::ocl::CLAHE a virtual destructor, for the usual reasons. --- modules/ocl/include/opencv2/ocl/ocl.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/ocl/include/opencv2/ocl/ocl.hpp b/modules/ocl/include/opencv2/ocl/ocl.hpp index d6dd4b983c..3324b7932e 100644 --- a/modules/ocl/include/opencv2/ocl/ocl.hpp +++ b/modules/ocl/include/opencv2/ocl/ocl.hpp @@ -520,6 +520,8 @@ namespace cv virtual Size getTilesGridSize() const = 0; virtual void collectGarbage() = 0; + + virtual ~CLAHE() {} }; CV_EXPORTS Ptr createCLAHE(double clipLimit = 40.0, Size tileGridSize = Size(8, 8)); From 37d19b9c46ebfc4be922237b11913cf838959b49 Mon Sep 17 00:00:00 2001 From: Roman Donchenko Date: Wed, 19 Jun 2013 17:44:12 +0400 Subject: [PATCH 130/178] Pass the HAVE_QT* flags through the config header, like all others. I don't know why it didn't work for the original author, but it definitely works now. --- cmake/OpenCVFindLibsGUI.cmake | 3 --- cmake/templates/cvconfig.h.cmake | 6 ++++++ modules/highgui/src/window_QT.cpp | 1 + 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/cmake/OpenCVFindLibsGUI.cmake b/cmake/OpenCVFindLibsGUI.cmake index 59ce1cd05d..d685d23feb 100644 --- a/cmake/OpenCVFindLibsGUI.cmake +++ b/cmake/OpenCVFindLibsGUI.cmake @@ -24,7 +24,6 @@ if(WITH_QT) if(Qt5Core_FOUND AND Qt5Gui_FOUND AND Qt5Widgets_FOUND AND Qt5Test_FOUND AND Qt5Concurrent_FOUND) set(HAVE_QT5 ON) set(HAVE_QT ON) - add_definitions(-DHAVE_QT) find_package(Qt5OpenGL) if(Qt5OpenGL_FOUND) set(QT_QTOPENGL_FOUND ON) @@ -36,7 +35,6 @@ if(WITH_QT) find_package(Qt4 REQUIRED QtCore QtGui QtTest) if(QT4_FOUND) set(HAVE_QT TRUE) - add_definitions(-DHAVE_QT) # We need to define the macro this way, using cvconfig.h does not work endif() endif() endif() @@ -61,7 +59,6 @@ if(WITH_OPENGL) list(APPEND OPENCV_LINKER_LIBS ${OPENGL_LIBRARIES}) if(QT_QTOPENGL_FOUND) set(HAVE_QT_OPENGL TRUE) - add_definitions(-DHAVE_QT_OPENGL) else() ocv_include_directories(${OPENGL_INCLUDE_DIR}) endif() diff --git a/cmake/templates/cvconfig.h.cmake b/cmake/templates/cvconfig.h.cmake index db46af4b6d..f12730988d 100644 --- a/cmake/templates/cvconfig.h.cmake +++ b/cmake/templates/cvconfig.h.cmake @@ -228,3 +228,9 @@ /* Clp support */ #cmakedefine HAVE_CLP + +/* Qt support */ +#cmakedefine HAVE_QT + +/* Qt OpenGL support */ +#cmakedefine HAVE_QT_OPENGL diff --git a/modules/highgui/src/window_QT.cpp b/modules/highgui/src/window_QT.cpp index 0c50c7070c..64d57ab269 100644 --- a/modules/highgui/src/window_QT.cpp +++ b/modules/highgui/src/window_QT.cpp @@ -38,6 +38,7 @@ //--------------------Google Code 2010 -- Yannick Verdie--------------------// +#include "precomp.hpp" #if defined(HAVE_QT) From 936236e4b1b190d7bc33a33df982fac8ab6cfc76 Mon Sep 17 00:00:00 2001 From: Roman Donchenko Date: Tue, 11 Jun 2013 16:06:51 +0400 Subject: [PATCH 131/178] Extended the CPU/GPU selection mechanism in performance tests. Now it allows choosing between arbitrary implementation variants. --- modules/gpu/perf/perf_main.cpp | 2 +- modules/nonfree/perf/perf_main.cpp | 2 +- modules/superres/perf/perf_main.cpp | 2 +- modules/ts/include/opencv2/ts/ts_perf.hpp | 20 +++-- modules/ts/src/ts_perf.cpp | 101 ++++++++++++++-------- 5 files changed, 81 insertions(+), 46 deletions(-) diff --git a/modules/gpu/perf/perf_main.cpp b/modules/gpu/perf/perf_main.cpp index a7ac1ccce8..f9f3a68547 100644 --- a/modules/gpu/perf/perf_main.cpp +++ b/modules/gpu/perf/perf_main.cpp @@ -44,4 +44,4 @@ using namespace perf; -CV_PERF_TEST_MAIN(gpu, printCudaInfo()) +CV_PERF_TEST_MAIN_WITH_IMPLS(gpu, ("cuda", "plain"), printCudaInfo()) diff --git a/modules/nonfree/perf/perf_main.cpp b/modules/nonfree/perf/perf_main.cpp index de1242149e..373e08aedb 100644 --- a/modules/nonfree/perf/perf_main.cpp +++ b/modules/nonfree/perf/perf_main.cpp @@ -1,4 +1,4 @@ #include "perf_precomp.hpp" #include "opencv2/ts/gpu_perf.hpp" -CV_PERF_TEST_MAIN(nonfree, perf::printCudaInfo()) +CV_PERF_TEST_MAIN_WITH_IMPLS(nonfree, ("cuda", "plain"), perf::printCudaInfo()) diff --git a/modules/superres/perf/perf_main.cpp b/modules/superres/perf/perf_main.cpp index adc69e6e8b..90a7f51251 100644 --- a/modules/superres/perf/perf_main.cpp +++ b/modules/superres/perf/perf_main.cpp @@ -44,4 +44,4 @@ using namespace perf; -CV_PERF_TEST_MAIN(superres, printCudaInfo()) +CV_PERF_TEST_MAIN_WITH_IMPLS(superres, ("cuda", "plain"), printCudaInfo()) diff --git a/modules/ts/include/opencv2/ts/ts_perf.hpp b/modules/ts/include/opencv2/ts/ts_perf.hpp index fe57655157..eb5e3e554e 100644 --- a/modules/ts/include/opencv2/ts/ts_perf.hpp +++ b/modules/ts/include/opencv2/ts/ts_perf.hpp @@ -210,18 +210,13 @@ private: #define SANITY_CHECK_KEYPOINTS(array, ...) ::perf::Regression::addKeypoints(this, #array, array , ## __VA_ARGS__) #define SANITY_CHECK_MATCHES(array, ...) ::perf::Regression::addMatches(this, #array, array , ## __VA_ARGS__) -#ifdef HAVE_CUDA class CV_EXPORTS GpuPerf { public: static bool targetDevice(); }; -# define PERF_RUN_GPU() ::perf::GpuPerf::targetDevice() -#else -# define PERF_RUN_GPU() false -#endif - +#define PERF_RUN_GPU() ::perf::GpuPerf::targetDevice() /*****************************************************************************************\ * Container for performance metrics * @@ -263,7 +258,10 @@ public: TestBase(); static void Init(int argc, const char* const argv[]); + static void Init(const std::vector & availableImpls, + int argc, const char* const argv[]); static std::string getDataPath(const std::string& relativePath); + static std::string getSelectedImpl(); protected: virtual void PerfTestBody() = 0; @@ -476,18 +474,24 @@ CV_EXPORTS void PrintTo(const Size& sz, ::std::ostream* os); INSTANTIATE_TEST_CASE_P(/*none*/, fixture##_##name, params);\ void fixture##_##name::PerfTestBody() +#define CV_PERF_UNWRAP_IMPLS(...) __VA_ARGS__ -#define CV_PERF_TEST_MAIN(testsuitname, ...) \ +// "plain" should always be one of the implementations +#define CV_PERF_TEST_MAIN_WITH_IMPLS(testsuitname, impls, ...) \ int main(int argc, char **argv)\ {\ while (++argc >= (--argc,-1)) {__VA_ARGS__; break;} /*this ugly construction is needed for VS 2005*/\ + std::string impls_[] = { CV_PERF_UNWRAP_IMPLS impls };\ ::perf::Regression::Init(#testsuitname);\ - ::perf::TestBase::Init(argc, argv);\ + ::perf::TestBase::Init(std::vector(impls_, impls_ + sizeof impls_ / sizeof *impls_),\ + argc, argv);\ ::testing::InitGoogleTest(&argc, argv);\ cvtest::printVersionInfo();\ return RUN_ALL_TESTS();\ } +#define CV_PERF_TEST_MAIN(testsuitname, ...) CV_PERF_TEST_MAIN_WITH_IMPLS(testsuitname, ("plain"), __VA_ARGS__) + #define TEST_CYCLE_N(n) for(declare.iterations(n); startTimer(), next(); stopTimer()) #define TEST_CYCLE() for(; startTimer(), next(); stopTimer()) #define TEST_CYCLE_MULTIRUN(runsNum) for(declare.runs(runsNum); startTimer(), next(); stopTimer()) for(int r = 0; r < runsNum; ++r) diff --git a/modules/ts/src/ts_perf.cpp b/modules/ts/src/ts_perf.cpp index c375e7c388..3b73ddcf73 100644 --- a/modules/ts/src/ts_perf.cpp +++ b/modules/ts/src/ts_perf.cpp @@ -14,30 +14,10 @@ int64 TestBase::timeLimitDefault = 0; unsigned int TestBase::iterationsLimitDefault = (unsigned int)(-1); int64 TestBase::_timeadjustment = 0; -const std::string command_line_keys = - "{ |perf_max_outliers |8 |percent of allowed outliers}" - "{ |perf_min_samples |10 |minimal required numer of samples}" - "{ |perf_force_samples |100 |force set maximum number of samples for all tests}" - "{ |perf_seed |809564 |seed for random numbers generator}" - "{ |perf_threads |-1 |the number of worker threads, if parallel execution is enabled}" - "{ |perf_write_sanity |false |create new records for sanity checks}" - "{ |perf_verify_sanity |false |fail tests having no regression data for sanity checks}" -#ifdef ANDROID - "{ |perf_time_limit |6.0 |default time limit for a single test (in seconds)}" - "{ |perf_affinity_mask |0 |set affinity mask for the main thread}" - "{ |perf_log_power_checkpoints | |additional xml logging for power measurement}" -#else - "{ |perf_time_limit |3.0 |default time limit for a single test (in seconds)}" -#endif - "{ |perf_max_deviation |1.0 |}" - "{h |help |false |print help info}" -#ifdef HAVE_CUDA - "{ |perf_run_cpu |false |run GPU performance tests for analogical CPU functions}" - "{ |perf_cuda_device |0 |run GPU test suite onto specific CUDA capable device}" - "{ |perf_cuda_info_only |false |print an information about system and an available CUDA devices and then exit.}" -#endif -; +// Item [0] will be considered the default implementation. +static std::vector available_impls; +static std::string param_impl; static double param_max_outliers; static double param_max_deviation; static unsigned int param_min_samples; @@ -48,7 +28,6 @@ static int param_threads; static bool param_write_sanity; static bool param_verify_sanity; #ifdef HAVE_CUDA -static bool param_run_cpu; static int param_cuda_device; #endif @@ -577,11 +556,12 @@ Regression& Regression::operator() (const std::string& name, cv::InputArray arra std::string nodename = getCurrentTestNodeName(); -#ifdef HAVE_CUDA - static const std::string prefix = (param_run_cpu)? "CPU_" : "GPU_"; + // This is a hack for compatibility and it should eventually get removed. + // gpu's tests don't even have CPU sanity data anymore. if(suiteName == "gpu") - nodename = prefix + nodename; -#endif + { + nodename = (PERF_RUN_GPU() ? "GPU_" : "CPU_") + nodename; + } cv::FileNode n = rootIn[nodename]; if(n.isNone()) @@ -646,6 +626,42 @@ performance_metrics::performance_metrics() void TestBase::Init(int argc, const char* const argv[]) { + std::vector plain_only; + plain_only.push_back("plain"); + TestBase::Init(plain_only, argc, argv); +} + +void TestBase::Init(const std::vector & availableImpls, + int argc, const char* const argv[]) +{ + available_impls = availableImpls; + + const std::string command_line_keys = + "{ |perf_max_outliers |8 |percent of allowed outliers}" + "{ |perf_min_samples |10 |minimal required numer of samples}" + "{ |perf_force_samples |100 |force set maximum number of samples for all tests}" + "{ |perf_seed |809564 |seed for random numbers generator}" + "{ |perf_threads |-1 |the number of worker threads, if parallel execution is enabled}" + "{ |perf_write_sanity |false |create new records for sanity checks}" + "{ |perf_verify_sanity |false |fail tests having no regression data for sanity checks}" + "{ |perf_impl |" + available_impls[0] + + "|the implementation variant of functions under test}" + "{ |perf_run_cpu |false |deprecated, equivalent to --perf_impl=plain}" +#ifdef ANDROID + "{ |perf_time_limit |6.0 |default time limit for a single test (in seconds)}" + "{ |perf_affinity_mask |0 |set affinity mask for the main thread}" + "{ |perf_log_power_checkpoints | |additional xml logging for power measurement}" +#else + "{ |perf_time_limit |3.0 |default time limit for a single test (in seconds)}" +#endif + "{ |perf_max_deviation |1.0 |}" + "{h |help |false |print help info}" +#ifdef HAVE_CUDA + "{ |perf_cuda_device |0 |run GPU test suite onto specific CUDA capable device}" + "{ |perf_cuda_info_only |false |print an information about system and an available CUDA devices and then exit.}" +#endif + ; + cv::CommandLineParser args(argc, argv, command_line_keys.c_str()); if (args.get("help")) { @@ -656,6 +672,7 @@ void TestBase::Init(int argc, const char* const argv[]) ::testing::AddGlobalTestEnvironment(new PerfEnvironment); + param_impl = args.get("perf_run_cpu") ? "plain" : args.get("perf_impl"); param_max_outliers = std::min(100., std::max(0., args.get("perf_max_outliers"))); param_min_samples = std::max(1u, args.get("perf_min_samples")); param_max_deviation = std::max(0., args.get("perf_max_deviation")); @@ -670,19 +687,28 @@ void TestBase::Init(int argc, const char* const argv[]) log_power_checkpoints = args.get("perf_log_power_checkpoints"); #endif + if (std::find(available_impls.begin(), available_impls.end(), param_impl) == available_impls.end()) + { + printf("No such implementation: %s\n", param_impl.c_str()); + exit(1); + } + #ifdef HAVE_CUDA bool printOnly = args.get("perf_cuda_info_only"); if (printOnly) exit(0); +#endif + + if (available_impls.size() > 1) + printf("[----------]\n[ INFO ] \tImplementation variant: %s.\n[----------]\n", param_impl.c_str()), fflush(stdout); + +#ifdef HAVE_CUDA - param_run_cpu = args.get("perf_run_cpu"); param_cuda_device = std::max(0, std::min(cv::gpu::getCudaEnabledDeviceCount(), args.get("perf_cuda_device"))); - if (param_run_cpu) - printf("[----------]\n[ GPU INFO ] \tRun test suite on CPU.\n[----------]\n"), fflush(stdout); - else + if (param_impl == "cuda") { cv::gpu::DeviceInfo info(param_cuda_device); if (!info.isCompatible()) @@ -708,6 +734,13 @@ void TestBase::Init(int argc, const char* const argv[]) _timeadjustment = _calibrate(); } + +std::string TestBase::getSelectedImpl() +{ + return param_impl; +} + + int64 TestBase::_calibrate() { class _helper : public ::perf::TestBase @@ -1325,12 +1358,10 @@ void perf::sort(std::vector& pts, cv::InputOutputArray descriptors /*****************************************************************************************\ * ::perf::GpuPerf \*****************************************************************************************/ -#ifdef HAVE_CUDA bool perf::GpuPerf::targetDevice() { - return !param_run_cpu; + return param_impl == "cuda"; } -#endif /*****************************************************************************************\ * ::perf::PrintTo From b581f27249250fa7454be0e56e1dfe0bbf264ab6 Mon Sep 17 00:00:00 2001 From: Roman Donchenko Date: Tue, 18 Jun 2013 18:40:55 +0400 Subject: [PATCH 132/178] Made perf tests record module name, selected implementation and number of threads. --- modules/ts/include/opencv2/ts/ts_perf.hpp | 9 ++++++--- modules/ts/src/ts_perf.cpp | 5 +++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/modules/ts/include/opencv2/ts/ts_perf.hpp b/modules/ts/include/opencv2/ts/ts_perf.hpp index eb5e3e554e..ba09964034 100644 --- a/modules/ts/include/opencv2/ts/ts_perf.hpp +++ b/modules/ts/include/opencv2/ts/ts_perf.hpp @@ -260,6 +260,7 @@ public: static void Init(int argc, const char* const argv[]); static void Init(const std::vector & availableImpls, int argc, const char* const argv[]); + static void RecordRunParameters(); static std::string getDataPath(const std::string& relativePath); static std::string getSelectedImpl(); @@ -477,20 +478,22 @@ CV_EXPORTS void PrintTo(const Size& sz, ::std::ostream* os); #define CV_PERF_UNWRAP_IMPLS(...) __VA_ARGS__ // "plain" should always be one of the implementations -#define CV_PERF_TEST_MAIN_WITH_IMPLS(testsuitname, impls, ...) \ +#define CV_PERF_TEST_MAIN_WITH_IMPLS(modulename, impls, ...) \ int main(int argc, char **argv)\ {\ while (++argc >= (--argc,-1)) {__VA_ARGS__; break;} /*this ugly construction is needed for VS 2005*/\ std::string impls_[] = { CV_PERF_UNWRAP_IMPLS impls };\ - ::perf::Regression::Init(#testsuitname);\ + ::perf::Regression::Init(#modulename);\ ::perf::TestBase::Init(std::vector(impls_, impls_ + sizeof impls_ / sizeof *impls_),\ argc, argv);\ ::testing::InitGoogleTest(&argc, argv);\ cvtest::printVersionInfo();\ + ::testing::Test::RecordProperty("cv_module_name", #modulename);\ + ::perf::TestBase::RecordRunParameters();\ return RUN_ALL_TESTS();\ } -#define CV_PERF_TEST_MAIN(testsuitname, ...) CV_PERF_TEST_MAIN_WITH_IMPLS(testsuitname, ("plain"), __VA_ARGS__) +#define CV_PERF_TEST_MAIN(modulename, ...) CV_PERF_TEST_MAIN_WITH_IMPLS(modulename, ("plain"), __VA_ARGS__) #define TEST_CYCLE_N(n) for(declare.iterations(n); startTimer(), next(); stopTimer()) #define TEST_CYCLE() for(; startTimer(), next(); stopTimer()) diff --git a/modules/ts/src/ts_perf.cpp b/modules/ts/src/ts_perf.cpp index 3b73ddcf73..e61878e19f 100644 --- a/modules/ts/src/ts_perf.cpp +++ b/modules/ts/src/ts_perf.cpp @@ -734,6 +734,11 @@ void TestBase::Init(const std::vector & availableImpls, _timeadjustment = _calibrate(); } +void TestBase::RecordRunParameters() +{ + ::testing::Test::RecordProperty("cv_implementation", param_impl); + ::testing::Test::RecordProperty("cv_num_threads", param_threads); +} std::string TestBase::getSelectedImpl() { From 7a104d2793ed0fde70b2ce3185823912d2455075 Mon Sep 17 00:00:00 2001 From: Roman Donchenko Date: Wed, 19 Jun 2013 18:47:15 +0400 Subject: [PATCH 133/178] Added an option to print available implementation variants. --- modules/ts/src/ts_perf.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/modules/ts/src/ts_perf.cpp b/modules/ts/src/ts_perf.cpp index e61878e19f..c2c1ee6bd2 100644 --- a/modules/ts/src/ts_perf.cpp +++ b/modules/ts/src/ts_perf.cpp @@ -646,6 +646,7 @@ void TestBase::Init(const std::vector & availableImpls, "{ |perf_verify_sanity |false |fail tests having no regression data for sanity checks}" "{ |perf_impl |" + available_impls[0] + "|the implementation variant of functions under test}" + "{ |perf_list_impls |false |list available implementation variants and exit}" "{ |perf_run_cpu |false |deprecated, equivalent to --perf_impl=plain}" #ifdef ANDROID "{ |perf_time_limit |6.0 |default time limit for a single test (in seconds)}" @@ -687,6 +688,19 @@ void TestBase::Init(const std::vector & availableImpls, log_power_checkpoints = args.get("perf_log_power_checkpoints"); #endif + bool param_list_impls = args.get("perf_list_impls"); + + if (param_list_impls) + { + fputs("Available implementation variants:", stdout); + for (size_t i = 0; i < available_impls.size(); ++i) { + putchar(' '); + fputs(available_impls[i].c_str(), stdout); + } + putchar('\n'); + exit(0); + } + if (std::find(available_impls.begin(), available_impls.end(), param_impl) == available_impls.end()) { printf("No such implementation: %s\n", param_impl.c_str()); From 51a672ec40d7637888fa6aae07247e4e737c64ce Mon Sep 17 00:00:00 2001 From: Roman Donchenko Date: Wed, 19 Jun 2013 19:16:18 +0400 Subject: [PATCH 134/178] Disabled the cuda variant when CUDA is not available. --- modules/gpu/perf/perf_main.cpp | 6 +++++- modules/nonfree/perf/perf_main.cpp | 6 +++++- modules/superres/perf/perf_main.cpp | 6 +++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/modules/gpu/perf/perf_main.cpp b/modules/gpu/perf/perf_main.cpp index f9f3a68547..db362af8fd 100644 --- a/modules/gpu/perf/perf_main.cpp +++ b/modules/gpu/perf/perf_main.cpp @@ -44,4 +44,8 @@ using namespace perf; -CV_PERF_TEST_MAIN_WITH_IMPLS(gpu, ("cuda", "plain"), printCudaInfo()) +CV_PERF_TEST_MAIN_WITH_IMPLS(gpu, ( +#ifdef HAVE_CUDA + "cuda", +#endif + "plain"), printCudaInfo()) diff --git a/modules/nonfree/perf/perf_main.cpp b/modules/nonfree/perf/perf_main.cpp index 373e08aedb..a3245186a7 100644 --- a/modules/nonfree/perf/perf_main.cpp +++ b/modules/nonfree/perf/perf_main.cpp @@ -1,4 +1,8 @@ #include "perf_precomp.hpp" #include "opencv2/ts/gpu_perf.hpp" -CV_PERF_TEST_MAIN_WITH_IMPLS(nonfree, ("cuda", "plain"), perf::printCudaInfo()) +CV_PERF_TEST_MAIN_WITH_IMPLS(nonfree, ( +#ifdef HAVE_CUDA + "cuda", +#endif + "plain"), perf::printCudaInfo()) diff --git a/modules/superres/perf/perf_main.cpp b/modules/superres/perf/perf_main.cpp index 90a7f51251..8bf217e30e 100644 --- a/modules/superres/perf/perf_main.cpp +++ b/modules/superres/perf/perf_main.cpp @@ -44,4 +44,8 @@ using namespace perf; -CV_PERF_TEST_MAIN_WITH_IMPLS(superres, ("cuda", "plain"), printCudaInfo()) +CV_PERF_TEST_MAIN_WITH_IMPLS(superres, ( +#ifdef HAVE_CUDA + "cuda", +#endif + "plain"), printCudaInfo()) From c1f4fe1637aa1279d7eef7ef95f26ea92c9de967 Mon Sep 17 00:00:00 2001 From: peng xiao Date: Thu, 20 Jun 2013 11:26:22 +0800 Subject: [PATCH 135/178] Fix a bug of convertTo. The bug was found that all 3-channel oclMat's were converted to 4-channel oclMat's after using convertTo function. --- modules/ocl/src/matrix_operations.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ocl/src/matrix_operations.cpp b/modules/ocl/src/matrix_operations.cpp index 268a1fe9b5..172dfa5a89 100644 --- a/modules/ocl/src/matrix_operations.cpp +++ b/modules/ocl/src/matrix_operations.cpp @@ -394,7 +394,7 @@ void cv::ocl::oclMat::convertTo( oclMat &dst, int rtype, double alpha, double be if( rtype < 0 ) rtype = type(); else - rtype = CV_MAKETYPE(CV_MAT_DEPTH(rtype), oclchannels()); + rtype = CV_MAKETYPE(CV_MAT_DEPTH(rtype), channels()); //int scn = channels(); int sdepth = depth(), ddepth = CV_MAT_DEPTH(rtype); From 3e2c4563134e2b88408ad7b1a280a312eb46d4a4 Mon Sep 17 00:00:00 2001 From: Roman Donchenko Date: Thu, 20 Jun 2013 14:27:51 +0400 Subject: [PATCH 136/178] A few minor improvements to the XLS report generator. * In comparison column headers, switched the order of labels, so that it's "to" vs "from". * When a test was present, but not run successfully, put its status in the corresponding cell instead of coloring it gray. --- modules/ts/misc/xls-report.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/modules/ts/misc/xls-report.py b/modules/ts/misc/xls-report.py index f6278bae00..c13842cdca 100755 --- a/modules/ts/misc/xls-report.py +++ b/modules/ts/misc/xls-report.py @@ -4,6 +4,7 @@ from __future__ import division import ast import logging +import numbers import os, os.path import re @@ -52,8 +53,7 @@ def collect_xml(collection, configuration, xml_fullname): for test in sorted(parseLogFile(xml_fullname)): test_results = module_tests.setdefault((test.shortName(), test.param()), {}) - if test.status == 'run': - test_results[configuration] = test.get("gmean") + test_results[configuration] = test.get("gmean") if test.status == 'run' else test.status def main(): arg_parser = ArgumentParser(description='Build an XLS performance report.') @@ -117,7 +117,7 @@ def main(): for i, caption in enumerate(['Module', 'Test', 'Image\nsize', 'Data\ntype', 'Parameters'] + config_names + [None] - + [comp['from'] + '\nvs\n' + comp['to'] for comp in sheet_comparisons]): + + [comp['to'] + '\nvs\n' + comp['from'] for comp in sheet_comparisons]): sheet.row(0).write(i, caption, header_style) row = 1 @@ -143,13 +143,13 @@ def main(): sheet.write(row, 5 + i, None, no_time_style) for i, comp in enumerate(sheet_comparisons): - left = configs.get(comp["from"]) - right = configs.get(comp["to"]) + cmp_from = configs.get(comp["from"]) + cmp_to = configs.get(comp["to"]) col = 5 + len(config_names) + 1 + i - if left is not None and right is not None: + if isinstance(cmp_from, numbers.Number) and isinstance(cmp_to, numbers.Number): try: - speedup = left / right + speedup = cmp_from / cmp_to sheet.write(row, col, speedup, good_speedup_style if speedup > 1.1 else bad_speedup_style if speedup < 0.9 else speedup_style) From 3ea4836a0a7e20455e6199d5bd32f0a462d286c6 Mon Sep 17 00:00:00 2001 From: Roman Donchenko Date: Thu, 20 Jun 2013 15:16:22 +0400 Subject: [PATCH 137/178] Changed the impls argument to be an array name. Turns out, you can't use preprocessor directives inside macro arguments. Who'd have thought? --- modules/gpu/perf/perf_main.cpp | 9 ++++++--- modules/nonfree/perf/perf_main.cpp | 9 ++++++--- modules/superres/perf/perf_main.cpp | 9 ++++++--- modules/ts/include/opencv2/ts/ts_perf.hpp | 24 ++++++++++++++--------- 4 files changed, 33 insertions(+), 18 deletions(-) diff --git a/modules/gpu/perf/perf_main.cpp b/modules/gpu/perf/perf_main.cpp index db362af8fd..53a19ca412 100644 --- a/modules/gpu/perf/perf_main.cpp +++ b/modules/gpu/perf/perf_main.cpp @@ -44,8 +44,11 @@ using namespace perf; -CV_PERF_TEST_MAIN_WITH_IMPLS(gpu, ( +static const char * impls[] = { #ifdef HAVE_CUDA - "cuda", + "cuda", #endif - "plain"), printCudaInfo()) + "plain" +}; + +CV_PERF_TEST_MAIN_WITH_IMPLS(gpu, impls, printCudaInfo()) diff --git a/modules/nonfree/perf/perf_main.cpp b/modules/nonfree/perf/perf_main.cpp index a3245186a7..d5f4a1a512 100644 --- a/modules/nonfree/perf/perf_main.cpp +++ b/modules/nonfree/perf/perf_main.cpp @@ -1,8 +1,11 @@ #include "perf_precomp.hpp" #include "opencv2/ts/gpu_perf.hpp" -CV_PERF_TEST_MAIN_WITH_IMPLS(nonfree, ( +static const char * impls[] = { #ifdef HAVE_CUDA - "cuda", + "cuda", #endif - "plain"), perf::printCudaInfo()) + "plain" +}; + +CV_PERF_TEST_MAIN_WITH_IMPLS(nonfree, impls, perf::printCudaInfo()) diff --git a/modules/superres/perf/perf_main.cpp b/modules/superres/perf/perf_main.cpp index 8bf217e30e..0a8ab5deaa 100644 --- a/modules/superres/perf/perf_main.cpp +++ b/modules/superres/perf/perf_main.cpp @@ -44,8 +44,11 @@ using namespace perf; -CV_PERF_TEST_MAIN_WITH_IMPLS(superres, ( +static const char * impls[] = { #ifdef HAVE_CUDA - "cuda", + "cuda", #endif - "plain"), printCudaInfo()) + "plain" +}; + +CV_PERF_TEST_MAIN_WITH_IMPLS(superres, impls, printCudaInfo()) diff --git a/modules/ts/include/opencv2/ts/ts_perf.hpp b/modules/ts/include/opencv2/ts/ts_perf.hpp index ba09964034..1e68cd49b0 100644 --- a/modules/ts/include/opencv2/ts/ts_perf.hpp +++ b/modules/ts/include/opencv2/ts/ts_perf.hpp @@ -475,25 +475,31 @@ CV_EXPORTS void PrintTo(const Size& sz, ::std::ostream* os); INSTANTIATE_TEST_CASE_P(/*none*/, fixture##_##name, params);\ void fixture##_##name::PerfTestBody() -#define CV_PERF_UNWRAP_IMPLS(...) __VA_ARGS__ -// "plain" should always be one of the implementations -#define CV_PERF_TEST_MAIN_WITH_IMPLS(modulename, impls, ...) \ -int main(int argc, char **argv)\ -{\ +#define CV_PERF_TEST_MAIN_INTERNALS(modulename, impls, ...) \ while (++argc >= (--argc,-1)) {__VA_ARGS__; break;} /*this ugly construction is needed for VS 2005*/\ - std::string impls_[] = { CV_PERF_UNWRAP_IMPLS impls };\ ::perf::Regression::Init(#modulename);\ - ::perf::TestBase::Init(std::vector(impls_, impls_ + sizeof impls_ / sizeof *impls_),\ + ::perf::TestBase::Init(std::vector(impls, impls + sizeof impls / sizeof *impls),\ argc, argv);\ ::testing::InitGoogleTest(&argc, argv);\ cvtest::printVersionInfo();\ ::testing::Test::RecordProperty("cv_module_name", #modulename);\ ::perf::TestBase::RecordRunParameters();\ - return RUN_ALL_TESTS();\ + return RUN_ALL_TESTS(); + +// impls must be an array, not a pointer; "plain" should always be one of the implementations +#define CV_PERF_TEST_MAIN_WITH_IMPLS(modulename, impls, ...) \ +int main(int argc, char **argv)\ +{\ + CV_PERF_TEST_MAIN_INTERNALS(modulename, impls, __VA_ARGS__)\ } -#define CV_PERF_TEST_MAIN(modulename, ...) CV_PERF_TEST_MAIN_WITH_IMPLS(modulename, ("plain"), __VA_ARGS__) +#define CV_PERF_TEST_MAIN(modulename, ...) \ +int main(int argc, char **argv)\ +{\ + const char * plain_only[] = { "plain" };\ + CV_PERF_TEST_MAIN_INTERNALS(modulename, plain_only, __VA_ARGS__)\ +} #define TEST_CYCLE_N(n) for(declare.iterations(n); startTimer(), next(); stopTimer()) #define TEST_CYCLE() for(; startTimer(), next(); stopTimer()) From 57317c3196fb9d5fbe9e00b16453dea7d534ac11 Mon Sep 17 00:00:00 2001 From: Roman Donchenko Date: Thu, 20 Jun 2013 19:39:02 +0400 Subject: [PATCH 138/178] Use log formatting as intended. --- modules/ts/misc/xls-report.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/ts/misc/xls-report.py b/modules/ts/misc/xls-report.py index c13842cdca..e79bb123dd 100755 --- a/modules/ts/misc/xls-report.py +++ b/modules/ts/misc/xls-report.py @@ -79,7 +79,7 @@ def main(): sheet_conf = ast.literal_eval(sheet_conf_file.read()) except Exception: sheet_conf = {} - logging.debug('no sheet.conf for {}'.format(sheet_path)) + logging.debug('no sheet.conf for %s', sheet_path) sheet_conf = dict(global_conf.items() + sheet_conf.items()) @@ -90,14 +90,14 @@ def main(): config_names = [p for p in os.listdir(sheet_path) if os.path.isdir(os.path.join(sheet_path, p))] except Exception as e: - logging.warning(e) + logging.warning('error while determining configuration names for %s: %s', sheet_path, e) continue collection = {} for configuration, configuration_path in \ [(c, os.path.join(sheet_path, c)) for c in config_names]: - logging.info('processing {}'.format(configuration_path)) + logging.info('processing %s', configuration_path) for xml_fullname in glob(os.path.join(configuration_path, '*.xml')): collect_xml(collection, configuration, xml_fullname) From 2688e22cb595c6b652538c36dacf2bb35cc58ac3 Mon Sep 17 00:00:00 2001 From: Roman Donchenko Date: Thu, 20 Jun 2013 19:34:32 +0400 Subject: [PATCH 139/178] Made xls-report.py use global properties in XML files. Now it can determine, without looking at the file name, both the module name and the configuration name (the latter with a little help from the configuration file). --- modules/ts/misc/testlog_parser.py | 48 +++++++++++++++---- modules/ts/misc/xls-report.py | 79 ++++++++++++++++++++++--------- 2 files changed, 96 insertions(+), 31 deletions(-) diff --git a/modules/ts/misc/testlog_parser.py b/modules/ts/misc/testlog_parser.py index 8ab21417ca..5d478645b2 100755 --- a/modules/ts/misc/testlog_parser.py +++ b/modules/ts/misc/testlog_parser.py @@ -1,6 +1,9 @@ #!/usr/bin/env python -import sys, re, os.path +import collections +import re +import os.path +import sys from xml.dom.minidom import parse class TestInfo(object): @@ -159,12 +162,31 @@ class TestInfo(object): return 1 return 0 +# This is a Sequence for compatibility with old scripts, +# which treat parseLogFile's return value as a list. +class TestRunInfo(collections.Sequence): + def __init__(self, properties, tests): + self.properties = properties + self.tests = tests + + def __len__(self): + return len(self.tests) + + def __getitem__(self, key): + return self.tests[key] + def parseLogFile(filename): - tests = [] log = parse(filename) - for case in log.getElementsByTagName("testcase"): - tests.append(TestInfo(case)) - return tests + + properties = { + attr_name[3:]: attr_value + for (attr_name, attr_value) in log.documentElement.attributes.items() + if attr_name.startswith('cv_') + } + + tests = map(TestInfo, log.getElementsByTagName("testcase")) + + return TestRunInfo(properties, tests) if __name__ == "__main__": @@ -173,8 +195,18 @@ if __name__ == "__main__": exit(0) for arg in sys.argv[1:]: - print "Tests found in", arg - tests = parseLogFile(arg) - for t in sorted(tests): + print "Processing {}...".format(arg) + + run = parseLogFile(arg) + + print "Properties:" + + for (prop_name, prop_value) in run.properties.items(): + print "\t{} = {}".format(prop_name, prop_value) + + print "Tests:" + + for t in sorted(run.tests): t.dump() + print diff --git a/modules/ts/misc/xls-report.py b/modules/ts/misc/xls-report.py index e79bb123dd..a3cf8daca2 100755 --- a/modules/ts/misc/xls-report.py +++ b/modules/ts/misc/xls-report.py @@ -3,6 +3,7 @@ from __future__ import division import ast +import fnmatch import logging import numbers import os, os.path @@ -45,15 +46,55 @@ no_speedup_style = no_time_style error_speedup_style = xlwt.easyxf('pattern: pattern solid, fore_color orange') header_style = xlwt.easyxf('font: bold true; alignment: horizontal centre, vertical top, wrap True') -def collect_xml(collection, configuration, xml_fullname): - xml_fname = os.path.split(xml_fullname)[1] - module = xml_fname[:xml_fname.index('_')] +class Collector(object): + def __init__(self, config_match_func): + self.__config_cache = {} + self.config_match_func = config_match_func + self.tests = {} - module_tests = collection.setdefault(module, OrderedDict()) + def collect_from(self, xml_path): + run = parseLogFile(xml_path) - for test in sorted(parseLogFile(xml_fullname)): - test_results = module_tests.setdefault((test.shortName(), test.param()), {}) - test_results[configuration] = test.get("gmean") if test.status == 'run' else test.status + module = run.properties['module_name'] + + properties = run.properties.copy() + del properties['module_name'] + + props_key = tuple(sorted(properties.iteritems())) # dicts can't be keys + + if props_key in self.__config_cache: + configuration = self.__config_cache[props_key] + else: + configuration = self.config_match_func(properties) + + if configuration is None: + logging.warning('failed to match properties to a configuration: %r', props_key) + else: + same_config_props = [it[0] for it in self.__config_cache.iteritems() if it[1] == configuration] + if len(same_config_props) > 0: + logging.warning('property set %r matches the same configuration %r as property set %r', + props_key, configuration, same_config_props[0]) + + self.__config_cache[props_key] = configuration + + if configuration is None: return + + module_tests = self.tests.setdefault(module, OrderedDict()) + + for test in run.tests: + test_results = module_tests.setdefault((test.shortName(), test.param()), {}) + test_results[configuration] = test.get("gmean") if test.status == 'run' else test.status + +def make_match_func(matchers): + def match_func(properties): + for matcher in matchers: + if all(properties.get(name) == value + for (name, value) in matcher['properties'].iteritems()): + return matcher['name'] + + return None + + return match_func def main(): arg_parser = ArgumentParser(description='Build an XLS performance report.') @@ -83,23 +124,15 @@ def main(): sheet_conf = dict(global_conf.items() + sheet_conf.items()) - if 'configurations' in sheet_conf: - config_names = sheet_conf['configurations'] - else: - try: - config_names = [p for p in os.listdir(sheet_path) - if os.path.isdir(os.path.join(sheet_path, p))] - except Exception as e: - logging.warning('error while determining configuration names for %s: %s', sheet_path, e) - continue + config_names = sheet_conf.get('configurations', []) + config_matchers = sheet_conf.get('configuration_matchers', []) - collection = {} + collector = Collector(make_match_func(config_matchers)) - for configuration, configuration_path in \ - [(c, os.path.join(sheet_path, c)) for c in config_names]: - logging.info('processing %s', configuration_path) - for xml_fullname in glob(os.path.join(configuration_path, '*.xml')): - collect_xml(collection, configuration, xml_fullname) + for root, _, filenames in os.walk(sheet_path): + logging.info('looking in %s', root) + for filename in fnmatch.filter(filenames, '*.xml'): + collector.collect_from(os.path.join(root, filename)) sheet = wb.add_sheet(sheet_conf.get('sheet_name', os.path.basename(os.path.abspath(sheet_path)))) @@ -126,7 +159,7 @@ def main(): module_styles = {module: xlwt.easyxf('pattern: pattern solid, fore_color {}'.format(color)) for module, color in module_colors.iteritems()} - for module, tests in sorted(collection.iteritems()): + for module, tests in sorted(collector.tests.iteritems()): for ((test, param), configs) in tests.iteritems(): sheet.write(row, 0, module, module_styles.get(module, xlwt.Style.default_style)) sheet.write(row, 1, test) From e12963826337daa5ff67198e25b17f0dfdbf2edf Mon Sep 17 00:00:00 2001 From: peng xiao Date: Fri, 21 Jun 2013 14:05:29 +0800 Subject: [PATCH 140/178] Add a workaround to interpolate between oclMat and Input/OutputArray. --- modules/core/include/opencv2/core/core.hpp | 3 ++- modules/core/src/matrix.cpp | 30 ++++++++++++++++++++++ modules/ocl/include/opencv2/ocl/ocl.hpp | 8 ++++++ modules/ocl/src/matrix_operations.cpp | 29 +++++++++++++++++++++ 4 files changed, 69 insertions(+), 1 deletion(-) diff --git a/modules/core/include/opencv2/core/core.hpp b/modules/core/include/opencv2/core/core.hpp index 2b7791958f..5ff31fe3a8 100644 --- a/modules/core/include/opencv2/core/core.hpp +++ b/modules/core/include/opencv2/core/core.hpp @@ -1322,7 +1322,8 @@ public: EXPR = 6 << KIND_SHIFT, OPENGL_BUFFER = 7 << KIND_SHIFT, OPENGL_TEXTURE = 8 << KIND_SHIFT, - GPU_MAT = 9 << KIND_SHIFT + GPU_MAT = 9 << KIND_SHIFT, + OCL_MAT =10 << KIND_SHIFT }; _InputArray(); diff --git a/modules/core/src/matrix.cpp b/modules/core/src/matrix.cpp index 7acb0e0dbd..c4c0041dd9 100644 --- a/modules/core/src/matrix.cpp +++ b/modules/core/src/matrix.cpp @@ -980,6 +980,11 @@ Mat _InputArray::getMat(int i) const return !v.empty() ? Mat(size(i), t, (void*)&v[0]) : Mat(); } + if( k == OCL_MAT ) + { + CV_Error(-1, "Not implemented"); + } + CV_Assert( k == STD_VECTOR_MAT ); //if( k == STD_VECTOR_MAT ) { @@ -1062,6 +1067,11 @@ void _InputArray::getMatVector(vector& mv) const return; } + if( k == OCL_MAT ) + { + CV_Error(-1, "Not implemented"); + } + CV_Assert( k == STD_VECTOR_MAT ); //if( k == STD_VECTOR_MAT ) { @@ -1189,6 +1199,11 @@ Size _InputArray::size(int i) const return tex->size(); } + if( k == OCL_MAT ) + { + CV_Error(-1, "Not implemented"); + } + CV_Assert( k == GPU_MAT ); //if( k == GPU_MAT ) { @@ -1303,6 +1318,11 @@ bool _InputArray::empty() const if( k == OPENGL_TEXTURE ) return ((const ogl::Texture2D*)obj)->empty(); + if( k == OCL_MAT ) + { + CV_Error(-1, "Not implemented"); + } + CV_Assert( k == GPU_MAT ); //if( k == GPU_MAT ) return ((const gpu::GpuMat*)obj)->empty(); @@ -1523,6 +1543,11 @@ void _OutputArray::create(int dims, const int* sizes, int mtype, int i, bool all return; } + if( k == OCL_MAT ) + { + CV_Error(-1, "Not implemented"); + } + if( k == NONE ) { CV_Error(CV_StsNullPtr, "create() called for the missing output array" ); @@ -1634,6 +1659,11 @@ void _OutputArray::release() const return; } + if( k == OCL_MAT ) + { + CV_Error(-1, "Not implemented"); + } + CV_Assert( k == STD_VECTOR_MAT ); //if( k == STD_VECTOR_MAT ) { diff --git a/modules/ocl/include/opencv2/ocl/ocl.hpp b/modules/ocl/include/opencv2/ocl/ocl.hpp index d6dd4b983c..9fdd8f3e99 100644 --- a/modules/ocl/include/opencv2/ocl/ocl.hpp +++ b/modules/ocl/include/opencv2/ocl/ocl.hpp @@ -248,6 +248,11 @@ namespace cv operator Mat() const; void download(cv::Mat &m) const; + //! convert to _InputArray + operator _InputArray(); + + //! convert to _OutputArray + operator _OutputArray(); //! returns a new oclMatrix header for the specified row oclMat row(int y) const; @@ -387,6 +392,9 @@ namespace cv int wholecols; }; + // convert InputArray/OutputArray to oclMat + CV_EXPORTS oclMat& getOclMat(InputArray src); + CV_EXPORTS oclMat& getOclMat(OutputArray src); ///////////////////// mat split and merge ///////////////////////////////// //! Compose a multi-channel array from several single-channel arrays diff --git a/modules/ocl/src/matrix_operations.cpp b/modules/ocl/src/matrix_operations.cpp index 268a1fe9b5..dc7deebe38 100644 --- a/modules/ocl/src/matrix_operations.cpp +++ b/modules/ocl/src/matrix_operations.cpp @@ -74,6 +74,7 @@ namespace cv } } + //////////////////////////////////////////////////////////////////////// // convert_C3C4 static void convert_C3C4(const cl_mem &src, oclMat &dst) @@ -227,6 +228,34 @@ void cv::ocl::oclMat::upload(const Mat &m) //download_channels = m.channels(); } +cv::ocl::oclMat::operator cv::_InputArray() +{ + _InputArray newInputArray; + newInputArray.flags = cv::_InputArray::OCL_MAT; + newInputArray.obj = reinterpret_cast(this); + return newInputArray; +} + +cv::ocl::oclMat::operator cv::_OutputArray() +{ + _OutputArray newOutputArray; + newOutputArray.flags = cv::_InputArray::OCL_MAT; + newOutputArray.obj = reinterpret_cast(this); + return newOutputArray; +} + +cv::ocl::oclMat& cv::ocl::getOclMat(InputArray src) +{ + CV_Assert(src.flags & cv::_InputArray::OCL_MAT); + return *reinterpret_cast(src.obj); +} + +cv::ocl::oclMat& cv::ocl::getOclMat(OutputArray src) +{ + CV_Assert(src.flags & cv::_InputArray::OCL_MAT); + return *reinterpret_cast(src.obj); +} + void cv::ocl::oclMat::download(cv::Mat &m) const { CV_DbgAssert(!this->empty()); From 6326739b443c0e87a251446893ee18225eeaf428 Mon Sep 17 00:00:00 2001 From: yao Date: Fri, 21 Jun 2013 14:50:08 +0800 Subject: [PATCH 141/178] a bug fix in stereo_match sample --- samples/ocl/stereo_match.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/samples/ocl/stereo_match.cpp b/samples/ocl/stereo_match.cpp index 565744baa6..abe75c70e1 100644 --- a/samples/ocl/stereo_match.cpp +++ b/samples/ocl/stereo_match.cpp @@ -192,10 +192,9 @@ void App::run() csbp(d_left, d_right, d_disp); break; } - workEnd(); - // Show results d_disp.download(disp); + workEnd(); if (method != BM) { disp.convertTo(disp, 0); From 290c8db0a85ff6e4a9d84243624852a21190598f Mon Sep 17 00:00:00 2001 From: peng xiao Date: Fri, 21 Jun 2013 14:51:23 +0800 Subject: [PATCH 142/178] Revise naming for getOclMat function. --- modules/core/src/matrix.cpp | 12 ++++++------ modules/ocl/include/opencv2/ocl/ocl.hpp | 6 +++--- modules/ocl/src/matrix_operations.cpp | 4 ++-- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/modules/core/src/matrix.cpp b/modules/core/src/matrix.cpp index c4c0041dd9..5a3600b9b3 100644 --- a/modules/core/src/matrix.cpp +++ b/modules/core/src/matrix.cpp @@ -982,7 +982,7 @@ Mat _InputArray::getMat(int i) const if( k == OCL_MAT ) { - CV_Error(-1, "Not implemented"); + CV_Error(CV_StsNotImplemented, "This method is not implemented for oclMat yet"); } CV_Assert( k == STD_VECTOR_MAT ); @@ -1069,7 +1069,7 @@ void _InputArray::getMatVector(vector& mv) const if( k == OCL_MAT ) { - CV_Error(-1, "Not implemented"); + CV_Error(CV_StsNotImplemented, "This method is not implemented for oclMat yet"); } CV_Assert( k == STD_VECTOR_MAT ); @@ -1201,7 +1201,7 @@ Size _InputArray::size(int i) const if( k == OCL_MAT ) { - CV_Error(-1, "Not implemented"); + CV_Error(CV_StsNotImplemented, "This method is not implemented for oclMat yet"); } CV_Assert( k == GPU_MAT ); @@ -1320,7 +1320,7 @@ bool _InputArray::empty() const if( k == OCL_MAT ) { - CV_Error(-1, "Not implemented"); + CV_Error(CV_StsNotImplemented, "This method is not implemented for oclMat yet"); } CV_Assert( k == GPU_MAT ); @@ -1545,7 +1545,7 @@ void _OutputArray::create(int dims, const int* sizes, int mtype, int i, bool all if( k == OCL_MAT ) { - CV_Error(-1, "Not implemented"); + CV_Error(CV_StsNotImplemented, "This method is not implemented for oclMat yet"); } if( k == NONE ) @@ -1661,7 +1661,7 @@ void _OutputArray::release() const if( k == OCL_MAT ) { - CV_Error(-1, "Not implemented"); + CV_Error(CV_StsNotImplemented, "This method is not implemented for oclMat yet"); } CV_Assert( k == STD_VECTOR_MAT ); diff --git a/modules/ocl/include/opencv2/ocl/ocl.hpp b/modules/ocl/include/opencv2/ocl/ocl.hpp index 9fdd8f3e99..ed887e61a6 100644 --- a/modules/ocl/include/opencv2/ocl/ocl.hpp +++ b/modules/ocl/include/opencv2/ocl/ocl.hpp @@ -392,9 +392,9 @@ namespace cv int wholecols; }; - // convert InputArray/OutputArray to oclMat - CV_EXPORTS oclMat& getOclMat(InputArray src); - CV_EXPORTS oclMat& getOclMat(OutputArray src); + // convert InputArray/OutputArray to oclMat references + CV_EXPORTS oclMat& getOclMatRef(InputArray src); + CV_EXPORTS oclMat& getOclMatRef(OutputArray src); ///////////////////// mat split and merge ///////////////////////////////// //! Compose a multi-channel array from several single-channel arrays diff --git a/modules/ocl/src/matrix_operations.cpp b/modules/ocl/src/matrix_operations.cpp index dc7deebe38..dcaf0418ac 100644 --- a/modules/ocl/src/matrix_operations.cpp +++ b/modules/ocl/src/matrix_operations.cpp @@ -244,13 +244,13 @@ cv::ocl::oclMat::operator cv::_OutputArray() return newOutputArray; } -cv::ocl::oclMat& cv::ocl::getOclMat(InputArray src) +cv::ocl::oclMat& cv::ocl::getOclMatRef(InputArray src) { CV_Assert(src.flags & cv::_InputArray::OCL_MAT); return *reinterpret_cast(src.obj); } -cv::ocl::oclMat& cv::ocl::getOclMat(OutputArray src) +cv::ocl::oclMat& cv::ocl::getOclMatRef(OutputArray src) { CV_Assert(src.flags & cv::_InputArray::OCL_MAT); return *reinterpret_cast(src.obj); From 0e3a9eaf980b484a9d5f56c0f38c92164e9c5910 Mon Sep 17 00:00:00 2001 From: Roman Donchenko Date: Fri, 21 Jun 2013 13:43:16 +0400 Subject: [PATCH 143/178] Made Collector render property sets as dicts instead of tuples of pairs. --- modules/ts/misc/xls-report.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/modules/ts/misc/xls-report.py b/modules/ts/misc/xls-report.py index a3cf8daca2..2dcbf89cfa 100755 --- a/modules/ts/misc/xls-report.py +++ b/modules/ts/misc/xls-report.py @@ -52,6 +52,12 @@ class Collector(object): self.config_match_func = config_match_func self.tests = {} + # Format a sorted sequence of pairs as if it was a dictionary. + # We can't just use a dictionary instead, since we want to preserve the sorted order of the keys. + @staticmethod + def __format_config_cache_key(pairs): + return '{' + ', '.join(repr(k) + ': ' + repr(v) for (k, v) in pairs) + '}' + def collect_from(self, xml_path): run = parseLogFile(xml_path) @@ -68,12 +74,15 @@ class Collector(object): configuration = self.config_match_func(properties) if configuration is None: - logging.warning('failed to match properties to a configuration: %r', props_key) + logging.warning('failed to match properties to a configuration: %s', + Collector.__format_config_cache_key(props_key)) else: same_config_props = [it[0] for it in self.__config_cache.iteritems() if it[1] == configuration] if len(same_config_props) > 0: - logging.warning('property set %r matches the same configuration %r as property set %r', - props_key, configuration, same_config_props[0]) + logging.warning('property set %s matches the same configuration %r as property set %s', + Collector.__format_config_cache_key(props_key), + configuration, + Collector.__format_config_cache_key(same_config_props[0])) self.__config_cache[props_key] = configuration From d4a8b87645f6df2ee5a61c8b0c52a4248d2c600a Mon Sep 17 00:00:00 2001 From: Roman Donchenko Date: Fri, 21 Jun 2013 16:45:17 +0400 Subject: [PATCH 144/178] Wrote relevant docs. --- modules/ts/misc/xls-report.py | 79 ++++++++++++++++++++++++++++------- 1 file changed, 64 insertions(+), 15 deletions(-) diff --git a/modules/ts/misc/xls-report.py b/modules/ts/misc/xls-report.py index 2dcbf89cfa..e911314e92 100755 --- a/modules/ts/misc/xls-report.py +++ b/modules/ts/misc/xls-report.py @@ -1,5 +1,69 @@ #!/usr/bin/env python +""" + This script can generate XLS reports from OpenCV tests' XML output files. + + To use it, first, create a directory for each machine you ran tests on. + Each such directory will become a sheet in the report. Put each XML file + into the corresponding directory. + + Then, create your configuration file(s). You can have a global configuration + file (specified with the -c option), and per-sheet configuration files, which + must be called sheet.conf and placed in the directory corresponding to the sheet. + The settings in the per-sheet configuration file will override those in the + global configuration file, if both are present. + + A configuration file must consist of a Python dictionary. The following keys + will be recognized: + + * 'comparisons': [{'from': string, 'to': string}] + List of configurations to compare performance between. For each item, + the sheet will have a column showing speedup from configuration named + 'from' to configuration named "to". + + * 'configuration_matchers': [{'properties': {string: object}, 'name': string}] + Instructions for matching test run property sets to configuration names. + + For each found XML file: + + 1) All attributes of the root element starting with the prefix 'cv_' are + placed in a dictionary, with the cv_ prefix stripped and the cv_module_name + element deleted. + + 2) The first matcher for which the XML's file property set contains the same + keys with equal values as its 'properties' dictionary is searched for. + A missing property can be matched by using None as the value. + + Corollary 1: you should place more specific matchers before less specific + ones. + + Corollary 2: an empty 'properties' dictionary matches every property set. + + 3) If a matching matcher is found, its 'name' string is presumed to be the name + of the configuration the XML file corresponds to. Otherwise, a warning is + printed. A warning is also printed if two different property sets match to the + same configuration name. + + * 'configurations': [string] + List of names for compile-time and runtime configurations of OpenCV. + Each item will correspond to a column of the sheet. + + * 'module_colors': {string: string} + Mapping from module name to color name. In the sheet, cells containing module + names from this mapping will be colored with the corresponding color. You can + find the list of available colors here: + . + + * 'sheet_name': string + Name for the sheet. If this parameter is missing, the name of sheet's directory + will be used. + + Note that all keys are optional, although to get useful results, you'll want to + specify at least 'configurations' and 'configuration_matchers'. + + Finally, run the script. Use the --help option for usage information. +""" + from __future__ import division import ast @@ -18,21 +82,6 @@ import xlwt from testlog_parser import parseLogFile -# To build XLS report you neet to put your xmls (OpenCV tests output) in the -# following way: -# -# "root" --- folder, representing the whole XLS document. It contains several -# subfolders --- sheet-paths of the XLS document. Each sheet-path contains it's -# subfolders --- config-paths. Config-paths are columns of the sheet and -# they contains xmls files --- output of OpenCV modules testing. -# Config-path means OpenCV build configuration, including different -# options such as NEON, TBB, GPU enabling/disabling. -# -# root -# root\sheet_path -# root\sheet_path\configuration1 (column 1) -# root\sheet_path\configuration2 (column 2) - re_image_size = re.compile(r'^ \d+ x \d+$', re.VERBOSE) re_data_type = re.compile(r'^ (?: 8 | 16 | 32 | 64 ) [USF] C [1234] $', re.VERBOSE) From c16316c4b49fe73b768210d43db46405f177e9fe Mon Sep 17 00:00:00 2001 From: Roman Donchenko Date: Fri, 21 Jun 2013 12:43:16 +0400 Subject: [PATCH 145/178] Replaced the semi-public CV_PARALLEL_FRAMEWORK macro with a function. That way, core/internal.hpp doesn't have to depend on cvconfig.h, which we don't ship. --- .../core/include/opencv2/core/internal.hpp | 31 ++++--------------- modules/core/src/parallel.cpp | 31 +++++++++++++++++++ modules/ts/src/precomp.hpp | 1 - modules/ts/src/ts_func.cpp | 11 +++---- 4 files changed, 42 insertions(+), 32 deletions(-) diff --git a/modules/core/include/opencv2/core/internal.hpp b/modules/core/include/opencv2/core/internal.hpp index 10cd2caf93..606c62f8f5 100644 --- a/modules/core/include/opencv2/core/internal.hpp +++ b/modules/core/include/opencv2/core/internal.hpp @@ -50,7 +50,8 @@ #include -#include "cvconfig.h" +#include "opencv2/core/core.hpp" +#include "opencv2/core/types_c.h" #if defined WIN32 || defined _WIN32 # ifndef WIN32 @@ -186,30 +187,6 @@ CV_INLINE IppiSize ippiSize(int width, int height) # include "opencv2/core/eigen.hpp" #endif -#ifdef _OPENMP -# define HAVE_OPENMP -#endif - -#ifdef __APPLE__ -# define HAVE_GCD -#endif - -#if defined _MSC_VER && _MSC_VER >= 1600 -# define HAVE_CONCURRENCY -#endif - -#if defined HAVE_TBB && TBB_VERSION_MAJOR*100 + TBB_VERSION_MINOR >= 202 -# define CV_PARALLEL_FRAMEWORK "tbb" -#elif defined HAVE_CSTRIPES -# define CV_PARALLEL_FRAMEWORK "cstripes" -#elif defined HAVE_OPENMP -# define CV_PARALLEL_FRAMEWORK "openmp" -#elif defined HAVE_GCD -# define CV_PARALLEL_FRAMEWORK "gcd" -#elif defined HAVE_CONCURRENCY -# define CV_PARALLEL_FRAMEWORK "ms-concurrency" -#endif - #ifdef __cplusplus namespace cv @@ -277,6 +254,10 @@ namespace cv body(range); } #endif + + // Returns a static string if there is a parallel framework, + // NULL otherwise. + CV_EXPORTS const char* currentParallelFramework(); } //namespace cv #define CV_INIT_ALGORITHM(classname, algname, memberinit) \ diff --git a/modules/core/src/parallel.cpp b/modules/core/src/parallel.cpp index 51b165275f..0a9ed09871 100644 --- a/modules/core/src/parallel.cpp +++ b/modules/core/src/parallel.cpp @@ -61,6 +61,17 @@ #endif #endif +#ifdef _OPENMP + #define HAVE_OPENMP +#endif + +#ifdef __APPLE__ + #define HAVE_GCD +#endif + +#if defined _MSC_VER && _MSC_VER >= 1600 + #define HAVE_CONCURRENCY +#endif /* IMPORTANT: always use the same order of defines 1. HAVE_TBB - 3rdparty library, should be explicitly enabled @@ -99,6 +110,18 @@ #endif #endif +#if defined HAVE_TBB && TBB_VERSION_MAJOR*100 + TBB_VERSION_MINOR >= 202 +# define CV_PARALLEL_FRAMEWORK "tbb" +#elif defined HAVE_CSTRIPES +# define CV_PARALLEL_FRAMEWORK "cstripes" +#elif defined HAVE_OPENMP +# define CV_PARALLEL_FRAMEWORK "openmp" +#elif defined HAVE_GCD +# define CV_PARALLEL_FRAMEWORK "gcd" +#elif defined HAVE_CONCURRENCY +# define CV_PARALLEL_FRAMEWORK "ms-concurrency" +#endif + namespace cv { ParallelLoopBody::~ParallelLoopBody() {} @@ -465,6 +488,14 @@ int cv::getNumberOfCPUs(void) #endif } +const char* cv::currentParallelFramework() { +#ifdef CV_PARALLEL_FRAMEWORK + return CV_PARALLEL_FRAMEWORK; +#else + return NULL; +#endif +} + CV_IMPL void cvSetNumThreads(int nt) { cv::setNumThreads(nt); diff --git a/modules/ts/src/precomp.hpp b/modules/ts/src/precomp.hpp index 0b2adacc4d..a74417da47 100644 --- a/modules/ts/src/precomp.hpp +++ b/modules/ts/src/precomp.hpp @@ -1,4 +1,3 @@ -#include "opencv2/core/core.hpp" #include "opencv2/core/core_c.h" #include "opencv2/core/internal.hpp" #include "opencv2/ts/ts.hpp" diff --git a/modules/ts/src/ts_func.cpp b/modules/ts/src/ts_func.cpp index e2998149d5..0186e9c8f0 100644 --- a/modules/ts/src/ts_func.cpp +++ b/modules/ts/src/ts_func.cpp @@ -2963,13 +2963,12 @@ void printVersionInfo(bool useStdOut) if(useStdOut) std::cout << ver << std::endl; } -#ifdef CV_PARALLEL_FRAMEWORK - ::testing::Test::RecordProperty("cv_parallel_framework", CV_PARALLEL_FRAMEWORK); - if (useStdOut) - { - std::cout << "Parallel framework: " << CV_PARALLEL_FRAMEWORK << std::endl; + const char* parallel_framework = currentParallelFramework(); + + if (parallel_framework) { + ::testing::Test::RecordProperty("cv_parallel_framework", parallel_framework); + if (useStdOut) std::cout << "Parallel framework: " << parallel_framework << std::endl; } -#endif std::string cpu_features; From e3577c2f586aaa83c858139d67c1317259416454 Mon Sep 17 00:00:00 2001 From: Alexander Smorkalov Date: Mon, 8 Apr 2013 18:13:49 -0700 Subject: [PATCH 146/178] Build with dev release of TBB enabled. --- 3rdparty/tbb/CMakeLists.txt | 47 ++++++++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/3rdparty/tbb/CMakeLists.txt b/3rdparty/tbb/CMakeLists.txt index af1581349e..9dcb63b7f0 100644 --- a/3rdparty/tbb/CMakeLists.txt +++ b/3rdparty/tbb/CMakeLists.txt @@ -1,13 +1,20 @@ #Cross compile TBB from source project(tbb) -# 4.1 update 2 - works fine -set(tbb_ver "tbb41_20130116oss") -set(tbb_url "http://threadingbuildingblocks.org/sites/default/files/software_releases/source/tbb41_20130116oss_src.tgz") -set(tbb_md5 "3809790e1001a1b32d59c9fee590ee85") +# 4.1 update 3 dev - works fine +set(tbb_ver "tbb41_20130401oss") +set(tbb_url "http://threadingbuildingblocks.org/sites/default/files/software_releases/source/tbb41_20130401oss_src.tgz") +set(tbb_md5 "f2f591a0d2ca8f801e221ce7d9ea84bb") set(tbb_version_file "version_string.ver") ocv_warnings_disable(CMAKE_CXX_FLAGS -Wshadow) +# 4.1 update 2 - works fine +#set(tbb_ver "tbb41_20130116oss") +#set(tbb_url "http://threadingbuildingblocks.org/sites/default/files/software_releases/source/tbb41_20130116oss_src.tgz") +#set(tbb_md5 "3809790e1001a1b32d59c9fee590ee85") +#set(tbb_version_file "version_string.ver") +#ocv_warnings_disable(CMAKE_CXX_FLAGS -Wshadow) + # 4.1 update 1 - works fine #set(tbb_ver "tbb41_20121003oss") #set(tbb_url "http://threadingbuildingblocks.org/sites/default/files/software_releases/source/tbb41_20121003oss_src.tgz") @@ -107,7 +114,8 @@ if(NOT EXISTS "${tbb_src_dir}") RESULT_VARIABLE tbb_untar_RESULT) if(NOT tbb_untar_RESULT EQUAL 0 OR NOT EXISTS "${tbb_src_dir}") - message(FATAL_ERROR "Failed to unpack TBB sources") + message(FATAL_ERROR "Failed to unpack TBB sources (${tbb_untar_RESULT} ${tbb_src_dir})") + endif() endif() @@ -124,11 +132,12 @@ list(APPEND lib_srcs "${tbb_src_dir}/src/rml/client/rml_tbb.cpp") if (WIN32) add_definitions(-D__TBB_DYNAMIC_LOAD_ENABLED=0 - -D__TBB_BUILD=1 - -D_UNICODE - -DUNICODE - -DWINAPI_FAMILY=WINAPI_FAMILY_APP - -DDO_ITT_NOTIFY=0 + /D__TBB_BUILD=1 + /D_UNICODE + /DUNICODE + /DWINAPI_FAMILY=WINAPI_FAMILY_APP + /DDO_ITT_NOTIFY=0 + /DUSE_WINTHREAD ) # defines were copied from windows.cl.inc set(CMAKE_LINKER_FLAGS "${CMAKE_LINKER_FLAGS} /APPCONTAINER") else() @@ -173,7 +182,23 @@ endif() set(TBB_SOURCE_FILES ${TBB_SOURCE_FILES} "${CMAKE_CURRENT_SOURCE_DIR}/${tbb_version_file}") add_library(tbb ${TBB_SOURCE_FILES}) -target_link_libraries(tbb c m dl) + +if (WIN32) + add_custom_command(TARGET tbb + PRE_BUILD + COMMAND ${CMAKE_C_COMPILER} /nologo /TC /EP ${tbb_src_dir}\\src\\tbb\\win32-tbb-export.def /DTBB_NO_LEGACY /DUSE_WINTHREAD /D_CRT_SECURE_NO_DEPRECATE /D_WIN32_WINNT=0x0400 /D__TBB_BUILD=1 /I${tbb_src_dir}\\src /I${tbb_src_dir}\\include > "${tbb_src_dir}\\src\\tbb\\tbb.def" + WORKING_DIRECTORY ${tbb_src_dir}\\src\\tbb + COMMENT "Generating tbb.def file" VERBATIM + ) +endif() + +if (WIN32) + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /DEF:${tbb_src_dir}/src/tbb/tbb.def /DLL /MAP /fixed:no /INCREMENTAL:NO") +endif() + +if (NOT WIN32) + target_link_libraries(tbb c m dl) +endif() ocv_warnings_disable(CMAKE_CXX_FLAGS -Wundef -Wmissing-declarations) string(REPLACE "-Werror=non-virtual-dtor" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") From 033e3092a3297d8cb3e49eeff825cb706dd01f95 Mon Sep 17 00:00:00 2001 From: Alexander Smorkalov Date: Tue, 16 Apr 2013 06:25:10 -0700 Subject: [PATCH 147/178] Media Foundation based VideoCapture improved Code formating fixed; GrabFrame method implemented correclty. --- modules/highgui/src/cap.cpp | 5 ++ modules/highgui/src/cap_msmf.cpp | 146 +++++++++++++++++++++++++++++-- 2 files changed, 144 insertions(+), 7 deletions(-) diff --git a/modules/highgui/src/cap.cpp b/modules/highgui/src/cap.cpp index 2c3b3a94c3..3750d1d663 100644 --- a/modules/highgui/src/cap.cpp +++ b/modules/highgui/src/cap.cpp @@ -117,6 +117,9 @@ CV_IMPL CvCapture * cvCreateCameraCapture (int index) #ifdef HAVE_DSHOW CV_CAP_DSHOW, #endif +#ifdef HAVE_MSMF + CV_CAP_MSMF, +#endif #if 1 CV_CAP_IEEE1394, // identical to CV_CAP_DC1394 #endif @@ -198,7 +201,9 @@ CV_IMPL CvCapture * cvCreateCameraCapture (int index) { #ifdef HAVE_MSMF case CV_CAP_MSMF: + printf("Creating Media foundation capture\n"); capture = cvCreateCameraCapture_MSMF (index); + printf("Capture address %p\n", capture); if (capture) return capture; break; diff --git a/modules/highgui/src/cap_msmf.cpp b/modules/highgui/src/cap_msmf.cpp index 52b780463a..28d92c3614 100644 --- a/modules/highgui/src/cap_msmf.cpp +++ b/modules/highgui/src/cap_msmf.cpp @@ -61,18 +61,22 @@ #include #include #include + #pragma warning(disable:4503) #pragma comment(lib, "mfplat") #pragma comment(lib, "mf") #pragma comment(lib, "mfuuid") #pragma comment(lib, "Strmiids") #pragma comment(lib, "MinCore_Downlevel") + struct IMFMediaType; struct IMFActivate; struct IMFMediaSource; struct IMFAttributes; + namespace { + template void SafeRelease(T **ppT) { if (*ppT) @@ -81,7 +85,8 @@ template void SafeRelease(T **ppT) *ppT = NULL; } } - /// Class for printing info into consol + +/// Class for printing info into consol class DebugPrintOut { public: @@ -93,6 +98,7 @@ public: private: DebugPrintOut(void); }; + // Structure for collecting info about types of video, which are supported by current video device struct MediaType { @@ -127,6 +133,7 @@ struct MediaType ~MediaType(); void Clear(); }; + /// Class for parsing info from IMFMediaType into the local MediaType class FormatReader { @@ -136,9 +143,11 @@ public: private: FormatReader(void); }; + DWORD WINAPI MainThreadFunction( LPVOID lpParam ); typedef void(*emergensyStopEventCallback)(int, void *); typedef unsigned char BYTE; + class RawImage { public: @@ -156,6 +165,7 @@ private: unsigned char *ri_pixels; RawImage(unsigned int size); }; + // Class for grabbing image from video stream class ImageGrabber : public IMFSampleGrabberSinkCallback { @@ -836,10 +846,13 @@ MediaType FormatReader::Read(IMFMediaType *pType) FormatReader::~FormatReader(void) { } -#define CHECK_HR(x) if (FAILED(x)) { goto done; } + +#define CHECK_HR(x) if (FAILED(x)) { printf("Checking failed !!!\n"); goto done; } + ImageGrabber::ImageGrabber(unsigned int deviceID): m_cRef(1), ig_DeviceID(deviceID), ig_pSource(NULL), ig_pSession(NULL), ig_pTopology(NULL), ig_RIE(true), ig_Close(false) { } + ImageGrabber::~ImageGrabber(void) { if (ig_pSession) @@ -851,6 +864,7 @@ ImageGrabber::~ImageGrabber(void) DebugPrintOut *DPO = &DebugPrintOut::getInstance(); DPO->printOut(L"IMAGEGRABBER VIDEODEVICE %i: Destroing instance of the ImageGrabber class \n", ig_DeviceID); } + HRESULT ImageGrabber::initImageGrabber(IMFMediaSource *pSource, GUID VideoFormat) { IMFActivate *pSinkActivate = NULL; @@ -871,23 +885,34 @@ HRESULT ImageGrabber::initImageGrabber(IMFMediaSource *pSource, GUID VideoFormat ig_pSource = pSource; hr = pSource->CreatePresentationDescriptor(&pPD); if (FAILED(hr)) + { + printf("Error creating CreatePresentationDescriptor()\n"); goto err; + } BOOL fSelected; hr = pPD->GetStreamDescriptorByIndex(0, &fSelected, &pSD); - if (FAILED(hr)) + if (FAILED(hr)) { + printf("Error GetStreamDescriptorByIndex()\n"); goto err; + } hr = pSD->GetMediaTypeHandler(&pHandler); - if (FAILED(hr)) + if (FAILED(hr)) { + printf("Error GetMediaTypeHandler()\n"); goto err; + } DWORD cTypes = 0; hr = pHandler->GetMediaTypeCount(&cTypes); - if (FAILED(hr)) + if (FAILED(hr)) { + printf("Error GetMediaTypeCount()\n"); goto err; + } if(cTypes > 0) { hr = pHandler->GetCurrentMediaType(&pCurrentType); - if (FAILED(hr)) + if (FAILED(hr)) { + printf("Error GetCurrentMediaType()\n"); goto err; + } MT = FormatReader::Read(pCurrentType); } err: @@ -904,6 +929,10 @@ err: { sizeRawImage = MT.MF_MT_FRAME_SIZE * 4; } + else + { + printf("Video format is not RBG 24/32!\n"); + } CHECK_HR(hr = RawImage::CreateInstance(&ig_RIFirst, sizeRawImage)); CHECK_HR(hr = RawImage::CreateInstance(&ig_RISecond, sizeRawImage)); ig_RIOut = ig_RISecond; @@ -936,6 +965,7 @@ done: SafeRelease(&pType); return hr; } + void ImageGrabber::stopGrabbing() { if(ig_pSession) @@ -943,6 +973,7 @@ void ImageGrabber::stopGrabbing() DebugPrintOut *DPO = &DebugPrintOut::getInstance(); DPO->printOut(L"IMAGEGRABBER VIDEODEVICE %i: Stopping of of grabbing of images\n", ig_DeviceID); } + HRESULT ImageGrabber::startGrabbing(void) { DebugPrintOut *DPO = &DebugPrintOut::getInstance(); @@ -995,12 +1026,15 @@ HRESULT ImageGrabber::startGrabbing(void) SafeRelease(&pEvent); } DPO->printOut(L"IMAGEGRABBER VIDEODEVICE %i: Finish startGrabbing \n", ig_DeviceID); + done: SafeRelease(&pEvent); SafeRelease(&ig_pSession); SafeRelease(&ig_pTopology); + return hr; } + HRESULT ImageGrabber::CreateTopology(IMFMediaSource *pSource, IMFActivate *pSinkActivate, IMFTopology **ppTopo) { IMFTopology *pTopology = NULL; @@ -1038,6 +1072,7 @@ HRESULT ImageGrabber::CreateTopology(IMFMediaSource *pSource, IMFActivate *pSink } *ppTopo = pTopology; (*ppTopo)->AddRef(); + done: SafeRelease(&pTopology); SafeRelease(&pNode1); @@ -1045,8 +1080,10 @@ done: SafeRelease(&pPD); SafeRelease(&pSD); SafeRelease(&pHandler); + return hr; } + HRESULT ImageGrabber::AddSourceNode( IMFTopology *pTopology, // Topology. IMFMediaSource *pSource, // Media source. @@ -1064,10 +1101,13 @@ HRESULT ImageGrabber::AddSourceNode( // Return the pointer to the caller. *ppNode = pNode; (*ppNode)->AddRef(); + done: SafeRelease(&pNode); + return hr; } + HRESULT ImageGrabber::AddOutputNode( IMFTopology *pTopology, // Topology. IMFActivate *pActivate, // Media sink activation object. @@ -1084,10 +1124,13 @@ HRESULT ImageGrabber::AddOutputNode( // Return the pointer to the caller. *ppNode = pNode; (*ppNode)->AddRef(); + done: SafeRelease(&pNode); + return hr; } + HRESULT ImageGrabber::CreateInstance(ImageGrabber **ppIG, unsigned int deviceID) { *ppIG = new (std::nothrow) ImageGrabber(deviceID); @@ -1099,6 +1142,7 @@ HRESULT ImageGrabber::CreateInstance(ImageGrabber **ppIG, unsigned int deviceID) DPO->printOut(L"IMAGEGRABBER VIDEODEVICE %i: Creating instance of ImageGrabber\n", deviceID); return S_OK; } + STDMETHODIMP ImageGrabber::QueryInterface(REFIID riid, void** ppv) { HRESULT hr = E_NOINTERFACE; @@ -1119,10 +1163,12 @@ STDMETHODIMP ImageGrabber::QueryInterface(REFIID riid, void** ppv) } return hr; } + STDMETHODIMP_(ULONG) ImageGrabber::AddRef() { return InterlockedIncrement(&m_cRef); } + STDMETHODIMP_(ULONG) ImageGrabber::Release() { ULONG cRef = InterlockedDecrement(&m_cRef); @@ -1132,38 +1178,45 @@ STDMETHODIMP_(ULONG) ImageGrabber::Release() } return cRef; } + STDMETHODIMP ImageGrabber::OnClockStart(MFTIME hnsSystemTime, LONGLONG llClockStartOffset) { (void)hnsSystemTime; (void)llClockStartOffset; return S_OK; } + STDMETHODIMP ImageGrabber::OnClockStop(MFTIME hnsSystemTime) { (void)hnsSystemTime; return S_OK; } + STDMETHODIMP ImageGrabber::OnClockPause(MFTIME hnsSystemTime) { (void)hnsSystemTime; return S_OK; } + STDMETHODIMP ImageGrabber::OnClockRestart(MFTIME hnsSystemTime) { (void)hnsSystemTime; return S_OK; } + STDMETHODIMP ImageGrabber::OnClockSetRate(MFTIME hnsSystemTime, float flRate) { (void)flRate; (void)hnsSystemTime; return S_OK; } + STDMETHODIMP ImageGrabber::OnSetPresentationClock(IMFPresentationClock* pClock) { (void)pClock; return S_OK; } + STDMETHODIMP ImageGrabber::OnProcessSample(REFGUID guidMajorMediaType, DWORD dwSampleFlags, LONGLONG llSampleTime, LONGLONG llSampleDuration, const BYTE * pSampleBuffer, DWORD dwSampleSize) @@ -1173,6 +1226,9 @@ STDMETHODIMP ImageGrabber::OnProcessSample(REFGUID guidMajorMediaType, DWORD dwS (void)dwSampleFlags; (void)llSampleDuration; (void)dwSampleSize; + + printf("ImageGrabber::OnProcessSample() -- begin\n"); + if(ig_RIE) { ig_RIFirst->fastCopy(pSampleBuffer); @@ -1184,22 +1240,29 @@ STDMETHODIMP ImageGrabber::OnProcessSample(REFGUID guidMajorMediaType, DWORD dwS ig_RIOut = ig_RISecond; } ig_RIE = !ig_RIE; + + printf("ImageGrabber::OnProcessSample() -- end\n"); + return S_OK; } + STDMETHODIMP ImageGrabber::OnShutdown() { return S_OK; } + RawImage *ImageGrabber::getRawImage() { return ig_RIOut; } + DWORD WINAPI MainThreadFunction( LPVOID lpParam ) { ImageGrabberThread *pIGT = (ImageGrabberThread *)lpParam; pIGT->run(); return 0; } + HRESULT ImageGrabberThread::CreateInstance(ImageGrabberThread **ppIGT, IMFMediaSource *pSource, unsigned int deviceID) { DebugPrintOut *DPO = &DebugPrintOut::getInstance(); @@ -1213,6 +1276,7 @@ HRESULT ImageGrabberThread::CreateInstance(ImageGrabberThread **ppIGT, IMFMediaS DPO->printOut(L"IMAGEGRABBERTHREAD VIDEODEVICE %i: Creating of the instance of ImageGrabberThread\n", deviceID); return S_OK; } + ImageGrabberThread::ImageGrabberThread(IMFMediaSource *pSource, unsigned int deviceID): igt_Handle(NULL), igt_stop(false) { DebugPrintOut *DPO = &DebugPrintOut::getInstance(); @@ -1235,6 +1299,7 @@ ImageGrabberThread::ImageGrabberThread(IMFMediaSource *pSource, unsigned int dev DPO->printOut(L"IMAGEGRABBERTHREAD VIDEODEVICE %i There is a problem with creation of the instance of the ImageGrabber class\n", deviceID); } } + void ImageGrabberThread::setEmergencyStopEvent(void *userData, void(*func)(int, void *)) { if(func) @@ -1243,12 +1308,14 @@ void ImageGrabberThread::setEmergencyStopEvent(void *userData, void(*func)(int, igt_userData = userData; } } + ImageGrabberThread::~ImageGrabberThread(void) { DebugPrintOut *DPO = &DebugPrintOut::getInstance(); DPO->printOut(L"IMAGEGRABBERTHREAD VIDEODEVICE %i: Destroing ImageGrabberThread\n", igt_DeviceID); delete igt_pImageGrabber; } + void ImageGrabberThread::stop() { igt_stop = true; @@ -1257,6 +1324,7 @@ void ImageGrabberThread::stop() igt_pImageGrabber->stopGrabbing(); } } + void ImageGrabberThread::start() { igt_Handle = CreateThread( @@ -1267,6 +1335,7 @@ void ImageGrabberThread::start() 0, // use default creation flags &igt_ThreadIdArray); // returns the thread identifier } + void ImageGrabberThread::run() { DebugPrintOut *DPO = &DebugPrintOut::getInstance(); @@ -1294,10 +1363,12 @@ void ImageGrabberThread::run() else DPO->printOut(L"IMAGEGRABBERTHREAD VIDEODEVICE %i: Finish thread\n", igt_DeviceID); } + ImageGrabber *ImageGrabberThread::getImageGrabber() { return igt_pImageGrabber; } + Media_Foundation::Media_Foundation(void) { HRESULT hr = MFStartup(MF_VERSION); @@ -1307,6 +1378,7 @@ Media_Foundation::Media_Foundation(void) DPO->printOut(L"MEDIA FOUNDATION: It cannot be created!!!\n"); } } + Media_Foundation::~Media_Foundation(void) { HRESULT hr = MFShutdown(); @@ -1316,6 +1388,7 @@ Media_Foundation::~Media_Foundation(void) DPO->printOut(L"MEDIA FOUNDATION: Resources cannot be released\n"); } } + bool Media_Foundation::buildListOfDevices() { HRESULT hr = S_OK; @@ -1342,30 +1415,36 @@ bool Media_Foundation::buildListOfDevices() SafeRelease(&pAttributes); return (SUCCEEDED(hr)); } + Media_Foundation& Media_Foundation::getInstance() { static Media_Foundation instance; return instance; } + RawImage::RawImage(unsigned int size): ri_new(false), ri_pixels(NULL) { ri_size = size; ri_pixels = new unsigned char[size]; memset((void *)ri_pixels,0,ri_size); } + bool RawImage::isNew() { return ri_new; } + unsigned int RawImage::getSize() { return ri_size; } + RawImage::~RawImage(void) { delete []ri_pixels; ri_pixels = NULL; } + long RawImage::CreateInstance(RawImage **ppRImage,unsigned int size) { *ppRImage = new (std::nothrow) RawImage(size); @@ -1375,25 +1454,30 @@ long RawImage::CreateInstance(RawImage **ppRImage,unsigned int size) } return S_OK; } + void RawImage::setCopy(const BYTE * pSampleBuffer) { memcpy(ri_pixels, pSampleBuffer, ri_size); ri_new = true; } + void RawImage::fastCopy(const BYTE * pSampleBuffer) { memcpy(ri_pixels, pSampleBuffer, ri_size); ri_new = true; } + unsigned char * RawImage::getpPixels() { ri_new = false; return ri_pixels; } + videoDevice::videoDevice(void): vd_IsSetuped(false), vd_LockOut(OpenLock), vd_pFriendlyName(NULL), vd_Width(0), vd_Height(0), vd_pSource(NULL), vd_func(NULL), vd_userData(NULL) { } + void videoDevice::setParametrs(CamParametrs parametrs) { if(vd_IsSetuped) @@ -1428,6 +1512,7 @@ void videoDevice::setParametrs(CamParametrs parametrs) } } } + CamParametrs videoDevice::getParametrs() { CamParametrs out; @@ -1472,6 +1557,7 @@ CamParametrs videoDevice::getParametrs() } return out; } + long videoDevice::resetDevice(IMFActivate *pActivate) { HRESULT hr = -1; @@ -1503,6 +1589,7 @@ long videoDevice::resetDevice(IMFActivate *pActivate) } return hr; } + long videoDevice::readInfoOfDevice(IMFActivate *pActivate, unsigned int Num) { HRESULT hr = -1; @@ -1510,6 +1597,7 @@ long videoDevice::readInfoOfDevice(IMFActivate *pActivate, unsigned int Num) hr = resetDevice(pActivate); return hr; } + long videoDevice::checkDevice(IMFAttributes *pAttributes, IMFActivate **pDevice) { HRESULT hr = S_OK; @@ -1911,8 +1999,10 @@ done: SafeRelease(&pType); return hr; } + videoDevices::videoDevices(void): count(0) {} + void videoDevices::clearDevices() { std::vector::iterator i = vds_Devices.begin(); @@ -1920,10 +2010,12 @@ void videoDevices::clearDevices() delete (*i); vds_Devices.clear(); } + videoDevices::~videoDevices(void) { clearDevices(); } + videoDevice * videoDevices::getDevice(unsigned int i) { if(i >= vds_Devices.size()) @@ -1936,6 +2028,7 @@ videoDevice * videoDevices::getDevice(unsigned int i) } return vds_Devices[i]; } + long videoDevices::initDevices(IMFAttributes *pAttributes) { HRESULT hr = S_OK; @@ -1965,15 +2058,18 @@ long videoDevices::initDevices(IMFAttributes *pAttributes) } return hr; } + size_t videoDevices::getCount() { return vds_Devices.size(); } + videoDevices& videoDevices::getInstance() { static videoDevices instance; return instance; } + Parametr::Parametr() { CurrentValue = 0; @@ -1983,6 +2079,7 @@ Parametr::Parametr() Default = 0; Flag = 0; } + MediaType::MediaType() { pMF_MT_AM_FORMAT_TYPEName = NULL; @@ -1990,10 +2087,12 @@ MediaType::MediaType() pMF_MT_SUBTYPEName = NULL; Clear(); } + MediaType::~MediaType() { Clear(); } + void MediaType::Clear() { MF_MT_FRAME_SIZE = 0; @@ -2021,6 +2120,7 @@ void MediaType::Clear() memset(&MF_MT_AM_FORMAT_TYPE, 0, sizeof(GUID)); memset(&MF_MT_SUBTYPE, 0, sizeof(GUID)); } + videoInput::videoInput(void): accessToDevices(false) { DebugPrintOut *DPO = &DebugPrintOut::getInstance(); @@ -2029,6 +2129,7 @@ videoInput::videoInput(void): accessToDevices(false) if(!accessToDevices) DPO->printOut(L"INITIALIZATION: Ther is not any suitable video device\n"); } + void videoInput::updateListOfDevices() { DebugPrintOut *DPO = &DebugPrintOut::getInstance(); @@ -2037,11 +2138,13 @@ void videoInput::updateListOfDevices() if(!accessToDevices) DPO->printOut(L"UPDATING: Ther is not any suitable video device\n"); } + videoInput::~videoInput(void) { DebugPrintOut *DPO = &DebugPrintOut::getInstance(); DPO->printOut(L"\n***** CLOSE VIDEOINPUT LIBRARY - 2013 *****\n\n"); } + IMFMediaSource *videoInput::getMediaSource(int deviceID) { DebugPrintOut *DPO = &DebugPrintOut::getInstance(); @@ -2063,6 +2166,7 @@ IMFMediaSource *videoInput::getMediaSource(int deviceID) } return NULL; } + bool videoInput::setupDevice(int deviceID, unsigned int id) { DebugPrintOut *DPO = &DebugPrintOut::getInstance(); @@ -2089,6 +2193,7 @@ bool videoInput::setupDevice(int deviceID, unsigned int id) } return false; } + bool videoInput::setupDevice(int deviceID, unsigned int w, unsigned int h, unsigned int idealFramerate) { DebugPrintOut *DPO = &DebugPrintOut::getInstance(); @@ -2115,6 +2220,7 @@ bool videoInput::setupDevice(int deviceID, unsigned int w, unsigned int h, unsig } return false; } + MediaType videoInput::getFormat(int deviceID, unsigned int id) { DebugPrintOut *DPO = &DebugPrintOut::getInstance(); @@ -2136,6 +2242,7 @@ MediaType videoInput::getFormat(int deviceID, unsigned int id) } return MediaType(); } + bool videoInput::isDeviceSetup(int deviceID) { DebugPrintOut *DPO = &DebugPrintOut::getInstance(); @@ -2157,6 +2264,7 @@ bool videoInput::isDeviceSetup(int deviceID) } return false; } + bool videoInput::isDeviceMediaSource(int deviceID) { DebugPrintOut *DPO = &DebugPrintOut::getInstance(); @@ -2178,6 +2286,7 @@ bool videoInput::isDeviceMediaSource(int deviceID) } return false; } + bool videoInput::isDeviceRawDataSource(int deviceID) { DebugPrintOut *DPO = &DebugPrintOut::getInstance(); @@ -2202,6 +2311,7 @@ bool videoInput::isDeviceRawDataSource(int deviceID) } return false; } + bool videoInput::isFrameNew(int deviceID) { DebugPrintOut *DPO = &DebugPrintOut::getInstance(); @@ -2230,6 +2340,7 @@ bool videoInput::isFrameNew(int deviceID) } return false; } + unsigned int videoInput::getCountFormats(int deviceID) { DebugPrintOut *DPO = &DebugPrintOut::getInstance(); @@ -2251,12 +2362,14 @@ unsigned int videoInput::getCountFormats(int deviceID) } return 0; } + void videoInput::closeAllDevices() { videoDevices *VDS = &videoDevices::getInstance(); for(unsigned int i = 0; i < VDS->getCount(); i++) closeDevice(i); } + void videoInput::setParametrs(int deviceID, CamParametrs parametrs) { DebugPrintOut *DPO = &DebugPrintOut::getInstance(); @@ -2277,6 +2390,7 @@ void videoInput::setParametrs(int deviceID, CamParametrs parametrs) DPO->printOut(L"VIDEODEVICE(s): There is not any suitable video device\n"); } } + CamParametrs videoInput::getParametrs(int deviceID) { DebugPrintOut *DPO = &DebugPrintOut::getInstance(); @@ -2299,6 +2413,7 @@ CamParametrs videoInput::getParametrs(int deviceID) } return out; } + void videoInput::closeDevice(int deviceID) { DebugPrintOut *DPO = &DebugPrintOut::getInstance(); @@ -2319,6 +2434,7 @@ void videoInput::closeDevice(int deviceID) DPO->printOut(L"VIDEODEVICE(s): There is not any suitable video device\n"); } } + unsigned int videoInput::getWidth(int deviceID) { DebugPrintOut *DPO = &DebugPrintOut::getInstance(); @@ -2340,6 +2456,7 @@ unsigned int videoInput::getWidth(int deviceID) } return 0; } + unsigned int videoInput::getHeight(int deviceID) { DebugPrintOut *DPO = &DebugPrintOut::getInstance(); @@ -2361,6 +2478,7 @@ unsigned int videoInput::getHeight(int deviceID) } return 0; } + wchar_t *videoInput::getNameVideoDevice(int deviceID) { DebugPrintOut *DPO = &DebugPrintOut::getInstance(); @@ -2382,6 +2500,7 @@ wchar_t *videoInput::getNameVideoDevice(int deviceID) } return L"Empty"; } + unsigned int videoInput::listDevices(bool silent) { DebugPrintOut *DPO = &DebugPrintOut::getInstance(); @@ -2405,20 +2524,24 @@ unsigned int videoInput::listDevices(bool silent) } return out; } + videoInput& videoInput::getInstance() { static videoInput instance; return instance; } + bool videoInput::isDevicesAcceable() { return accessToDevices; } + void videoInput::setVerbose(bool state) { DebugPrintOut *DPO = &DebugPrintOut::getInstance(); DPO->setVerbose(state); } + void videoInput::setEmergencyStopEvent(int deviceID, void *userData, void(*func)(int, void *)) { DebugPrintOut *DPO = &DebugPrintOut::getInstance(); @@ -2442,6 +2565,7 @@ void videoInput::setEmergencyStopEvent(int deviceID, void *userData, void(*func) DPO->printOut(L"VIDEODEVICE(s): There is not any suitable video device\n"); } } + bool videoInput::getPixels(int deviceID, unsigned char * dstBuffer, bool flipRedAndBlue, bool flipImage) { bool success = false; @@ -2491,6 +2615,7 @@ bool videoInput::getPixels(int deviceID, unsigned char * dstBuffer, bool flipRed } return success; } + void videoInput::processPixels(unsigned char * src, unsigned char * dst, unsigned int width, unsigned int height, unsigned int bpp, bool bRGB, bool bFlip) { @@ -2553,6 +2678,7 @@ void videoInput::processPixels(unsigned char * src, unsigned char * dst, unsigne } } } + /******* Capturing video from camera via Microsoft Media Foundation **********/ class CvCaptureCAM_MSMF : public CvCapture { @@ -2605,6 +2731,7 @@ void CvCaptureCAM_MSMF::close() } widthSet = heightSet = width = height = -1; } + // Initialize camera input bool CvCaptureCAM_MSMF::open( int _index ) { @@ -2621,10 +2748,14 @@ bool CvCaptureCAM_MSMF::open( int _index ) index = try_index; return true; } + bool CvCaptureCAM_MSMF::grabFrame() { - return true; + while (VI.isDeviceSetup(index) && !VI.isFrameNew(index)) + Sleep(1); + return VI.isDeviceSetup(index); } + IplImage* CvCaptureCAM_MSMF::retrieveFrame(int) { if( !frame || (int)VI.getWidth(index) != frame->width || (int)VI.getHeight(index) != frame->height ) @@ -2637,6 +2768,7 @@ IplImage* CvCaptureCAM_MSMF::retrieveFrame(int) VI.getPixels( index, (uchar*)frame->imageData, false, true ); return frame; } + double CvCaptureCAM_MSMF::getProperty( int property_id ) { // image format proprrties From ccb8292e8ec245944d71fe38c32a70ecb0428feb Mon Sep 17 00:00:00 2001 From: Alexander Smorkalov Date: Mon, 6 May 2013 03:36:51 -0700 Subject: [PATCH 148/178] Media Foundation-based VideoWriter added --- modules/highgui/src/cap.cpp | 12 + modules/highgui/src/cap_ffmpeg.cpp | 4 - modules/highgui/src/cap_msmf.cpp | 356 ++++++++++++++++++++++++++++- modules/highgui/src/precomp.hpp | 2 + 4 files changed, 367 insertions(+), 7 deletions(-) diff --git a/modules/highgui/src/cap.cpp b/modules/highgui/src/cap.cpp index 3750d1d663..afab8d4b55 100644 --- a/modules/highgui/src/cap.cpp +++ b/modules/highgui/src/cap.cpp @@ -408,8 +408,20 @@ CV_IMPL CvVideoWriter* cvCreateVideoWriter( const char* filename, int fourcc, if(!fourcc || !fps) result = cvCreateVideoWriter_Images(filename); +#ifdef HAVE_FFMPEG if(!result) result = cvCreateVideoWriter_FFMPEG_proxy (filename, fourcc, fps, frameSize, is_color); +#endif + +#ifdef HAVE_VFW + if(!result) + return cvCreateVideoWriter_VFW(filename, fourcc, fps, frameSize, isColor); +#endif + +#ifdef HAVE_MSMF + if (!result) + result = cvCreateVideoWriter_MSMF(filename, fourcc, fps, frameSize, is_color); +#endif /* #ifdef HAVE_XINE if(!result) diff --git a/modules/highgui/src/cap_ffmpeg.cpp b/modules/highgui/src/cap_ffmpeg.cpp index 669ebda125..7d4d6af38b 100644 --- a/modules/highgui/src/cap_ffmpeg.cpp +++ b/modules/highgui/src/cap_ffmpeg.cpp @@ -263,9 +263,5 @@ CvVideoWriter* cvCreateVideoWriter_FFMPEG_proxy( const char* filename, int fourc if( result->open( filename, fourcc, fps, frameSize, isColor != 0 )) return result; delete result; -#ifdef HAVE_VFW - return cvCreateVideoWriter_VFW(filename, fourcc, fps, frameSize, isColor); - #else return 0; -#endif } diff --git a/modules/highgui/src/cap_msmf.cpp b/modules/highgui/src/cap_msmf.cpp index 28d92c3614..1d6bb597b7 100644 --- a/modules/highgui/src/cap_msmf.cpp +++ b/modules/highgui/src/cap_msmf.cpp @@ -54,6 +54,8 @@ #include #include #include "Strsafe.h" +#include +#include #include #include #include @@ -67,6 +69,7 @@ #pragma comment(lib, "mf") #pragma comment(lib, "mfuuid") #pragma comment(lib, "Strmiids") +#pragma comment(lib, "Mfreadwrite") #pragma comment(lib, "MinCore_Downlevel") struct IMFMediaType; @@ -146,7 +149,6 @@ private: DWORD WINAPI MainThreadFunction( LPVOID lpParam ); typedef void(*emergensyStopEventCallback)(int, void *); -typedef unsigned char BYTE; class RawImage { @@ -1031,7 +1033,7 @@ done: SafeRelease(&pEvent); SafeRelease(&ig_pSession); SafeRelease(&ig_pTopology); - + return hr; } @@ -1080,7 +1082,7 @@ done: SafeRelease(&pPD); SafeRelease(&pSD); SafeRelease(&pHandler); - + return hr; } @@ -2939,4 +2941,352 @@ CvCapture* cvCreateCameraCapture_MSMF( int index ) delete capture; return 0; } + + +// +// +// Media Foundation-based Video Writer +// +// + +using namespace Microsoft::WRL; + +class CvVideoWriter_MSMF : public CvVideoWriter +{ +public: + CvVideoWriter_MSMF(); + virtual ~CvVideoWriter_MSMF(); + virtual bool open( const char* filename, int fourcc, + double fps, CvSize frameSize, bool isColor ); + virtual void close(); + virtual bool writeFrame( const IplImage* img); + +private: + UINT32 videoWidth; + UINT32 videoHeight; + double fps; + UINT32 bitRate; + UINT32 frameSize; + GUID encodingFormat; + GUID inputFormat; + + DWORD streamIndex; + ComPtr sinkWriter; + + bool initiated; + + LONGLONG rtStart; + UINT64 rtDuration; + + HRESULT InitializeSinkWriter(const char* filename); + HRESULT WriteFrame(DWORD *videoFrameBuffer, const LONGLONG& rtStart, const LONGLONG& rtDuration); +}; + +CvVideoWriter_MSMF::CvVideoWriter_MSMF() +{ +} + +CvVideoWriter_MSMF::~CvVideoWriter_MSMF() +{ + close(); +} + +bool CvVideoWriter_MSMF::open( const char* filename, int fourcc, + double _fps, CvSize frameSize, bool isColor ) +{ + videoWidth = frameSize.width; + videoHeight = frameSize.height; + fps = _fps; + bitRate = 4*800000; + encodingFormat = MFVideoFormat_WMV3; + inputFormat = MFVideoFormat_RGB32; + + HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); + if (SUCCEEDED(hr)) + { + printf("CoInitializeEx is successfull\n"); + hr = MFStartup(MF_VERSION); + if (SUCCEEDED(hr)) + { + printf("MFStartup is successfull\n"); + hr = InitializeSinkWriter(filename); + if (SUCCEEDED(hr)) + { + printf("InitializeSinkWriter is successfull\n"); + initiated = true; + rtStart = 0; + MFFrameRateToAverageTimePerFrame(fps, 1, &rtDuration); + printf("duration: %d\n", rtDuration); + } + } + } + + return SUCCEEDED(hr); +} + +void CvVideoWriter_MSMF::close() +{ + printf("VideoWriter::close()\n"); + if (!initiated) + { + printf("VideoWriter was not Initialized\n"); + return; + } + + initiated = false; + HRESULT hr = sinkWriter->Finalize(); + printf("sinkWriter Finalize status %u\n", hr); + MFShutdown(); +} + +bool CvVideoWriter_MSMF::writeFrame(const IplImage* img) +{ + if (!img) + return false; + + printf("Writing not empty IplImage\n"); + + auto length = img->width * img->height * 4; + printf("Image: %dx%d, %d\n", img->width, img->height, length); + DWORD* target = new DWORD[length]; + + printf("Before for loop\n"); + for (int rowIdx = 0; rowIdx < img->height; rowIdx++) + { + char* rowStart = img->imageData + rowIdx*img->widthStep; + for (int colIdx = 0; colIdx < img->width; colIdx++) + { + BYTE b = rowStart[colIdx * img->nChannels + 0]; + BYTE g = rowStart[colIdx * img->nChannels + 1]; + BYTE r = rowStart[colIdx * img->nChannels + 2]; + + // On ARM devices data is stored starting from the last line + // (and not the first line) so you have to revert them on the Y axis +#if _M_ARM + auto row = index / videoWidth; + auto targetRow = videoHeight - row - 1; + auto column = index - (row * videoWidth); + target[(targetRow * videoWidth) + column] = (r << 16) + (g << 8) + b; +#else + target[rowIdx*img->width+colIdx] = (r << 16) + (g << 8) + b; +#endif + } + } + + // Send frame to the sink writer. + printf("Before private WriteFrame call\n"); + HRESULT hr = WriteFrame(target, rtStart, rtDuration); + printf("After private WriteFrame call\n"); + if (FAILED(hr)) + { + printf("Private WriteFrame failed\n"); + delete[] target; + return false; + } + rtStart += rtDuration; + + printf("End of writing IplImage\n"); + + delete[] target; + + return true; +} + +HRESULT CvVideoWriter_MSMF::InitializeSinkWriter(const char* filename) +{ + ComPtr spAttr; + ComPtr mediaTypeOut; + ComPtr mediaTypeIn; + ComPtr spByteStream; + + MFCreateAttributes(&spAttr, 10); + spAttr->SetUINT32(MF_READWRITE_ENABLE_HARDWARE_TRANSFORMS, true); + + wchar_t* unicodeFileName = new wchar_t[strlen(filename)+1]; + MultiByteToWideChar(CP_ACP, 0, filename, -1, unicodeFileName, strlen(filename)+1); + + HRESULT hr = MFCreateSinkWriterFromURL(unicodeFileName, NULL, spAttr.Get(), &sinkWriter); + + delete[] unicodeFileName; + + // Set the output media type. + if (SUCCEEDED(hr)) + { + printf("MFCreateSinkWriterFromURL is successfull\n"); + hr = MFCreateMediaType(&mediaTypeOut); + } + if (SUCCEEDED(hr)) + { + hr = mediaTypeOut->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video); + } + if (SUCCEEDED(hr)) + { + hr = mediaTypeOut->SetGUID(MF_MT_SUBTYPE, encodingFormat); + } + if (SUCCEEDED(hr)) + { + hr = mediaTypeOut->SetUINT32(MF_MT_AVG_BITRATE, bitRate); + } + if (SUCCEEDED(hr)) + { + hr = mediaTypeOut->SetUINT32(MF_MT_INTERLACE_MODE, MFVideoInterlace_Progressive); + } + if (SUCCEEDED(hr)) + { + hr = MFSetAttributeSize(mediaTypeOut.Get(), MF_MT_FRAME_SIZE, videoWidth, videoHeight); + } + if (SUCCEEDED(hr)) + { + hr = MFSetAttributeRatio(mediaTypeOut.Get(), MF_MT_FRAME_RATE, fps, 1); + } + if (SUCCEEDED(hr)) + { + hr = MFSetAttributeRatio(mediaTypeOut.Get(), MF_MT_PIXEL_ASPECT_RATIO, 1, 1); + } + if (SUCCEEDED(hr)) + { + hr = sinkWriter->AddStream(mediaTypeOut.Get(), &streamIndex); + } + + // Set the input media type. + if (SUCCEEDED(hr)) + { + hr = MFCreateMediaType(&mediaTypeIn); + } + if (SUCCEEDED(hr)) + { + hr = mediaTypeIn->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video); + } + if (SUCCEEDED(hr)) + { + hr = mediaTypeIn->SetGUID(MF_MT_SUBTYPE, inputFormat); + } + if (SUCCEEDED(hr)) + { + hr = mediaTypeIn->SetUINT32(MF_MT_INTERLACE_MODE, MFVideoInterlace_Progressive); + } + if (SUCCEEDED(hr)) + { + hr = MFSetAttributeSize(mediaTypeIn.Get(), MF_MT_FRAME_SIZE, videoWidth, videoHeight); + } + if (SUCCEEDED(hr)) + { + hr = MFSetAttributeRatio(mediaTypeIn.Get(), MF_MT_FRAME_RATE, fps, 1); + } + if (SUCCEEDED(hr)) + { + hr = MFSetAttributeRatio(mediaTypeIn.Get(), MF_MT_PIXEL_ASPECT_RATIO, 1, 1); + } + if (SUCCEEDED(hr)) + { + hr = sinkWriter->SetInputMediaType(streamIndex, mediaTypeIn.Get(), NULL); + } + + // Tell the sink writer to start accepting data. + if (SUCCEEDED(hr)) + { + hr = sinkWriter->BeginWriting(); + } + + return hr; +} + +HRESULT CvVideoWriter_MSMF::WriteFrame(DWORD *videoFrameBuffer, const LONGLONG& Start, const LONGLONG& Duration) +{ + printf("Private WriteFrame(%p, %llu, %llu)\n", videoFrameBuffer, Start, Duration); + IMFSample* sample; + IMFMediaBuffer* buffer; + + const LONG cbWidth = 4 * videoWidth; + const DWORD cbBuffer = cbWidth * videoHeight; + + BYTE *pData = NULL; + + // Create a new memory buffer. + HRESULT hr = MFCreateMemoryBuffer(cbBuffer, &buffer); + + // Lock the buffer and copy the video frame to the buffer. + if (SUCCEEDED(hr)) + { + printf("MFCreateMemoryBuffer successfull\n"); + hr = buffer->Lock(&pData, NULL, NULL); + } + + if (SUCCEEDED(hr)) + { + printf("Before MFCopyImage(%p, %d, %p, %d, %d %d)\n", pData, cbWidth, videoFrameBuffer, cbWidth, cbWidth, videoHeight); + hr = MFCopyImage( + pData, // Destination buffer. + cbWidth, // Destination stride. + (BYTE*)videoFrameBuffer, // First row in source image. + cbWidth, // Source stride. + cbWidth, // Image width in bytes. + videoHeight // Image height in pixels. + ); + printf("After MFCopyImage()\n"); + } + + printf("Before buffer.Get()\n"); + if (buffer) + { + printf("Before buffer->Unlock\n"); + buffer->Unlock(); + printf("After buffer->Unlock\n"); + } + + // Set the data length of the buffer. + if (SUCCEEDED(hr)) + { + printf("MFCopyImage successfull\n"); + hr = buffer->SetCurrentLength(cbBuffer); + } + + // Create a media sample and add the buffer to the sample. + if (SUCCEEDED(hr)) + { + hr = MFCreateSample(&sample); + } + if (SUCCEEDED(hr)) + { + hr = sample->AddBuffer(buffer); + } + + // Set the time stamp and the duration. + if (SUCCEEDED(hr)) + { + printf("Sample time: %d\n", Start); + hr = sample->SetSampleTime(Start); + } + if (SUCCEEDED(hr)) + { + printf("Duration: %d\n", Duration); + hr = sample->SetSampleDuration(Duration); + } + + // Send the sample to the Sink Writer. + if (SUCCEEDED(hr)) + { + printf("Setting writer params successfull\n"); + hr = sinkWriter->WriteSample(streamIndex, sample); + } + + printf("Private WriteFrame(%d, %p) end with status %u\n", streamIndex, sample, hr); + + SafeRelease(&sample); + SafeRelease(&buffer); + + return hr; +} + +CvVideoWriter* cvCreateVideoWriter_MSMF( const char* filename, int fourcc, + double fps, CvSize frameSize, int isColor ) +{ + printf("Creating Media Foundation VideoWriter\n"); + CvVideoWriter_MSMF* writer = new CvVideoWriter_MSMF; + if( writer->open( filename, fourcc, fps, frameSize, isColor != 0 )) + return writer; + delete writer; + return NULL; +} + #endif \ No newline at end of file diff --git a/modules/highgui/src/precomp.hpp b/modules/highgui/src/precomp.hpp index aa327d6d7c..b9896955cc 100644 --- a/modules/highgui/src/precomp.hpp +++ b/modules/highgui/src/precomp.hpp @@ -119,6 +119,8 @@ CvVideoWriter* cvCreateVideoWriter_VFW( const char* filename, int fourcc, double fps, CvSize frameSize, int is_color ); CvCapture* cvCreateCameraCapture_DShow( int index ); CvCapture* cvCreateCameraCapture_MSMF( int index ); +CvVideoWriter* cvCreateVideoWriter_MSMF( const char* filename, int fourcc, + double fps, CvSize frameSize, int is_color ); CvCapture* cvCreateCameraCapture_OpenNI( int index ); CvCapture* cvCreateFileCapture_OpenNI( const char* filename ); CvCapture* cvCreateCameraCapture_Android( int index ); From 22b0cfbaa215878809d4685d5bf14dce75ee401f Mon Sep 17 00:00:00 2001 From: Alexander Smorkalov Date: Mon, 6 May 2013 07:17:53 -0700 Subject: [PATCH 149/178] Media Foundation-based VideoWriter improvements. FourCC parameter handlig added; Smart pointers instead SafeRelease call; Windows RT support (vertical mirroring). --- modules/highgui/src/cap_msmf.cpp | 87 ++++++++++++++++++++++++-------- 1 file changed, 67 insertions(+), 20 deletions(-) diff --git a/modules/highgui/src/cap_msmf.cpp b/modules/highgui/src/cap_msmf.cpp index 1d6bb597b7..d3c365f546 100644 --- a/modules/highgui/src/cap_msmf.cpp +++ b/modules/highgui/src/cap_msmf.cpp @@ -2979,6 +2979,7 @@ private: UINT64 rtDuration; HRESULT InitializeSinkWriter(const char* filename); + static const GUID FourCC2GUID(int fourcc); HRESULT WriteFrame(DWORD *videoFrameBuffer, const LONGLONG& rtStart, const LONGLONG& rtDuration); }; @@ -2991,14 +2992,65 @@ CvVideoWriter_MSMF::~CvVideoWriter_MSMF() close(); } +const GUID CvVideoWriter_MSMF::FourCC2GUID(int fourcc) +{ + switch(fourcc) + { + case 'dv25': + return MFVideoFormat_DV25; break; + case 'dv50': + return MFVideoFormat_DV50; break; + case 'dvc ': + return MFVideoFormat_DVC; break; + case 'dvh1': + return MFVideoFormat_DVH1; break; + case 'dvhd': + return MFVideoFormat_DVHD; break; + case 'dvsd': + return MFVideoFormat_DVSD; break; + case 'dvsl': + return MFVideoFormat_DVSL; break; + case 'H263': + return MFVideoFormat_H263; break; + case 'H264': + return MFVideoFormat_H264; break; + case 'M4S2': + return MFVideoFormat_M4S2; break; + case 'MJPG': + return MFVideoFormat_MJPG; break; + case 'MP43': + return MFVideoFormat_MP43; break; + case 'MP4S': + return MFVideoFormat_MP4S; break; + case 'MP4V': + return MFVideoFormat_MP4V; break; + case 'MPG1': + return MFVideoFormat_MPG1; break; + case 'MSS1': + return MFVideoFormat_MSS1; break; + case 'MSS2': + return MFVideoFormat_MSS2; break; + case 'WMV1': + return MFVideoFormat_WMV1; break; + case 'WMV2': + return MFVideoFormat_WMV2; break; + case 'WMV3': + return MFVideoFormat_WMV3; break; + case 'WVC1': + return MFVideoFormat_WVC1; break; + default: + return MFVideoFormat_H264; + } +} + bool CvVideoWriter_MSMF::open( const char* filename, int fourcc, double _fps, CvSize frameSize, bool isColor ) { videoWidth = frameSize.width; videoHeight = frameSize.height; fps = _fps; - bitRate = 4*800000; - encodingFormat = MFVideoFormat_WMV3; + bitRate = videoWidth*videoHeight; // 1-bit per pixel + encodingFormat = FourCC2GUID(fourcc); inputFormat = MFVideoFormat_RGB32; HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); @@ -3015,7 +3067,7 @@ bool CvVideoWriter_MSMF::open( const char* filename, int fourcc, printf("InitializeSinkWriter is successfull\n"); initiated = true; rtStart = 0; - MFFrameRateToAverageTimePerFrame(fps, 1, &rtDuration); + MFFrameRateToAverageTimePerFrame((UINT32)fps, 1, &rtDuration); printf("duration: %d\n", rtDuration); } } @@ -3046,7 +3098,7 @@ bool CvVideoWriter_MSMF::writeFrame(const IplImage* img) printf("Writing not empty IplImage\n"); - auto length = img->width * img->height * 4; + int length = img->width * img->height * 4; printf("Image: %dx%d, %d\n", img->width, img->height, length); DWORD* target = new DWORD[length]; @@ -3060,13 +3112,11 @@ bool CvVideoWriter_MSMF::writeFrame(const IplImage* img) BYTE g = rowStart[colIdx * img->nChannels + 1]; BYTE r = rowStart[colIdx * img->nChannels + 2]; - // On ARM devices data is stored starting from the last line - // (and not the first line) so you have to revert them on the Y axis + // On ARM devices data is stored starting from the last line + // (and not the first line) so you have to revert them on the Y axis #if _M_ARM - auto row = index / videoWidth; - auto targetRow = videoHeight - row - 1; - auto column = index - (row * videoWidth); - target[(targetRow * videoWidth) + column] = (r << 16) + (g << 8) + b; + int targetRow = videoHeight - rowIdx - 1; + target[(targetRow * videoWidth) + colIdx] = (r << 16) + (g << 8) + b; #else target[rowIdx*img->width+colIdx] = (r << 16) + (g << 8) + b; #endif @@ -3137,7 +3187,7 @@ HRESULT CvVideoWriter_MSMF::InitializeSinkWriter(const char* filename) } if (SUCCEEDED(hr)) { - hr = MFSetAttributeRatio(mediaTypeOut.Get(), MF_MT_FRAME_RATE, fps, 1); + hr = MFSetAttributeRatio(mediaTypeOut.Get(), MF_MT_FRAME_RATE, (UINT32)fps, 1); } if (SUCCEEDED(hr)) { @@ -3171,7 +3221,7 @@ HRESULT CvVideoWriter_MSMF::InitializeSinkWriter(const char* filename) } if (SUCCEEDED(hr)) { - hr = MFSetAttributeRatio(mediaTypeIn.Get(), MF_MT_FRAME_RATE, fps, 1); + hr = MFSetAttributeRatio(mediaTypeIn.Get(), MF_MT_FRAME_RATE, (UINT32)fps, 1); } if (SUCCEEDED(hr)) { @@ -3194,8 +3244,8 @@ HRESULT CvVideoWriter_MSMF::InitializeSinkWriter(const char* filename) HRESULT CvVideoWriter_MSMF::WriteFrame(DWORD *videoFrameBuffer, const LONGLONG& Start, const LONGLONG& Duration) { printf("Private WriteFrame(%p, %llu, %llu)\n", videoFrameBuffer, Start, Duration); - IMFSample* sample; - IMFMediaBuffer* buffer; + ComPtr sample; + ComPtr buffer; const LONG cbWidth = 4 * videoWidth; const DWORD cbBuffer = cbWidth * videoHeight; @@ -3248,7 +3298,7 @@ HRESULT CvVideoWriter_MSMF::WriteFrame(DWORD *videoFrameBuffer, const LONGLONG& } if (SUCCEEDED(hr)) { - hr = sample->AddBuffer(buffer); + hr = sample->AddBuffer(buffer.Get()); } // Set the time stamp and the duration. @@ -3267,13 +3317,10 @@ HRESULT CvVideoWriter_MSMF::WriteFrame(DWORD *videoFrameBuffer, const LONGLONG& if (SUCCEEDED(hr)) { printf("Setting writer params successfull\n"); - hr = sinkWriter->WriteSample(streamIndex, sample); + hr = sinkWriter->WriteSample(streamIndex, sample.Get()); } - printf("Private WriteFrame(%d, %p) end with status %u\n", streamIndex, sample, hr); - - SafeRelease(&sample); - SafeRelease(&buffer); + printf("Private WriteFrame(%d, %p) end with status %u\n", streamIndex, sample.Get(), hr); return hr; } From 9fb762ccecbfc5768e44db7753047dde8f2b8383 Mon Sep 17 00:00:00 2001 From: Alexander Smorkalov Date: Tue, 14 May 2013 05:17:34 -0700 Subject: [PATCH 150/178] VideoCapture for video files implemented. Set and Get methods are not implemented; Camera based video capture is broken due to modifications. --- modules/highgui/src/cap.cpp | 16 +- modules/highgui/src/cap_ffmpeg.cpp | 4 - modules/highgui/src/cap_msmf.cpp | 525 +++++++++++++++++++++++++++-- modules/highgui/src/precomp.hpp | 1 + 4 files changed, 521 insertions(+), 25 deletions(-) diff --git a/modules/highgui/src/cap.cpp b/modules/highgui/src/cap.cpp index afab8d4b55..8db8731020 100644 --- a/modules/highgui/src/cap.cpp +++ b/modules/highgui/src/cap.cpp @@ -363,6 +363,20 @@ CV_IMPL CvCapture * cvCreateFileCapture (const char * filename) if (! result) result = cvCreateFileCapture_FFMPEG_proxy (filename); +#ifdef HAVE_MSMF + if (! result) + { + printf("Creating Media foundation based reader\n"); + result = cvCreateFileCapture_MSMF (filename); + printf("Construction result %p\n", result); + } +#endif + +#ifdef HAVE_VFW + if (! result) + result = cvCreateFileCapture_VFW (filename); +#endif + #ifdef HAVE_XINE if (! result) result = cvCreateFileCapture_XINE (filename); @@ -415,7 +429,7 @@ CV_IMPL CvVideoWriter* cvCreateVideoWriter( const char* filename, int fourcc, #ifdef HAVE_VFW if(!result) - return cvCreateVideoWriter_VFW(filename, fourcc, fps, frameSize, isColor); + return cvCreateVideoWriter_VFW(filename, fourcc, fps, frameSize, is_color); #endif #ifdef HAVE_MSMF diff --git a/modules/highgui/src/cap_ffmpeg.cpp b/modules/highgui/src/cap_ffmpeg.cpp index 7d4d6af38b..bf73c0810f 100644 --- a/modules/highgui/src/cap_ffmpeg.cpp +++ b/modules/highgui/src/cap_ffmpeg.cpp @@ -209,11 +209,7 @@ CvCapture* cvCreateFileCapture_FFMPEG_proxy(const char * filename) if( result->open( filename )) return result; delete result; -#ifdef HAVE_VFW - return cvCreateFileCapture_VFW(filename); -#else return 0; -#endif } class CvVideoWriter_FFMPEG_proxy : diff --git a/modules/highgui/src/cap_msmf.cpp b/modules/highgui/src/cap_msmf.cpp index d3c365f546..c037d2f982 100644 --- a/modules/highgui/src/cap_msmf.cpp +++ b/modules/highgui/src/cap_msmf.cpp @@ -175,10 +175,17 @@ public: ~ImageGrabber(void); HRESULT initImageGrabber(IMFMediaSource *pSource, GUID VideoFormat); HRESULT startGrabbing(void); + void pauseGrabbing(); + void resumeGrabbing(); void stopGrabbing(); RawImage *getRawImage(); // Function of creation of the instance of the class static HRESULT CreateInstance(ImageGrabber **ppIG,unsigned int deviceID); + + HANDLE ig_hFrameReady; + HANDLE ig_hFrameGrabbed; + HANDLE ig_hFinish; + private: bool ig_RIE; bool ig_Close; @@ -198,11 +205,7 @@ private: IMFPresentationDescriptor *pPD, IMFStreamDescriptor *pSD, IMFTopologyNode **ppNode); - HRESULT AddOutputNode( - IMFTopology *pTopology, - IMFActivate *pActivate, - DWORD dwId, - IMFTopologyNode **ppNode); + HRESULT AddOutputNode(IMFTopology *pTopology, IMFActivate *pActivate, DWORD dwId, IMFTopologyNode **ppNode); // IUnknown methods STDMETHODIMP QueryInterface(REFIID iid, void** ppv); STDMETHODIMP_(ULONG) AddRef(); @@ -851,20 +854,37 @@ FormatReader::~FormatReader(void) #define CHECK_HR(x) if (FAILED(x)) { printf("Checking failed !!!\n"); goto done; } -ImageGrabber::ImageGrabber(unsigned int deviceID): m_cRef(1), ig_DeviceID(deviceID), ig_pSource(NULL), ig_pSession(NULL), ig_pTopology(NULL), ig_RIE(true), ig_Close(false) -{ -} +ImageGrabber::ImageGrabber(unsigned int deviceID): + m_cRef(1), + ig_DeviceID(deviceID), + ig_pSource(NULL), + ig_pSession(NULL), + ig_pTopology(NULL), + ig_RIE(true), + ig_Close(false), + ig_hFrameReady(CreateEvent(NULL, FALSE, FALSE, "ig_hFrameReady")), + ig_hFrameGrabbed(CreateEvent(NULL, FALSE, TRUE, "ig_hFrameGrabbed")), + ig_hFinish(CreateEvent(NULL, FALSE, FALSE, "ig_hFinish")) +{} ImageGrabber::~ImageGrabber(void) { + printf("ImageGrabber::~ImageGrabber()\n"); if (ig_pSession) { + printf("ig_pSession->Shutdown()"); ig_pSession->Shutdown(); } - //SafeRelease(&ig_pSession); - //SafeRelease(&ig_pTopology); + + CloseHandle(ig_hFrameReady); + CloseHandle(ig_hFrameGrabbed); + CloseHandle(ig_hFinish); + + SafeRelease(&ig_pSession); + SafeRelease(&ig_pTopology); DebugPrintOut *DPO = &DebugPrintOut::getInstance(); - DPO->printOut(L"IMAGEGRABBER VIDEODEVICE %i: Destroing instance of the ImageGrabber class \n", ig_DeviceID); + + DPO->printOut(L"IMAGEGRABBER VIDEODEVICE %i: Destroing instance of the ImageGrabber class\n", ig_DeviceID); } HRESULT ImageGrabber::initImageGrabber(IMFMediaSource *pSource, GUID VideoFormat) @@ -983,9 +1003,17 @@ HRESULT ImageGrabber::startGrabbing(void) PROPVARIANT var; PropVariantInit(&var); HRESULT hr = S_OK; - CHECK_HR(hr = ig_pSession->SetTopology(0, ig_pTopology)); - CHECK_HR(hr = ig_pSession->Start(&GUID_NULL, &var)); + hr = ig_pSession->SetTopology(0, ig_pTopology); + if (FAILED(hr)) + { + printf("Error: cannot set topology (status %u)\n", hr); + } DPO->printOut(L"IMAGEGRABBER VIDEODEVICE %i: Start Grabbing of the images\n", ig_DeviceID); + hr = ig_pSession->Start(&GUID_NULL, &var); + if (FAILED(hr)) + { + printf("Error: cannot start session (status %u)\n", hr); + } for(;;) { HRESULT hrStatus = S_OK; @@ -1027,6 +1055,9 @@ HRESULT ImageGrabber::startGrabbing(void) } SafeRelease(&pEvent); } + + SetEvent(ig_hFinish); + DPO->printOut(L"IMAGEGRABBER VIDEODEVICE %i: Finish startGrabbing \n", ig_DeviceID); done: @@ -1037,6 +1068,14 @@ done: return hr; } +void ImageGrabber::pauseGrabbing() +{ +} + +void ImageGrabber::resumeGrabbing() +{ +} + HRESULT ImageGrabber::CreateTopology(IMFMediaSource *pSource, IMFActivate *pSinkActivate, IMFTopology **ppTopo) { IMFTopology *pTopology = NULL; @@ -1229,6 +1268,8 @@ STDMETHODIMP ImageGrabber::OnProcessSample(REFGUID guidMajorMediaType, DWORD dwS (void)llSampleDuration; (void)dwSampleSize; + WaitForSingleObject(ig_hFrameGrabbed, INFINITE); + printf("ImageGrabber::OnProcessSample() -- begin\n"); if(ig_RIE) @@ -1245,11 +1286,14 @@ STDMETHODIMP ImageGrabber::OnProcessSample(REFGUID guidMajorMediaType, DWORD dwS printf("ImageGrabber::OnProcessSample() -- end\n"); + SetEvent(ig_hFrameReady); + return S_OK; } STDMETHODIMP ImageGrabber::OnShutdown() { + SetEvent(ig_hFrameGrabbed); return S_OK; } @@ -1279,7 +1323,7 @@ HRESULT ImageGrabberThread::CreateInstance(ImageGrabberThread **ppIGT, IMFMediaS return S_OK; } -ImageGrabberThread::ImageGrabberThread(IMFMediaSource *pSource, unsigned int deviceID): igt_Handle(NULL), igt_stop(false) +ImageGrabberThread::ImageGrabberThread(IMFMediaSource *pSource, unsigned int deviceID): igt_func(NULL), igt_Handle(NULL), igt_stop(false) { DebugPrintOut *DPO = &DebugPrintOut::getInstance(); HRESULT hr = ImageGrabber::CreateInstance(&igt_pImageGrabber, deviceID); @@ -1330,11 +1374,11 @@ void ImageGrabberThread::stop() void ImageGrabberThread::start() { igt_Handle = CreateThread( - NULL, // default security attributes - 0, // use default stack size - MainThreadFunction, // thread function name - this, // argument to thread function - 0, // use default creation flags + NULL, // default security attributes + 0, // use default stack size + MainThreadFunction, // thread function name + this, // argument to thread function + 0, // use default creation flags &igt_ThreadIdArray); // returns the thread identifier } @@ -1359,6 +1403,7 @@ void ImageGrabberThread::run() DPO->printOut(L"IMAGEGRABBERTHREAD VIDEODEVICE %i: Emergency Stop thread\n", igt_DeviceID); if(igt_func) { + printf("Calling Emergency stop even handler\n"); igt_func(igt_DeviceID, igt_userData); } } @@ -2701,11 +2746,14 @@ protected: IplImage* frame; videoInput VI; }; + struct SuppressVideoInputMessages { SuppressVideoInputMessages() { videoInput::setVerbose(true); } }; + static SuppressVideoInputMessages do_it; + CvCaptureCAM_MSMF::CvCaptureCAM_MSMF(): index(-1), width(-1), @@ -2713,7 +2761,7 @@ CvCaptureCAM_MSMF::CvCaptureCAM_MSMF(): fourcc(-1), widthSet(-1), heightSet(-1), - frame(0), + frame(NULL), VI(videoInput::getInstance()) { CoInitialize(0); @@ -2925,6 +2973,435 @@ bool CvCaptureCAM_MSMF::setProperty( int property_id, double value ) } return false; } + +class CvCaptureFile_MSMF : public CvCapture +{ +public: + CvCaptureFile_MSMF(); + virtual ~CvCaptureFile_MSMF(); + + virtual bool open( const char* filename ); + virtual void close(); + + virtual double getProperty(int); + virtual bool setProperty(int, double); + virtual bool grabFrame(); + virtual IplImage* retrieveFrame(int); + virtual int getCaptureDomain() { return CV_CAP_MSMF; } +protected: + ImageGrabberThread* grabberThread; + IMFMediaSource* videoFileSource; + std::vector captureFormats; + int captureFormatIndex; + IplImage* frame; + bool isOpened; + + long enumerateCaptureFormats(IMFMediaSource *pSource); + void processPixels(unsigned char * src, unsigned char * dst, unsigned int width, + unsigned int height, unsigned int bpp, bool bRGB, bool bFlip); +}; + +CvCaptureFile_MSMF::CvCaptureFile_MSMF(): + grabberThread(NULL), + videoFileSource(NULL), + captureFormatIndex(0), + frame(NULL), + isOpened(false) +{ + MFStartup(MF_VERSION); +} + +CvCaptureFile_MSMF::~CvCaptureFile_MSMF() +{ + MFShutdown(); +} + +bool CvCaptureFile_MSMF::open(const char* filename) +{ + if (!filename) + return false; + + wchar_t* unicodeFileName = new wchar_t[strlen(filename)+1]; + MultiByteToWideChar(CP_ACP, 0, filename, -1, unicodeFileName, strlen(filename)+1); + + HRESULT hr = S_OK; + + MF_OBJECT_TYPE ObjectType = MF_OBJECT_INVALID; + + IMFSourceResolver* pSourceResolver = NULL; + IUnknown* pUnkSource = NULL; + + hr = MFCreateSourceResolver(&pSourceResolver); + + if (SUCCEEDED(hr)) + { + hr = pSourceResolver->CreateObjectFromURL( + unicodeFileName, + MF_RESOLUTION_MEDIASOURCE, + NULL, // Optional property store. + &ObjectType, + &pUnkSource + ); + } + + // Get the IMFMediaSource from the IUnknown pointer. + if (SUCCEEDED(hr)) + { + hr = pUnkSource->QueryInterface(IID_PPV_ARGS(&videoFileSource)); + } + + SafeRelease(&pSourceResolver); + SafeRelease(&pUnkSource); + + enumerateCaptureFormats(videoFileSource); + + if (SUCCEEDED(hr)) + { + hr = ImageGrabberThread::CreateInstance(&grabberThread, videoFileSource, -2); + } + + if (SUCCEEDED(hr)) + { + grabberThread->start(); + } + + isOpened = true; + + return true; +} + +void CvCaptureFile_MSMF::close() +{ + if (grabberThread) + { + isOpened = false; + SetEvent(grabberThread->getImageGrabber()->ig_hFrameReady); + grabberThread->stop(); + delete grabberThread; + } + + if (videoFileSource) + { + HRESULT hr = videoFileSource->Shutdown(); + if (FAILED(hr)) + { + printf("VideoCapture Closing failed!\n"); + } + } +} + +bool CvCaptureFile_MSMF::setProperty(int property_id, double value) +{ + // image capture properties + bool handled = false; + int width, height, fourcc; + switch( property_id ) + { + case CV_CAP_PROP_FRAME_WIDTH: + // width = cvRound(value); + // handled = true; + break; + case CV_CAP_PROP_FRAME_HEIGHT: + // height = cvRound(value); + // handled = true; + break; + case CV_CAP_PROP_FOURCC: + fourcc = (int)(unsigned long)(value); + if ( fourcc == -1 ) { + // following cvCreateVideo usage will pop up caprturepindialog here if fourcc=-1 + // TODO - how to create a capture pin dialog + } + handled = true; + break; + case CV_CAP_PROP_FPS: + // FIXME: implement method in VideoInput back end + // int fps = cvRound(value); + // if (fps != VI.getFPS(index)) + // { + // VI.stopDevice(index); + // VI.setIdealFramerate(index,fps); + // if (widthSet > 0 && heightSet > 0) + // VI.setupDevice(index, widthSet, heightSet); + // else + // VI.setupDevice(index); + // } + // return VI.isDeviceSetup(index); + ; + } + if ( handled ) { + // a stream setting + if( width > 0 && height > 0 ) + { + if( width != captureFormats[captureFormatIndex].width || + height != captureFormats[captureFormatIndex].height )//|| fourcc != VI.getFourcc(index) ) + { + // FIXME: implement method in VideoInput back end + // int fps = static_cast(VI.getFPS(index)); + // VI.stopDevice(index); + // VI.setIdealFramerate(index, fps); + // VI.setupDeviceFourcc(index, width, height, fourcc); + } + if (isOpened) + { + // widthSet = width; + // heightSet = height; + // width = height = fourcc = -1; + } + return isOpened; + } + return true; + } + // show video/camera filter dialog + // FIXME: implement method in VideoInput back end + // if ( property_id == CV_CAP_PROP_SETTINGS ) { + // VI.showSettingsWindow(index); + // return true; + // } + //video Filter properties + switch( property_id ) + { + case CV_CAP_PROP_BRIGHTNESS: + case CV_CAP_PROP_CONTRAST: + case CV_CAP_PROP_HUE: + case CV_CAP_PROP_SATURATION: + case CV_CAP_PROP_SHARPNESS: + case CV_CAP_PROP_GAMMA: + case CV_CAP_PROP_MONOCROME: + case CV_CAP_PROP_WHITE_BALANCE_BLUE_U: + case CV_CAP_PROP_BACKLIGHT: + case CV_CAP_PROP_GAIN: + // FIXME: implement method in VideoInput back end + //return VI.setVideoSettingFilter(index,VI.getVideoPropertyFromCV(property_id),(long)value); + ; + } + //camera properties + switch( property_id ) + { + case CV_CAP_PROP_PAN: + case CV_CAP_PROP_TILT: + case CV_CAP_PROP_ROLL: + case CV_CAP_PROP_ZOOM: + case CV_CAP_PROP_EXPOSURE: + case CV_CAP_PROP_IRIS: + case CV_CAP_PROP_FOCUS: + // FIXME: implement method in VideoInput back end + //return VI.setVideoSettingCamera(index,VI.getCameraPropertyFromCV(property_id),(long)value); + ; + } + + return false; +} + +double CvCaptureFile_MSMF::getProperty(int property_id) +{ + // image format proprrties + switch( property_id ) + { + case CV_CAP_PROP_FRAME_WIDTH: + return captureFormats[captureFormatIndex].width; + case CV_CAP_PROP_FRAME_HEIGHT: + return captureFormats[captureFormatIndex].height; + case CV_CAP_PROP_FOURCC: + // FIXME: implement method in VideoInput back end + //return VI.getFourcc(index); + ; + case CV_CAP_PROP_FPS: + // FIXME: implement method in VideoInput back end + //return VI.getFPS(index); + ; + } + // video filter properties + switch( property_id ) + { + case CV_CAP_PROP_BRIGHTNESS: + case CV_CAP_PROP_CONTRAST: + case CV_CAP_PROP_HUE: + case CV_CAP_PROP_SATURATION: + case CV_CAP_PROP_SHARPNESS: + case CV_CAP_PROP_GAMMA: + case CV_CAP_PROP_MONOCROME: + case CV_CAP_PROP_WHITE_BALANCE_BLUE_U: + case CV_CAP_PROP_BACKLIGHT: + case CV_CAP_PROP_GAIN: + // FIXME: implement method in VideoInput back end + // if ( VI.getVideoSettingFilter(index, VI.getVideoPropertyFromCV(property_id), min_value, + // max_value, stepping_delta, current_value, flags,defaultValue) ) + // return (double)current_value; + return 0.; + } + // camera properties + switch( property_id ) + { + case CV_CAP_PROP_PAN: + case CV_CAP_PROP_TILT: + case CV_CAP_PROP_ROLL: + case CV_CAP_PROP_ZOOM: + case CV_CAP_PROP_EXPOSURE: + case CV_CAP_PROP_IRIS: + case CV_CAP_PROP_FOCUS: + // FIXME: implement method in VideoInput back end + // if (VI.getVideoSettingCamera(index,VI.getCameraPropertyFromCV(property_id),min_value, + // max_value,stepping_delta,current_value,flags,defaultValue) ) return (double)current_value; + return 0.; + } + // unknown parameter or value not available + return -1; +} + +bool CvCaptureFile_MSMF::grabFrame() +{ + DWORD waitResult; + if (isOpened) + { + HANDLE tmp[] = {grabberThread->getImageGrabber()->ig_hFrameReady, grabberThread->getImageGrabber()->ig_hFinish, 0}; + waitResult = WaitForMultipleObjects(2, tmp, FALSE, INFINITE); + SetEvent(grabberThread->getImageGrabber()->ig_hFrameGrabbed); + } + + return isOpened && (waitResult == WAIT_OBJECT_0); +} + +IplImage* CvCaptureFile_MSMF::retrieveFrame(int) +{ + unsigned int width = captureFormats[captureFormatIndex].width; + unsigned int height = captureFormats[captureFormatIndex].height; + unsigned int bytes = 3; + if( !frame || width != frame->width || height != frame->height ) + { + if (frame) + cvReleaseImage( &frame ); + frame = cvCreateImage( cvSize(width,height), 8, 3 ); + } + + RawImage *RIOut = grabberThread->getImageGrabber()->getRawImage(); + unsigned int size = bytes * width * height; + + if(RIOut && size == RIOut->getSize()) + { + processPixels(RIOut->getpPixels(), (unsigned char*)frame->imageData, width, height, bytes, true, false); + } + + return frame; +} + +void CvCaptureFile_MSMF::processPixels(unsigned char * src, unsigned char * dst, unsigned int width, + unsigned int height, unsigned int bpp, bool bRGB, bool bFlip) +{ + unsigned int widthInBytes = width * bpp; + unsigned int numBytes = widthInBytes * height; + int *dstInt, *srcInt; + if(!bRGB) + { + if(bFlip) + { + for(unsigned int y = 0; y < height; y++) + { + dstInt = (int *)(dst + (y * widthInBytes)); + srcInt = (int *)(src + ( (height -y -1) * widthInBytes)); + memcpy(dstInt, srcInt, widthInBytes); + } + } + else + { + memcpy(dst, src, numBytes); + } + } + else + { + if(bFlip) + { + unsigned int x = 0; + unsigned int y = (height - 1) * widthInBytes; + src += y; + for(unsigned int i = 0; i < numBytes; i+=3) + { + if(x >= width) + { + x = 0; + src -= widthInBytes*2; + } + *dst = *(src+2); + dst++; + *dst = *(src+1); + dst++; + *dst = *src; + dst++; + src+=3; + x++; + } + } + else + { + for(unsigned int i = 0; i < numBytes; i+=3) + { + *dst = *(src+2); + dst++; + *dst = *(src+1); + dst++; + *dst = *src; + dst++; + src+=3; + } + } + } +} + +long CvCaptureFile_MSMF::enumerateCaptureFormats(IMFMediaSource *pSource) +{ + IMFPresentationDescriptor *pPD = NULL; + IMFStreamDescriptor *pSD = NULL; + IMFMediaTypeHandler *pHandler = NULL; + IMFMediaType *pType = NULL; + HRESULT hr = pSource->CreatePresentationDescriptor(&pPD); + if (FAILED(hr)) + { + goto done; + } + + DWORD cnt; + + pPD->GetStreamDescriptorCount(&cnt); + + printf("Stream count: %d\n", cnt); + + BOOL fSelected; + hr = pPD->GetStreamDescriptorByIndex(0, &fSelected, &pSD); + if (FAILED(hr)) + { + goto done; + } + hr = pSD->GetMediaTypeHandler(&pHandler); + if (FAILED(hr)) + { + goto done; + } + DWORD cTypes = 0; + hr = pHandler->GetMediaTypeCount(&cTypes); + if (FAILED(hr)) + { + goto done; + } + for (DWORD i = 0; i < cTypes; i++) + { + hr = pHandler->GetMediaTypeByIndex(i, &pType); + if (FAILED(hr)) + { + goto done; + } + MediaType MT = FormatReader::Read(pType); + captureFormats.push_back(MT); + SafeRelease(&pType); + } + +done: + SafeRelease(&pPD); + SafeRelease(&pSD); + SafeRelease(&pHandler); + SafeRelease(&pType); + return hr; +} + + CvCapture* cvCreateCameraCapture_MSMF( int index ) { CvCaptureCAM_MSMF* capture = new CvCaptureCAM_MSMF; @@ -2942,6 +3419,14 @@ CvCapture* cvCreateCameraCapture_MSMF( int index ) return 0; } +CvCapture* cvCreateFileCapture_MSMF (const char* filename) +{ + CvCaptureFile_MSMF* capture = new CvCaptureFile_MSMF; + if( capture->open(filename) ) + return capture; + delete capture; + return 0; +} // // diff --git a/modules/highgui/src/precomp.hpp b/modules/highgui/src/precomp.hpp index b9896955cc..dcd4afdc01 100644 --- a/modules/highgui/src/precomp.hpp +++ b/modules/highgui/src/precomp.hpp @@ -119,6 +119,7 @@ CvVideoWriter* cvCreateVideoWriter_VFW( const char* filename, int fourcc, double fps, CvSize frameSize, int is_color ); CvCapture* cvCreateCameraCapture_DShow( int index ); CvCapture* cvCreateCameraCapture_MSMF( int index ); +CvCapture* cvCreateFileCapture_MSMF (const char* filename); CvVideoWriter* cvCreateVideoWriter_MSMF( const char* filename, int fourcc, double fps, CvSize frameSize, int is_color ); CvCapture* cvCreateCameraCapture_OpenNI( int index ); From e94cc0b5ee4f1d4d4207ef73aeb65a59b41a0424 Mon Sep 17 00:00:00 2001 From: Alexander Smorkalov Date: Wed, 15 May 2013 05:12:25 -0700 Subject: [PATCH 151/178] Media Foundation camera capture fixed. Camera-based VideoCapture updated to fit changes in ImageGrabber from prev commit --- modules/highgui/src/cap_msmf.cpp | 68 ++++++++++++++++++++------------ 1 file changed, 43 insertions(+), 25 deletions(-) diff --git a/modules/highgui/src/cap_msmf.cpp b/modules/highgui/src/cap_msmf.cpp index c037d2f982..f5ea616bf7 100644 --- a/modules/highgui/src/cap_msmf.cpp +++ b/modules/highgui/src/cap_msmf.cpp @@ -180,15 +180,16 @@ public: void stopGrabbing(); RawImage *getRawImage(); // Function of creation of the instance of the class - static HRESULT CreateInstance(ImageGrabber **ppIG,unsigned int deviceID); + static HRESULT CreateInstance(ImageGrabber **ppIG, unsigned int deviceID, bool synchronous = false); - HANDLE ig_hFrameReady; - HANDLE ig_hFrameGrabbed; - HANDLE ig_hFinish; + const HANDLE ig_hFrameReady; + const HANDLE ig_hFrameGrabbed; + const HANDLE ig_hFinish; private: bool ig_RIE; bool ig_Close; + bool ig_Synchronous; long m_cRef; unsigned int ig_DeviceID; IMFMediaSource *ig_pSource; @@ -197,7 +198,7 @@ private: RawImage *ig_RIFirst; RawImage *ig_RISecond; RawImage *ig_RIOut; - ImageGrabber(unsigned int deviceID); + ImageGrabber(unsigned int deviceID, bool synchronous); HRESULT CreateTopology(IMFMediaSource *pSource, IMFActivate *pSinkActivate, IMFTopology **ppTopo); HRESULT AddSourceNode( IMFTopology *pTopology, @@ -229,7 +230,7 @@ class ImageGrabberThread friend DWORD WINAPI MainThreadFunction( LPVOID lpParam ); public: ~ImageGrabberThread(void); - static HRESULT CreateInstance(ImageGrabberThread **ppIGT, IMFMediaSource *pSource, unsigned int deviceID); + static HRESULT CreateInstance(ImageGrabberThread **ppIGT, IMFMediaSource *pSource, unsigned int deviceID, bool synchronious = false); void start(); void stop(); void setEmergencyStopEvent(void *userData, void(*func)(int, void *)); @@ -237,7 +238,7 @@ public: protected: virtual void run(); private: - ImageGrabberThread(IMFMediaSource *pSource, unsigned int deviceID); + ImageGrabberThread(IMFMediaSource *pSource, unsigned int deviceID, bool synchronious); HANDLE igt_Handle; DWORD igt_ThreadIdArray; ImageGrabber *igt_pImageGrabber; @@ -854,7 +855,7 @@ FormatReader::~FormatReader(void) #define CHECK_HR(x) if (FAILED(x)) { printf("Checking failed !!!\n"); goto done; } -ImageGrabber::ImageGrabber(unsigned int deviceID): +ImageGrabber::ImageGrabber(unsigned int deviceID, bool synchronous): m_cRef(1), ig_DeviceID(deviceID), ig_pSource(NULL), @@ -862,9 +863,10 @@ ImageGrabber::ImageGrabber(unsigned int deviceID): ig_pTopology(NULL), ig_RIE(true), ig_Close(false), - ig_hFrameReady(CreateEvent(NULL, FALSE, FALSE, "ig_hFrameReady")), - ig_hFrameGrabbed(CreateEvent(NULL, FALSE, TRUE, "ig_hFrameGrabbed")), - ig_hFinish(CreateEvent(NULL, FALSE, FALSE, "ig_hFinish")) + ig_Synchronous(synchronous), + ig_hFrameReady(synchronous ? CreateEvent(NULL, FALSE, FALSE, "ig_hFrameReady"): 0), + ig_hFrameGrabbed(synchronous ? CreateEvent(NULL, FALSE, TRUE, "ig_hFrameGrabbed"): 0), + ig_hFinish(synchronous ? CreateEvent(NULL, FALSE, FALSE, "ig_hFinish") : 0) {} ImageGrabber::~ImageGrabber(void) @@ -876,9 +878,12 @@ ImageGrabber::~ImageGrabber(void) ig_pSession->Shutdown(); } - CloseHandle(ig_hFrameReady); - CloseHandle(ig_hFrameGrabbed); - CloseHandle(ig_hFinish); + if (ig_Synchronous) + { + CloseHandle(ig_hFrameReady); + CloseHandle(ig_hFrameGrabbed); + CloseHandle(ig_hFinish); + } SafeRelease(&ig_pSession); SafeRelease(&ig_pTopology); @@ -1056,7 +1061,10 @@ HRESULT ImageGrabber::startGrabbing(void) SafeRelease(&pEvent); } - SetEvent(ig_hFinish); + if (ig_Synchronous) + { + SetEvent(ig_hFinish); + } DPO->printOut(L"IMAGEGRABBER VIDEODEVICE %i: Finish startGrabbing \n", ig_DeviceID); @@ -1172,9 +1180,9 @@ done: return hr; } -HRESULT ImageGrabber::CreateInstance(ImageGrabber **ppIG, unsigned int deviceID) +HRESULT ImageGrabber::CreateInstance(ImageGrabber **ppIG, unsigned int deviceID, bool synchronious) { - *ppIG = new (std::nothrow) ImageGrabber(deviceID); + *ppIG = new (std::nothrow) ImageGrabber(deviceID, synchronious); if (ppIG == NULL) { return E_OUTOFMEMORY; @@ -1286,14 +1294,21 @@ STDMETHODIMP ImageGrabber::OnProcessSample(REFGUID guidMajorMediaType, DWORD dwS printf("ImageGrabber::OnProcessSample() -- end\n"); - SetEvent(ig_hFrameReady); + if (ig_Synchronous) + { + SetEvent(ig_hFrameReady); + } return S_OK; } STDMETHODIMP ImageGrabber::OnShutdown() { - SetEvent(ig_hFrameGrabbed); + if (ig_Synchronous) + { + SetEvent(ig_hFrameGrabbed); + } + return S_OK; } @@ -1309,10 +1324,10 @@ DWORD WINAPI MainThreadFunction( LPVOID lpParam ) return 0; } -HRESULT ImageGrabberThread::CreateInstance(ImageGrabberThread **ppIGT, IMFMediaSource *pSource, unsigned int deviceID) +HRESULT ImageGrabberThread::CreateInstance(ImageGrabberThread **ppIGT, IMFMediaSource *pSource, unsigned int deviceID, bool synchronious) { DebugPrintOut *DPO = &DebugPrintOut::getInstance(); - *ppIGT = new (std::nothrow) ImageGrabberThread(pSource, deviceID); + *ppIGT = new (std::nothrow) ImageGrabberThread(pSource, deviceID, synchronious); if (ppIGT == NULL) { DPO->printOut(L"IMAGEGRABBERTHREAD VIDEODEVICE %i: Memory cannot be allocated\n", deviceID); @@ -1323,10 +1338,13 @@ HRESULT ImageGrabberThread::CreateInstance(ImageGrabberThread **ppIGT, IMFMediaS return S_OK; } -ImageGrabberThread::ImageGrabberThread(IMFMediaSource *pSource, unsigned int deviceID): igt_func(NULL), igt_Handle(NULL), igt_stop(false) +ImageGrabberThread::ImageGrabberThread(IMFMediaSource *pSource, unsigned int deviceID, bool synchronious): + igt_func(NULL), + igt_Handle(NULL), + igt_stop(false) { DebugPrintOut *DPO = &DebugPrintOut::getInstance(); - HRESULT hr = ImageGrabber::CreateInstance(&igt_pImageGrabber, deviceID); + HRESULT hr = ImageGrabber::CreateInstance(&igt_pImageGrabber, deviceID, synchronious); igt_DeviceID = deviceID; if(SUCCEEDED(hr)) { @@ -3057,7 +3075,7 @@ bool CvCaptureFile_MSMF::open(const char* filename) if (SUCCEEDED(hr)) { - hr = ImageGrabberThread::CreateInstance(&grabberThread, videoFileSource, -2); + hr = ImageGrabberThread::CreateInstance(&grabberThread, videoFileSource, -2, true); } if (SUCCEEDED(hr)) @@ -3278,7 +3296,7 @@ IplImage* CvCaptureFile_MSMF::retrieveFrame(int) if(RIOut && size == RIOut->getSize()) { - processPixels(RIOut->getpPixels(), (unsigned char*)frame->imageData, width, height, bytes, true, false); + processPixels(RIOut->getpPixels(), (unsigned char*)frame->imageData, width, height, bytes, false, false); } return frame; From 0c9d776083db4579404e5f1b5cb9b1bee7582881 Mon Sep 17 00:00:00 2001 From: Alexander Smorkalov Date: Wed, 15 May 2013 06:09:03 -0700 Subject: [PATCH 152/178] Media Foundation-based code refactoring. I* + SafeRelease -> ComPtr. --- modules/highgui/src/cap_msmf.cpp | 191 ++++++++++++++----------------- 1 file changed, 86 insertions(+), 105 deletions(-) diff --git a/modules/highgui/src/cap_msmf.cpp b/modules/highgui/src/cap_msmf.cpp index f5ea616bf7..15f2bdcab5 100644 --- a/modules/highgui/src/cap_msmf.cpp +++ b/modules/highgui/src/cap_msmf.cpp @@ -53,7 +53,7 @@ #include #include #include -#include "Strsafe.h" +#include #include #include #include @@ -72,6 +72,8 @@ #pragma comment(lib, "Mfreadwrite") #pragma comment(lib, "MinCore_Downlevel") +using namespace Microsoft::WRL; + struct IMFMediaType; struct IMFActivate; struct IMFMediaSource; @@ -894,12 +896,12 @@ ImageGrabber::~ImageGrabber(void) HRESULT ImageGrabber::initImageGrabber(IMFMediaSource *pSource, GUID VideoFormat) { - IMFActivate *pSinkActivate = NULL; - IMFMediaType *pType = NULL; - IMFPresentationDescriptor *pPD = NULL; - IMFStreamDescriptor *pSD = NULL; - IMFMediaTypeHandler *pHandler = NULL; - IMFMediaType *pCurrentType = NULL; + ComPtr pSinkActivate = NULL; + ComPtr pType = NULL; + ComPtr pPD = NULL; + ComPtr pSD = NULL; + ComPtr pHandler = NULL; + ComPtr pCurrentType = NULL; HRESULT hr = S_OK; MediaType MT; // Clean up. @@ -940,13 +942,9 @@ HRESULT ImageGrabber::initImageGrabber(IMFMediaSource *pSource, GUID VideoFormat printf("Error GetCurrentMediaType()\n"); goto err; } - MT = FormatReader::Read(pCurrentType); + MT = FormatReader::Read(pCurrentType.Get()); } err: - SafeRelease(&pPD); - SafeRelease(&pSD); - SafeRelease(&pHandler); - SafeRelease(&pCurrentType); unsigned int sizeRawImage = 0; if(VideoFormat == MFVideoFormat_RGB24) { @@ -966,17 +964,17 @@ err: // Configure the media type that the Sample Grabber will receive. // Setting the major and subtype is usually enough for the topology loader // to resolve the topology. - CHECK_HR(hr = MFCreateMediaType(&pType)); + CHECK_HR(hr = MFCreateMediaType(pType.GetAddressOf())); CHECK_HR(hr = pType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video)); CHECK_HR(hr = pType->SetGUID(MF_MT_SUBTYPE, VideoFormat)); // Create the sample grabber sink. - CHECK_HR(hr = MFCreateSampleGrabberSinkActivate(pType, this, &pSinkActivate)); + CHECK_HR(hr = MFCreateSampleGrabberSinkActivate(pType.Get(), this, pSinkActivate.GetAddressOf())); // To run as fast as possible, set this attribute (requires Windows 7): CHECK_HR(hr = pSinkActivate->SetUINT32(MF_SAMPLEGRABBERSINK_IGNORE_CLOCK, TRUE)); // Create the Media Session. CHECK_HR(hr = MFCreateMediaSession(NULL, &ig_pSession)); // Create the topology. - CHECK_HR(hr = CreateTopology(pSource, pSinkActivate, &ig_pTopology)); + CHECK_HR(hr = CreateTopology(pSource, pSinkActivate.Get(), &ig_pTopology)); done: // Clean up. if (FAILED(hr)) @@ -988,8 +986,7 @@ done: SafeRelease(&ig_pSession); SafeRelease(&ig_pTopology); } - SafeRelease(&pSinkActivate); - SafeRelease(&pType); + return hr; } @@ -1004,7 +1001,7 @@ void ImageGrabber::stopGrabbing() HRESULT ImageGrabber::startGrabbing(void) { DebugPrintOut *DPO = &DebugPrintOut::getInstance(); - IMFMediaEvent *pEvent = NULL; + ComPtr pEvent = NULL; PROPVARIANT var; PropVariantInit(&var); HRESULT hr = S_OK; @@ -1058,7 +1055,6 @@ HRESULT ImageGrabber::startGrabbing(void) DPO->printOut(L"IMAGEGRABBER VIDEODEVICE %i: MEVideoCaptureDeviceRemoved \n", ig_DeviceID); break; } - SafeRelease(&pEvent); } if (ig_Synchronous) @@ -1069,7 +1065,6 @@ HRESULT ImageGrabber::startGrabbing(void) DPO->printOut(L"IMAGEGRABBER VIDEODEVICE %i: Finish startGrabbing \n", ig_DeviceID); done: - SafeRelease(&pEvent); SafeRelease(&ig_pSession); SafeRelease(&ig_pTopology); @@ -1086,16 +1081,16 @@ void ImageGrabber::resumeGrabbing() HRESULT ImageGrabber::CreateTopology(IMFMediaSource *pSource, IMFActivate *pSinkActivate, IMFTopology **ppTopo) { - IMFTopology *pTopology = NULL; - IMFPresentationDescriptor *pPD = NULL; - IMFStreamDescriptor *pSD = NULL; - IMFMediaTypeHandler *pHandler = NULL; - IMFTopologyNode *pNode1 = NULL; - IMFTopologyNode *pNode2 = NULL; + ComPtr pTopology = NULL; + ComPtr pPD = NULL; + ComPtr pSD = NULL; + ComPtr pHandler = NULL; + ComPtr pNode1 = NULL; + ComPtr pNode2 = NULL; HRESULT hr = S_OK; DWORD cStreams = 0; - CHECK_HR(hr = MFCreateTopology(&pTopology)); - CHECK_HR(hr = pSource->CreatePresentationDescriptor(&pPD)); + CHECK_HR(hr = MFCreateTopology(pTopology.GetAddressOf())); + CHECK_HR(hr = pSource->CreatePresentationDescriptor(pPD.GetAddressOf())); CHECK_HR(hr = pPD->GetStreamDescriptorCount(&cStreams)); for (DWORD i = 0; i < cStreams; i++) { @@ -1107,29 +1102,20 @@ HRESULT ImageGrabber::CreateTopology(IMFMediaSource *pSource, IMFActivate *pSink CHECK_HR(hr = pHandler->GetMajorType(&majorType)); if (majorType == MFMediaType_Video && fSelected) { - CHECK_HR(hr = AddSourceNode(pTopology, pSource, pPD, pSD, &pNode1)); - CHECK_HR(hr = AddOutputNode(pTopology, pSinkActivate, 0, &pNode2)); - CHECK_HR(hr = pNode1->ConnectOutput(0, pNode2, 0)); + CHECK_HR(hr = AddSourceNode(pTopology.Get(), pSource, pPD.Get(), pSD.Get(), pNode1.GetAddressOf())); + CHECK_HR(hr = AddOutputNode(pTopology.Get(), pSinkActivate, 0, pNode2.GetAddressOf())); + CHECK_HR(hr = pNode1->ConnectOutput(0, pNode2.Get(), 0)); break; } else { CHECK_HR(hr = pPD->DeselectStream(i)); } - SafeRelease(&pSD); - SafeRelease(&pHandler); } - *ppTopo = pTopology; + *ppTopo = pTopology.Get(); (*ppTopo)->AddRef(); done: - SafeRelease(&pTopology); - SafeRelease(&pNode1); - SafeRelease(&pNode2); - SafeRelease(&pPD); - SafeRelease(&pSD); - SafeRelease(&pHandler); - return hr; } @@ -1140,20 +1126,18 @@ HRESULT ImageGrabber::AddSourceNode( IMFStreamDescriptor *pSD, // Stream descriptor. IMFTopologyNode **ppNode) // Receives the node pointer. { - IMFTopologyNode *pNode = NULL; + ComPtr pNode = NULL; HRESULT hr = S_OK; - CHECK_HR(hr = MFCreateTopologyNode(MF_TOPOLOGY_SOURCESTREAM_NODE, &pNode)); + CHECK_HR(hr = MFCreateTopologyNode(MF_TOPOLOGY_SOURCESTREAM_NODE, pNode.GetAddressOf())); CHECK_HR(hr = pNode->SetUnknown(MF_TOPONODE_SOURCE, pSource)); CHECK_HR(hr = pNode->SetUnknown(MF_TOPONODE_PRESENTATION_DESCRIPTOR, pPD)); CHECK_HR(hr = pNode->SetUnknown(MF_TOPONODE_STREAM_DESCRIPTOR, pSD)); - CHECK_HR(hr = pTopology->AddNode(pNode)); + CHECK_HR(hr = pTopology->AddNode(pNode.Get())); // Return the pointer to the caller. - *ppNode = pNode; + *ppNode = pNode.Get(); (*ppNode)->AddRef(); done: - SafeRelease(&pNode); - return hr; } @@ -1163,20 +1147,18 @@ HRESULT ImageGrabber::AddOutputNode( DWORD dwId, // Identifier of the stream sink. IMFTopologyNode **ppNode) // Receives the node pointer. { - IMFTopologyNode *pNode = NULL; + ComPtr pNode = NULL; HRESULT hr = S_OK; - CHECK_HR(hr = MFCreateTopologyNode(MF_TOPOLOGY_OUTPUT_NODE, &pNode)); + CHECK_HR(hr = MFCreateTopologyNode(MF_TOPOLOGY_OUTPUT_NODE, pNode.GetAddressOf())); CHECK_HR(hr = pNode->SetObject(pActivate)); CHECK_HR(hr = pNode->SetUINT32(MF_TOPONODE_STREAMID, dwId)); CHECK_HR(hr = pNode->SetUINT32(MF_TOPONODE_NOSHUTDOWN_ON_REMOVE, FALSE)); - CHECK_HR(hr = pTopology->AddNode(pNode)); + CHECK_HR(hr = pTopology->AddNode(pNode.Get())); // Return the pointer to the caller. - *ppNode = pNode; + *ppNode = pNode.Get(); (*ppNode)->AddRef(); done: - SafeRelease(&pNode); - return hr; } @@ -1457,9 +1439,9 @@ Media_Foundation::~Media_Foundation(void) bool Media_Foundation::buildListOfDevices() { HRESULT hr = S_OK; - IMFAttributes *pAttributes = NULL; + ComPtr pAttributes = NULL; CoInitialize(NULL); - hr = MFCreateAttributes(&pAttributes, 1); + hr = MFCreateAttributes(pAttributes.GetAddressOf(), 1); if (SUCCEEDED(hr)) { hr = pAttributes->SetGUID( @@ -1470,14 +1452,14 @@ bool Media_Foundation::buildListOfDevices() if (SUCCEEDED(hr)) { videoDevices *vDs = &videoDevices::getInstance(); - hr = vDs->initDevices(pAttributes); + hr = vDs->initDevices(pAttributes.Get()); } else { DebugPrintOut *DPO = &DebugPrintOut::getInstance(); DPO->printOut(L"MEDIA FOUNDATION: The access to the video cameras denied\n"); } - SafeRelease(&pAttributes); + return (SUCCEEDED(hr)); } @@ -1721,14 +1703,15 @@ long videoDevice::checkDevice(IMFAttributes *pAttributes, IMFActivate **pDevice) } return hr; } + long videoDevice::initDevice() { HRESULT hr = -1; - IMFAttributes *pAttributes = NULL; - IMFActivate * vd_pActivate= NULL; + ComPtr pAttributes = NULL; + IMFActivate *vd_pActivate = NULL; DebugPrintOut *DPO = &DebugPrintOut::getInstance(); CoInitialize(NULL); - hr = MFCreateAttributes(&pAttributes, 1); + hr = MFCreateAttributes(pAttributes.GetAddressOf(), 1); if (SUCCEEDED(hr)) { hr = pAttributes->SetGUID( @@ -1738,7 +1721,7 @@ long videoDevice::initDevice() } if (SUCCEEDED(hr)) { - hr = checkDevice(pAttributes, &vd_pActivate); + hr = checkDevice(pAttributes.Get(), &vd_pActivate); if (SUCCEEDED(hr) && vd_pActivate) { SafeRelease(&vd_pSource); @@ -1760,9 +1743,10 @@ long videoDevice::initDevice() { DPO->printOut(L"VIDEODEVICE %i: The attribute of video cameras cannot be getting \n", vd_CurrentNumber); } - SafeRelease(&pAttributes); + return hr; } + MediaType videoDevice::getFormat(unsigned int id) { if(id < vd_CurrentFormats.size()) @@ -1887,45 +1871,45 @@ void videoDevice::buildLibraryofTypes() count++; } } + long videoDevice::setDeviceFormat(IMFMediaSource *pSource, unsigned long dwFormatIndex) { - IMFPresentationDescriptor *pPD = NULL; - IMFStreamDescriptor *pSD = NULL; - IMFMediaTypeHandler *pHandler = NULL; - IMFMediaType *pType = NULL; - HRESULT hr = pSource->CreatePresentationDescriptor(&pPD); + ComPtr pPD = NULL; + ComPtr pSD = NULL; + ComPtr pHandler = NULL; + ComPtr pType = NULL; + HRESULT hr = pSource->CreatePresentationDescriptor(pPD.GetAddressOf()); if (FAILED(hr)) { goto done; } BOOL fSelected; - hr = pPD->GetStreamDescriptorByIndex(0, &fSelected, &pSD); + hr = pPD->GetStreamDescriptorByIndex(0, &fSelected, pSD.GetAddressOf()); if (FAILED(hr)) { goto done; } - hr = pSD->GetMediaTypeHandler(&pHandler); + hr = pSD->GetMediaTypeHandler(pHandler.GetAddressOf()); if (FAILED(hr)) { goto done; } - hr = pHandler->GetMediaTypeByIndex((DWORD)dwFormatIndex, &pType); + hr = pHandler->GetMediaTypeByIndex((DWORD)dwFormatIndex, pType.GetAddressOf()); if (FAILED(hr)) { goto done; } - hr = pHandler->SetCurrentMediaType(pType); + hr = pHandler->SetCurrentMediaType(pType.Get()); + done: - SafeRelease(&pPD); - SafeRelease(&pSD); - SafeRelease(&pHandler); - SafeRelease(&pType); return hr; } + bool videoDevice::isDeviceSetup() { return vd_IsSetuped; } + RawImage * videoDevice::getRawImageOut() { if(!vd_IsSetuped) return NULL; @@ -1938,6 +1922,7 @@ RawImage * videoDevice::getRawImageOut() } return NULL; } + bool videoDevice::isFrameNew() { if(!vd_IsSetuped) return false; @@ -1962,16 +1947,19 @@ bool videoDevice::isFrameNew() } return false; } + bool videoDevice::isDeviceMediaSource() { if(vd_LockOut == MediaSourceLock) return true; return false; } + bool videoDevice::isDeviceRawDataSource() { if(vd_LockOut == RawDataLock) return true; return false; } + bool videoDevice::setupDevice(unsigned int id) { DebugPrintOut *DPO = &DebugPrintOut::getInstance(); @@ -2002,15 +1990,18 @@ bool videoDevice::setupDevice(unsigned int id) return false; } } + bool videoDevice::setupDevice(unsigned int w, unsigned int h, unsigned int idealFramerate) { unsigned int id = findType(w * h, idealFramerate); return setupDevice(id); } + wchar_t *videoDevice::getName() { return vd_pFriendlyName; } + videoDevice::~videoDevice(void) { closeDevice(); @@ -2018,24 +2009,25 @@ videoDevice::~videoDevice(void) if(vd_pFriendlyName) CoTaskMemFree(vd_pFriendlyName); } + long videoDevice::enumerateCaptureFormats(IMFMediaSource *pSource) { - IMFPresentationDescriptor *pPD = NULL; - IMFStreamDescriptor *pSD = NULL; - IMFMediaTypeHandler *pHandler = NULL; - IMFMediaType *pType = NULL; - HRESULT hr = pSource->CreatePresentationDescriptor(&pPD); + ComPtr pPD = NULL; + ComPtr pSD = NULL; + ComPtr pHandler = NULL; + ComPtr pType = NULL; + HRESULT hr = pSource->CreatePresentationDescriptor(pPD.GetAddressOf()); if (FAILED(hr)) { goto done; } BOOL fSelected; - hr = pPD->GetStreamDescriptorByIndex(0, &fSelected, &pSD); + hr = pPD->GetStreamDescriptorByIndex(0, &fSelected, pSD.GetAddressOf()); if (FAILED(hr)) { goto done; } - hr = pSD->GetMediaTypeHandler(&pHandler); + hr = pSD->GetMediaTypeHandler(pHandler.GetAddressOf()); if (FAILED(hr)) { goto done; @@ -2048,20 +2040,16 @@ long videoDevice::enumerateCaptureFormats(IMFMediaSource *pSource) } for (DWORD i = 0; i < cTypes; i++) { - hr = pHandler->GetMediaTypeByIndex(i, &pType); + hr = pHandler->GetMediaTypeByIndex(i, pType.GetAddressOf()); if (FAILED(hr)) { goto done; } - MediaType MT = FormatReader::Read(pType); + MediaType MT = FormatReader::Read(pType.Get()); vd_CurrentFormats.push_back(MT); - SafeRelease(&pType); } + done: - SafeRelease(&pPD); - SafeRelease(&pSD); - SafeRelease(&pHandler); - SafeRelease(&pType); return hr; } @@ -3366,11 +3354,11 @@ void CvCaptureFile_MSMF::processPixels(unsigned char * src, unsigned char * dst, long CvCaptureFile_MSMF::enumerateCaptureFormats(IMFMediaSource *pSource) { - IMFPresentationDescriptor *pPD = NULL; - IMFStreamDescriptor *pSD = NULL; - IMFMediaTypeHandler *pHandler = NULL; - IMFMediaType *pType = NULL; - HRESULT hr = pSource->CreatePresentationDescriptor(&pPD); + ComPtr pPD = NULL; + ComPtr pSD = NULL; + ComPtr pHandler = NULL; + ComPtr pType = NULL; + HRESULT hr = pSource->CreatePresentationDescriptor(pPD.GetAddressOf()); if (FAILED(hr)) { goto done; @@ -3383,12 +3371,12 @@ long CvCaptureFile_MSMF::enumerateCaptureFormats(IMFMediaSource *pSource) printf("Stream count: %d\n", cnt); BOOL fSelected; - hr = pPD->GetStreamDescriptorByIndex(0, &fSelected, &pSD); + hr = pPD->GetStreamDescriptorByIndex(0, &fSelected, pSD.GetAddressOf()); if (FAILED(hr)) { goto done; } - hr = pSD->GetMediaTypeHandler(&pHandler); + hr = pSD->GetMediaTypeHandler(pHandler.GetAddressOf()); if (FAILED(hr)) { goto done; @@ -3401,21 +3389,16 @@ long CvCaptureFile_MSMF::enumerateCaptureFormats(IMFMediaSource *pSource) } for (DWORD i = 0; i < cTypes; i++) { - hr = pHandler->GetMediaTypeByIndex(i, &pType); + hr = pHandler->GetMediaTypeByIndex(i, pType.GetAddressOf()); if (FAILED(hr)) { goto done; } - MediaType MT = FormatReader::Read(pType); + MediaType MT = FormatReader::Read(pType.Get()); captureFormats.push_back(MT); - SafeRelease(&pType); } done: - SafeRelease(&pPD); - SafeRelease(&pSD); - SafeRelease(&pHandler); - SafeRelease(&pType); return hr; } @@ -3452,8 +3435,6 @@ CvCapture* cvCreateFileCapture_MSMF (const char* filename) // // -using namespace Microsoft::WRL; - class CvVideoWriter_MSMF : public CvVideoWriter { public: From 996f02a531318f5aa3004d876fb1b3f2af429e3b Mon Sep 17 00:00:00 2001 From: Alexander Smorkalov Date: Sat, 18 May 2013 13:04:31 -0700 Subject: [PATCH 153/178] Multiple Media Foundation video i/o fixes. Video i/o tests enabled for media foundation; Negative stride support added to VideoCapture; Error handling improved, dead lock in case of playback error fixed; Some code refacotring done. --- modules/highgui/src/cap_msmf.cpp | 178 ++++++++++--------------- modules/highgui/test/test_precomp.hpp | 6 +- modules/highgui/test/test_video_io.cpp | 38 +++++- 3 files changed, 112 insertions(+), 110 deletions(-) diff --git a/modules/highgui/src/cap_msmf.cpp b/modules/highgui/src/cap_msmf.cpp index 15f2bdcab5..814fb75beb 100644 --- a/modules/highgui/src/cap_msmf.cpp +++ b/modules/highgui/src/cap_msmf.cpp @@ -54,7 +54,6 @@ #include #include #include -#include #include #include #include @@ -72,6 +71,8 @@ #pragma comment(lib, "Mfreadwrite") #pragma comment(lib, "MinCore_Downlevel") +// for ComPtr usage +#include using namespace Microsoft::WRL; struct IMFMediaType; @@ -112,7 +113,7 @@ struct MediaType unsigned int width; unsigned int MF_MT_YUV_MATRIX; unsigned int MF_MT_VIDEO_LIGHTING; - unsigned int MF_MT_DEFAULT_STRIDE; + int MF_MT_DEFAULT_STRIDE; // stride is negative if image is bottom-up unsigned int MF_MT_VIDEO_CHROMA_SITING; GUID MF_MT_AM_FORMAT_TYPE; wchar_t *pMF_MT_AM_FORMAT_TYPEName; @@ -226,6 +227,7 @@ private: DWORD dwSampleSize); STDMETHODIMP OnShutdown(); }; + /// Class for controlling of thread of the grabbing raw data from video device class ImageGrabberThread { @@ -249,6 +251,7 @@ private: bool igt_stop; unsigned int igt_DeviceID; }; + // Structure for collecting info about one parametr of current video device struct Parametr { @@ -260,6 +263,7 @@ struct Parametr long Flag; Parametr(); }; + // Structure for collecting info about 17 parametrs of current video device struct CamParametrs { @@ -281,11 +285,13 @@ struct CamParametrs Parametr Iris; Parametr Focus; }; + typedef std::wstring String; typedef std::vector vectorNum; typedef std::map SUBTYPEMap; typedef std::map FrameRateMap; typedef void(*emergensyStopEventCallback)(int, void *); + /// Class for controlling of video device class videoDevice { @@ -329,7 +335,7 @@ private: IMFMediaSource *vd_pSource; emergensyStopEventCallback vd_func; void *vd_userData; - long enumerateCaptureFormats(IMFMediaSource *pSource); + HRESULT enumerateCaptureFormats(IMFMediaSource *pSource); long setDeviceFormat(IMFMediaSource *pSource, unsigned long dwFormatIndex); void buildLibraryofTypes(); int findType(unsigned int size, unsigned int frameRate = 0); @@ -337,6 +343,7 @@ private: long initDevice(); long checkDevice(IMFAttributes *pAttributes, IMFActivate **pDevice); }; + /// Class for managing of list of video devices class videoDevices { @@ -352,6 +359,7 @@ private: std::vector vds_Devices; videoDevices(void); }; + // Class for creating of Media Foundation context class Media_Foundation { @@ -362,6 +370,7 @@ public: private: Media_Foundation(void); }; + /// The only visiable class for controlling of video devices in format singelton class videoInput { @@ -411,23 +420,27 @@ public: bool isFrameNew(int deviceID); // Writing of Raw Data pixels from video device with deviceID with correction of RedAndBlue flipping flipRedAndBlue and vertical flipping flipImage bool getPixels(int deviceID, unsigned char * pixels, bool flipRedAndBlue = false, bool flipImage = false); + static void processPixels(unsigned char * src, unsigned char * dst, unsigned int width, unsigned int height, unsigned int bpp, bool bRGB, bool bFlip); private: bool accessToDevices; videoInput(void); - void processPixels(unsigned char * src, unsigned char * dst, unsigned int width, unsigned int height, unsigned int bpp, bool bRGB, bool bFlip); void updateListOfDevices(); }; + DebugPrintOut::DebugPrintOut(void):verbose(true) { } + DebugPrintOut::~DebugPrintOut(void) { } + DebugPrintOut& DebugPrintOut::getInstance() { static DebugPrintOut instance; return instance; } + void DebugPrintOut::printOut(const wchar_t *format, ...) { if(verbose) @@ -448,14 +461,17 @@ void DebugPrintOut::printOut(const wchar_t *format, ...) va_end (args); } } + void DebugPrintOut::setVerbose(bool state) { verbose = state; } + LPCWSTR GetGUIDNameConstNew(const GUID& guid); HRESULT GetGUIDNameNew(const GUID& guid, WCHAR **ppwsz); HRESULT LogAttributeValueByIndexNew(IMFAttributes *pAttr, DWORD index); HRESULT SpecialCaseAttributeValueNew(GUID guid, const PROPVARIANT& var, MediaType &out); + unsigned int *GetParametr(GUID guid, MediaType &out) { if(guid == MF_MT_YUV_MATRIX) @@ -463,7 +479,7 @@ unsigned int *GetParametr(GUID guid, MediaType &out) if(guid == MF_MT_VIDEO_LIGHTING) return &(out.MF_MT_VIDEO_LIGHTING); if(guid == MF_MT_DEFAULT_STRIDE) - return &(out.MF_MT_DEFAULT_STRIDE); + return (unsigned int*)&(out.MF_MT_DEFAULT_STRIDE); if(guid == MF_MT_VIDEO_CHROMA_SITING) return &(out.MF_MT_VIDEO_CHROMA_SITING); if(guid == MF_MT_VIDEO_NOMINAL_RANGE) @@ -480,6 +496,7 @@ unsigned int *GetParametr(GUID guid, MediaType &out) return &(out.MF_MT_INTERLACE_MODE); return NULL; } + HRESULT LogAttributeValueByIndexNew(IMFAttributes *pAttr, DWORD index, MediaType &out) { WCHAR *pGuidName = NULL; @@ -566,6 +583,7 @@ done: PropVariantClear(&var); return hr; } + HRESULT GetGUIDNameNew(const GUID& guid, WCHAR **ppwsz) { HRESULT hr = S_OK; @@ -625,6 +643,10 @@ HRESULT LogVideoAreaNew(const PROPVARIANT& var) } HRESULT SpecialCaseAttributeValueNew(GUID guid, const PROPVARIANT& var, MediaType &out) { + if (guid == MF_MT_DEFAULT_STRIDE) + { + out.MF_MT_DEFAULT_STRIDE = var.intVal; + } else if (guid == MF_MT_FRAME_SIZE) { UINT32 uHigh = 0, uLow = 0; @@ -1039,6 +1061,7 @@ HRESULT ImageGrabber::startGrabbing(void) hr = S_OK; goto done; } + printf("media foundation event: %d\n", met); if (met == MESessionEnded) { DPO->printOut(L"IMAGEGRABBER VIDEODEVICE %i: MESessionEnded \n", ig_DeviceID); @@ -1055,16 +1078,21 @@ HRESULT ImageGrabber::startGrabbing(void) DPO->printOut(L"IMAGEGRABBER VIDEODEVICE %i: MEVideoCaptureDeviceRemoved \n", ig_DeviceID); break; } + if ((met == MEError) || (met == MENonFatalError)) + { + pEvent->GetStatus(&hrStatus); + DPO->printOut(L"IMAGEGRABBER VIDEODEVICE %i: MEError | MENonFatalError: %u\n", ig_DeviceID, hrStatus); + break; + } } + DPO->printOut(L"IMAGEGRABBER VIDEODEVICE %i: Finish startGrabbing \n", ig_DeviceID); +done: if (ig_Synchronous) { SetEvent(ig_hFinish); } - DPO->printOut(L"IMAGEGRABBER VIDEODEVICE %i: Finish startGrabbing \n", ig_DeviceID); - -done: SafeRelease(&ig_pSession); SafeRelease(&ig_pTopology); @@ -2010,7 +2038,7 @@ videoDevice::~videoDevice(void) CoTaskMemFree(vd_pFriendlyName); } -long videoDevice::enumerateCaptureFormats(IMFMediaSource *pSource) +HRESULT videoDevice::enumerateCaptureFormats(IMFMediaSource *pSource) { ComPtr pPD = NULL; ComPtr pSD = NULL; @@ -3002,9 +3030,7 @@ protected: IplImage* frame; bool isOpened; - long enumerateCaptureFormats(IMFMediaSource *pSource); - void processPixels(unsigned char * src, unsigned char * dst, unsigned int width, - unsigned int height, unsigned int bpp, bool bRGB, bool bFlip); + HRESULT enumerateCaptureFormats(IMFMediaSource *pSource); }; CvCaptureFile_MSMF::CvCaptureFile_MSMF(): @@ -3034,10 +3060,10 @@ bool CvCaptureFile_MSMF::open(const char* filename) MF_OBJECT_TYPE ObjectType = MF_OBJECT_INVALID; - IMFSourceResolver* pSourceResolver = NULL; + ComPtr pSourceResolver = NULL; IUnknown* pUnkSource = NULL; - hr = MFCreateSourceResolver(&pSourceResolver); + hr = MFCreateSourceResolver(pSourceResolver.GetAddressOf()); if (SUCCEEDED(hr)) { @@ -3056,10 +3082,12 @@ bool CvCaptureFile_MSMF::open(const char* filename) hr = pUnkSource->QueryInterface(IID_PPV_ARGS(&videoFileSource)); } - SafeRelease(&pSourceResolver); SafeRelease(&pUnkSource); - enumerateCaptureFormats(videoFileSource); + if (SUCCEEDED(hr)) + { + hr = enumerateCaptureFormats(videoFileSource); + } if (SUCCEEDED(hr)) { @@ -3071,9 +3099,9 @@ bool CvCaptureFile_MSMF::open(const char* filename) grabberThread->start(); } - isOpened = true; + isOpened = SUCCEEDED(hr); - return true; + return isOpened; } void CvCaptureFile_MSMF::close() @@ -3207,6 +3235,8 @@ double CvCaptureFile_MSMF::getProperty(int property_id) return captureFormats[captureFormatIndex].width; case CV_CAP_PROP_FRAME_HEIGHT: return captureFormats[captureFormatIndex].height; + case CV_CAP_PROP_FRAME_COUNT: + return 30; case CV_CAP_PROP_FOURCC: // FIXME: implement method in VideoInput back end //return VI.getFourcc(index); @@ -3282,77 +3312,18 @@ IplImage* CvCaptureFile_MSMF::retrieveFrame(int) RawImage *RIOut = grabberThread->getImageGrabber()->getRawImage(); unsigned int size = bytes * width * height; + bool verticalFlip = captureFormats[captureFormatIndex].MF_MT_DEFAULT_STRIDE < 0; + if(RIOut && size == RIOut->getSize()) { - processPixels(RIOut->getpPixels(), (unsigned char*)frame->imageData, width, height, bytes, false, false); + videoInput::processPixels(RIOut->getpPixels(), (unsigned char*)frame->imageData, width, + height, bytes, false, verticalFlip); } return frame; } -void CvCaptureFile_MSMF::processPixels(unsigned char * src, unsigned char * dst, unsigned int width, - unsigned int height, unsigned int bpp, bool bRGB, bool bFlip) -{ - unsigned int widthInBytes = width * bpp; - unsigned int numBytes = widthInBytes * height; - int *dstInt, *srcInt; - if(!bRGB) - { - if(bFlip) - { - for(unsigned int y = 0; y < height; y++) - { - dstInt = (int *)(dst + (y * widthInBytes)); - srcInt = (int *)(src + ( (height -y -1) * widthInBytes)); - memcpy(dstInt, srcInt, widthInBytes); - } - } - else - { - memcpy(dst, src, numBytes); - } - } - else - { - if(bFlip) - { - unsigned int x = 0; - unsigned int y = (height - 1) * widthInBytes; - src += y; - for(unsigned int i = 0; i < numBytes; i+=3) - { - if(x >= width) - { - x = 0; - src -= widthInBytes*2; - } - *dst = *(src+2); - dst++; - *dst = *(src+1); - dst++; - *dst = *src; - dst++; - src+=3; - x++; - } - } - else - { - for(unsigned int i = 0; i < numBytes; i+=3) - { - *dst = *(src+2); - dst++; - *dst = *(src+1); - dst++; - *dst = *src; - dst++; - src+=3; - } - } - } -} - -long CvCaptureFile_MSMF::enumerateCaptureFormats(IMFMediaSource *pSource) +HRESULT CvCaptureFile_MSMF::enumerateCaptureFormats(IMFMediaSource *pSource) { ComPtr pPD = NULL; ComPtr pSD = NULL; @@ -3364,12 +3335,6 @@ long CvCaptureFile_MSMF::enumerateCaptureFormats(IMFMediaSource *pSource) goto done; } - DWORD cnt; - - pPD->GetStreamDescriptorCount(&cnt); - - printf("Stream count: %d\n", cnt); - BOOL fSelected; hr = pPD->GetStreamDescriptorByIndex(0, &fSelected, pSD.GetAddressOf()); if (FAILED(hr)) @@ -3423,10 +3388,21 @@ CvCapture* cvCreateCameraCapture_MSMF( int index ) CvCapture* cvCreateFileCapture_MSMF (const char* filename) { CvCaptureFile_MSMF* capture = new CvCaptureFile_MSMF; - if( capture->open(filename) ) - return capture; - delete capture; - return 0; + try + { + if( capture->open(filename) ) + return capture; + else + { + delete capture; + return NULL; + } + } + catch(...) + { + delete capture; + throw; + } } // @@ -3440,10 +3416,10 @@ class CvVideoWriter_MSMF : public CvVideoWriter public: CvVideoWriter_MSMF(); virtual ~CvVideoWriter_MSMF(); - virtual bool open( const char* filename, int fourcc, - double fps, CvSize frameSize, bool isColor ); + virtual bool open(const char* filename, int fourcc, + double fps, CvSize frameSize, bool isColor); virtual void close(); - virtual bool writeFrame( const IplImage* img); + virtual bool writeFrame(const IplImage* img); private: UINT32 videoWidth; @@ -3533,7 +3509,7 @@ bool CvVideoWriter_MSMF::open( const char* filename, int fourcc, videoWidth = frameSize.width; videoHeight = frameSize.height; fps = _fps; - bitRate = videoWidth*videoHeight; // 1-bit per pixel + bitRate = fps*videoWidth*videoHeight; // 1-bit per pixel encodingFormat = FourCC2GUID(fourcc); inputFormat = MFVideoFormat_RGB32; @@ -3596,14 +3572,7 @@ bool CvVideoWriter_MSMF::writeFrame(const IplImage* img) BYTE g = rowStart[colIdx * img->nChannels + 1]; BYTE r = rowStart[colIdx * img->nChannels + 2]; - // On ARM devices data is stored starting from the last line - // (and not the first line) so you have to revert them on the Y axis -#if _M_ARM - int targetRow = videoHeight - rowIdx - 1; - target[(targetRow * videoWidth) + colIdx] = (r << 16) + (g << 8) + b; -#else target[rowIdx*img->width+colIdx] = (r << 16) + (g << 8) + b; -#endif } } @@ -3677,6 +3646,7 @@ HRESULT CvVideoWriter_MSMF::InitializeSinkWriter(const char* filename) { hr = MFSetAttributeRatio(mediaTypeOut.Get(), MF_MT_PIXEL_ASPECT_RATIO, 1, 1); } + if (SUCCEEDED(hr)) { hr = sinkWriter->AddStream(mediaTypeOut.Get(), &streamIndex); @@ -3711,7 +3681,7 @@ HRESULT CvVideoWriter_MSMF::InitializeSinkWriter(const char* filename) { hr = MFSetAttributeRatio(mediaTypeIn.Get(), MF_MT_PIXEL_ASPECT_RATIO, 1, 1); } - if (SUCCEEDED(hr)) + if (SUCCEEDED(hr)) { hr = sinkWriter->SetInputMediaType(streamIndex, mediaTypeIn.Get(), NULL); } diff --git a/modules/highgui/test/test_precomp.hpp b/modules/highgui/test/test_precomp.hpp index 0d0bd80228..be06c0643a 100644 --- a/modules/highgui/test/test_precomp.hpp +++ b/modules/highgui/test/test_precomp.hpp @@ -47,7 +47,8 @@ defined(HAVE_QUICKTIME) || \ defined(HAVE_AVFOUNDATION) || \ /*defined(HAVE_OPENNI) || too specialized */ \ - defined(HAVE_FFMPEG) + defined(HAVE_FFMPEG) || \ + defined(HAVE_MSMF) # define BUILD_WITH_VIDEO_INPUT_SUPPORT 1 #else # define BUILD_WITH_VIDEO_INPUT_SUPPORT 0 @@ -57,7 +58,8 @@ defined(HAVE_GSTREAMER) || \ defined(HAVE_QUICKTIME) || \ defined(HAVE_AVFOUNDATION) || \ - defined(HAVE_FFMPEG) + defined(HAVE_FFMPEG) || \ + defined(HAVE_MSMF) # define BUILD_WITH_VIDEO_OUTPUT_SUPPORT 1 #else # define BUILD_WITH_VIDEO_OUTPUT_SUPPORT 0 diff --git a/modules/highgui/test/test_video_io.cpp b/modules/highgui/test/test_video_io.cpp index b0c2e53ba5..34ec0bdd80 100644 --- a/modules/highgui/test/test_video_io.cpp +++ b/modules/highgui/test/test_video_io.cpp @@ -54,6 +54,33 @@ string fourccToString(int fourcc) return format("%c%c%c%c", fourcc & 255, (fourcc >> 8) & 255, (fourcc >> 16) & 255, (fourcc >> 24) & 255); } +#ifdef HAVE_MSMF +const VideoFormat g_specific_fmt_list[] = +{ +/* VideoFormat("avi", 'dv25'), + VideoFormat("avi", 'dv50'), + VideoFormat("avi", 'dvc '), + VideoFormat("avi", 'dvh1'), + VideoFormat("avi", 'dvhd'), + VideoFormat("avi", 'dvsd'), + VideoFormat("avi", 'dvsl'), + VideoFormat("avi", 'M4S2'), */ + VideoFormat("wmv", 'WMV3'), + // VideoFormat("avi", 'H264'), + // VideoFormat("avi", 'MJPG'), + // VideoFormat("avi", 'MP43'), + // VideoFormat("avi", 'MP4S'), + // VideoFormat("avi", 'MP4V'), +/* VideoFormat("avi", 'MPG1'), + VideoFormat("avi", 'MSS1'), + VideoFormat("avi", 'MSS2'), + VideoFormat("avi", 'WMV1'), + VideoFormat("avi", 'WMV2'), + VideoFormat("avi", 'WMV3'), + VideoFormat("avi", 'WVC1'), */ + VideoFormat() +}; +#else const VideoFormat g_specific_fmt_list[] = { VideoFormat("avi", CV_FOURCC('X', 'V', 'I', 'D')), @@ -63,17 +90,17 @@ const VideoFormat g_specific_fmt_list[] = VideoFormat("mkv", CV_FOURCC('X', 'V', 'I', 'D')), VideoFormat("mkv", CV_FOURCC('M', 'P', 'E', 'G')), VideoFormat("mkv", CV_FOURCC('M', 'J', 'P', 'G')), - VideoFormat("mov", CV_FOURCC('m', 'p', '4', 'v')), VideoFormat() }; +#endif } class CV_HighGuiTest : public cvtest::BaseTest { protected: - void ImageTest(const string& dir); + void ImageTest (const string& dir); void VideoTest (const string& dir, const cvtest::VideoFormat& fmt); void SpecificImageTest (const string& dir); void SpecificVideoTest (const string& dir, const cvtest::VideoFormat& fmt); @@ -291,8 +318,11 @@ void CV_HighGuiTest::VideoTest(const string& dir, const cvtest::VideoFormat& fmt if (psnr < thresDbell) { printf("Too low psnr = %gdb\n", psnr); - // imwrite("img.png", img); - // imwrite("img1.png", img1); + //imwrite("original.png", img); + //imwrite("after_test.png", img1); + //Mat diff; + //absdiff(img, img1, diff); + //imwrite("diff.png", diff); ts->set_failed_test_info(ts->FAIL_MISMATCH); break; } From bb9a0b725341d939ba08808e495b836d3ba41834 Mon Sep 17 00:00:00 2001 From: Alexander Shishkov Date: Mon, 24 Jun 2013 14:52:17 +0400 Subject: [PATCH 154/178] Update Info.plist.in --- platforms/ios/Info.plist.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platforms/ios/Info.plist.in b/platforms/ios/Info.plist.in index 012de88568..6bcfe862d0 100644 --- a/platforms/ios/Info.plist.in +++ b/platforms/ios/Info.plist.in @@ -5,7 +5,7 @@ CFBundleName OpenCV CFBundleIdentifier - opencv.org + org.opencv CFBundleVersion ${VERSION} CFBundleShortVersionString From 6db776f957253d1e484bba8b05afd5a9a8f415a1 Mon Sep 17 00:00:00 2001 From: yao Date: Tue, 25 Jun 2013 14:11:28 +0800 Subject: [PATCH 155/178] add "-c" for cpu ocl mode in perf tests --- modules/ocl/perf/main.cpp | 61 ++++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/modules/ocl/perf/main.cpp b/modules/ocl/perf/main.cpp index dfcac20bc0..bd2a4ec4b6 100644 --- a/modules/ocl/perf/main.cpp +++ b/modules/ocl/perf/main.cpp @@ -44,43 +44,21 @@ int main(int argc, const char *argv[]) { - vector oclinfo; - int num_devices = getDevice(oclinfo); - - if (num_devices < 1) - { - cerr << "no device found\n"; - return -1; - } - // set this to overwrite binary cache every time the test starts - ocl::setBinaryDiskCache(ocl::CACHE_UPDATE); - - int devidx = 0; - - for (size_t i = 0; i < oclinfo.size(); i++) - { - for (size_t j = 0; j < oclinfo[i].DeviceName.size(); j++) - { - printf("device %d: %s\n", devidx++, oclinfo[i].DeviceName[j].c_str()); - } - } - - redirectError(cvErrorCallback); - const char *keys = "{ h | help | false | print help message }" "{ f | filter | | filter for test }" "{ w | workdir | | set working directory }" "{ l | list | false | show all tests }" "{ d | device | 0 | device id }" + "{ c | cpu_ocl | false | use cpu as ocl device}" "{ i | iters | 10 | iteration count }" "{ m | warmup | 1 | gpu warm up iteration count}" - "{ t | xtop | 1.1 | xfactor top boundary}" - "{ b | xbottom | 0.9 | xfactor bottom boundary}" + "{ t | xtop | 1.1 | xfactor top boundary}" + "{ b | xbottom | 0.9 | xfactor bottom boundary}" "{ v | verify | false | only run gpu once to verify if problems occur}"; + redirectError(cvErrorCallback); CommandLineParser cmd(argc, argv, keys); - if (cmd.get("help")) { cout << "Avaible options:" << endl; @@ -88,14 +66,40 @@ int main(int argc, const char *argv[]) return 0; } - int device = cmd.get("device"); + // get ocl devices + bool use_cpu = cmd.get("c"); + vector oclinfo; + int num_devices = 0; + if(use_cpu) + num_devices = getDevice(oclinfo, ocl::CVCL_DEVICE_TYPE_CPU); + else + num_devices = getDevice(oclinfo); + if (num_devices < 1) + { + cerr << "no device found\n"; + return -1; + } + // show device info + int devidx = 0; + for (size_t i = 0; i < oclinfo.size(); i++) + { + for (size_t j = 0; j < oclinfo[i].DeviceName.size(); j++) + { + cout << "device " << devidx++ << ": " << oclinfo[i].DeviceName[j] << endl; + } + } + + int device = cmd.get("device"); if (device < 0 || device >= num_devices) { cerr << "Invalid device ID" << endl; return -1; } + // set this to overwrite binary cache every time the test starts + ocl::setBinaryDiskCache(ocl::CACHE_UPDATE); + if (cmd.get("verify")) { TestSystem::instance().setNumIters(1); @@ -104,7 +108,6 @@ int main(int argc, const char *argv[]) } devidx = 0; - for (size_t i = 0; i < oclinfo.size(); i++) { for (size_t j = 0; j < oclinfo[i].DeviceName.size(); j++, devidx++) @@ -113,7 +116,7 @@ int main(int argc, const char *argv[]) { ocl::setDevice(oclinfo[i], (int)j); TestSystem::instance().setRecordName(oclinfo[i].DeviceName[j]); - printf("\nuse %d: %s\n", devidx, oclinfo[i].DeviceName[j].c_str()); + cout << "use " << devidx << ": " < Date: Tue, 25 Jun 2013 14:12:02 +0800 Subject: [PATCH 156/178] fix stereobm crash on some cpu ocl --- modules/ocl/src/opencl/stereobm.cl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ocl/src/opencl/stereobm.cl b/modules/ocl/src/opencl/stereobm.cl index bd86a7f3fb..552874d427 100644 --- a/modules/ocl/src/opencl/stereobm.cl +++ b/modules/ocl/src/opencl/stereobm.cl @@ -162,8 +162,8 @@ __kernel void stereoKernel(__global unsigned char *left, __global unsigned char int y_tex; int x_tex = X - radius; - if (x_tex >= cwidth) - return; + //if (x_tex >= cwidth) + // return; for(int d = STEREO_MIND; d < maxdisp; d += STEREO_DISP_STEP) { From 1227e00f3d03daed6a96ff52c32e3051b5114782 Mon Sep 17 00:00:00 2001 From: yao Date: Tue, 25 Jun 2013 16:26:33 +0800 Subject: [PATCH 157/178] fix moments --- modules/ocl/src/moments.cpp | 43 ++- modules/ocl/src/opencl/moments.cl | 536 +++++++++++++++--------------- modules/ocl/test/test_moments.cpp | 8 +- 3 files changed, 290 insertions(+), 297 deletions(-) diff --git a/modules/ocl/src/moments.cpp b/modules/ocl/src/moments.cpp index d6baba207c..cb16fb136d 100644 --- a/modules/ocl/src/moments.cpp +++ b/modules/ocl/src/moments.cpp @@ -16,7 +16,7 @@ // Third party copyrights are property of their respective owners. // // @Authors -// Sen Liu, sen@multicorewareinc.com +// Sen Liu, swjtuls1987@126.com // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: @@ -277,8 +277,8 @@ static void ocl_cvMoments( const void* array, CvMoments* mom, int binary ) blocky = size.height/TILE_SIZE; else blocky = size.height/TILE_SIZE + 1; - cv::ocl::oclMat dst_m(blocky * 10, blockx, CV_64FC1); - cl_mem sum = openCLCreateBuffer(src.clCxt,CL_MEM_READ_WRITE,10*sizeof(double)); + oclMat dst_m(blocky * 10, blockx, CV_64FC1); + oclMat sum(1, 10, CV_64FC1); int tile_width = std::min(size.width,TILE_SIZE); int tile_height = std::min(size.height,TILE_SIZE); size_t localThreads[3] = { tile_height, 1, 1}; @@ -288,19 +288,16 @@ static void ocl_cvMoments( const void* array, CvMoments* mom, int binary ) args.push_back( make_pair( sizeof(cl_int) , (void *)&src.rows )); args.push_back( make_pair( sizeof(cl_int) , (void *)&src.cols )); args.push_back( make_pair( sizeof(cl_int) , (void *)&src.step )); - args.push_back( make_pair( sizeof(cl_int) , (void *)&tileSize.width )); - args.push_back( make_pair( sizeof(cl_int) , (void *)&tileSize.height )); args.push_back( make_pair( sizeof(cl_mem) , (void *)&dst_m.data )); args.push_back( make_pair( sizeof(cl_int) , (void *)&dst_m.cols )); args.push_back( make_pair( sizeof(cl_int) , (void *)&dst_m.step )); args.push_back( make_pair( sizeof(cl_int) , (void *)&blocky )); - args.push_back( make_pair( sizeof(cl_int) , (void *)&type )); args.push_back( make_pair( sizeof(cl_int) , (void *)&depth )); args.push_back( make_pair( sizeof(cl_int) , (void *)&cn )); args.push_back( make_pair( sizeof(cl_int) , (void *)&coi )); args.push_back( make_pair( sizeof(cl_int) , (void *)&binary )); args.push_back( make_pair( sizeof(cl_int) , (void *)&TILE_SIZE )); - openCLExecuteKernel(dst_m.clCxt, &moments, "CvMoments", globalThreads, localThreads, args, -1, depth); + openCLExecuteKernel(Context::getContext(), &moments, "CvMoments", globalThreads, localThreads, args, -1, depth); size_t localThreadss[3] = { 128, 1, 1}; size_t globalThreadss[3] = { 128, 1, 1}; @@ -309,25 +306,23 @@ static void ocl_cvMoments( const void* array, CvMoments* mom, int binary ) args_sum.push_back( make_pair( sizeof(cl_int) , (void *)&tile_height )); args_sum.push_back( make_pair( sizeof(cl_int) , (void *)&tile_width )); args_sum.push_back( make_pair( sizeof(cl_int) , (void *)&TILE_SIZE )); - args_sum.push_back( make_pair( sizeof(cl_mem) , (void *)&sum )); + args_sum.push_back( make_pair( sizeof(cl_mem) , (void *)&sum.data )); args_sum.push_back( make_pair( sizeof(cl_mem) , (void *)&dst_m.data )); args_sum.push_back( make_pair( sizeof(cl_int) , (void *)&dst_m.step )); - openCLExecuteKernel(dst_m.clCxt, &moments, "dst_sum", globalThreadss, localThreadss, args_sum, -1, -1); - double* dstsum = new double[10]; - memset(dstsum,0,10*sizeof(double)); - openCLReadBuffer(dst_m.clCxt,sum,(void *)dstsum,10*sizeof(double)); - mom->m00 = dstsum[0]; - mom->m10 = dstsum[1]; - mom->m01 = dstsum[2]; - mom->m20 = dstsum[3]; - mom->m11 = dstsum[4]; - mom->m02 = dstsum[5]; - mom->m30 = dstsum[6]; - mom->m21 = dstsum[7]; - mom->m12 = dstsum[8]; - mom->m03 = dstsum[9]; - delete [] dstsum; - openCLSafeCall(clReleaseMemObject(sum)); + openCLExecuteKernel(Context::getContext(), &moments, "dst_sum", globalThreadss, localThreadss, args_sum, -1, -1); + + Mat dstsum(sum); + mom->m00 = dstsum.at(0, 0); + mom->m10 = dstsum.at(0, 1); + mom->m01 = dstsum.at(0, 2); + mom->m20 = dstsum.at(0, 3); + mom->m11 = dstsum.at(0, 4); + mom->m02 = dstsum.at(0, 5); + mom->m30 = dstsum.at(0, 6); + mom->m21 = dstsum.at(0, 7); + mom->m12 = dstsum.at(0, 8); + mom->m03 = dstsum.at(0, 9); + icvCompleteMomentState( mom ); } diff --git a/modules/ocl/src/opencl/moments.cl b/modules/ocl/src/opencl/moments.cl index 2378f4f849..71313017a9 100644 --- a/modules/ocl/src/opencl/moments.cl +++ b/modules/ocl/src/opencl/moments.cl @@ -173,10 +173,10 @@ __kernel void dst_sum(int src_rows, int src_cols, int tile_height, int tile_widt sum[i] = dst_sum[i][0]; } -__kernel void CvMoments_D0(__global uchar16* src_data, int src_rows, int src_cols, int src_step, int tileSize_width, int tileSize_height, +__kernel void CvMoments_D0(__global uchar16* src_data, int src_rows, int src_cols, int src_step, __global F* dst_m, int dst_cols, int dst_step, int blocky, - int type, int depth, int cn, int coi, int binary, int TILE_SIZE) + int depth, int cn, int coi, int binary, int TILE_SIZE) { uchar tmp_coi[16]; // get the coi data uchar16 tmp[16]; @@ -192,35 +192,43 @@ __kernel void CvMoments_D0(__global uchar16* src_data, int src_rows, int src_col int x = wgidx*TILE_SIZE; // vector length of uchar int kcn = (cn==2)?2:4; int rstep = min(src_step, TILE_SIZE); - tileSize_height = min(TILE_SIZE, src_rows - y); - tileSize_width = min(TILE_SIZE, src_cols - x); + int tileSize_height = min(TILE_SIZE, src_rows - y); + int tileSize_width = min(TILE_SIZE, src_cols - x); + + if ( y+lidy < src_rows ) + { + if( tileSize_width < TILE_SIZE ) + for(int i = tileSize_width; i < rstep && (x+i) < src_cols; i++ ) + *((__global uchar*)src_data+(y+lidy)*src_step+x+i) = 0; + + if( coi > 0 ) //channel of interest + for(int i = 0; i < tileSize_width; i += VLEN_C) + { + for(int j=0; j 0 ) //channel of interest - for(int i = 0; i < tileSize_width; i += VLEN_C) - { - for(int j=0; j TILE_SIZE && tileSize_width < TILE_SIZE) - for(int i=tileSize_width; i < rstep; i++ ) - *((__global ushort*)src_data+(y+lidy)*src_step/2+x+i) = 0; - if( coi > 0 ) - for(int i=0; i < tileSize_width; i+=VLEN_US) - { - for(int j=0; j TILE_SIZE && tileSize_width < TILE_SIZE) + for(int i=tileSize_width; i < rstep && (x+i) < src_cols; i++ ) + *((__global ushort*)src_data+(y+lidy)*src_step/2+x+i) = 0; + if( coi > 0 ) + for(int i=0; i < tileSize_width; i+=VLEN_US) + { + for(int j=0; j= 1; j = j/2 ) { if(lidy < j) for( int i = 0; i < 10; i++ ) lm[i] = lm[i] + m[i][lidy]; - barrier(CLK_LOCAL_MEM_FENCE); + } + barrier(CLK_LOCAL_MEM_FENCE); + for( int j = TILE_SIZE/2; j >= 1; j = j/2 ) + { if(lidy >= j/2&&lidy < j) for( int i = 0; i < 10; i++ ) m[i][lidy-j/2] = lm[i]; - barrier(CLK_LOCAL_MEM_FENCE); } + barrier(CLK_LOCAL_MEM_FENCE); + if(lidy == 0&&lidx == 0) { for(int mt = 0; mt < 10; mt++ ) @@ -482,10 +501,10 @@ __kernel void CvMoments_D2(__global ushort8* src_data, int src_rows, int src_col } } -__kernel void CvMoments_D3(__global short8* src_data, int src_rows, int src_cols, int src_step, int tileSize_width, int tileSize_height, +__kernel void CvMoments_D3(__global short8* src_data, int src_rows, int src_cols, int src_step, __global F* dst_m, int dst_cols, int dst_step, int blocky, - int type, int depth, int cn, int coi, int binary, const int TILE_SIZE) + int depth, int cn, int coi, int binary, const int TILE_SIZE) { short tmp_coi[8]; // get the coi data short8 tmp[32]; @@ -500,21 +519,26 @@ __kernel void CvMoments_D3(__global short8* src_data, int src_rows, int src_cols int x = wgidx*TILE_SIZE; // real X index of pixel int kcn = (cn==2)?2:4; int rstep = min(src_step/2, TILE_SIZE); - tileSize_height = min(TILE_SIZE, src_rows - y); - tileSize_width = min(TILE_SIZE, src_cols -x); - if(tileSize_width < TILE_SIZE) - for(int i = tileSize_width; i < rstep; i++ ) - *((__global short*)src_data+(y+lidy)*src_step/2+x+i) = 0; - if( coi > 0 ) - for(int i=0; i < tileSize_width; i+=VLEN_S) - { - for(int j=0; j 0 ) + for(int i=0; i < tileSize_width; i+=VLEN_S) + { + for(int j=0; j 0 ) - for(int i=0; i < tileSize_width; i+=VLEN_F) - { -#pragma unroll - for(int j=0; j<4; j++) + + if ( y+lidy < src_rows ) + { + if(tileSize_width < TILE_SIZE) + for(int i = tileSize_width; i < rstep && (x+i) < src_cols; i++ ) + *((__global float*)src_data+(y+lidy)*src_step/4+x+i) = 0; + if( coi > 0 ) + for(int i=0; i < tileSize_width; i+=VLEN_F) { - index = yOff+(x+i+j)*kcn+coi-1; - if (index < maxIdx) - tmp_coi[j] = *(src_data+index); - else - tmp_coi[j] = 0; + for(int j=0; j<4; j++) + tmp_coi[j] = *(src_data+(y+lidy)*src_step/4+(x+i+j)*kcn+coi-1); + tmp[i/VLEN_F] = (float4)(tmp_coi[0],tmp_coi[1],tmp_coi[2],tmp_coi[3]); } - tmp[i/VLEN_F] = (float4)(tmp_coi[0],tmp_coi[1],tmp_coi[2],tmp_coi[3]); - } - else - for(int i=0; i < tileSize_width && (yOff+x+i) < maxIdx; i+=VLEN_F) - tmp[i/VLEN_F] = (*(__global float4 *)(src_data+yOff+x+i)); + else + for(int i=0; i < tileSize_width; i+=VLEN_F) + tmp[i/VLEN_F] = (float4)(*(src_data+(y+lidy)*src_step/4+x+i),*(src_data+(y+lidy)*src_step/4+x+i+1),*(src_data+(y+lidy)*src_step/4+x+i+2),*(src_data+(y+lidy)*src_step/4+x+i+3)); + } + float4 zero = (float4)(0); float4 full = (float4)(255); if( binary ) @@ -688,10 +708,9 @@ __kernel void CvMoments_D5( __global float* src_data, int src_rows, int src_cols tmp[i/VLEN_F] = (tmp[i/VLEN_F]!=zero)?full:zero; F mom[10]; __local F m[10][128]; - if(lidy == 0) + if(lidy < 128) for(int i = 0; i < 10; i ++) - for(int j = 0; j < 128; j ++) - m[i][j] = 0; + m[i][lidy] = 0; barrier(CLK_LOCAL_MEM_FENCE); F lm[10] = {0}; F4 x0 = (F4)(0); @@ -729,185 +748,6 @@ __kernel void CvMoments_D5( __global float* src_data, int src_rows, int src_cols m[0][lidy-bheight] = x0.s0; // m00 } - else if(lidy < bheight) - { - lm[9] = ((F)py) * sy; // m03 - lm[8] = ((F)x1.s0) * sy; // m12 - lm[7] = ((F)x2.s0) * lidy; // m21 - lm[6] = x3.s0; // m30 - lm[5] = x0.s0 * sy; // m02 - lm[4] = x1.s0 * lidy; // m11 - lm[3] = x2.s0; // m20 - lm[2] = py; // m01 - lm[1] = x1.s0; // m10 - lm[0] = x0.s0; // m00 - } - barrier(CLK_LOCAL_MEM_FENCE); - for( int j = TILE_SIZE/2; j >= 1; j = j/2 ) - { - if(lidy < j) - for( int i = 0; i < 10; i++ ) - lm[i] = lm[i] + m[i][lidy]; - barrier(CLK_LOCAL_MEM_FENCE); - if(lidy >= j/2&&lidy < j) - for( int i = 0; i < 10; i++ ) - m[i][lidy-j/2] = lm[i]; - barrier(CLK_LOCAL_MEM_FENCE); - } - if(lidy == 0&&lidx == 0) - { - for( int mt = 0; mt < 10; mt++ ) - mom[mt] = (F)lm[mt]; - if(binary) - { - F s = 1./255; - for( int mt = 0; mt < 10; mt++ ) - mom[mt] *= s; - } - - F xm = x * mom[0], ym = y * mom[0]; - - // accumulate moments computed in each tile - dst_step /= sizeof(F); - - int dst_x_off = mad24(wgidy, dst_cols, wgidx); - int dst_off = 0; - int max_dst_index = 10 * blocky * get_global_size(1); - - // + m00 ( = m00' ) - dst_off = mad24(DST_ROW_00 * blocky, dst_step, dst_x_off); - if (dst_off < max_dst_index) - *(dst_m + dst_off) = mom[0]; - - // + m10 ( = m10' + x*m00' ) - dst_off = mad24(DST_ROW_10 * blocky, dst_step, dst_x_off); - if (dst_off < max_dst_index) - *(dst_m + dst_off) = mom[1] + xm; - - // + m01 ( = m01' + y*m00' ) - dst_off = mad24(DST_ROW_01 * blocky, dst_step, dst_x_off); - if (dst_off < max_dst_index) - *(dst_m + dst_off) = mom[2] + ym; - - // + m20 ( = m20' + 2*x*m10' + x*x*m00' ) - dst_off = mad24(DST_ROW_20 * blocky, dst_step, dst_x_off); - if (dst_off < max_dst_index) - *(dst_m + dst_off) = mom[3] + x * (mom[1] * 2 + xm); - - // + m11 ( = m11' + x*m01' + y*m10' + x*y*m00' ) - dst_off = mad24(DST_ROW_11 * blocky, dst_step, dst_x_off); - if (dst_off < max_dst_index) - *(dst_m + dst_off) = mom[4] + x * (mom[2] + ym) + y * mom[1]; - - // + m02 ( = m02' + 2*y*m01' + y*y*m00' ) - dst_off = mad24(DST_ROW_02 * blocky, dst_step, dst_x_off); - if (dst_off < max_dst_index) - *(dst_m + dst_off) = mom[5] + y * (mom[2] * 2 + ym); - - // + m30 ( = m30' + 3*x*m20' + 3*x*x*m10' + x*x*x*m00' ) - dst_off = mad24(DST_ROW_30 * blocky, dst_step, dst_x_off); - if (dst_off < max_dst_index) - *(dst_m + dst_off) = mom[6] + x * (3. * mom[3] + x * (3. * mom[1] + xm)); - - // + m21 ( = m21' + x*(2*m11' + 2*y*m10' + x*m01' + x*y*m00') + y*m20') - dst_off = mad24(DST_ROW_21 * blocky, dst_step, dst_x_off); - if (dst_off < max_dst_index) - *(dst_m + dst_off) = mom[7] + x * (2 * (mom[4] + y * mom[1]) + x * (mom[2] + ym)) + y * mom[3]; - - // + m12 ( = m12' + y*(2*m11' + 2*x*m01' + y*m10' + x*y*m00') + x*m02') - dst_off = mad24(DST_ROW_12 * blocky, dst_step, dst_x_off); - if (dst_off < max_dst_index) - *(dst_m + dst_off) = mom[8] + y * (2 * (mom[4] + x * mom[2]) + y * (mom[1] + xm)) + x * mom[5]; - - // + m03 ( = m03' + 3*y*m02' + 3*y*y*m01' + y*y*y*m00' ) - dst_off = mad24(DST_ROW_03 * blocky, dst_step, dst_x_off); - if (dst_off < max_dst_index) - *(dst_m + dst_off) = mom[9] + y * (3. * mom[5] + y * (3. * mom[2] + ym)); - } -} - -__kernel void CvMoments_D6(__global F* src_data, int src_rows, int src_cols, int src_step, int tileSize_width, int tileSize_height, - __global F* dst_m, - int dst_cols, int dst_step, int blocky, - int type, int depth, int cn, int coi, int binary, const int TILE_SIZE) -{ - F tmp_coi[4]; // get the coi data - F4 tmp[64]; - int VLEN_D = 4; // length of vetor - int gidy = get_global_id(0); - int gidx = get_global_id(1); - int wgidy = get_group_id(0); - int wgidx = get_group_id(1); - int lidy = get_local_id(0); - int lidx = get_local_id(1); - int y = wgidy*TILE_SIZE; // real Y index of pixel - int x = wgidx*TILE_SIZE; // real X index of pixel - int kcn = (cn==2)?2:4; - int rstep = min(src_step/8, TILE_SIZE); - tileSize_height = min(TILE_SIZE, src_rows - y); - tileSize_width = min(TILE_SIZE, src_cols - x); - - if(tileSize_width < TILE_SIZE) - for(int i = tileSize_width; i < rstep; i++ ) - *((__global F*)src_data+(y+lidy)*src_step/8+x+i) = 0; - if( coi > 0 ) - for(int i=0; i < tileSize_width; i+=VLEN_D) - { - for(int j=0; j<4; j++) - tmp_coi[j] = *(src_data+(y+lidy)*src_step/8+(x+i+j)*kcn+coi-1); - tmp[i/VLEN_D] = (F4)(tmp_coi[0],tmp_coi[1],tmp_coi[2],tmp_coi[3]); - } - else - for(int i=0; i < tileSize_width; i+=VLEN_D) - tmp[i/VLEN_D] = (F4)(*(src_data+(y+lidy)*src_step/8+x+i),*(src_data+(y+lidy)*src_step/8+x+i+1),*(src_data+(y+lidy)*src_step/8+x+i+2),*(src_data+(y+lidy)*src_step/8+x+i+3)); - F4 zero = (F4)(0); - F4 full = (F4)(255); - if( binary ) - for(int i=0; i < tileSize_width; i+=VLEN_D) - tmp[i/VLEN_D] = (tmp[i/VLEN_D]!=zero)?full:zero; - F mom[10]; - __local F m[10][128]; - if(lidy == 0) - for(int i=0; i<10; i++) - for(int j=0; j<128; j++) - m[i][j]=0; - barrier(CLK_LOCAL_MEM_FENCE); - F lm[10] = {0}; - F4 x0 = (F4)(0); - F4 x1 = (F4)(0); - F4 x2 = (F4)(0); - F4 x3 = (F4)(0); - for( int xt = 0 ; xt < tileSize_width; xt+=VLEN_D ) - { - F4 v_xt = (F4)(xt, xt+1, xt+2, xt+3); - F4 p = tmp[xt/VLEN_D]; - F4 xp = v_xt * p, xxp = xp * v_xt; - x0 += p; - x1 += xp; - x2 += xxp; - x3 += xxp *v_xt; - } - x0.s0 += x0.s1 + x0.s2 + x0.s3; - x1.s0 += x1.s1 + x1.s2 + x1.s3; - x2.s0 += x2.s1 + x2.s2 + x2.s3; - x3.s0 += x3.s1 + x3.s2 + x3.s3; - - F py = lidy * x0.s0, sy = lidy*lidy; - int bheight = min(tileSize_height, TILE_SIZE/2); - if(bheight >= TILE_SIZE/2&&lidy > bheight-1&&lidy < tileSize_height) - { - m[9][lidy-bheight] = ((F)py) * sy; // m03 - m[8][lidy-bheight] = ((F)x1.s0) * sy; // m12 - m[7][lidy-bheight] = ((F)x2.s0) * lidy; // m21 - m[6][lidy-bheight] = x3.s0; // m30 - m[5][lidy-bheight] = x0.s0 * sy; // m02 - m[4][lidy-bheight] = x1.s0 * lidy; // m11 - m[3][lidy-bheight] = x2.s0; // m20 - m[2][lidy-bheight] = py; // m01 - m[1][lidy-bheight] = x1.s0; // m10 - m[0][lidy-bheight] = x0.s0; // m00 - } - else if(lidy < bheight) { lm[9] = ((F)py) * sy; // m03 @@ -922,6 +762,164 @@ __kernel void CvMoments_D6(__global F* src_data, int src_rows, int src_cols, in lm[0] = x0.s0; // m00 } barrier(CLK_LOCAL_MEM_FENCE); + for( int j = TILE_SIZE/2; j >= 1; j = j/2 ) + { + if(lidy < j) + for( int i = 0; i < 10; i++ ) + lm[i] = lm[i] + m[i][lidy]; + barrier(CLK_LOCAL_MEM_FENCE); + if(lidy >= j/2&&lidy < j) + for( int i = 0; i < 10; i++ ) + m[i][lidy-j/2] = lm[i]; + barrier(CLK_LOCAL_MEM_FENCE); + } + if(lidy == 0&&lidx == 0) + { + for( int mt = 0; mt < 10; mt++ ) + mom[mt] = (F)lm[mt]; + if(binary) + { + F s = 1./255; + for( int mt = 0; mt < 10; mt++ ) + mom[mt] *= s; + } + + F xm = x * mom[0], ym = y * mom[0]; + + // accumulate moments computed in each tile + dst_step /= sizeof(F); + + // + m00 ( = m00' ) + *(dst_m + mad24(DST_ROW_00 * blocky, dst_step, mad24(wgidy, dst_cols, wgidx))) = mom[0]; + + // + m10 ( = m10' + x*m00' ) + *(dst_m + mad24(DST_ROW_10 * blocky, dst_step, mad24(wgidy, dst_cols, wgidx))) = mom[1] + xm; + + // + m01 ( = m01' + y*m00' ) + *(dst_m + mad24(DST_ROW_01 * blocky, dst_step, mad24(wgidy, dst_cols, wgidx))) = mom[2] + ym; + + // + m20 ( = m20' + 2*x*m10' + x*x*m00' ) + *(dst_m + mad24(DST_ROW_20 * blocky, dst_step, mad24(wgidy, dst_cols, wgidx))) = mom[3] + x * (mom[1] * 2 + xm); + + // + m11 ( = m11' + x*m01' + y*m10' + x*y*m00' ) + *(dst_m + mad24(DST_ROW_11 * blocky, dst_step, mad24(wgidy, dst_cols, wgidx))) = mom[4] + x * (mom[2] + ym) + y * mom[1]; + + // + m02 ( = m02' + 2*y*m01' + y*y*m00' ) + *(dst_m + mad24(DST_ROW_02 * blocky, dst_step, mad24(wgidy, dst_cols, wgidx))) = mom[5] + y * (mom[2] * 2 + ym); + + // + m30 ( = m30' + 3*x*m20' + 3*x*x*m10' + x*x*x*m00' ) + *(dst_m + mad24(DST_ROW_30 * blocky, dst_step, mad24(wgidy, dst_cols, wgidx))) = mom[6] + x * (3. * mom[3] + x * (3. * mom[1] + xm)); + + // + m21 ( = m21' + x*(2*m11' + 2*y*m10' + x*m01' + x*y*m00') + y*m20') + *(dst_m + mad24(DST_ROW_21 * blocky, dst_step, mad24(wgidy, dst_cols, wgidx))) = mom[7] + x * (2 * (mom[4] + y * mom[1]) + x * (mom[2] + ym)) + y * mom[3]; + + // + m12 ( = m12' + y*(2*m11' + 2*x*m01' + y*m10' + x*y*m00') + x*m02') + *(dst_m + mad24(DST_ROW_12 * blocky, dst_step, mad24(wgidy, dst_cols, wgidx))) = mom[8] + y * (2 * (mom[4] + x * mom[2]) + y * (mom[1] + xm)) + x * mom[5]; + + // + m03 ( = m03' + 3*y*m02' + 3*y*y*m01' + y*y*y*m00' ) + *(dst_m + mad24(DST_ROW_03 * blocky, dst_step, mad24(wgidy, dst_cols, wgidx))) = mom[9] + y * (3. * mom[5] + y * (3. * mom[2] + ym)); + } +} + +__kernel void CvMoments_D6(__global F* src_data, int src_rows, int src_cols, int src_step, + __global F* dst_m, + int dst_cols, int dst_step, int blocky, + int depth, int cn, int coi, int binary, const int TILE_SIZE) +{ + F tmp_coi[4]; // get the coi data + F4 tmp[64]; + int VLEN_D = 4; // length of vetor + int gidy = get_global_id(0); + int gidx = get_global_id(1); + int wgidy = get_group_id(0); + int wgidx = get_group_id(1); + int lidy = get_local_id(0); + int lidx = get_local_id(1); + int y = wgidy*TILE_SIZE; // real Y index of pixel + int x = wgidx*TILE_SIZE; // real X index of pixel + int kcn = (cn==2)?2:4; + int rstep = min(src_step/8, TILE_SIZE); + int tileSize_height = min(TILE_SIZE, src_rows - y); + int tileSize_width = min(TILE_SIZE, src_cols - x); + + if ( y+lidy < src_rows ) + { + if(tileSize_width < TILE_SIZE) + for(int i = tileSize_width; i < rstep && (x+i) < src_cols; i++ ) + *((__global F*)src_data+(y+lidy)*src_step/8+x+i) = 0; + if( coi > 0 ) + for(int i=0; i < tileSize_width; i+=VLEN_D) + { + for(int j=0; j<4 && ((x+i+j)*kcn+coi-1)= TILE_SIZE/2&&lidy > bheight-1&&lidy < tileSize_height) + { + m[9][lidy-bheight] = ((F)py) * sy; // m03 + m[8][lidy-bheight] = ((F)x1.s0) * sy; // m12 + m[7][lidy-bheight] = ((F)x2.s0) * lidy; // m21 + m[6][lidy-bheight] = x3.s0; // m30 + m[5][lidy-bheight] = x0.s0 * sy; // m02 + m[4][lidy-bheight] = x1.s0 * lidy; // m11 + m[3][lidy-bheight] = x2.s0; // m20 + m[2][lidy-bheight] = py; // m01 + m[1][lidy-bheight] = x1.s0; // m10 + m[0][lidy-bheight] = x0.s0; // m00 + } + else if(lidy < bheight) + { + lm[9] = ((F)py) * sy; // m03 + lm[8] = ((F)x1.s0) * sy; // m12 + lm[7] = ((F)x2.s0) * lidy; // m21 + lm[6] = x3.s0; // m30 + lm[5] = x0.s0 * sy; // m02 + lm[4] = x1.s0 * lidy; // m11 + lm[3] = x2.s0; // m20 + lm[2] = py; // m01 + lm[1] = x1.s0; // m10 + lm[0] = x0.s0; // m00 + } + barrier(CLK_LOCAL_MEM_FENCE); + for( int j = TILE_SIZE/2; j >= 1; j = j/2 ) { if(lidy < j) diff --git a/modules/ocl/test/test_moments.cpp b/modules/ocl/test/test_moments.cpp index 98c66def31..86f4779d68 100644 --- a/modules/ocl/test/test_moments.cpp +++ b/modules/ocl/test/test_moments.cpp @@ -45,12 +45,12 @@ TEST_P(MomentsTest, Mat) { if(test_contours) { - Mat src = imread( workdir + "../cpp/pic3.png", 1 ); - Mat src_gray, canny_output; - cvtColor( src, src_gray, CV_BGR2GRAY ); + Mat src = imread( workdir + "../cpp/pic3.png", IMREAD_GRAYSCALE ); + ASSERT_FALSE(src.empty()); + Mat canny_output; vector > contours; vector hierarchy; - Canny( src_gray, canny_output, 100, 200, 3 ); + Canny( src, canny_output, 100, 200, 3 ); findContours( canny_output, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0) ); for( size_t i = 0; i < contours.size(); i++ ) { From 43122939cbe17e534442bd9ba9bb299e752a13a5 Mon Sep 17 00:00:00 2001 From: Alexander Smorkalov Date: Wed, 22 May 2013 04:21:23 -0700 Subject: [PATCH 158/178] Media foundation video i/o fixes. Bug in Video for Windows capture init fixed; Media Foundation based capture finalization fixed; Highgui tests for video i/o updated. --- .../include/opencv2/highgui/highgui_c.h | 4 +- modules/highgui/src/cap.cpp | 39 ++--- modules/highgui/src/cap_msmf.cpp | 138 ++++++++---------- modules/highgui/src/cap_vfw.cpp | 4 +- modules/highgui/test/test_video_io.cpp | 58 ++++---- 5 files changed, 110 insertions(+), 133 deletions(-) diff --git a/modules/highgui/include/opencv2/highgui/highgui_c.h b/modules/highgui/include/opencv2/highgui/highgui_c.h index 12be9867a2..fbcdba24fd 100644 --- a/modules/highgui/include/opencv2/highgui/highgui_c.h +++ b/modules/highgui/include/opencv2/highgui/highgui_c.h @@ -558,9 +558,11 @@ CVAPI(int) cvGetCaptureDomain( CvCapture* capture); /* "black box" video file writer structure */ typedef struct CvVideoWriter CvVideoWriter; +#define CV_FOURCC_MACRO(c1, c2, c3, c4) ((c1 & 255) + ((c2 & 255) << 8) + ((c3 & 255) << 16) + ((c4 & 255) << 24)) + CV_INLINE int CV_FOURCC(char c1, char c2, char c3, char c4) { - return (c1 & 255) + ((c2 & 255) << 8) + ((c3 & 255) << 16) + ((c4 & 255) << 24); + return CV_FOURCC_MACRO(c1, c2, c3, c4); } #define CV_FOURCC_PROMPT -1 /* Open Codec Selection Dialog (Windows only) */ diff --git a/modules/highgui/src/cap.cpp b/modules/highgui/src/cap.cpp index 8db8731020..cc92da3d0c 100644 --- a/modules/highgui/src/cap.cpp +++ b/modules/highgui/src/cap.cpp @@ -199,15 +199,6 @@ CV_IMPL CvCapture * cvCreateCameraCapture (int index) switch (domains[i]) { -#ifdef HAVE_MSMF - case CV_CAP_MSMF: - printf("Creating Media foundation capture\n"); - capture = cvCreateCameraCapture_MSMF (index); - printf("Capture address %p\n", capture); - if (capture) - return capture; - break; -#endif #ifdef HAVE_DSHOW case CV_CAP_DSHOW: capture = cvCreateCameraCapture_DShow (index); @@ -215,7 +206,13 @@ CV_IMPL CvCapture * cvCreateCameraCapture (int index) return capture; break; #endif - +#ifdef HAVE_MSMF + case CV_CAP_MSMF: + capture = cvCreateCameraCapture_MSMF (index); + if (capture) + return capture; + break; +#endif #ifdef HAVE_TYZX case CV_CAP_STEREO: capture = cvCreateCameraCapture_TYZX (index); @@ -223,14 +220,12 @@ CV_IMPL CvCapture * cvCreateCameraCapture (int index) return capture; break; #endif - - case CV_CAP_VFW: #ifdef HAVE_VFW + case CV_CAP_VFW: capture = cvCreateCameraCapture_VFW (index); if (capture) return capture; #endif - #if defined HAVE_LIBV4L || defined HAVE_CAMV4L || defined HAVE_CAMV4L2 || defined HAVE_VIDEOIO capture = cvCreateCameraCapture_V4L (index); if (capture) @@ -363,20 +358,16 @@ CV_IMPL CvCapture * cvCreateFileCapture (const char * filename) if (! result) result = cvCreateFileCapture_FFMPEG_proxy (filename); -#ifdef HAVE_MSMF - if (! result) - { - printf("Creating Media foundation based reader\n"); - result = cvCreateFileCapture_MSMF (filename); - printf("Construction result %p\n", result); - } -#endif - #ifdef HAVE_VFW if (! result) result = cvCreateFileCapture_VFW (filename); #endif +#ifdef HAVE_MSMF + if (! result) + result = cvCreateFileCapture_MSMF (filename); +#endif + #ifdef HAVE_XINE if (! result) result = cvCreateFileCapture_XINE (filename); @@ -422,14 +413,12 @@ CV_IMPL CvVideoWriter* cvCreateVideoWriter( const char* filename, int fourcc, if(!fourcc || !fps) result = cvCreateVideoWriter_Images(filename); -#ifdef HAVE_FFMPEG if(!result) result = cvCreateVideoWriter_FFMPEG_proxy (filename, fourcc, fps, frameSize, is_color); -#endif #ifdef HAVE_VFW if(!result) - return cvCreateVideoWriter_VFW(filename, fourcc, fps, frameSize, is_color); + result = cvCreateVideoWriter_VFW(filename, fourcc, fps, frameSize, is_color); #endif #ifdef HAVE_MSMF diff --git a/modules/highgui/src/cap_msmf.cpp b/modules/highgui/src/cap_msmf.cpp index 814fb75beb..7af85382b7 100644 --- a/modules/highgui/src/cap_msmf.cpp +++ b/modules/highgui/src/cap_msmf.cpp @@ -840,9 +840,11 @@ LPCWSTR GetGUIDNameConstNew(const GUID& guid) IF_EQUAL_RETURN(guid, MFAudioFormat_ADTS); // WAVE_FORMAT_MPEG_ADTS_AAC return NULL; } + FormatReader::FormatReader(void) { } + MediaType FormatReader::Read(IMFMediaType *pType) { UINT32 count = 0; @@ -888,9 +890,9 @@ ImageGrabber::ImageGrabber(unsigned int deviceID, bool synchronous): ig_RIE(true), ig_Close(false), ig_Synchronous(synchronous), - ig_hFrameReady(synchronous ? CreateEvent(NULL, FALSE, FALSE, "ig_hFrameReady"): 0), - ig_hFrameGrabbed(synchronous ? CreateEvent(NULL, FALSE, TRUE, "ig_hFrameGrabbed"): 0), - ig_hFinish(synchronous ? CreateEvent(NULL, FALSE, FALSE, "ig_hFinish") : 0) + ig_hFrameReady(synchronous ? CreateEvent(NULL, FALSE, FALSE, NULL): 0), + ig_hFrameGrabbed(synchronous ? CreateEvent(NULL, FALSE, TRUE, NULL): 0), + ig_hFinish(CreateEvent(NULL, TRUE, FALSE, NULL)) {} ImageGrabber::~ImageGrabber(void) @@ -898,15 +900,16 @@ ImageGrabber::~ImageGrabber(void) printf("ImageGrabber::~ImageGrabber()\n"); if (ig_pSession) { - printf("ig_pSession->Shutdown()"); + printf("ig_pSession->Shutdown()\n"); ig_pSession->Shutdown(); } + CloseHandle(ig_hFinish); + if (ig_Synchronous) { CloseHandle(ig_hFrameReady); CloseHandle(ig_hFrameGrabbed); - CloseHandle(ig_hFinish); } SafeRelease(&ig_pSession); @@ -1061,7 +1064,6 @@ HRESULT ImageGrabber::startGrabbing(void) hr = S_OK; goto done; } - printf("media foundation event: %d\n", met); if (met == MESessionEnded) { DPO->printOut(L"IMAGEGRABBER VIDEODEVICE %i: MESessionEnded \n", ig_DeviceID); @@ -1088,13 +1090,7 @@ HRESULT ImageGrabber::startGrabbing(void) DPO->printOut(L"IMAGEGRABBER VIDEODEVICE %i: Finish startGrabbing \n", ig_DeviceID); done: - if (ig_Synchronous) - { - SetEvent(ig_hFinish); - } - - SafeRelease(&ig_pSession); - SafeRelease(&ig_pTopology); + SetEvent(ig_hFinish); return hr; } @@ -1109,7 +1105,7 @@ void ImageGrabber::resumeGrabbing() HRESULT ImageGrabber::CreateTopology(IMFMediaSource *pSource, IMFActivate *pSinkActivate, IMFTopology **ppTopo) { - ComPtr pTopology = NULL; + IMFTopology* pTopology = NULL; ComPtr pPD = NULL; ComPtr pSD = NULL; ComPtr pHandler = NULL; @@ -1117,7 +1113,7 @@ HRESULT ImageGrabber::CreateTopology(IMFMediaSource *pSource, IMFActivate *pSink ComPtr pNode2 = NULL; HRESULT hr = S_OK; DWORD cStreams = 0; - CHECK_HR(hr = MFCreateTopology(pTopology.GetAddressOf())); + CHECK_HR(hr = MFCreateTopology(&pTopology)); CHECK_HR(hr = pSource->CreatePresentationDescriptor(pPD.GetAddressOf())); CHECK_HR(hr = pPD->GetStreamDescriptorCount(&cStreams)); for (DWORD i = 0; i < cStreams; i++) @@ -1130,8 +1126,8 @@ HRESULT ImageGrabber::CreateTopology(IMFMediaSource *pSource, IMFActivate *pSink CHECK_HR(hr = pHandler->GetMajorType(&majorType)); if (majorType == MFMediaType_Video && fSelected) { - CHECK_HR(hr = AddSourceNode(pTopology.Get(), pSource, pPD.Get(), pSD.Get(), pNode1.GetAddressOf())); - CHECK_HR(hr = AddOutputNode(pTopology.Get(), pSinkActivate, 0, pNode2.GetAddressOf())); + CHECK_HR(hr = AddSourceNode(pTopology, pSource, pPD.Get(), pSD.Get(), pNode1.GetAddressOf())); + CHECK_HR(hr = AddOutputNode(pTopology, pSinkActivate, 0, pNode2.GetAddressOf())); CHECK_HR(hr = pNode1->ConnectOutput(0, pNode2.Get(), 0)); break; } @@ -1140,7 +1136,7 @@ HRESULT ImageGrabber::CreateTopology(IMFMediaSource *pSource, IMFActivate *pSink CHECK_HR(hr = pPD->DeselectStream(i)); } } - *ppTopo = pTopology.Get(); + *ppTopo = pTopology; (*ppTopo)->AddRef(); done: @@ -1286,9 +1282,15 @@ STDMETHODIMP ImageGrabber::OnProcessSample(REFGUID guidMajorMediaType, DWORD dwS (void)llSampleDuration; (void)dwSampleSize; - WaitForSingleObject(ig_hFrameGrabbed, INFINITE); + //printf("ImageGrabber::OnProcessSample() -- begin\n"); + HANDLE tmp[] = {ig_hFinish, ig_hFrameGrabbed, NULL}; - printf("ImageGrabber::OnProcessSample() -- begin\n"); + DWORD status = WaitForMultipleObjects(2, tmp, FALSE, INFINITE); + if (status == WAIT_OBJECT_0) + { + printf("OnProcessFrame called after ig_hFinish event\n"); + return S_OK; + } if(ig_RIE) { @@ -1300,25 +1302,24 @@ STDMETHODIMP ImageGrabber::OnProcessSample(REFGUID guidMajorMediaType, DWORD dwS ig_RISecond->fastCopy(pSampleBuffer); ig_RIOut = ig_RISecond; } - ig_RIE = !ig_RIE; - printf("ImageGrabber::OnProcessSample() -- end\n"); + //printf("ImageGrabber::OnProcessSample() -- end\n"); if (ig_Synchronous) { SetEvent(ig_hFrameReady); } + else + { + ig_RIE = !ig_RIE; + } return S_OK; } STDMETHODIMP ImageGrabber::OnShutdown() { - if (ig_Synchronous) - { - SetEvent(ig_hFrameGrabbed); - } - + SetEvent(ig_hFinish); return S_OK; } @@ -1387,6 +1388,8 @@ ImageGrabberThread::~ImageGrabberThread(void) { DebugPrintOut *DPO = &DebugPrintOut::getInstance(); DPO->printOut(L"IMAGEGRABBERTHREAD VIDEODEVICE %i: Destroing ImageGrabberThread\n", igt_DeviceID); + if (igt_Handle) + WaitForSingleObject(igt_Handle, INFINITE); delete igt_pImageGrabber; } @@ -1431,7 +1434,6 @@ void ImageGrabberThread::run() DPO->printOut(L"IMAGEGRABBERTHREAD VIDEODEVICE %i: Emergency Stop thread\n", igt_DeviceID); if(igt_func) { - printf("Calling Emergency stop even handler\n"); igt_func(igt_DeviceID, igt_userData); } } @@ -3045,6 +3047,7 @@ CvCaptureFile_MSMF::CvCaptureFile_MSMF(): CvCaptureFile_MSMF::~CvCaptureFile_MSMF() { + close(); MFShutdown(); } @@ -3109,7 +3112,7 @@ void CvCaptureFile_MSMF::close() if (grabberThread) { isOpened = false; - SetEvent(grabberThread->getImageGrabber()->ig_hFrameReady); + SetEvent(grabberThread->getImageGrabber()->ig_hFinish); grabberThread->stop(); delete grabberThread; } @@ -3289,12 +3292,12 @@ bool CvCaptureFile_MSMF::grabFrame() DWORD waitResult; if (isOpened) { + SetEvent(grabberThread->getImageGrabber()->ig_hFrameGrabbed); HANDLE tmp[] = {grabberThread->getImageGrabber()->ig_hFrameReady, grabberThread->getImageGrabber()->ig_hFinish, 0}; waitResult = WaitForMultipleObjects(2, tmp, FALSE, INFINITE); - SetEvent(grabberThread->getImageGrabber()->ig_hFrameGrabbed); } - return isOpened && (waitResult == WAIT_OBJECT_0); + return isOpened && grabberThread->getImageGrabber()->getRawImage()->isNew() && (waitResult == WAIT_OBJECT_0); } IplImage* CvCaptureFile_MSMF::retrieveFrame(int) @@ -3443,7 +3446,8 @@ private: HRESULT WriteFrame(DWORD *videoFrameBuffer, const LONGLONG& rtStart, const LONGLONG& rtDuration); }; -CvVideoWriter_MSMF::CvVideoWriter_MSMF() +CvVideoWriter_MSMF::CvVideoWriter_MSMF(): + initiated(false) { } @@ -3456,47 +3460,47 @@ const GUID CvVideoWriter_MSMF::FourCC2GUID(int fourcc) { switch(fourcc) { - case 'dv25': + case CV_FOURCC_MACRO('d', 'v', '2', '5'): return MFVideoFormat_DV25; break; - case 'dv50': + case CV_FOURCC_MACRO('d', 'v', '5', '0'): return MFVideoFormat_DV50; break; - case 'dvc ': + case CV_FOURCC_MACRO('d', 'v', 'c', ' '): return MFVideoFormat_DVC; break; - case 'dvh1': + case CV_FOURCC_MACRO('d', 'v', 'h', '1'): return MFVideoFormat_DVH1; break; - case 'dvhd': + case CV_FOURCC_MACRO('d', 'v', 'h', 'd'): return MFVideoFormat_DVHD; break; - case 'dvsd': + case CV_FOURCC_MACRO('d', 'v', 's', 'd'): return MFVideoFormat_DVSD; break; - case 'dvsl': + case CV_FOURCC_MACRO('d', 'v', 's', 'l'): return MFVideoFormat_DVSL; break; - case 'H263': + case CV_FOURCC_MACRO('H', '2', '6', '3'): return MFVideoFormat_H263; break; - case 'H264': + case CV_FOURCC_MACRO('H', '2', '6', '4'): return MFVideoFormat_H264; break; - case 'M4S2': + case CV_FOURCC_MACRO('M', '4', 'S', '2'): return MFVideoFormat_M4S2; break; - case 'MJPG': + case CV_FOURCC_MACRO('M', 'J', 'P', 'G'): return MFVideoFormat_MJPG; break; - case 'MP43': + case CV_FOURCC_MACRO('M', 'P', '4', '3'): return MFVideoFormat_MP43; break; - case 'MP4S': + case CV_FOURCC_MACRO('M', 'P', '4', 'S'): return MFVideoFormat_MP4S; break; - case 'MP4V': + case CV_FOURCC_MACRO('M', 'P', '4', 'V'): return MFVideoFormat_MP4V; break; - case 'MPG1': + case CV_FOURCC_MACRO('M', 'P', 'G', '1'): return MFVideoFormat_MPG1; break; - case 'MSS1': + case CV_FOURCC_MACRO('M', 'S', 'S', '1'): return MFVideoFormat_MSS1; break; - case 'MSS2': + case CV_FOURCC_MACRO('M', 'S', 'S', '2'): return MFVideoFormat_MSS2; break; - case 'WMV1': + case CV_FOURCC_MACRO('W', 'M', 'V', '1'): return MFVideoFormat_WMV1; break; - case 'WMV2': + case CV_FOURCC_MACRO('W', 'M', 'V', '2'): return MFVideoFormat_WMV2; break; - case 'WMV3': + case CV_FOURCC_MACRO('W', 'M', 'V', '3'): return MFVideoFormat_WMV3; break; - case 'WVC1': + case CV_FOURCC_MACRO('W', 'V', 'C', '1'): return MFVideoFormat_WVC1; break; default: return MFVideoFormat_H264; @@ -3516,19 +3520,15 @@ bool CvVideoWriter_MSMF::open( const char* filename, int fourcc, HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); if (SUCCEEDED(hr)) { - printf("CoInitializeEx is successfull\n"); hr = MFStartup(MF_VERSION); if (SUCCEEDED(hr)) { - printf("MFStartup is successfull\n"); hr = InitializeSinkWriter(filename); if (SUCCEEDED(hr)) { - printf("InitializeSinkWriter is successfull\n"); initiated = true; rtStart = 0; MFFrameRateToAverageTimePerFrame((UINT32)fps, 1, &rtDuration); - printf("duration: %d\n", rtDuration); } } } @@ -3556,13 +3556,9 @@ bool CvVideoWriter_MSMF::writeFrame(const IplImage* img) if (!img) return false; - printf("Writing not empty IplImage\n"); - int length = img->width * img->height * 4; - printf("Image: %dx%d, %d\n", img->width, img->height, length); DWORD* target = new DWORD[length]; - printf("Before for loop\n"); for (int rowIdx = 0; rowIdx < img->height; rowIdx++) { char* rowStart = img->imageData + rowIdx*img->widthStep; @@ -3577,9 +3573,7 @@ bool CvVideoWriter_MSMF::writeFrame(const IplImage* img) } // Send frame to the sink writer. - printf("Before private WriteFrame call\n"); HRESULT hr = WriteFrame(target, rtStart, rtDuration); - printf("After private WriteFrame call\n"); if (FAILED(hr)) { printf("Private WriteFrame failed\n"); @@ -3588,8 +3582,6 @@ bool CvVideoWriter_MSMF::writeFrame(const IplImage* img) } rtStart += rtDuration; - printf("End of writing IplImage\n"); - delete[] target; return true; @@ -3681,7 +3673,8 @@ HRESULT CvVideoWriter_MSMF::InitializeSinkWriter(const char* filename) { hr = MFSetAttributeRatio(mediaTypeIn.Get(), MF_MT_PIXEL_ASPECT_RATIO, 1, 1); } - if (SUCCEEDED(hr)) + + if (SUCCEEDED(hr)) { hr = sinkWriter->SetInputMediaType(streamIndex, mediaTypeIn.Get(), NULL); } @@ -3697,7 +3690,6 @@ HRESULT CvVideoWriter_MSMF::InitializeSinkWriter(const char* filename) HRESULT CvVideoWriter_MSMF::WriteFrame(DWORD *videoFrameBuffer, const LONGLONG& Start, const LONGLONG& Duration) { - printf("Private WriteFrame(%p, %llu, %llu)\n", videoFrameBuffer, Start, Duration); ComPtr sample; ComPtr buffer; @@ -3712,13 +3704,11 @@ HRESULT CvVideoWriter_MSMF::WriteFrame(DWORD *videoFrameBuffer, const LONGLONG& // Lock the buffer and copy the video frame to the buffer. if (SUCCEEDED(hr)) { - printf("MFCreateMemoryBuffer successfull\n"); hr = buffer->Lock(&pData, NULL, NULL); } if (SUCCEEDED(hr)) { - printf("Before MFCopyImage(%p, %d, %p, %d, %d %d)\n", pData, cbWidth, videoFrameBuffer, cbWidth, cbWidth, videoHeight); hr = MFCopyImage( pData, // Destination buffer. cbWidth, // Destination stride. @@ -3727,21 +3717,16 @@ HRESULT CvVideoWriter_MSMF::WriteFrame(DWORD *videoFrameBuffer, const LONGLONG& cbWidth, // Image width in bytes. videoHeight // Image height in pixels. ); - printf("After MFCopyImage()\n"); } - printf("Before buffer.Get()\n"); if (buffer) { - printf("Before buffer->Unlock\n"); buffer->Unlock(); - printf("After buffer->Unlock\n"); } // Set the data length of the buffer. if (SUCCEEDED(hr)) { - printf("MFCopyImage successfull\n"); hr = buffer->SetCurrentLength(cbBuffer); } @@ -3758,24 +3743,19 @@ HRESULT CvVideoWriter_MSMF::WriteFrame(DWORD *videoFrameBuffer, const LONGLONG& // Set the time stamp and the duration. if (SUCCEEDED(hr)) { - printf("Sample time: %d\n", Start); hr = sample->SetSampleTime(Start); } if (SUCCEEDED(hr)) { - printf("Duration: %d\n", Duration); hr = sample->SetSampleDuration(Duration); } // Send the sample to the Sink Writer. if (SUCCEEDED(hr)) { - printf("Setting writer params successfull\n"); hr = sinkWriter->WriteSample(streamIndex, sample.Get()); } - printf("Private WriteFrame(%d, %p) end with status %u\n", streamIndex, sample.Get(), hr); - return hr; } diff --git a/modules/highgui/src/cap_vfw.cpp b/modules/highgui/src/cap_vfw.cpp index d419a48912..d845953f8e 100644 --- a/modules/highgui/src/cap_vfw.cpp +++ b/modules/highgui/src/cap_vfw.cpp @@ -613,8 +613,10 @@ bool CvVideoWriter_VFW::open( const char* filename, int _fourcc, double _fps, Cv close(); return false; } + return true; } - return true; + else + return false; } diff --git a/modules/highgui/test/test_video_io.cpp b/modules/highgui/test/test_video_io.cpp index 34ec0bdd80..f46235b3e9 100644 --- a/modules/highgui/test/test_video_io.cpp +++ b/modules/highgui/test/test_video_io.cpp @@ -57,27 +57,27 @@ string fourccToString(int fourcc) #ifdef HAVE_MSMF const VideoFormat g_specific_fmt_list[] = { -/* VideoFormat("avi", 'dv25'), - VideoFormat("avi", 'dv50'), - VideoFormat("avi", 'dvc '), - VideoFormat("avi", 'dvh1'), - VideoFormat("avi", 'dvhd'), - VideoFormat("avi", 'dvsd'), - VideoFormat("avi", 'dvsl'), - VideoFormat("avi", 'M4S2'), */ - VideoFormat("wmv", 'WMV3'), - // VideoFormat("avi", 'H264'), - // VideoFormat("avi", 'MJPG'), - // VideoFormat("avi", 'MP43'), - // VideoFormat("avi", 'MP4S'), - // VideoFormat("avi", 'MP4V'), -/* VideoFormat("avi", 'MPG1'), - VideoFormat("avi", 'MSS1'), - VideoFormat("avi", 'MSS2'), - VideoFormat("avi", 'WMV1'), - VideoFormat("avi", 'WMV2'), - VideoFormat("avi", 'WMV3'), - VideoFormat("avi", 'WVC1'), */ + /*VideoFormat("wmv", CV_FOURCC_MACRO('d', 'v', '2', '5')), + VideoFormat("wmv", CV_FOURCC_MACRO('d', 'v', '5', '0')), + VideoFormat("wmv", CV_FOURCC_MACRO('d', 'v', 'c', ' ')), + VideoFormat("wmv", CV_FOURCC_MACRO('d', 'v', 'h', '1')), + VideoFormat("wmv", CV_FOURCC_MACRO('d', 'v', 'h', 'd')), + VideoFormat("wmv", CV_FOURCC_MACRO('d', 'v', 's', 'd')), + VideoFormat("wmv", CV_FOURCC_MACRO('d', 'v', 's', 'l')), + VideoFormat("wmv", CV_FOURCC_MACRO('H', '2', '6', '3')), + VideoFormat("wmv", CV_FOURCC_MACRO('M', '4', 'S', '2')), + VideoFormat("avi", CV_FOURCC_MACRO('M', 'J', 'P', 'G')), + VideoFormat("mp4", CV_FOURCC_MACRO('M', 'P', '4', 'S')), + VideoFormat("mp4", CV_FOURCC_MACRO('M', 'P', '4', 'V')), + VideoFormat("wmv", CV_FOURCC_MACRO('M', 'P', '4', '3')), + VideoFormat("wmv", CV_FOURCC_MACRO('M', 'P', 'G', '1')), + VideoFormat("wmv", CV_FOURCC_MACRO('M', 'S', 'S', '1')), + VideoFormat("wmv", CV_FOURCC_MACRO('M', 'S', 'S', '2')),*/ + //VideoFormat("avi", CV_FOURCC_MACRO('H', '2', '6', '4')), + VideoFormat("wmv", CV_FOURCC_MACRO('W', 'M', 'V', '1')), + VideoFormat("wmv", CV_FOURCC_MACRO('W', 'M', 'V', '2')), + VideoFormat("wmv", CV_FOURCC_MACRO('W', 'M', 'V', '3')), + //VideoFormat("wmv", CV_FOURCC_MACRO('W', 'V', 'C', '1')), VideoFormat() }; #else @@ -269,19 +269,19 @@ void CV_HighGuiTest::VideoTest(const string& dir, const cvtest::VideoFormat& fmt for(;;) { - IplImage * img = cvQueryFrame( cap ); + IplImage* img = cvQueryFrame( cap ); if (!img) break; frames.push_back(Mat(img).clone()); - if (writer == 0) + if (writer == NULL) { writer = cvCreateVideoWriter(tmp_name.c_str(), fmt.fourcc, 24, cvGetSize(img)); - if (writer == 0) + if (writer == NULL) { - ts->printf(ts->LOG, "can't create writer (with fourcc : %d)\n", + ts->printf(ts->LOG, "can't create writer (with fourcc : %s)\n", cvtest::fourccToString(fmt.fourcc).c_str()); cvReleaseCapture( &cap ); ts->set_failed_test_info(ts->FAIL_MISMATCH); @@ -317,18 +317,22 @@ void CV_HighGuiTest::VideoTest(const string& dir, const cvtest::VideoFormat& fmt double psnr = PSNR(img1, img); if (psnr < thresDbell) { - printf("Too low psnr = %gdb\n", psnr); + ts->printf(ts->LOG, "Too low frame %d psnr = %gdb\n", i, psnr); + ts->set_failed_test_info(ts->FAIL_MISMATCH); + //imwrite("original.png", img); //imwrite("after_test.png", img1); //Mat diff; //absdiff(img, img1, diff); //imwrite("diff.png", diff); - ts->set_failed_test_info(ts->FAIL_MISMATCH); + break; } } + printf("Before saved release for %s\n", tmp_name.c_str()); cvReleaseCapture( &saved ); + printf("After release\n"); ts->printf(ts->LOG, "end test function : ImagesVideo \n"); } From 08a0e1c91b121cea67d08000c5339a6d7960e43d Mon Sep 17 00:00:00 2001 From: Alexander Smorkalov Date: Wed, 22 May 2013 07:26:43 -0700 Subject: [PATCH 159/178] TBB support for WinRT fixed. Development release of TBB with WinRT support added; TBB.dll is placed in bin folder now. --- 3rdparty/tbb/CMakeLists.txt | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/3rdparty/tbb/CMakeLists.txt b/3rdparty/tbb/CMakeLists.txt index 9dcb63b7f0..9424041339 100644 --- a/3rdparty/tbb/CMakeLists.txt +++ b/3rdparty/tbb/CMakeLists.txt @@ -1,12 +1,19 @@ #Cross compile TBB from source project(tbb) -# 4.1 update 3 dev - works fine -set(tbb_ver "tbb41_20130401oss") -set(tbb_url "http://threadingbuildingblocks.org/sites/default/files/software_releases/source/tbb41_20130401oss_src.tgz") -set(tbb_md5 "f2f591a0d2ca8f801e221ce7d9ea84bb") +# Development release +set(tbb_ver "tbb41_20130516oss") +set(tbb_url "http://threadingbuildingblocks.org/sites/default/files/software_releases/source/tbb41_20130516oss_src.tgz") +set(tbb_md5 "08c14d1c196bc9595d8c52bedc239937") set(tbb_version_file "version_string.ver") -ocv_warnings_disable(CMAKE_CXX_FLAGS -Wshadow) +ocv_warnings_disable(CMAKE_CXX_FLAGS -Wshadow -Wunused-parameter) + +# 4.1 update 3 dev - works fine +#set(tbb_ver "tbb41_20130401oss") +#set(tbb_url "http://threadingbuildingblocks.org/sites/default/files/software_releases/source/tbb41_20130401oss_src.tgz") +#set(tbb_md5 "f2f591a0d2ca8f801e221ce7d9ea84bb") +#set(tbb_version_file "version_string.ver") +#ocv_warnings_disable(CMAKE_CXX_FLAGS -Wshadow) # 4.1 update 2 - works fine #set(tbb_ver "tbb41_20130116oss") @@ -115,7 +122,6 @@ if(NOT EXISTS "${tbb_src_dir}") if(NOT tbb_untar_RESULT EQUAL 0 OR NOT EXISTS "${tbb_src_dir}") message(FATAL_ERROR "Failed to unpack TBB sources (${tbb_untar_RESULT} ${tbb_src_dir})") - endif() endif() @@ -133,12 +139,16 @@ list(APPEND lib_srcs "${tbb_src_dir}/src/rml/client/rml_tbb.cpp") if (WIN32) add_definitions(-D__TBB_DYNAMIC_LOAD_ENABLED=0 /D__TBB_BUILD=1 + /DTBB_NO_LEGACY=1 /D_UNICODE /DUNICODE /DWINAPI_FAMILY=WINAPI_FAMILY_APP /DDO_ITT_NOTIFY=0 /DUSE_WINTHREAD + /D_WIN32_WINNT=0x0602 + /D__TBB_WIN32_USE_CL_BUILTINS ) # defines were copied from windows.cl.inc + set(CMAKE_LINKER_FLAGS "${CMAKE_LINKER_FLAGS} /APPCONTAINER") else() add_definitions(-D__TBB_DYNAMIC_LOAD_ENABLED=0 #required @@ -183,10 +193,10 @@ set(TBB_SOURCE_FILES ${TBB_SOURCE_FILES} "${CMAKE_CURRENT_SOURCE_DIR}/${tbb_vers add_library(tbb ${TBB_SOURCE_FILES}) -if (WIN32) +if (ARM AND WIN32) add_custom_command(TARGET tbb PRE_BUILD - COMMAND ${CMAKE_C_COMPILER} /nologo /TC /EP ${tbb_src_dir}\\src\\tbb\\win32-tbb-export.def /DTBB_NO_LEGACY /DUSE_WINTHREAD /D_CRT_SECURE_NO_DEPRECATE /D_WIN32_WINNT=0x0400 /D__TBB_BUILD=1 /I${tbb_src_dir}\\src /I${tbb_src_dir}\\include > "${tbb_src_dir}\\src\\tbb\\tbb.def" + COMMAND ${CMAKE_C_COMPILER} /nologo /TC /EP ${tbb_src_dir}\\src\\tbb\\win32-tbb-export.def /DTBB_NO_LEGACY=1 /D_CRT_SECURE_NO_DEPRECATE /D__TBB_BUILD=1 /D_M_ARM=1 /I${tbb_src_dir}\\src /I${tbb_src_dir}\\include > "${tbb_src_dir}\\src\\tbb\\tbb.def" WORKING_DIRECTORY ${tbb_src_dir}\\src\\tbb COMMENT "Generating tbb.def file" VERBATIM ) @@ -194,9 +204,7 @@ endif() if (WIN32) set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /DEF:${tbb_src_dir}/src/tbb/tbb.def /DLL /MAP /fixed:no /INCREMENTAL:NO") -endif() - -if (NOT WIN32) +else() target_link_libraries(tbb c m dl) endif() @@ -207,6 +215,7 @@ set_target_properties(tbb PROPERTIES OUTPUT_NAME tbb DEBUG_POSTFIX "${OPENCV_DEBUG_POSTFIX}" ARCHIVE_OUTPUT_DIRECTORY ${3P_LIBRARY_OUTPUT_PATH} + RUNTIME_OUTPUT_DIRECTORY ${EXECUTABLE_OUTPUT_PATH} ) if(ENABLE_SOLUTION_FOLDERS) From 2bc1d3709c40dfa326b1f2b6a0725c1ffa42f506 Mon Sep 17 00:00:00 2001 From: Alexander Smorkalov Date: Wed, 22 May 2013 09:50:54 -0700 Subject: [PATCH 160/178] GetProperty method for MSMF VideoCapture implemented. --- modules/highgui/src/cap_msmf.cpp | 103 ++++++++++++++----------------- 1 file changed, 48 insertions(+), 55 deletions(-) diff --git a/modules/highgui/src/cap_msmf.cpp b/modules/highgui/src/cap_msmf.cpp index 7af85382b7..ae82b2c67e 100644 --- a/modules/highgui/src/cap_msmf.cpp +++ b/modules/highgui/src/cap_msmf.cpp @@ -119,8 +119,8 @@ struct MediaType wchar_t *pMF_MT_AM_FORMAT_TYPEName; unsigned int MF_MT_FIXED_SIZE_SAMPLES; unsigned int MF_MT_VIDEO_NOMINAL_RANGE; - unsigned int MF_MT_FRAME_RATE; - unsigned int MF_MT_FRAME_RATE_low; + unsigned int MF_MT_FRAME_RATE_NUMERATOR; + unsigned int MF_MT_FRAME_RATE_DENOMINATOR; unsigned int MF_MT_PIXEL_ASPECT_RATIO; unsigned int MF_MT_PIXEL_ASPECT_RATIO_low; unsigned int MF_MT_ALL_SAMPLES_INDEPENDENT; @@ -625,14 +625,17 @@ done: } return hr; } + void LogUINT32AsUINT64New(const PROPVARIANT& var, UINT32 &uHigh, UINT32 &uLow) { Unpack2UINT32AsUINT64(var.uhVal.QuadPart, &uHigh, &uLow); } + float OffsetToFloatNew(const MFOffset& offset) { return offset.value + (static_cast(offset.fract) / 65536.0f); } + HRESULT LogVideoAreaNew(const PROPVARIANT& var) { if (var.caub.cElems < sizeof(MFVideoArea)) @@ -641,6 +644,7 @@ HRESULT LogVideoAreaNew(const PROPVARIANT& var) } return S_OK; } + HRESULT SpecialCaseAttributeValueNew(GUID guid, const PROPVARIANT& var, MediaType &out) { if (guid == MF_MT_DEFAULT_STRIDE) @@ -660,8 +664,8 @@ HRESULT SpecialCaseAttributeValueNew(GUID guid, const PROPVARIANT& var, MediaTyp { UINT32 uHigh = 0, uLow = 0; LogUINT32AsUINT64New(var, uHigh, uLow); - out.MF_MT_FRAME_RATE = uHigh; - out.MF_MT_FRAME_RATE_low = uLow; + out.MF_MT_FRAME_RATE_NUMERATOR = uHigh; + out.MF_MT_FRAME_RATE_DENOMINATOR = uLow; } else if (guid == MF_MT_FRAME_RATE_RANGE_MAX) @@ -693,9 +697,11 @@ HRESULT SpecialCaseAttributeValueNew(GUID guid, const PROPVARIANT& var, MediaTyp } return S_OK; } + #ifndef IF_EQUAL_RETURN #define IF_EQUAL_RETURN(param, val) if(val == param) return L#val #endif + LPCWSTR GetGUIDNameConstNew(const GUID& guid) { IF_EQUAL_RETURN(guid, MF_MT_MAJOR_TYPE); @@ -875,6 +881,7 @@ MediaType FormatReader::Read(IMFMediaType *pType) } return out; } + FormatReader::~FormatReader(void) { } @@ -1880,6 +1887,7 @@ int videoDevice::findType(unsigned int size, unsigned int frameRate) return 0; return VN[0]; } + void videoDevice::buildLibraryofTypes() { unsigned int size; @@ -1889,7 +1897,7 @@ void videoDevice::buildLibraryofTypes() for(; i != vd_CurrentFormats.end(); i++) { size = (*i).MF_MT_FRAME_SIZE; - framerate = (*i).MF_MT_FRAME_RATE; + framerate = (*i).MF_MT_FRAME_RATE_NUMERATOR; FrameRateMap FRM = vd_CaptureFormats[size]; SUBTYPEMap STM = FRM[framerate]; String subType((*i).pMF_MT_SUBTYPEName); @@ -2187,8 +2195,8 @@ void MediaType::Clear() MF_MT_VIDEO_CHROMA_SITING = 0; MF_MT_FIXED_SIZE_SAMPLES = 0; MF_MT_VIDEO_NOMINAL_RANGE = 0; - MF_MT_FRAME_RATE = 0; - MF_MT_FRAME_RATE_low = 0; + MF_MT_FRAME_RATE_NUMERATOR = 0; + MF_MT_FRAME_RATE_DENOMINATOR = 0; MF_MT_PIXEL_ASPECT_RATIO = 0; MF_MT_PIXEL_ASPECT_RATIO_low = 0; MF_MT_ALL_SAMPLES_INDEPENDENT = 0; @@ -3033,6 +3041,7 @@ protected: bool isOpened; HRESULT enumerateCaptureFormats(IMFMediaSource *pSource); + HRESULT getSourceDuration(IMFMediaSource *pSource, MFTIME *pDuration); }; CvCaptureFile_MSMF::CvCaptureFile_MSMF(): @@ -3094,7 +3103,7 @@ bool CvCaptureFile_MSMF::open(const char* filename) if (SUCCEEDED(hr)) { - hr = ImageGrabberThread::CreateInstance(&grabberThread, videoFileSource, -2, true); + hr = ImageGrabberThread::CreateInstance(&grabberThread, videoFileSource, (unsigned int)-2, true); } if (SUCCEEDED(hr)) @@ -3131,7 +3140,8 @@ bool CvCaptureFile_MSMF::setProperty(int property_id, double value) { // image capture properties bool handled = false; - int width, height, fourcc; + unsigned int width, height; + int fourcc; switch( property_id ) { case CV_CAP_PROP_FRAME_WIDTH: @@ -3239,57 +3249,26 @@ double CvCaptureFile_MSMF::getProperty(int property_id) case CV_CAP_PROP_FRAME_HEIGHT: return captureFormats[captureFormatIndex].height; case CV_CAP_PROP_FRAME_COUNT: - return 30; + { + MFTIME duration; + getSourceDuration(this->videoFileSource, &duration); + double fps = ((double)captureFormats[captureFormatIndex].MF_MT_FRAME_RATE_NUMERATOR) / + ((double)captureFormats[captureFormatIndex].MF_MT_FRAME_RATE_DENOMINATOR); + return (double)floor(((double)duration/1e7)*fps+0.5); + } case CV_CAP_PROP_FOURCC: - // FIXME: implement method in VideoInput back end - //return VI.getFourcc(index); - ; + return captureFormats[captureFormatIndex].MF_MT_SUBTYPE.Data1; case CV_CAP_PROP_FPS: - // FIXME: implement method in VideoInput back end - //return VI.getFPS(index); - ; + return ((double)captureFormats[captureFormatIndex].MF_MT_FRAME_RATE_NUMERATOR) / + ((double)captureFormats[captureFormatIndex].MF_MT_FRAME_RATE_DENOMINATOR); } - // video filter properties - switch( property_id ) - { - case CV_CAP_PROP_BRIGHTNESS: - case CV_CAP_PROP_CONTRAST: - case CV_CAP_PROP_HUE: - case CV_CAP_PROP_SATURATION: - case CV_CAP_PROP_SHARPNESS: - case CV_CAP_PROP_GAMMA: - case CV_CAP_PROP_MONOCROME: - case CV_CAP_PROP_WHITE_BALANCE_BLUE_U: - case CV_CAP_PROP_BACKLIGHT: - case CV_CAP_PROP_GAIN: - // FIXME: implement method in VideoInput back end - // if ( VI.getVideoSettingFilter(index, VI.getVideoPropertyFromCV(property_id), min_value, - // max_value, stepping_delta, current_value, flags,defaultValue) ) - // return (double)current_value; - return 0.; - } - // camera properties - switch( property_id ) - { - case CV_CAP_PROP_PAN: - case CV_CAP_PROP_TILT: - case CV_CAP_PROP_ROLL: - case CV_CAP_PROP_ZOOM: - case CV_CAP_PROP_EXPOSURE: - case CV_CAP_PROP_IRIS: - case CV_CAP_PROP_FOCUS: - // FIXME: implement method in VideoInput back end - // if (VI.getVideoSettingCamera(index,VI.getCameraPropertyFromCV(property_id),min_value, - // max_value,stepping_delta,current_value,flags,defaultValue) ) return (double)current_value; - return 0.; - } - // unknown parameter or value not available + return -1; } bool CvCaptureFile_MSMF::grabFrame() { - DWORD waitResult; + DWORD waitResult = -1; if (isOpened) { SetEvent(grabberThread->getImageGrabber()->ig_hFrameGrabbed); @@ -3305,7 +3284,7 @@ IplImage* CvCaptureFile_MSMF::retrieveFrame(int) unsigned int width = captureFormats[captureFormatIndex].width; unsigned int height = captureFormats[captureFormatIndex].height; unsigned int bytes = 3; - if( !frame || width != frame->width || height != frame->height ) + if( !frame || (int)width != frame->width || (int)height != frame->height ) { if (frame) cvReleaseImage( &frame ); @@ -3370,6 +3349,20 @@ done: return hr; } +HRESULT CvCaptureFile_MSMF::getSourceDuration(IMFMediaSource *pSource, MFTIME *pDuration) +{ + *pDuration = 0; + + IMFPresentationDescriptor *pPD = NULL; + + HRESULT hr = pSource->CreatePresentationDescriptor(&pPD); + if (SUCCEEDED(hr)) + { + hr = pPD->GetUINT64(MF_PD_DURATION, (UINT64*)pDuration); + pPD->Release(); + } + return hr; +} CvCapture* cvCreateCameraCapture_MSMF( int index ) { @@ -3508,12 +3501,12 @@ const GUID CvVideoWriter_MSMF::FourCC2GUID(int fourcc) } bool CvVideoWriter_MSMF::open( const char* filename, int fourcc, - double _fps, CvSize frameSize, bool isColor ) + double _fps, CvSize frameSize, bool /*isColor*/ ) { videoWidth = frameSize.width; videoHeight = frameSize.height; fps = _fps; - bitRate = fps*videoWidth*videoHeight; // 1-bit per pixel + bitRate = (UINT32)fps*videoWidth*videoHeight; // 1-bit per pixel encodingFormat = FourCC2GUID(fourcc); inputFormat = MFVideoFormat_RGB32; From 34c659875293058f3db7c082d6c2917dc8ea190e Mon Sep 17 00:00:00 2001 From: Alexander Smorkalov Date: Fri, 24 May 2013 06:34:42 -0700 Subject: [PATCH 161/178] Perf test failure fixes for Media Foundation. --- modules/highgui/perf/perf_input.cpp | 10 ++++++++++ modules/highgui/perf/perf_output.cpp | 12 +++++++++--- modules/highgui/perf/perf_precomp.hpp | 2 ++ 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/modules/highgui/perf/perf_input.cpp b/modules/highgui/perf/perf_input.cpp index 0c1e8e0a73..414c85365f 100644 --- a/modules/highgui/perf/perf_input.cpp +++ b/modules/highgui/perf/perf_input.cpp @@ -11,11 +11,21 @@ using std::tr1::get; typedef perf::TestBaseWithParam VideoCapture_Reading; +#if defined(HAVE_MSMF) +// MPEG2 is not supported by Media Foundation yet +// http://social.msdn.microsoft.com/Forums/en-US/mediafoundationdevelopment/thread/39a36231-8c01-40af-9af5-3c105d684429 +PERF_TEST_P(VideoCapture_Reading, ReadFile, testing::Values( "highgui/video/big_buck_bunny.avi", + "highgui/video/big_buck_bunny.mov", + "highgui/video/big_buck_bunny.mp4", + "highgui/video/big_buck_bunny.wmv" ) ) + +#else PERF_TEST_P(VideoCapture_Reading, ReadFile, testing::Values( "highgui/video/big_buck_bunny.avi", "highgui/video/big_buck_bunny.mov", "highgui/video/big_buck_bunny.mp4", "highgui/video/big_buck_bunny.mpg", "highgui/video/big_buck_bunny.wmv" ) ) +#endif { string filename = getDataPath(GetParam()); diff --git a/modules/highgui/perf/perf_output.cpp b/modules/highgui/perf/perf_output.cpp index 6428bb4f03..2adfe89655 100644 --- a/modules/highgui/perf/perf_output.cpp +++ b/modules/highgui/perf/perf_output.cpp @@ -22,10 +22,16 @@ PERF_TEST_P(VideoWriter_Writing, WriteFrame, { string filename = getDataPath(get<0>(GetParam())); bool isColor = get<1>(GetParam()); + Mat image = imread(filename, 1); +#if defined(HAVE_MSMF) && !defined(HAVE_VFW) && !defined(HAVE_FFMPEG) // VFW has greater priority + VideoWriter writer(cv::tempfile(".wmv"), CV_FOURCC('W', 'M', 'V', '3'), + 25, cv::Size(image.cols, image.rows), isColor); +#else + VideoWriter writer(cv::tempfile(".avi"), CV_FOURCC('X', 'V', 'I', 'D'), + 25, cv::Size(image.cols, image.rows), isColor); +#endif - VideoWriter writer(cv::tempfile(".avi"), CV_FOURCC('X', 'V', 'I', 'D'), 25, cv::Size(640, 480), isColor); - - TEST_CYCLE() { Mat image = imread(filename, 1); writer << image; } + TEST_CYCLE() { image = imread(filename, 1); writer << image; } bool dummy = writer.isOpened(); SANITY_CHECK(dummy); diff --git a/modules/highgui/perf/perf_precomp.hpp b/modules/highgui/perf/perf_precomp.hpp index 529187d3b2..d6b28b6d23 100644 --- a/modules/highgui/perf/perf_precomp.hpp +++ b/modules/highgui/perf/perf_precomp.hpp @@ -21,6 +21,7 @@ defined(HAVE_QUICKTIME) || \ defined(HAVE_AVFOUNDATION) || \ defined(HAVE_FFMPEG) || \ + defined(HAVE_MSMF) || \ defined(HAVE_VFW) /*defined(HAVE_OPENNI) too specialized */ \ @@ -34,6 +35,7 @@ defined(HAVE_QUICKTIME) || \ defined(HAVE_AVFOUNDATION) || \ defined(HAVE_FFMPEG) || \ + defined(HAVE_MSMF) || \ defined(HAVE_VFW) # define BUILD_WITH_VIDEO_OUTPUT_SUPPORT 1 #else From ee591efb9fbd6b1bc274fd56a39cca734900e7cb Mon Sep 17 00:00:00 2001 From: Alexander Smorkalov Date: Wed, 29 May 2013 01:14:01 -0700 Subject: [PATCH 162/178] Build fix for Windows RT. --- modules/contrib/src/inputoutput.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/contrib/src/inputoutput.cpp b/modules/contrib/src/inputoutput.cpp index d10d884c83..a711f242ad 100644 --- a/modules/contrib/src/inputoutput.cpp +++ b/modules/contrib/src/inputoutput.cpp @@ -1,7 +1,7 @@ #include "opencv2/contrib/contrib.hpp" -#ifdef WIN32 +#if defined(WIN32) || defined(_WIN32) #include #include #else From 49903367608af0a89adb5ff83f61e2d7b9b7c514 Mon Sep 17 00:00:00 2001 From: Alexander Smorkalov Date: Thu, 6 Jun 2013 01:34:57 -0700 Subject: [PATCH 163/178] Base camera access sample for Windows RT added. Microsoft Media Foundation Camera Sample for Windows RT added. --- .../C++/AdvancedCapture.xaml | 81 + .../C++/AdvancedCapture.xaml.cpp | 1034 +++++ .../C++/AdvancedCapture.xaml.h | 103 + samples/winrt/ImageManipulations/C++/App.xaml | 30 + .../winrt/ImageManipulations/C++/App.xaml.cpp | 114 + .../winrt/ImageManipulations/C++/App.xaml.h | 35 + .../ImageManipulations/C++/AudioCapture.xaml | 62 + .../C++/AudioCapture.xaml.cpp | 366 ++ .../C++/AudioCapture.xaml.h | 70 + .../ImageManipulations/C++/BasicCapture.xaml | 87 + .../C++/BasicCapture.xaml.cpp | 535 +++ .../C++/BasicCapture.xaml.h | 88 + .../ImageManipulations/C++/Constants.cpp | 24 + .../winrt/ImageManipulations/C++/Constants.h | 45 + .../ImageManipulations/C++/MainPage.xaml | 166 + .../ImageManipulations/C++/MainPage.xaml.cpp | 315 ++ .../ImageManipulations/C++/MainPage.xaml.h | 105 + .../ImageManipulations/C++/MediaCapture.sln | 52 + .../C++/MediaCapture.vcxproj | 200 + .../C++/MediaCapture.vcxproj.filters | 88 + .../C++/MediaExtensions/Common/AsyncCB.h | 81 + .../C++/MediaExtensions/Common/BufferLock.h | 102 + .../C++/MediaExtensions/Common/CritSec.h | 62 + .../C++/MediaExtensions/Common/LinkList.h | 516 +++ .../C++/MediaExtensions/Common/OpQueue.h | 222 + .../MediaExtensions/Grayscale/Grayscale.cpp | 1783 ++++++++ .../MediaExtensions/Grayscale/Grayscale.def | 4 + .../C++/MediaExtensions/Grayscale/Grayscale.h | 266 ++ .../Grayscale/Grayscale.vcxproj | 313 ++ .../Grayscale/Grayscale.vcxproj.filters | 22 + .../Grayscale/GrayscaleTransform.idl | 11 + .../C++/MediaExtensions/Grayscale/dllmain.cpp | 58 + .../C++/Package.appxmanifest | 39 + .../C++/assets/microsoft-sdk.png | Bin 0 -> 1583 bytes .../C++/assets/placeholder-sdk.png | Bin 0 -> 8991 bytes .../C++/assets/smallTile-sdk.png | Bin 0 -> 1248 bytes .../C++/assets/splash-sdk.png | Bin 0 -> 5068 bytes .../C++/assets/squareTile-sdk.png | Bin 0 -> 2482 bytes .../C++/assets/storeLogo-sdk.png | Bin 0 -> 1550 bytes .../C++/assets/tile-sdk.png | Bin 0 -> 2665 bytes .../C++/assets/windows-sdk.png | Bin 0 -> 2997 bytes .../C++/common/LayoutAwarePage.cpp | 452 ++ .../C++/common/LayoutAwarePage.h | 88 + .../C++/common/StandardStyles.xaml | 978 +++++ .../C++/common/suspensionmanager.cpp | 481 +++ .../C++/common/suspensionmanager.h | 50 + samples/winrt/ImageManipulations/C++/pch.cpp | 16 + samples/winrt/ImageManipulations/C++/pch.h | 23 + .../sample-utils/SampleTemplateStyles.xaml | 51 + .../winrt/ImageManipulations/description.html | 238 ++ ...a0-3e7e-46df-b80b-1692acc1c812Combined.css | 0 .../ImageManipulations/description/Brand.css | 3629 +++++++++++++++++ .../description/Combined.css | 0 .../description/Galleries.css | 418 ++ .../ImageManipulations/description/Layout.css | 147 + ...69f54-1c43-4037-b90b-5f775f1d945fBrand.css | 303 ++ .../description/iframedescription.css | 179 + .../ImageManipulations/description/offline.js | 52 + samples/winrt/ImageManipulations/license.rtf | 25 + 59 files changed, 14209 insertions(+) create mode 100644 samples/winrt/ImageManipulations/C++/AdvancedCapture.xaml create mode 100644 samples/winrt/ImageManipulations/C++/AdvancedCapture.xaml.cpp create mode 100644 samples/winrt/ImageManipulations/C++/AdvancedCapture.xaml.h create mode 100644 samples/winrt/ImageManipulations/C++/App.xaml create mode 100644 samples/winrt/ImageManipulations/C++/App.xaml.cpp create mode 100644 samples/winrt/ImageManipulations/C++/App.xaml.h create mode 100644 samples/winrt/ImageManipulations/C++/AudioCapture.xaml create mode 100644 samples/winrt/ImageManipulations/C++/AudioCapture.xaml.cpp create mode 100644 samples/winrt/ImageManipulations/C++/AudioCapture.xaml.h create mode 100644 samples/winrt/ImageManipulations/C++/BasicCapture.xaml create mode 100644 samples/winrt/ImageManipulations/C++/BasicCapture.xaml.cpp create mode 100644 samples/winrt/ImageManipulations/C++/BasicCapture.xaml.h create mode 100644 samples/winrt/ImageManipulations/C++/Constants.cpp create mode 100644 samples/winrt/ImageManipulations/C++/Constants.h create mode 100644 samples/winrt/ImageManipulations/C++/MainPage.xaml create mode 100644 samples/winrt/ImageManipulations/C++/MainPage.xaml.cpp create mode 100644 samples/winrt/ImageManipulations/C++/MainPage.xaml.h create mode 100644 samples/winrt/ImageManipulations/C++/MediaCapture.sln create mode 100644 samples/winrt/ImageManipulations/C++/MediaCapture.vcxproj create mode 100644 samples/winrt/ImageManipulations/C++/MediaCapture.vcxproj.filters create mode 100644 samples/winrt/ImageManipulations/C++/MediaExtensions/Common/AsyncCB.h create mode 100644 samples/winrt/ImageManipulations/C++/MediaExtensions/Common/BufferLock.h create mode 100644 samples/winrt/ImageManipulations/C++/MediaExtensions/Common/CritSec.h create mode 100644 samples/winrt/ImageManipulations/C++/MediaExtensions/Common/LinkList.h create mode 100644 samples/winrt/ImageManipulations/C++/MediaExtensions/Common/OpQueue.h create mode 100644 samples/winrt/ImageManipulations/C++/MediaExtensions/Grayscale/Grayscale.cpp create mode 100644 samples/winrt/ImageManipulations/C++/MediaExtensions/Grayscale/Grayscale.def create mode 100644 samples/winrt/ImageManipulations/C++/MediaExtensions/Grayscale/Grayscale.h create mode 100644 samples/winrt/ImageManipulations/C++/MediaExtensions/Grayscale/Grayscale.vcxproj create mode 100644 samples/winrt/ImageManipulations/C++/MediaExtensions/Grayscale/Grayscale.vcxproj.filters create mode 100644 samples/winrt/ImageManipulations/C++/MediaExtensions/Grayscale/GrayscaleTransform.idl create mode 100644 samples/winrt/ImageManipulations/C++/MediaExtensions/Grayscale/dllmain.cpp create mode 100644 samples/winrt/ImageManipulations/C++/Package.appxmanifest create mode 100644 samples/winrt/ImageManipulations/C++/assets/microsoft-sdk.png create mode 100644 samples/winrt/ImageManipulations/C++/assets/placeholder-sdk.png create mode 100644 samples/winrt/ImageManipulations/C++/assets/smallTile-sdk.png create mode 100644 samples/winrt/ImageManipulations/C++/assets/splash-sdk.png create mode 100644 samples/winrt/ImageManipulations/C++/assets/squareTile-sdk.png create mode 100644 samples/winrt/ImageManipulations/C++/assets/storeLogo-sdk.png create mode 100644 samples/winrt/ImageManipulations/C++/assets/tile-sdk.png create mode 100644 samples/winrt/ImageManipulations/C++/assets/windows-sdk.png create mode 100644 samples/winrt/ImageManipulations/C++/common/LayoutAwarePage.cpp create mode 100644 samples/winrt/ImageManipulations/C++/common/LayoutAwarePage.h create mode 100644 samples/winrt/ImageManipulations/C++/common/StandardStyles.xaml create mode 100644 samples/winrt/ImageManipulations/C++/common/suspensionmanager.cpp create mode 100644 samples/winrt/ImageManipulations/C++/common/suspensionmanager.h create mode 100644 samples/winrt/ImageManipulations/C++/pch.cpp create mode 100644 samples/winrt/ImageManipulations/C++/pch.h create mode 100644 samples/winrt/ImageManipulations/C++/sample-utils/SampleTemplateStyles.xaml create mode 100644 samples/winrt/ImageManipulations/description.html create mode 100644 samples/winrt/ImageManipulations/description/4ee0dda0-3e7e-46df-b80b-1692acc1c812Combined.css create mode 100644 samples/winrt/ImageManipulations/description/Brand.css create mode 100644 samples/winrt/ImageManipulations/description/Combined.css create mode 100644 samples/winrt/ImageManipulations/description/Galleries.css create mode 100644 samples/winrt/ImageManipulations/description/Layout.css create mode 100644 samples/winrt/ImageManipulations/description/c2e69f54-1c43-4037-b90b-5f775f1d945fBrand.css create mode 100644 samples/winrt/ImageManipulations/description/iframedescription.css create mode 100644 samples/winrt/ImageManipulations/description/offline.js create mode 100644 samples/winrt/ImageManipulations/license.rtf diff --git a/samples/winrt/ImageManipulations/C++/AdvancedCapture.xaml b/samples/winrt/ImageManipulations/C++/AdvancedCapture.xaml new file mode 100644 index 0000000000..4e6ebfd301 --- /dev/null +++ b/samples/winrt/ImageManipulations/C++/AdvancedCapture.xaml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/winrt/ImageManipulations/C++/AdvancedCapture.xaml.cpp b/samples/winrt/ImageManipulations/C++/AdvancedCapture.xaml.cpp new file mode 100644 index 0000000000..dc59acc2e3 --- /dev/null +++ b/samples/winrt/ImageManipulations/C++/AdvancedCapture.xaml.cpp @@ -0,0 +1,1034 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. +// +//********************************************************* + +// +// AdvancedCapture.xaml.cpp +// Implementation of the AdvancedCapture class +// + +#include "pch.h" +#include "AdvancedCapture.xaml.h" + +using namespace SDKSample::MediaCapture; + +using namespace Windows::UI::Xaml; +using namespace Windows::UI::Xaml::Navigation; +using namespace Windows::UI::Xaml::Data; +using namespace Windows::System; +using namespace Windows::Foundation; +using namespace Platform; +using namespace Windows::UI; +using namespace Windows::UI::Core; +using namespace Windows::UI::Xaml; +using namespace Windows::UI::Xaml::Controls; +using namespace Windows::UI::Xaml::Data; +using namespace Windows::UI::Xaml::Media; +using namespace Windows::Storage; +using namespace Windows::Media::MediaProperties; +using namespace Windows::Storage::Streams; +using namespace Windows::System; +using namespace Windows::UI::Xaml::Media::Imaging; +using namespace Windows::Devices::Enumeration; + +ref class ReencodeState sealed +{ +public: + ReencodeState() + { + } + + virtual ~ReencodeState() + { + if (InputStream != nullptr) + { + delete InputStream; + } + if (OutputStream != nullptr) + { + delete OutputStream; + } + } + +internal: + Windows::Storage::Streams::IRandomAccessStream ^InputStream; + Windows::Storage::Streams::IRandomAccessStream ^OutputStream; + Windows::Storage::StorageFile ^PhotoStorage; + Windows::Graphics::Imaging::BitmapDecoder ^Decoder; + Windows::Graphics::Imaging::BitmapEncoder ^Encoder; +}; + +AdvancedCapture::AdvancedCapture() +{ + InitializeComponent(); + ScenarioInit(); +} + +///

+/// Invoked when this page is about to be displayed in a Frame. +/// +/// Event data that describes how this page was reached. The Parameter +/// property is typically used to configure the page. +void AdvancedCapture::OnNavigatedTo(NavigationEventArgs^ e) +{ + // A pointer back to the main page. This is needed if you want to call methods in MainPage such + // as NotifyUser() + rootPage = MainPage::Current; + m_eventRegistrationToken = Windows::Media::MediaControl::SoundLevelChanged += ref new EventHandler(this, &AdvancedCapture::SoundLevelChanged); + + m_orientationChangedEventToken = Windows::Graphics::Display::DisplayProperties::OrientationChanged += ref new Windows::Graphics::Display::DisplayPropertiesEventHandler(this, &AdvancedCapture::DisplayProperties_OrientationChanged); +} + +void AdvancedCapture::OnNavigatedFrom(NavigationEventArgs^ e) +{ + Windows::Media::MediaControl::SoundLevelChanged -= m_eventRegistrationToken; + Windows::Graphics::Display::DisplayProperties::OrientationChanged -= m_orientationChangedEventToken; +} + +void AdvancedCapture::ScenarioInit() +{ + rootPage = MainPage::Current; + btnStartDevice2->IsEnabled = true; + btnStartPreview2->IsEnabled = false; + btnStartStopRecord2->IsEnabled = false; + m_bRecording = false; + m_bPreviewing = false; + m_bEffectAdded = false; + btnStartStopRecord2->Content = "StartRecord"; + btnTakePhoto2->IsEnabled = false; + previewElement2->Source = nullptr; + playbackElement2->Source = nullptr; + imageElement2->Source= nullptr; + ShowStatusMessage(""); + chkAddRemoveEffect->IsChecked = false; + chkAddRemoveEffect->IsEnabled = false; + previewCanvas2->Visibility = Windows::UI::Xaml::Visibility::Collapsed; + EnumerateWebcamsAsync(); + m_bSuspended = false; +} + +void AdvancedCapture::ScenarioReset() +{ + previewCanvas2->Visibility = Windows::UI::Xaml::Visibility::Collapsed; + ScenarioInit(); +} + +void AdvancedCapture::SoundLevelChanged(Object^ sender, Object^ e) +{ + create_task(Dispatcher->RunAsync(Windows::UI::Core::CoreDispatcherPriority::High, ref new Windows::UI::Core::DispatchedHandler([this]() + { + if(Windows::Media::MediaControl::SoundLevel != Windows::Media::SoundLevel::Muted) + { + ScenarioReset(); + } + else + { + if (m_bRecording) + { + ShowStatusMessage("Stopping Record on invisibility"); + + create_task(m_mediaCaptureMgr->StopRecordAsync()).then([this](task recordTask) + { + try + { + recordTask.get(); + m_bRecording = false; + } + catch (Exception ^e) + { + ShowExceptionMessage(e); + } + }); + } + if (m_bPreviewing) + { + ShowStatusMessage("Stopping Preview on invisibility"); + + create_task(m_mediaCaptureMgr->StopPreviewAsync()).then([this](task previewTask) + { + try + { + previewTask.get(); + m_bPreviewing = false; + + }catch (Exception ^e) + { + ShowExceptionMessage(e); + } + }); + } + } + }))); +} + +void AdvancedCapture::RecordLimitationExceeded(Windows::Media::Capture::MediaCapture ^currentCaptureObject) +{ + try + { + if (m_bRecording) + { + create_task(Dispatcher->RunAsync(Windows::UI::Core::CoreDispatcherPriority::High, ref new Windows::UI::Core::DispatchedHandler([this]() + { + try + { + ShowStatusMessage("Stopping Record on exceeding max record duration"); + EnableButton(false, "StartStopRecord"); + create_task(m_mediaCaptureMgr->StopRecordAsync()).then([this](task recordTask) + { + try + { + recordTask.get(); + m_bRecording = false; + SwitchRecordButtonContent(); + EnableButton(true, "StartStopRecord"); + ShowStatusMessage("Stopped record on exceeding max record duration:" + m_recordStorageFile->Path); + } + catch (Exception ^e) + { + ShowExceptionMessage(e); + m_bRecording = false; + SwitchRecordButtonContent(); + EnableButton(true, "StartStopRecord"); + } + }); + } + catch (Exception ^e) + { + m_bRecording = false; + SwitchRecordButtonContent(); + EnableButton(true, "StartStopRecord"); + ShowExceptionMessage(e); + } + }))); + } + } + catch (Exception ^e) + { + m_bRecording = false; + SwitchRecordButtonContent(); + EnableButton(true, "StartStopRecord"); + ShowExceptionMessage(e); + } +} + +void AdvancedCapture::Failed(Windows::Media::Capture::MediaCapture ^currentCaptureObject, Windows::Media::Capture::MediaCaptureFailedEventArgs^ currentFailure) +{ + String ^message = "Fatal error" + currentFailure->Message; + create_task(Dispatcher->RunAsync(Windows::UI::Core::CoreDispatcherPriority::High, + ref new Windows::UI::Core::DispatchedHandler([this, message]() + { + ShowStatusMessage(message); + }))); +} + +void AdvancedCapture::btnStartDevice_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e) +{ + try + { + EnableButton(false, "StartDevice"); + ShowStatusMessage("Starting device"); + auto mediaCapture = ref new Windows::Media::Capture::MediaCapture(); + m_mediaCaptureMgr = mediaCapture; + auto settings = ref new Windows::Media::Capture::MediaCaptureInitializationSettings(); + auto chosenDevInfo = m_devInfoCollection->GetAt(EnumedDeviceList2->SelectedIndex); + settings->VideoDeviceId = chosenDevInfo->Id; + if (chosenDevInfo->EnclosureLocation != nullptr && chosenDevInfo->EnclosureLocation->Panel == Windows::Devices::Enumeration::Panel::Back) + { + m_bRotateVideoOnOrientationChange = true; + m_bReversePreviewRotation = false; + } + else if (chosenDevInfo->EnclosureLocation != nullptr && chosenDevInfo->EnclosureLocation->Panel == Windows::Devices::Enumeration::Panel::Front) + { + m_bRotateVideoOnOrientationChange = true; + m_bReversePreviewRotation = true; + } + else + { + m_bRotateVideoOnOrientationChange = false; + } + + create_task(mediaCapture->InitializeAsync(settings)).then([this](task initTask) + { + try + { + initTask.get(); + + auto mediaCapture = m_mediaCaptureMgr.Get(); + + DisplayProperties_OrientationChanged(nullptr); + + EnableButton(true, "StartPreview"); + EnableButton(true, "StartStopRecord"); + EnableButton(true, "TakePhoto"); + ShowStatusMessage("Device initialized successful"); + chkAddRemoveEffect->IsEnabled = true; + mediaCapture->RecordLimitationExceeded += ref new Windows::Media::Capture::RecordLimitationExceededEventHandler(this, &AdvancedCapture::RecordLimitationExceeded); + mediaCapture->Failed += ref new Windows::Media::Capture::MediaCaptureFailedEventHandler(this, &AdvancedCapture::Failed); + } + catch (Exception ^ e) + { + ShowExceptionMessage(e); + } + }); + } + catch (Platform::Exception^ e) + { + ShowExceptionMessage(e); + } +} + +void AdvancedCapture::btnStartPreview_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e) +{ + m_bPreviewing = false; + try + { + ShowStatusMessage("Starting preview"); + EnableButton(false, "StartPreview"); + + auto mediaCapture = m_mediaCaptureMgr.Get(); + previewCanvas2->Visibility = Windows::UI::Xaml::Visibility::Visible; + previewElement2->Source = mediaCapture; + create_task(mediaCapture->StartPreviewAsync()).then([this](task previewTask) + { + try + { + previewTask.get(); + m_bPreviewing = true; + ShowStatusMessage("Start preview successful"); + } + catch (Exception ^e) + { + ShowExceptionMessage(e); + } + }); + } + catch (Platform::Exception^ e) + { + m_bPreviewing = false; + previewElement2->Source = nullptr; + EnableButton(true, "StartPreview"); + ShowExceptionMessage(e); + } +} + +void AdvancedCapture::btnTakePhoto_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e) +{ + try + { + ShowStatusMessage("Taking photo"); + EnableButton(false, "TakePhoto"); + auto currentRotation = GetCurrentPhotoRotation(); + + task(KnownFolders::PicturesLibrary->CreateFileAsync(TEMP_PHOTO_FILE_NAME, Windows::Storage::CreationCollisionOption::GenerateUniqueName)).then([this, currentRotation](task getFileTask) + { + try + { + auto tempPhotoStorageFile = getFileTask.get(); + ShowStatusMessage("Create photo file successful"); + ImageEncodingProperties^ imageProperties = ImageEncodingProperties::CreateJpeg(); + + create_task(m_mediaCaptureMgr->CapturePhotoToStorageFileAsync(imageProperties, tempPhotoStorageFile)).then([this,tempPhotoStorageFile,currentRotation](task photoTask) + { + try + { + photoTask.get(); + + ReencodePhotoAsync(tempPhotoStorageFile, currentRotation).then([this] (task reencodeImageTask) + { + try + { + auto photoStorageFile = reencodeImageTask.get(); + + EnableButton(true, "TakePhoto"); + ShowStatusMessage("Photo taken"); + + task(photoStorageFile->OpenAsync(FileAccessMode::Read)).then([this](task getStreamTask) + { + try + { + auto photoStream = getStreamTask.get(); + ShowStatusMessage("File open successful"); + auto bmpimg = ref new BitmapImage(); + + bmpimg->SetSource(photoStream); + imageElement2->Source = bmpimg; + } + catch (Exception^ e) + { + ShowExceptionMessage(e); + EnableButton(true, "TakePhoto"); + } + }); + } + catch (Platform::Exception ^ e) + { + ShowExceptionMessage(e); + EnableButton(true, "TakePhoto"); + } + }); + } + catch (Platform::Exception ^ e) + { + ShowExceptionMessage(e); + EnableButton(true, "TakePhoto"); + } + }); + } + catch (Exception^ e) + { + ShowExceptionMessage(e); + EnableButton(true, "TakePhoto"); + + } + }); + } + catch (Platform::Exception^ e) + { + ShowExceptionMessage(e); + EnableButton(true, "TakePhoto"); + } +} + +void AdvancedCapture::btnStartStopRecord_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e) +{ + try + { + String ^fileName; + EnableButton(false, "StartStopRecord"); + + if (!m_bRecording) + { + ShowStatusMessage("Starting Record"); + + fileName = VIDEO_FILE_NAME; + + PrepareForVideoRecording(); + + task(KnownFolders::VideosLibrary->CreateFileAsync(fileName, Windows::Storage::CreationCollisionOption::GenerateUniqueName)).then([this](task fileTask) + { + try + { + this->m_recordStorageFile = fileTask.get(); + ShowStatusMessage("Create record file successful"); + + MediaEncodingProfile^ recordProfile= nullptr; + recordProfile = MediaEncodingProfile::CreateMp4(Windows::Media::MediaProperties::VideoEncodingQuality::Auto); + + create_task(m_mediaCaptureMgr->StartRecordToStorageFileAsync(recordProfile, this->m_recordStorageFile)).then([this](task recordTask) + { + try + { + recordTask.get(); + m_bRecording = true; + SwitchRecordButtonContent(); + EnableButton(true, "StartStopRecord"); + + ShowStatusMessage("Start Record successful"); + } + catch (Exception ^e) + { + m_bRecording = true; + SwitchRecordButtonContent(); + EnableButton(true, "StartStopRecord"); + ShowExceptionMessage(e); + } + }); + } + catch (Exception ^e) + { + m_bRecording = false; + EnableButton(true, "StartStopRecord"); + ShowExceptionMessage(e); + } + }); + } + else + { + ShowStatusMessage("Stopping Record"); + + create_task(m_mediaCaptureMgr->StopRecordAsync()).then([this](task recordTask) + { + try + { + recordTask.get(); + m_bRecording = false; + EnableButton(true, "StartStopRecord"); + SwitchRecordButtonContent(); + + ShowStatusMessage("Stop record successful"); + if (!m_bSuspended) + { + task(this->m_recordStorageFile->OpenAsync(FileAccessMode::Read)).then([this](task streamTask) + { + try + { + auto stream = streamTask.get(); + ShowStatusMessage("Record file opened"); + ShowStatusMessage(this->m_recordStorageFile->Path); + playbackElement2->AutoPlay = true; + playbackElement2->SetSource(stream, this->m_recordStorageFile->FileType); + playbackElement2->Play(); + } + catch (Exception ^e) + { + ShowExceptionMessage(e); + m_bRecording = false; + EnableButton(true, "StartStopRecord"); + SwitchRecordButtonContent(); + } + }); + } + } + catch (Exception ^e) + { + m_bRecording = false; + EnableButton(true, "StartStopRecord"); + SwitchRecordButtonContent(); + ShowExceptionMessage(e); + } + }); + } + } + catch (Platform::Exception^ e) + { + EnableButton(true, "StartStopRecord"); + ShowExceptionMessage(e); + m_bRecording = false; + SwitchRecordButtonContent(); + } +} +void AdvancedCapture::lstEnumedDevices_SelectionChanged(Platform::Object^ sender, Windows::UI::Xaml::Controls::SelectionChangedEventArgs^ e) +{ + if ( m_bPreviewing ) + { + create_task(m_mediaCaptureMgr->StopPreviewAsync()).then([this](task previewTask) + { + try + { + previewTask.get(); + m_bPreviewing = false; + } + catch (Exception ^e) + { + ShowExceptionMessage(e); + } + }); + } + + btnStartDevice2->IsEnabled = true; + btnStartPreview2->IsEnabled = false; + btnStartStopRecord2->IsEnabled = false; + m_bRecording = false; + btnStartStopRecord2->Content = "StartRecord"; + btnTakePhoto2->IsEnabled = false; + previewElement2->Source = nullptr; + playbackElement2->Source = nullptr; + imageElement2->Source= nullptr; + chkAddRemoveEffect->IsEnabled = false; + chkAddRemoveEffect->IsChecked = false; + m_bEffectAdded = false; + m_bEffectAddedToRecord = false; + m_bEffectAddedToPhoto = false; + ShowStatusMessage(""); +} + +void AdvancedCapture::EnumerateWebcamsAsync() +{ + try + { + ShowStatusMessage("Enumerating Webcams..."); + m_devInfoCollection = nullptr; + + EnumedDeviceList2->Items->Clear(); + + task(DeviceInformation::FindAllAsync(DeviceClass::VideoCapture)).then([this](task findTask) + { + try + { + m_devInfoCollection = findTask.get(); + if (m_devInfoCollection == nullptr || m_devInfoCollection->Size == 0) + { + ShowStatusMessage("No WebCams found."); + } + else + { + for(unsigned int i = 0; i < m_devInfoCollection->Size; i++) + { + auto devInfo = m_devInfoCollection->GetAt(i); + EnumedDeviceList2->Items->Append(devInfo->Name); + } + EnumedDeviceList2->SelectedIndex = 0; + ShowStatusMessage("Enumerating Webcams completed successfully."); + btnStartDevice2->IsEnabled = true; + } + } + catch (Exception ^e) + { + ShowExceptionMessage(e); + } + }); + } + catch (Platform::Exception^ e) + { + ShowExceptionMessage(e); + } +} + +void AdvancedCapture::AddEffectToImageStream() +{ + auto mediaCapture = m_mediaCaptureMgr.Get(); + Windows::Media::Capture::VideoDeviceCharacteristic charecteristic = mediaCapture->MediaCaptureSettings->VideoDeviceCharacteristic; + + if((charecteristic != Windows::Media::Capture::VideoDeviceCharacteristic::AllStreamsIdentical) && + (charecteristic != Windows::Media::Capture::VideoDeviceCharacteristic::PreviewPhotoStreamsIdentical) && + (charecteristic != Windows::Media::Capture::VideoDeviceCharacteristic::RecordPhotoStreamsIdentical)) + { + Windows::Media::MediaProperties::IMediaEncodingProperties ^props = mediaCapture->VideoDeviceController->GetMediaStreamProperties(Windows::Media::Capture::MediaStreamType::Photo); + if(props->Type->Equals("Image")) + { + //Switch to a video media type instead since we cant add an effect to a image media type + Windows::Foundation::Collections::IVectorView^ supportedPropsList = mediaCapture->VideoDeviceController->GetAvailableMediaStreamProperties(Windows::Media::Capture::MediaStreamType::Photo); + { + unsigned int i = 0; + while (i< supportedPropsList->Size) + { + Windows::Media::MediaProperties::IMediaEncodingProperties^ props = supportedPropsList->GetAt(i); + + String^ s = props->Type; + if(props->Type->Equals("Video")) + { + task(mediaCapture->VideoDeviceController->SetMediaStreamPropertiesAsync(Windows::Media::Capture::MediaStreamType::Photo,props)).then([this](task changeTypeTask) + { + try + { + changeTypeTask.get(); + ShowStatusMessage("Change type on photo stream successful"); + //Now add the effect on the image pin + task(m_mediaCaptureMgr->AddEffectAsync(Windows::Media::Capture::MediaStreamType::Photo,"GrayscaleTransform.GrayscaleEffect", nullptr)).then([this](task effectTask3) + { + try + { + effectTask3.get(); + m_bEffectAddedToPhoto = true; + ShowStatusMessage("Adding effect to photo stream successful"); + chkAddRemoveEffect->IsEnabled = true; + + } + catch(Exception ^e) + { + ShowExceptionMessage(e); + chkAddRemoveEffect->IsEnabled = true; + chkAddRemoveEffect->IsChecked = false; + } + }); + + } + catch(Exception ^e) + { + ShowExceptionMessage(e); + chkAddRemoveEffect->IsEnabled = true; + chkAddRemoveEffect->IsChecked = false; + + } + + }); + break; + + } + i++; + } + } + } + else + { + //Add the effect to the image pin if the type is already "Video" + task(mediaCapture->AddEffectAsync(Windows::Media::Capture::MediaStreamType::Photo,"GrayscaleTransform.GrayscaleEffect", nullptr)).then([this](task effectTask3) + { + try + { + effectTask3.get(); + m_bEffectAddedToPhoto = true; + ShowStatusMessage("Adding effect to photo stream successful"); + chkAddRemoveEffect->IsEnabled = true; + + } + catch(Exception ^e) + { + ShowExceptionMessage(e); + chkAddRemoveEffect->IsEnabled = true; + chkAddRemoveEffect->IsChecked = false; + } + }); + } + } +} + + + +void AdvancedCapture::chkAddRemoveEffect_Checked(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e) +{ + try + { + chkAddRemoveEffect->IsEnabled = false; + m_bEffectAdded = true; + create_task(m_mediaCaptureMgr->AddEffectAsync(Windows::Media::Capture::MediaStreamType::VideoPreview,"GrayscaleTransform.GrayscaleEffect", nullptr)).then([this](task effectTask) + { + try + { + effectTask.get(); + + auto mediaCapture = m_mediaCaptureMgr.Get(); + Windows::Media::Capture::VideoDeviceCharacteristic charecteristic = mediaCapture->MediaCaptureSettings->VideoDeviceCharacteristic; + + ShowStatusMessage("Add effect successful to preview stream successful"); + if((charecteristic != Windows::Media::Capture::VideoDeviceCharacteristic::AllStreamsIdentical) && + (charecteristic != Windows::Media::Capture::VideoDeviceCharacteristic::PreviewRecordStreamsIdentical)) + { + Windows::Media::MediaProperties::IMediaEncodingProperties ^props = mediaCapture->VideoDeviceController->GetMediaStreamProperties(Windows::Media::Capture::MediaStreamType::VideoRecord); + Windows::Media::MediaProperties::VideoEncodingProperties ^videoEncodingProperties = static_cast(props); + if(!videoEncodingProperties->Subtype->Equals("H264")) //Cant add an effect to an H264 stream + { + task(mediaCapture->AddEffectAsync(Windows::Media::Capture::MediaStreamType::VideoRecord,"GrayscaleTransform.GrayscaleEffect", nullptr)).then([this](task effectTask2) + { + try + { + effectTask2.get(); + ShowStatusMessage("Add effect successful to record stream successful"); + m_bEffectAddedToRecord = true; + AddEffectToImageStream(); + chkAddRemoveEffect->IsEnabled = true; + } + catch(Exception ^e) + { + ShowExceptionMessage(e); + chkAddRemoveEffect->IsEnabled = true; + chkAddRemoveEffect->IsChecked = false; + } + }); + } + else + { + AddEffectToImageStream(); + chkAddRemoveEffect->IsEnabled = true; + } + + } + else + { + AddEffectToImageStream(); + chkAddRemoveEffect->IsEnabled = true; + } + } + catch (Exception ^e) + { + ShowExceptionMessage(e); + chkAddRemoveEffect->IsEnabled = true; + chkAddRemoveEffect->IsChecked = false; + } + }); + } + catch (Platform::Exception ^e) + { + ShowExceptionMessage(e); + chkAddRemoveEffect->IsEnabled = true; + chkAddRemoveEffect->IsChecked = false; + } +} + +void AdvancedCapture::chkAddRemoveEffect_Unchecked(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e) +{ + try + { + chkAddRemoveEffect->IsEnabled = false; + m_bEffectAdded = false; + create_task(m_mediaCaptureMgr->ClearEffectsAsync(Windows::Media::Capture::MediaStreamType::VideoPreview)).then([this](task effectTask) + { + try + { + effectTask.get(); + ShowStatusMessage("Remove effect from video preview stream successful"); + if(m_bEffectAddedToRecord) + { + task(m_mediaCaptureMgr->ClearEffectsAsync(Windows::Media::Capture::MediaStreamType::VideoRecord)).then([this](task effectTask) + { + try + { + effectTask.get(); + ShowStatusMessage("Remove effect from video record stream successful"); + m_bEffectAddedToRecord = false; + if(m_bEffectAddedToPhoto) + { + task(m_mediaCaptureMgr->ClearEffectsAsync(Windows::Media::Capture::MediaStreamType::Photo)).then([this](task effectTask) + { + try + { + effectTask.get(); + ShowStatusMessage("Remove effect from Photo stream successful"); + m_bEffectAddedToPhoto = false; + + } + catch(Exception ^e) + { + ShowExceptionMessage(e); + chkAddRemoveEffect->IsEnabled = true; + chkAddRemoveEffect->IsChecked = true; + } + + }); + } + else + { + } + chkAddRemoveEffect->IsEnabled = true; + } + catch(Exception ^e) + { + ShowExceptionMessage(e); + chkAddRemoveEffect->IsEnabled = true; + chkAddRemoveEffect->IsChecked = true; + + } + + }); + + } + else if(m_bEffectAddedToPhoto) + { + task(m_mediaCaptureMgr->ClearEffectsAsync(Windows::Media::Capture::MediaStreamType::Photo)).then([this](task effectTask) + { + try + { + effectTask.get(); + ShowStatusMessage("Remove effect from Photo stream successful"); + m_bEffectAddedToPhoto = false; + + } + catch(Exception ^e) + { + ShowExceptionMessage(e); + chkAddRemoveEffect->IsEnabled = true; + chkAddRemoveEffect->IsChecked = true; + } + + }); + } + else + { + chkAddRemoveEffect->IsEnabled = true; + chkAddRemoveEffect->IsChecked = true; + } + } + catch (Exception ^e) + { + ShowExceptionMessage(e); + chkAddRemoveEffect->IsEnabled = true; + chkAddRemoveEffect->IsChecked = true; + } + }); + } + catch (Platform::Exception ^e) + { + ShowExceptionMessage(e); + chkAddRemoveEffect->IsEnabled = true; + chkAddRemoveEffect->IsChecked = true; + } +} + +void AdvancedCapture::ShowStatusMessage(Platform::String^ text) +{ + rootPage->NotifyUser(text, NotifyType::StatusMessage); +} + +void AdvancedCapture::ShowExceptionMessage(Platform::Exception^ ex) +{ + rootPage->NotifyUser(ex->Message, NotifyType::ErrorMessage); +} + +void AdvancedCapture::SwitchRecordButtonContent() +{ + if (m_bRecording) + { + btnStartStopRecord2->Content="StopRecord"; + } + else + { + btnStartStopRecord2->Content="StartRecord"; + } +} + +void AdvancedCapture::EnableButton(bool enabled, String^ name) +{ + if (name->Equals("StartDevice")) + { + btnStartDevice2->IsEnabled = enabled; + } + else if (name->Equals("StartPreview")) + { + btnStartPreview2->IsEnabled = enabled; + } + else if (name->Equals("StartStopRecord")) + { + btnStartStopRecord2->IsEnabled = enabled; + } + else if (name->Equals("TakePhoto")) + { + btnTakePhoto2->IsEnabled = enabled; + } +} + +task AdvancedCapture::ReencodePhotoAsync( + Windows::Storage::StorageFile ^tempStorageFile, + Windows::Storage::FileProperties::PhotoOrientation photoRotation) +{ + ReencodeState ^state = ref new ReencodeState(); + + return create_task(tempStorageFile->OpenAsync(Windows::Storage::FileAccessMode::Read)).then([state](Windows::Storage::Streams::IRandomAccessStream ^stream) + { + state->InputStream = stream; + return Windows::Graphics::Imaging::BitmapDecoder::CreateAsync(state->InputStream); + }).then([state](Windows::Graphics::Imaging::BitmapDecoder ^decoder) + { + state->Decoder = decoder; + return Windows::Storage::KnownFolders::PicturesLibrary->CreateFileAsync(PHOTO_FILE_NAME, Windows::Storage::CreationCollisionOption::GenerateUniqueName); + }).then([state](Windows::Storage::StorageFile ^storageFile) + { + state->PhotoStorage = storageFile; + return state->PhotoStorage->OpenAsync(Windows::Storage::FileAccessMode::ReadWrite); + }).then([state](Windows::Storage::Streams::IRandomAccessStream ^stream) + { + state->OutputStream = stream; + state->OutputStream->Size = 0; + return Windows::Graphics::Imaging::BitmapEncoder::CreateForTranscodingAsync(state->OutputStream, state->Decoder); + }).then([state, photoRotation](Windows::Graphics::Imaging::BitmapEncoder ^encoder) + { + state->Encoder = encoder; + auto properties = ref new Windows::Graphics::Imaging::BitmapPropertySet(); + properties->Insert("System.Photo.Orientation", + ref new Windows::Graphics::Imaging::BitmapTypedValue((unsigned short)photoRotation, Windows::Foundation::PropertyType::UInt16)); + return create_task(state->Encoder->BitmapProperties->SetPropertiesAsync(properties)); + }).then([state]() + { + return state->Encoder->FlushAsync(); + }).then([tempStorageFile, state](task previousTask) + { + auto result = state->PhotoStorage; + delete state; + + tempStorageFile->DeleteAsync(Windows::Storage::StorageDeleteOption::PermanentDelete); + + previousTask.get(); + + return result; + }); +} + +Windows::Storage::FileProperties::PhotoOrientation AdvancedCapture::GetCurrentPhotoRotation() +{ + bool counterclockwiseRotation = m_bReversePreviewRotation; + + if (m_bRotateVideoOnOrientationChange) + { + return PhotoRotationLookup(Windows::Graphics::Display::DisplayProperties::CurrentOrientation, counterclockwiseRotation); + } + else + { + return Windows::Storage::FileProperties::PhotoOrientation::Normal; + } +} + +void AdvancedCapture::PrepareForVideoRecording() +{ + Windows::Media::Capture::MediaCapture ^mediaCapture = m_mediaCaptureMgr.Get(); + if (mediaCapture == nullptr) + { + return; + } + + bool counterclockwiseRotation = m_bReversePreviewRotation; + + if (m_bRotateVideoOnOrientationChange) + { + mediaCapture->SetRecordRotation(VideoRotationLookup(Windows::Graphics::Display::DisplayProperties::CurrentOrientation, counterclockwiseRotation)); + } + else + { + mediaCapture->SetRecordRotation(Windows::Media::Capture::VideoRotation::None); + } +} + +void AdvancedCapture::DisplayProperties_OrientationChanged(Platform::Object^ sender) +{ + Windows::Media::Capture::MediaCapture ^mediaCapture = m_mediaCaptureMgr.Get(); + if (mediaCapture == nullptr) + { + return; + } + + bool previewMirroring = mediaCapture->GetPreviewMirroring(); + bool counterclockwiseRotation = (previewMirroring && !m_bReversePreviewRotation) || + (!previewMirroring && m_bReversePreviewRotation); + + if (m_bRotateVideoOnOrientationChange) + { + mediaCapture->SetPreviewRotation(VideoRotationLookup(Windows::Graphics::Display::DisplayProperties::CurrentOrientation, counterclockwiseRotation)); + } + else + { + mediaCapture->SetPreviewRotation(Windows::Media::Capture::VideoRotation::None); + } +} + +Windows::Storage::FileProperties::PhotoOrientation AdvancedCapture::PhotoRotationLookup( + Windows::Graphics::Display::DisplayOrientations displayOrientation, bool counterclockwise) +{ + switch (displayOrientation) + { + case Windows::Graphics::Display::DisplayOrientations::Landscape: + return Windows::Storage::FileProperties::PhotoOrientation::Normal; + + case Windows::Graphics::Display::DisplayOrientations::Portrait: + return (counterclockwise) ? Windows::Storage::FileProperties::PhotoOrientation::Rotate270: + Windows::Storage::FileProperties::PhotoOrientation::Rotate90; + + case Windows::Graphics::Display::DisplayOrientations::LandscapeFlipped: + return Windows::Storage::FileProperties::PhotoOrientation::Rotate180; + + case Windows::Graphics::Display::DisplayOrientations::PortraitFlipped: + return (counterclockwise) ? Windows::Storage::FileProperties::PhotoOrientation::Rotate90 : + Windows::Storage::FileProperties::PhotoOrientation::Rotate270; + + default: + return Windows::Storage::FileProperties::PhotoOrientation::Unspecified; + } +} + +Windows::Media::Capture::VideoRotation AdvancedCapture::VideoRotationLookup( + Windows::Graphics::Display::DisplayOrientations displayOrientation, bool counterclockwise) +{ + switch (displayOrientation) + { + case Windows::Graphics::Display::DisplayOrientations::Landscape: + return Windows::Media::Capture::VideoRotation::None; + + case Windows::Graphics::Display::DisplayOrientations::Portrait: + return (counterclockwise) ? Windows::Media::Capture::VideoRotation::Clockwise270Degrees : + Windows::Media::Capture::VideoRotation::Clockwise90Degrees; + + case Windows::Graphics::Display::DisplayOrientations::LandscapeFlipped: + return Windows::Media::Capture::VideoRotation::Clockwise180Degrees; + + case Windows::Graphics::Display::DisplayOrientations::PortraitFlipped: + return (counterclockwise) ? Windows::Media::Capture::VideoRotation::Clockwise90Degrees: + Windows::Media::Capture::VideoRotation::Clockwise270Degrees ; + + default: + return Windows::Media::Capture::VideoRotation::None; + } +} + diff --git a/samples/winrt/ImageManipulations/C++/AdvancedCapture.xaml.h b/samples/winrt/ImageManipulations/C++/AdvancedCapture.xaml.h new file mode 100644 index 0000000000..83556b95e2 --- /dev/null +++ b/samples/winrt/ImageManipulations/C++/AdvancedCapture.xaml.h @@ -0,0 +1,103 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. +// +//********************************************************* + +// +// AdvancedCapture.xaml.h +// Declaration of the AdvancedCapture class +// + +#pragma once + +#include "pch.h" +#include "AdvancedCapture.g.h" +#include "MainPage.xaml.h" +#include + +#define VIDEO_FILE_NAME "video.mp4" +#define PHOTO_FILE_NAME "photo.jpg" +#define TEMP_PHOTO_FILE_NAME "photoTmp.jpg" + +using namespace concurrency; +using namespace Windows::Devices::Enumeration; + +namespace SDKSample +{ + namespace MediaCapture + { + /// + /// An empty page that can be used on its own or navigated to within a Frame. + /// + [Windows::Foundation::Metadata::WebHostHidden] + public ref class AdvancedCapture sealed + { + public: + AdvancedCapture(); + + protected: + virtual void OnNavigatedTo(Windows::UI::Xaml::Navigation::NavigationEventArgs^ e) override; + virtual void OnNavigatedFrom(Windows::UI::Xaml::Navigation::NavigationEventArgs^ e) override; + + private: + MainPage^ rootPage; + void ScenarioInit(); + void ScenarioReset(); + + void SoundLevelChanged(Object^ sender, Object^ e); + void RecordLimitationExceeded(Windows::Media::Capture::MediaCapture ^ mediaCapture); + void Failed(Windows::Media::Capture::MediaCapture ^ mediaCapture, Windows::Media::Capture::MediaCaptureFailedEventArgs ^ args); + + void btnStartDevice_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e); + + void btnStartPreview_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e); + + void btnStartStopRecord_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e); + + void btnTakePhoto_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e); + + void lstEnumedDevices_SelectionChanged(Platform::Object^ sender, Windows::UI::Xaml::Controls::SelectionChangedEventArgs^ e); + void EnumerateWebcamsAsync(); + + void chkAddRemoveEffect_Checked(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e); + void chkAddRemoveEffect_Unchecked(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e); + void AddEffectToImageStream(); + + void ShowStatusMessage(Platform::String^ text); + void ShowExceptionMessage(Platform::Exception^ ex); + + void EnableButton(bool enabled, Platform::String ^name); + void SwitchRecordButtonContent(); + + task ReencodePhotoAsync( + Windows::Storage::StorageFile ^tempStorageFile, + Windows::Storage::FileProperties::PhotoOrientation photoRotation); + Windows::Storage::FileProperties::PhotoOrientation GetCurrentPhotoRotation(); + void PrepareForVideoRecording(); + void DisplayProperties_OrientationChanged(Platform::Object^ sender); + Windows::Storage::FileProperties::PhotoOrientation PhotoRotationLookup( + Windows::Graphics::Display::DisplayOrientations displayOrientation, bool counterclockwise); + Windows::Media::Capture::VideoRotation VideoRotationLookup( + Windows::Graphics::Display::DisplayOrientations displayOrientation, bool counterclockwise); + + Platform::Agile m_mediaCaptureMgr; + Windows::Storage::StorageFile^ m_recordStorageFile; + bool m_bRecording; + bool m_bEffectAdded; + bool m_bEffectAddedToRecord; + bool m_bEffectAddedToPhoto; + bool m_bSuspended; + bool m_bPreviewing; + DeviceInformationCollection^ m_devInfoCollection; + Windows::Foundation::EventRegistrationToken m_eventRegistrationToken; + bool m_bRotateVideoOnOrientationChange; + bool m_bReversePreviewRotation; + Windows::Foundation::EventRegistrationToken m_orientationChangedEventToken; + }; + } +} diff --git a/samples/winrt/ImageManipulations/C++/App.xaml b/samples/winrt/ImageManipulations/C++/App.xaml new file mode 100644 index 0000000000..2edfd7790e --- /dev/null +++ b/samples/winrt/ImageManipulations/C++/App.xaml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + diff --git a/samples/winrt/ImageManipulations/C++/App.xaml.cpp b/samples/winrt/ImageManipulations/C++/App.xaml.cpp new file mode 100644 index 0000000000..ef733a1cc9 --- /dev/null +++ b/samples/winrt/ImageManipulations/C++/App.xaml.cpp @@ -0,0 +1,114 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. +// +//********************************************************* + +// +// App.xaml.cpp +// Implementation of the App.xaml class. +// + +#include "pch.h" +#include "MainPage.xaml.h" +#include "Common\SuspensionManager.h" + +using namespace SDKSample; +using namespace SDKSample::Common; + +using namespace Concurrency; +using namespace Platform; +using namespace Windows::ApplicationModel; +using namespace Windows::ApplicationModel::Activation; +using namespace Windows::Foundation; +using namespace Windows::Foundation::Collections; +using namespace Windows::UI::Core; +using namespace Windows::UI::Xaml; +using namespace Windows::UI::Xaml::Controls; +using namespace Windows::UI::Xaml::Controls::Primitives; +using namespace Windows::UI::Xaml::Data; +using namespace Windows::UI::Xaml::Input; +using namespace Windows::UI::Xaml::Interop; +using namespace Windows::UI::Xaml::Media; +using namespace Windows::UI::Xaml::Navigation; + +/// +/// Initializes the singleton application object. This is the first line of authored code +/// executed, and as such is the logical equivalent of main() or WinMain(). +/// +App::App() +{ + InitializeComponent(); + this->Suspending += ref new SuspendingEventHandler(this, &SDKSample::App::OnSuspending); +} + +/// +/// Invoked when the application is launched normally by the end user. Other entry points will +/// be used when the application is launched to open a specific file, to display search results, +/// and so forth. +/// +/// Details about the launch request and process. +void App::OnLaunched(LaunchActivatedEventArgs^ pArgs) +{ + this->LaunchArgs = pArgs; + + // Do not repeat app initialization when already running, just ensure that + // the window is active + if (pArgs->PreviousExecutionState == ApplicationExecutionState::Running) + { + Window::Current->Activate(); + return; + } + + // Create a Frame to act as the navigation context and associate it with + // a SuspensionManager key + auto rootFrame = ref new Frame(); + SuspensionManager::RegisterFrame(rootFrame, "AppFrame"); + + auto prerequisite = task([](){}); + if (pArgs->PreviousExecutionState == ApplicationExecutionState::Terminated) + { + // Restore the saved session state only when appropriate, scheduling the + // final launch steps after the restore is complete + prerequisite = SuspensionManager::RestoreAsync(); + } + prerequisite.then([=]() + { + // When the navigation stack isn't restored navigate to the first page, + // configuring the new page by passing required information as a navigation + // parameter + if (rootFrame->Content == nullptr) + { + if (!rootFrame->Navigate(TypeName(MainPage::typeid))) + { + throw ref new FailureException("Failed to create initial page"); + } + } + + // Place the frame in the current Window and ensure that it is active + Window::Current->Content = rootFrame; + Window::Current->Activate(); + }, task_continuation_context::use_current()); +} + +/// +/// Invoked when application execution is being suspended. Application state is saved +/// without knowing whether the application will be terminated or resumed with the contents +/// of memory still intact. +/// +/// The source of the suspend request. +/// Details about the suspend request. +void App::OnSuspending(Object^ sender, SuspendingEventArgs^ e) +{ + (void) sender; // Unused parameter + + auto deferral = e->SuspendingOperation->GetDeferral(); + SuspensionManager::SaveAsync().then([=]() + { + deferral->Complete(); + }); +} diff --git a/samples/winrt/ImageManipulations/C++/App.xaml.h b/samples/winrt/ImageManipulations/C++/App.xaml.h new file mode 100644 index 0000000000..a8b6064248 --- /dev/null +++ b/samples/winrt/ImageManipulations/C++/App.xaml.h @@ -0,0 +1,35 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. +// +//********************************************************* + +// +// App.xaml.h +// Declaration of the App.xaml class. +// + +#pragma once + +#include "pch.h" +#include "App.g.h" +#include "MainPage.g.h" + +namespace SDKSample +{ + ref class App + { + internal: + App(); + virtual void OnSuspending(Platform::Object^ sender, Windows::ApplicationModel::SuspendingEventArgs^ pArgs); + Windows::ApplicationModel::Activation::LaunchActivatedEventArgs^ LaunchArgs; + protected: + virtual void OnLaunched(Windows::ApplicationModel::Activation::LaunchActivatedEventArgs^ pArgs) override; + private: + Windows::UI::Xaml::Controls::Frame^ rootFrame; + }; +} diff --git a/samples/winrt/ImageManipulations/C++/AudioCapture.xaml b/samples/winrt/ImageManipulations/C++/AudioCapture.xaml new file mode 100644 index 0000000000..be65bcd8c1 --- /dev/null +++ b/samples/winrt/ImageManipulations/C++/AudioCapture.xaml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + This scenario shows how to do an audio only capture using the default microphone. Click on StartRecord to start recording. + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/winrt/ImageManipulations/C++/AudioCapture.xaml.cpp b/samples/winrt/ImageManipulations/C++/AudioCapture.xaml.cpp new file mode 100644 index 0000000000..37fc379d38 --- /dev/null +++ b/samples/winrt/ImageManipulations/C++/AudioCapture.xaml.cpp @@ -0,0 +1,366 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. +// +//********************************************************* + +// +// AudioCapture.xaml.cpp +// Implementation of the AudioCapture class +// + +#include "pch.h" +#include "AudioCapture.xaml.h" +#include +using namespace concurrency; + +using namespace SDKSample::MediaCapture; + +using namespace Windows::UI::Xaml; +using namespace Windows::UI::Xaml::Navigation; +using namespace Windows::UI::Xaml::Data; +using namespace Windows::System; +using namespace Windows::Foundation; +using namespace Platform; +using namespace Windows::UI; +using namespace Windows::UI::Core; +using namespace Windows::UI::Xaml; +using namespace Windows::UI::Xaml::Controls; +using namespace Windows::UI::Xaml::Data; +using namespace Windows::UI::Xaml::Media; +using namespace Windows::Storage; +using namespace Windows::Media::MediaProperties; +using namespace Windows::Storage::Streams; +using namespace Windows::System; +using namespace Windows::UI::Xaml::Media::Imaging; + + +AudioCapture::AudioCapture() +{ + InitializeComponent(); + ScenarioInit(); +} + +/// +/// Invoked when this page is about to be displayed in a Frame. +/// +/// Event data that describes how this page was reached. The Parameter +/// property is typically used to configure the page. +void AudioCapture::OnNavigatedTo(NavigationEventArgs^ e) +{ + // A pointer back to the main page. This is needed if you want to call methods in MainPage such + // as NotifyUser() + rootPage = MainPage::Current; + m_eventRegistrationToken = Windows::Media::MediaControl::SoundLevelChanged += ref new EventHandler(this, &AudioCapture::SoundLevelChanged); +} + +void AudioCapture::OnNavigatedFrom(NavigationEventArgs^ e) +{ + // A pointer back to the main page. This is needed if you want to call methods in MainPage such + // as NotifyUser() + Windows::Media::MediaControl::SoundLevelChanged -= m_eventRegistrationToken; +} + +void AudioCapture::ScenarioInit() +{ + try + { + rootPage = MainPage::Current; + btnStartDevice3->IsEnabled = true; + btnStartStopRecord3->IsEnabled = false; + m_bRecording = false; + playbackElement3->Source = nullptr; + m_bSuspended = false; + ShowStatusMessage(""); + } + catch (Exception ^e) + { + ShowExceptionMessage(e); + } + +} + +void AudioCapture::ScenarioReset() +{ + ScenarioInit(); +} + + +void AudioCapture::SoundLevelChanged(Object^ sender, Object^ e) +{ + create_task(Dispatcher->RunAsync(Windows::UI::Core::CoreDispatcherPriority::High, ref new Windows::UI::Core::DispatchedHandler([this]() + { + if(Windows::Media::MediaControl::SoundLevel != Windows::Media::SoundLevel::Muted) + { + ScenarioReset(); + } + else + { + if (m_bRecording) + { + ShowStatusMessage("Stopping Record on invisibility"); + + create_task(m_mediaCaptureMgr->StopRecordAsync()).then([this](task recordTask) + { + try + { + recordTask.get(); + m_bRecording = false; + }catch (Exception ^e) + { + ShowExceptionMessage(e); + } + }); + } + } + }))); +} + +void AudioCapture::RecordLimitationExceeded(Windows::Media::Capture::MediaCapture ^currentCaptureObject) +{ + try + { + if (m_bRecording) + { + create_task(Dispatcher->RunAsync(Windows::UI::Core::CoreDispatcherPriority::High, ref new Windows::UI::Core::DispatchedHandler([this](){ + try + { + ShowStatusMessage("Stopping Record on exceeding max record duration"); + EnableButton(false, "StartStopRecord"); + create_task(m_mediaCaptureMgr->StopRecordAsync()).then([this](task recordTask) + { + try + { + recordTask.get(); + m_bRecording = false; + SwitchRecordButtonContent(); + EnableButton(true, "StartStopRecord"); + ShowStatusMessage("Stopped record on exceeding max record duration:" + m_recordStorageFile->Path); + } + catch (Exception ^e) + { + ShowExceptionMessage(e); + m_bRecording = false; + SwitchRecordButtonContent(); + EnableButton(true, "StartStopRecord"); + } + }); + + } + catch (Exception ^e) + { + m_bRecording = false; + SwitchRecordButtonContent(); + EnableButton(true, "StartStopRecord"); + ShowExceptionMessage(e); + } + + }))); + } + } + catch (Exception ^e) + { + m_bRecording = false; + SwitchRecordButtonContent(); + EnableButton(true, "StartStopRecord"); + ShowExceptionMessage(e); + } +} + +void AudioCapture::Failed(Windows::Media::Capture::MediaCapture ^currentCaptureObject, Windows::Media::Capture::MediaCaptureFailedEventArgs^ currentFailure) +{ + String ^message = "Fatal error: " + currentFailure->Message; + create_task(Dispatcher->RunAsync(Windows::UI::Core::CoreDispatcherPriority::High, + ref new Windows::UI::Core::DispatchedHandler([this, message]() + { + ShowStatusMessage(message); + }))); +} + +void AudioCapture::btnStartDevice_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e) +{ + try + { + EnableButton(false, "StartDevice"); + ShowStatusMessage("Starting device"); + auto mediaCapture = ref new Windows::Media::Capture::MediaCapture(); + m_mediaCaptureMgr = mediaCapture; + auto settings = ref new Windows::Media::Capture::MediaCaptureInitializationSettings(); + settings->StreamingCaptureMode = Windows::Media::Capture::StreamingCaptureMode::Audio; + create_task(mediaCapture->InitializeAsync()).then([this](task initTask) + { + try + { + initTask.get(); + + auto mediaCapture = m_mediaCaptureMgr.Get(); + EnableButton(true, "StartPreview"); + EnableButton(true, "StartStopRecord"); + EnableButton(true, "TakePhoto"); + ShowStatusMessage("Device initialized successful"); + mediaCapture->RecordLimitationExceeded += ref new Windows::Media::Capture::RecordLimitationExceededEventHandler(this, &AudioCapture::RecordLimitationExceeded); + mediaCapture->Failed += ref new Windows::Media::Capture::MediaCaptureFailedEventHandler(this, &AudioCapture::Failed); + } + catch (Exception ^ e) + { + ShowExceptionMessage(e); + } + }); + } + catch (Platform::Exception^ e) + { + ShowExceptionMessage(e); + } +} + +void AudioCapture::btnStartStopRecord_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e) +{ + try + { + String ^fileName; + EnableButton(false, "StartStopRecord"); + + if (!m_bRecording) + { + ShowStatusMessage("Starting Record"); + + fileName = AUDIO_FILE_NAME; + + task(KnownFolders::VideosLibrary->CreateFileAsync(fileName, Windows::Storage::CreationCollisionOption::GenerateUniqueName)).then([this](task fileTask) + { + try + { + this->m_recordStorageFile = fileTask.get(); + ShowStatusMessage("Create record file successful"); + + MediaEncodingProfile^ recordProfile= nullptr; + recordProfile = MediaEncodingProfile::CreateM4a(Windows::Media::MediaProperties::AudioEncodingQuality::Auto); + + create_task(m_mediaCaptureMgr->StartRecordToStorageFileAsync(recordProfile, this->m_recordStorageFile)).then([this](task recordTask) + { + try + { + recordTask.get(); + m_bRecording = true; + SwitchRecordButtonContent(); + EnableButton(true, "StartStopRecord"); + + ShowStatusMessage("Start Record successful"); + + + }catch (Exception ^e) + { + ShowExceptionMessage(e); + m_bRecording = false; + SwitchRecordButtonContent(); + EnableButton(true, "StartStopRecord"); + } + }); + } + catch (Exception ^e) + { + m_bRecording = false; + SwitchRecordButtonContent(); + EnableButton(true, "StartStopRecord"); + ShowExceptionMessage(e); + } + } + ); + } + else + { + ShowStatusMessage("Stopping Record"); + + create_task(m_mediaCaptureMgr->StopRecordAsync()).then([this](task) + { + try + { + m_bRecording = false; + EnableButton(true, "StartStopRecord"); + SwitchRecordButtonContent(); + + ShowStatusMessage("Stop record successful"); + if (!m_bSuspended) + { + task(this->m_recordStorageFile->OpenAsync(FileAccessMode::Read)).then([this](task streamTask) + { + try + { + ShowStatusMessage("Record file opened"); + auto stream = streamTask.get(); + ShowStatusMessage(this->m_recordStorageFile->Path); + playbackElement3->AutoPlay = true; + playbackElement3->SetSource(stream, this->m_recordStorageFile->FileType); + playbackElement3->Play(); + } + catch (Exception ^e) + { + ShowExceptionMessage(e); + m_bRecording = false; + SwitchRecordButtonContent(); + EnableButton(true, "StartStopRecord"); + } + }); + } + } + catch (Exception ^e) + { + m_bRecording = false; + SwitchRecordButtonContent(); + EnableButton(true, "StartStopRecord"); + ShowExceptionMessage(e); + } + }); + } + } + catch (Platform::Exception^ e) + { + EnableButton(true, "StartStopRecord"); + ShowExceptionMessage(e); + m_bRecording = false; + SwitchRecordButtonContent(); + } +} + + +void AudioCapture::ShowStatusMessage(Platform::String^ text) +{ + rootPage->NotifyUser(text, NotifyType::StatusMessage); +} + +void AudioCapture::ShowExceptionMessage(Platform::Exception^ ex) +{ + rootPage->NotifyUser(ex->Message, NotifyType::ErrorMessage); +} + +void AudioCapture::SwitchRecordButtonContent() +{ + { + if (m_bRecording) + { + btnStartStopRecord3->Content="StopRecord"; + } + else + { + btnStartStopRecord3->Content="StartRecord"; + } + } +} +void AudioCapture::EnableButton(bool enabled, String^ name) +{ + if (name->Equals("StartDevice")) + { + btnStartDevice3->IsEnabled = enabled; + } + + else if (name->Equals("StartStopRecord")) + { + btnStartStopRecord3->IsEnabled = enabled; + } + +} + diff --git a/samples/winrt/ImageManipulations/C++/AudioCapture.xaml.h b/samples/winrt/ImageManipulations/C++/AudioCapture.xaml.h new file mode 100644 index 0000000000..b75efdc728 --- /dev/null +++ b/samples/winrt/ImageManipulations/C++/AudioCapture.xaml.h @@ -0,0 +1,70 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. +// +//********************************************************* + +// +// AudioCapture.xaml.h +// Declaration of the AudioCapture class +// + +#pragma once + +#include "pch.h" +#include "AudioCapture.g.h" +#include "MainPage.xaml.h" + +#define AUDIO_FILE_NAME "audio.mp4" + +namespace SDKSample +{ + namespace MediaCapture + { + /// + /// An empty page that can be used on its own or navigated to within a Frame. + /// + [Windows::Foundation::Metadata::WebHostHidden] + public ref class AudioCapture sealed + { + public: + AudioCapture(); + + protected: + virtual void OnNavigatedTo(Windows::UI::Xaml::Navigation::NavigationEventArgs^ e) override; + virtual void OnNavigatedFrom(Windows::UI::Xaml::Navigation::NavigationEventArgs^ e) override; + private: + MainPage^ rootPage; + + void ScenarioInit(); + void ScenarioReset(); + + void SoundLevelChanged(Object^ sender, Object^ e); + void RecordLimitationExceeded(Windows::Media::Capture::MediaCapture ^ mediaCapture); + void Failed(Windows::Media::Capture::MediaCapture ^ mediaCapture, Windows::Media::Capture::MediaCaptureFailedEventArgs ^ args); + + void btnStartDevice_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e); + + void btnStartPreview_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e); + + void btnStartStopRecord_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e); + + void ShowStatusMessage(Platform::String^ text); + void ShowExceptionMessage(Platform::Exception^ ex); + + void EnableButton(bool enabled, Platform::String ^name); + void SwitchRecordButtonContent(); + + Platform::Agile m_mediaCaptureMgr; + Windows::Storage::StorageFile^ m_photoStorageFile; + Windows::Storage::StorageFile^ m_recordStorageFile; + bool m_bRecording; + bool m_bSuspended; + Windows::Foundation::EventRegistrationToken m_eventRegistrationToken; + }; + } +} diff --git a/samples/winrt/ImageManipulations/C++/BasicCapture.xaml b/samples/winrt/ImageManipulations/C++/BasicCapture.xaml new file mode 100644 index 0000000000..2cc0b0a5ff --- /dev/null +++ b/samples/winrt/ImageManipulations/C++/BasicCapture.xaml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + This scenario demonstrates how to use the MediaCapture API to preview the camera stream, record a video, and take a picture using default initialization settings. + You can also adjust the brightness and contrast. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/winrt/ImageManipulations/C++/BasicCapture.xaml.cpp b/samples/winrt/ImageManipulations/C++/BasicCapture.xaml.cpp new file mode 100644 index 0000000000..f385fa9a74 --- /dev/null +++ b/samples/winrt/ImageManipulations/C++/BasicCapture.xaml.cpp @@ -0,0 +1,535 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. +// +//********************************************************* + +// +// BasicCapture.xaml.cpp +// Implementation of the BasicCapture class +// + +#include "pch.h" +#include "BasicCapture.xaml.h" +#include "ppl.h" + +using namespace Windows::System; +using namespace Windows::Foundation; +using namespace Platform; +using namespace Windows::UI; +using namespace Windows::UI::Core; +using namespace Windows::UI::Xaml; +using namespace Windows::UI::Xaml::Controls; +using namespace Windows::UI::Xaml::Navigation; +using namespace Windows::UI::Xaml::Data; +using namespace Windows::UI::Xaml::Media; +using namespace Windows::Storage; +using namespace Windows::Media::MediaProperties; +using namespace Windows::Storage::Streams; +using namespace Windows::System; +using namespace Windows::UI::Xaml::Media::Imaging; + +using namespace SDKSample::MediaCapture; +using namespace concurrency; + + +BasicCapture::BasicCapture() +{ + InitializeComponent(); + ScenarioInit(); +} + +/// +/// Invoked when this page is about to be displayed in a Frame. +/// +/// Event data that describes how this page was reached. The Parameter +/// property is typically used to configure the page. +void BasicCapture::OnNavigatedTo(NavigationEventArgs^ e) +{ + // A pointer back to the main page. This is needed if you want to call methods in MainPage such + // as NotifyUser() + rootPage = MainPage::Current; + m_eventRegistrationToken = Windows::Media::MediaControl::SoundLevelChanged += ref new EventHandler(this, &BasicCapture::SoundLevelChanged); +} + +void BasicCapture::OnNavigatedFrom(NavigationEventArgs^ e) +{ + // A pointer back to the main page. This is needed if you want to call methods in MainPage such + // as NotifyUser() + + Windows::Media::MediaControl::SoundLevelChanged -= m_eventRegistrationToken; + m_currentScenarioLoaded = false; +} + + +void BasicCapture::ScenarioInit() +{ + try + { + btnStartDevice1->IsEnabled = true; + btnStartPreview1->IsEnabled = false; + btnStartStopRecord1->IsEnabled = false; + m_bRecording = false; + m_bPreviewing = false; + btnStartStopRecord1->Content = "StartRecord"; + btnTakePhoto1->IsEnabled = false; + previewElement1->Source = nullptr; + playbackElement1->Source = nullptr; + imageElement1->Source= nullptr; + sldBrightness->IsEnabled = false; + sldContrast->IsEnabled = false; + m_bSuspended = false; + previewCanvas1->Visibility = Windows::UI::Xaml::Visibility::Collapsed; + + } + catch (Exception ^e) + { + ShowExceptionMessage(e); + } + +} + +void BasicCapture::ScenarioReset() +{ + previewCanvas1->Visibility = Windows::UI::Xaml::Visibility::Collapsed; + ScenarioInit(); +} + +void BasicCapture::SoundLevelChanged(Object^ sender, Object^ e) +{ + create_task(Dispatcher->RunAsync(Windows::UI::Core::CoreDispatcherPriority::High, ref new Windows::UI::Core::DispatchedHandler([this]() + { + if(Windows::Media::MediaControl::SoundLevel != Windows::Media::SoundLevel::Muted) + { + ScenarioReset(); + } + else + { + if (m_bRecording) + { + ShowStatusMessage("Stopping Record on invisibility"); + + create_task(m_mediaCaptureMgr->StopRecordAsync()).then([this](task recordTask) + { + m_bRecording = false; + }); + } + if (m_bPreviewing) + { + ShowStatusMessage("Stopping Preview on invisibility"); + + create_task(m_mediaCaptureMgr->StopPreviewAsync()).then([this](task previewTask) + { + try + { + previewTask.get(); + m_bPreviewing = false; + } + catch (Exception ^e) + { + ShowExceptionMessage(e); + } + }); + } + } + }))); +} + +void BasicCapture::RecordLimitationExceeded(Windows::Media::Capture::MediaCapture ^currentCaptureObject) +{ + try + { + if (m_bRecording) + { + create_task(Dispatcher->RunAsync(Windows::UI::Core::CoreDispatcherPriority::High, ref new Windows::UI::Core::DispatchedHandler([this](){ + try + { + ShowStatusMessage("Stopping Record on exceeding max record duration"); + EnableButton(false, "StartStopRecord"); + create_task(m_mediaCaptureMgr->StopRecordAsync()).then([this](task recordTask) + { + try + { + recordTask.get(); + m_bRecording = false; + SwitchRecordButtonContent(); + EnableButton(true, "StartStopRecord"); + ShowStatusMessage("Stopped record on exceeding max record duration:" + m_recordStorageFile->Path); + } + catch (Exception ^e) + { + ShowExceptionMessage(e); + m_bRecording = false; + SwitchRecordButtonContent(); + EnableButton(true, "StartStopRecord"); + } + }); + + } + catch (Exception ^e) + { + m_bRecording = false; + SwitchRecordButtonContent(); + EnableButton(true, "StartStopRecord"); + ShowExceptionMessage(e); + } + + }))); + } + } + catch (Exception ^e) + { + m_bRecording = false; + SwitchRecordButtonContent(); + EnableButton(true, "StartStopRecord"); + ShowExceptionMessage(e); + } +} + +void BasicCapture::Failed(Windows::Media::Capture::MediaCapture ^currentCaptureObject, Windows::Media::Capture::MediaCaptureFailedEventArgs^ currentFailure) +{ + String ^message = "Fatal error: " + currentFailure->Message; + create_task(Dispatcher->RunAsync(Windows::UI::Core::CoreDispatcherPriority::High, + ref new Windows::UI::Core::DispatchedHandler([this, message]() + { + ShowStatusMessage(message); + }))); +} + +void BasicCapture::btnStartDevice_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e) +{ + try + { + EnableButton(false, "StartDevice"); + ShowStatusMessage("Starting device"); + auto mediaCapture = ref new Windows::Media::Capture::MediaCapture(); + m_mediaCaptureMgr = mediaCapture; + create_task(mediaCapture->InitializeAsync()).then([this](task initTask) + { + try + { + initTask.get(); + + auto mediaCapture = m_mediaCaptureMgr.Get(); + EnableButton(true, "StartPreview"); + EnableButton(true, "StartStopRecord"); + EnableButton(true, "TakePhoto"); + ShowStatusMessage("Device initialized successful"); + mediaCapture->RecordLimitationExceeded += ref new Windows::Media::Capture::RecordLimitationExceededEventHandler(this, &BasicCapture::RecordLimitationExceeded); + mediaCapture->Failed += ref new Windows::Media::Capture::MediaCaptureFailedEventHandler(this, &BasicCapture::Failed); + } + catch (Exception ^ e) + { + ShowExceptionMessage(e); + } + } + ); + } + catch (Platform::Exception^ e) + { + ShowExceptionMessage(e); + } +} + +void BasicCapture::btnStartPreview_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e) +{ + m_bPreviewing = false; + try + { + ShowStatusMessage("Starting preview"); + EnableButton(false, "StartPreview"); + auto mediaCapture = m_mediaCaptureMgr.Get(); + + previewCanvas1->Visibility = Windows::UI::Xaml::Visibility::Visible; + previewElement1->Source = mediaCapture; + create_task(mediaCapture->StartPreviewAsync()).then([this](task previewTask) + { + try + { + previewTask.get(); + auto mediaCapture = m_mediaCaptureMgr.Get(); + m_bPreviewing = true; + ShowStatusMessage("Start preview successful"); + if(mediaCapture->VideoDeviceController->Brightness) + { + SetupVideoDeviceControl(mediaCapture->VideoDeviceController->Brightness, sldBrightness); + } + if(mediaCapture->VideoDeviceController->Contrast) + { + SetupVideoDeviceControl(mediaCapture->VideoDeviceController->Contrast, sldContrast); + } + + }catch (Exception ^e) + { + ShowExceptionMessage(e); + } + }); + } + catch (Platform::Exception^ e) + { + m_bPreviewing = false; + previewElement1->Source = nullptr; + EnableButton(true, "StartPreview"); + ShowExceptionMessage(e); + } +} + +void BasicCapture::btnTakePhoto_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e) +{ + try + { + ShowStatusMessage("Taking photo"); + EnableButton(false, "TakePhoto"); + + task(KnownFolders::PicturesLibrary->CreateFileAsync(PHOTO_FILE_NAME, Windows::Storage::CreationCollisionOption::GenerateUniqueName)).then([this](task getFileTask) + { + try + { + this->m_photoStorageFile = getFileTask.get(); + ShowStatusMessage("Create photo file successful"); + ImageEncodingProperties^ imageProperties = ImageEncodingProperties::CreateJpeg(); + + create_task(m_mediaCaptureMgr->CapturePhotoToStorageFileAsync(imageProperties, this->m_photoStorageFile)).then([this](task photoTask) + { + try + { + photoTask.get(); + EnableButton(true, "TakePhoto"); + ShowStatusMessage("Photo taken"); + + task(this->m_photoStorageFile->OpenAsync(FileAccessMode::Read)).then([this](task getStreamTask) + { + try + { + auto photoStream = getStreamTask.get(); + ShowStatusMessage("File open successful"); + auto bmpimg = ref new BitmapImage(); + + bmpimg->SetSource(photoStream); + imageElement1->Source = bmpimg; + } + catch (Exception^ e) + { + ShowExceptionMessage(e); + EnableButton(true, "TakePhoto"); + } + }); + } + catch (Platform::Exception ^ e) + { + ShowExceptionMessage(e); + EnableButton(true, "TakePhoto"); + } + }); + } + catch (Exception^ e) + { + ShowExceptionMessage(e); + EnableButton(true, "TakePhoto"); + } + }); + } + catch (Platform::Exception^ e) + { + ShowExceptionMessage(e); + EnableButton(true, "TakePhoto"); + } +} + +void BasicCapture::btnStartStopRecord_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e) +{ + try + { + String ^fileName; + EnableButton(false, "StartStopRecord"); + + if (!m_bRecording) + { + ShowStatusMessage("Starting Record"); + + fileName = VIDEO_FILE_NAME; + + task(KnownFolders::VideosLibrary->CreateFileAsync(fileName,Windows::Storage::CreationCollisionOption::GenerateUniqueName )).then([this](task fileTask) + { + try + { + this->m_recordStorageFile = fileTask.get(); + ShowStatusMessage("Create record file successful"); + + MediaEncodingProfile^ recordProfile= nullptr; + recordProfile = MediaEncodingProfile::CreateMp4(Windows::Media::MediaProperties::VideoEncodingQuality::Auto); + + create_task(m_mediaCaptureMgr->StartRecordToStorageFileAsync(recordProfile, this->m_recordStorageFile)).then([this](task recordTask) + { + try + { + recordTask.get(); + m_bRecording = true; + SwitchRecordButtonContent(); + EnableButton(true, "StartStopRecord"); + + ShowStatusMessage("Start Record successful"); + } + catch (Exception ^e) + { + ShowExceptionMessage(e); + } + }); + } + catch (Exception ^e) + { + m_bRecording = false; + SwitchRecordButtonContent(); + EnableButton(true, "StartStopRecord"); + ShowExceptionMessage(e); + } + } + ); + } + else + { + ShowStatusMessage("Stopping Record"); + + create_task(m_mediaCaptureMgr->StopRecordAsync()).then([this](task recordTask) + { + try + { + recordTask.get(); + m_bRecording = false; + EnableButton(true, "StartStopRecord"); + SwitchRecordButtonContent(); + + ShowStatusMessage("Stop record successful"); + if (!m_bSuspended) + { + task(this->m_recordStorageFile->OpenAsync(FileAccessMode::Read)).then([this](task streamTask) + { + try + { + auto stream = streamTask.get(); + ShowStatusMessage("Record file opened"); + ShowStatusMessage(this->m_recordStorageFile->Path); + playbackElement1->AutoPlay = true; + playbackElement1->SetSource(stream, this->m_recordStorageFile->FileType); + playbackElement1->Play(); + } + catch (Exception ^e) + { + ShowExceptionMessage(e); + m_bRecording = false; + EnableButton(true, "StartStopRecord"); + SwitchRecordButtonContent(); + } + }); + } + } + catch (Exception ^e) + { + m_bRecording = false; + EnableButton(true, "StartStopRecord"); + SwitchRecordButtonContent(); + ShowExceptionMessage(e); + } + }); + } + } + catch (Platform::Exception^ e) + { + EnableButton(true, "StartStopRecord"); + ShowExceptionMessage(e); + SwitchRecordButtonContent(); + m_bRecording = false; + } +} + +void BasicCapture::SetupVideoDeviceControl(Windows::Media::Devices::MediaDeviceControl^ videoDeviceControl, Slider^ slider) +{ + try + { + if ((videoDeviceControl->Capabilities)->Supported) + { + slider->IsEnabled = true; + slider->Maximum = videoDeviceControl->Capabilities->Max; + slider->Minimum = videoDeviceControl->Capabilities->Min; + slider->StepFrequency = videoDeviceControl->Capabilities->Step; + double controlValue = 0; + if (videoDeviceControl->TryGetValue(&controlValue)) + { + slider->Value = controlValue; + } + } + else + { + slider->IsEnabled = false; + } + } + catch (Platform::Exception^ e) + { + ShowExceptionMessage(e); + } +} + +// VideoDeviceControllers +void BasicCapture::sldBrightness_ValueChanged(Platform::Object^ sender, Windows::UI::Xaml::Controls::Primitives::RangeBaseValueChangedEventArgs^ e) +{ + bool succeeded = m_mediaCaptureMgr->VideoDeviceController->Brightness->TrySetValue(sldBrightness->Value); + if (!succeeded) + { + ShowStatusMessage("Set Brightness failed"); + } +} + +void BasicCapture::sldContrast_ValueChanged(Platform::Object^ sender, Windows::UI::Xaml::Controls::Primitives::RangeBaseValueChangedEventArgs ^e) +{ + bool succeeded = m_mediaCaptureMgr->VideoDeviceController->Contrast->TrySetValue(sldContrast->Value); + if (!succeeded) + { + ShowStatusMessage("Set Contrast failed"); + } +} + +void BasicCapture::ShowStatusMessage(Platform::String^ text) +{ + rootPage->NotifyUser(text, NotifyType::StatusMessage); +} + +void BasicCapture::ShowExceptionMessage(Platform::Exception^ ex) +{ + rootPage->NotifyUser(ex->Message, NotifyType::ErrorMessage); +} + +void BasicCapture::SwitchRecordButtonContent() +{ + if (m_bRecording) + { + btnStartStopRecord1->Content="StopRecord"; + } + else + { + btnStartStopRecord1->Content="StartRecord"; + } +} +void BasicCapture::EnableButton(bool enabled, String^ name) +{ + if (name->Equals("StartDevice")) + { + btnStartDevice1->IsEnabled = enabled; + } + else if (name->Equals("StartPreview")) + { + btnStartPreview1->IsEnabled = enabled; + } + else if (name->Equals("StartStopRecord")) + { + btnStartStopRecord1->IsEnabled = enabled; + } + else if (name->Equals("TakePhoto")) + { + btnTakePhoto1->IsEnabled = enabled; + } +} + diff --git a/samples/winrt/ImageManipulations/C++/BasicCapture.xaml.h b/samples/winrt/ImageManipulations/C++/BasicCapture.xaml.h new file mode 100644 index 0000000000..28129efb72 --- /dev/null +++ b/samples/winrt/ImageManipulations/C++/BasicCapture.xaml.h @@ -0,0 +1,88 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. +// +//********************************************************* + +// +// BasicCapture.xaml.h +// Declaration of the BasicCapture class +// + +#pragma once + +#include "pch.h" +#include "BasicCapture.g.h" +#include "MainPage.xaml.h" + +using namespace Windows::UI::Xaml; +using namespace Windows::UI::Xaml::Controls; +using namespace Windows::Graphics::Display; +using namespace Windows::UI::ViewManagement; +using namespace Windows::Devices::Enumeration; +#define VIDEO_FILE_NAME "video.mp4" +#define PHOTO_FILE_NAME "photo.jpg" +namespace SDKSample +{ + namespace MediaCapture + { + /// + /// An empty page that can be used on its own or navigated to within a Frame. + /// + [Windows::Foundation::Metadata::WebHostHidden] + public ref class BasicCapture sealed + { + public: + BasicCapture(); + + protected: + virtual void OnNavigatedTo(Windows::UI::Xaml::Navigation::NavigationEventArgs^ e) override; + virtual void OnNavigatedFrom(Windows::UI::Xaml::Navigation::NavigationEventArgs^ e) override; + + private: + MainPage^ rootPage; + void ScenarioInit(); + void ScenarioReset(); + + void Suspending(Object^ sender, Windows::ApplicationModel::SuspendingEventArgs^ e); + void Resuming(Object^ sender, Object^ e); + + void btnStartDevice_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e); + void SoundLevelChanged(Object^ sender, Object^ e); + void RecordLimitationExceeded(Windows::Media::Capture::MediaCapture ^ mediaCapture); + void Failed(Windows::Media::Capture::MediaCapture ^ mediaCapture, Windows::Media::Capture::MediaCaptureFailedEventArgs ^ args); + + + void btnStartPreview_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e); + + void btnStartStopRecord_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e); + + void btnTakePhoto_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e); + + void SetupVideoDeviceControl(Windows::Media::Devices::MediaDeviceControl^ videoDeviceControl, Slider^ slider); + void sldBrightness_ValueChanged(Platform::Object^ sender, Windows::UI::Xaml::Controls::Primitives::RangeBaseValueChangedEventArgs^ e); + void sldContrast_ValueChanged(Platform::Object^ sender, Windows::UI::Xaml::Controls::Primitives::RangeBaseValueChangedEventArgs^ e); + + void ShowStatusMessage(Platform::String^ text); + void ShowExceptionMessage(Platform::Exception^ ex); + + void EnableButton(bool enabled, Platform::String ^name); + void SwitchRecordButtonContent(); + + Platform::Agile m_mediaCaptureMgr; + Windows::Storage::StorageFile^ m_photoStorageFile; + Windows::Storage::StorageFile^ m_recordStorageFile; + bool m_bRecording; + bool m_bEffectAdded; + bool m_bSuspended; + bool m_bPreviewing; + Windows::UI::Xaml::WindowVisibilityChangedEventHandler ^m_visbilityHandler; + Windows::Foundation::EventRegistrationToken m_eventRegistrationToken; + bool m_currentScenarioLoaded; + }; + } +} diff --git a/samples/winrt/ImageManipulations/C++/Constants.cpp b/samples/winrt/ImageManipulations/C++/Constants.cpp new file mode 100644 index 0000000000..873b983819 --- /dev/null +++ b/samples/winrt/ImageManipulations/C++/Constants.cpp @@ -0,0 +1,24 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. +// +//********************************************************* + +#include "pch.h" +#include "MainPage.xaml.h" +#include "Constants.h" + +using namespace SDKSample; + +Platform::Array^ MainPage::scenariosInner = ref new Platform::Array +{ + // The format here is the following: + // { "Description for the sample", "Fully quaified name for the class that implements the scenario" } + { "Video preview, record and take pictures", "SDKSample.MediaCapture.BasicCapture" }, + { "Enumerate cameras and add a video effect", "SDKSample.MediaCapture.AdvancedCapture" }, + { "Audio Capture", "SDKSample.MediaCapture.AudioCapture" } +}; diff --git a/samples/winrt/ImageManipulations/C++/Constants.h b/samples/winrt/ImageManipulations/C++/Constants.h new file mode 100644 index 0000000000..917f66487e --- /dev/null +++ b/samples/winrt/ImageManipulations/C++/Constants.h @@ -0,0 +1,45 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. +// +//********************************************************* + +#pragma once + +#include +namespace SDKSample +{ + public value struct Scenario + { + Platform::String^ Title; + Platform::String^ ClassName; + }; + + partial ref class MainPage + { + public: + static property Platform::String^ FEATURE_NAME + { + Platform::String^ get() + { + return ref new Platform::String(L"MediaCapture CPP sample"); + } + } + + static property Platform::Array^ scenarios + { + Platform::Array^ get() + { + return scenariosInner; + } + } + private: + static Platform::Array^ scenariosInner; + }; + + +} diff --git a/samples/winrt/ImageManipulations/C++/MainPage.xaml b/samples/winrt/ImageManipulations/C++/MainPage.xaml new file mode 100644 index 0000000000..d830e3cf06 --- /dev/null +++ b/samples/winrt/ImageManipulations/C++/MainPage.xaml @@ -0,0 +1,166 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 20,20,20,20 + + + + + + + + + diff --git a/samples/winrt/ImageManipulations/C++/MainPage.xaml.cpp b/samples/winrt/ImageManipulations/C++/MainPage.xaml.cpp new file mode 100644 index 0000000000..0702781910 --- /dev/null +++ b/samples/winrt/ImageManipulations/C++/MainPage.xaml.cpp @@ -0,0 +1,315 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. +// +//********************************************************* + +// +// MainPage.xaml.cpp +// Implementation of the MainPage.xaml class. +// + +#include "pch.h" +#include "MainPage.xaml.h" +#include "App.xaml.h" + +#include + +using namespace Windows::UI::Xaml; +using namespace Windows::UI::Xaml::Controls; +using namespace Windows::Foundation; +using namespace Windows::Foundation::Collections; +using namespace Platform; +using namespace SDKSample; +using namespace Windows::UI::Xaml::Navigation; +using namespace Windows::UI::Xaml::Interop; +using namespace Windows::Graphics::Display; +using namespace Windows::UI::ViewManagement; + +MainPage^ MainPage::Current = nullptr; + +MainPage::MainPage() +{ + InitializeComponent(); + + // This frame is hidden, meaning it is never shown. It is simply used to load + // each scenario page and then pluck out the input and output sections and + // place them into the UserControls on the main page. + HiddenFrame = ref new Windows::UI::Xaml::Controls::Frame(); + HiddenFrame->Visibility = Windows::UI::Xaml::Visibility::Collapsed; + ContentRoot->Children->Append(HiddenFrame); + + FeatureName->Text = FEATURE_NAME; + + this->SizeChanged += ref new SizeChangedEventHandler(this, &MainPage::MainPage_SizeChanged); + Scenarios->SelectionChanged += ref new SelectionChangedEventHandler(this, &MainPage::Scenarios_SelectionChanged); + + MainPage::Current = this; + autoSizeInputSectionWhenSnapped = true; +} + +/// +/// We need to handle SizeChanged so that we can make the sample layout property +/// in the various layouts. +/// +/// +/// +void MainPage::MainPage_SizeChanged(Object^ sender, SizeChangedEventArgs^ e) +{ + InvalidateSize(); + MainPageSizeChangedEventArgs^ args = ref new MainPageSizeChangedEventArgs(); + args->ViewState = ApplicationView::Value; + MainPageResized(this, args); + +} + +void MainPage::InvalidateSize() +{ + // Get the window width + double windowWidth = this->ActualWidth; + + if (windowWidth != 0.0) + { + // Get the width of the ListBox. + double listBoxWidth = Scenarios->ActualWidth; + + // Is the ListBox using any margins that we need to consider? + double listBoxMarginLeft = Scenarios->Margin.Left; + double listBoxMarginRight = Scenarios->Margin.Right; + + // Figure out how much room is left after considering the list box width + double availableWidth = windowWidth - listBoxWidth; + + // Is the top most child using margins? + double layoutRootMarginLeft = ContentRoot->Margin.Left; + double layoutRootMarginRight = ContentRoot->Margin.Right; + + // We have different widths to use depending on the view state + if (ApplicationView::Value != ApplicationViewState::Snapped) + { + // Make us as big as the the left over space, factoring in the ListBox width, the ListBox margins. + // and the LayoutRoot's margins + InputSection->Width = ((availableWidth) - + (layoutRootMarginLeft + layoutRootMarginRight + listBoxMarginLeft + listBoxMarginRight)); + } + else + { + // Make us as big as the left over space, factoring in just the LayoutRoot's margins. + if (autoSizeInputSectionWhenSnapped) + { + InputSection->Width = (windowWidth - (layoutRootMarginLeft + layoutRootMarginRight)); + } + } + } + InvalidateViewState(); +} + +void MainPage::InvalidateViewState() +{ + // Are we going to snapped mode? + if (ApplicationView::Value == ApplicationViewState::Snapped) + { + Grid::SetRow(DescriptionText, 3); + Grid::SetColumn(DescriptionText, 0); + + Grid::SetRow(InputSection, 4); + Grid::SetColumn(InputSection, 0); + + Grid::SetRow(FooterPanel, 2); + Grid::SetColumn(FooterPanel, 0); + } + else + { + Grid::SetRow(DescriptionText, 1); + Grid::SetColumn(DescriptionText, 1); + + Grid::SetRow(InputSection, 2); + Grid::SetColumn(InputSection, 1); + + Grid::SetRow(FooterPanel, 1); + Grid::SetColumn(FooterPanel, 1); + } + + // Since we don't load the scenario page in the traditional manner (we just pluck out the + // input and output sections from the page) we need to ensure that any VSM code used + // by the scenario's input and output sections is fired. + VisualStateManager::GoToState(InputSection, "Input" + LayoutAwarePage::DetermineVisualState(ApplicationView::Value), false); + VisualStateManager::GoToState(OutputSection, "Output" + LayoutAwarePage::DetermineVisualState(ApplicationView::Value), false); +} + +void MainPage::PopulateScenarios() +{ + ScenarioList = ref new Platform::Collections::Vector(); + + // Populate the ListBox with the list of scenarios as defined in Constants.cpp. + for (unsigned int i = 0; i < scenarios->Length; ++i) + { + Scenario s = scenarios[i]; + ListBoxItem^ item = ref new ListBoxItem(); + item->Name = s.ClassName; + item->Content = (i + 1).ToString() + ") " + s.Title; + ScenarioList->Append(item); + } + + // Bind the ListBox to the scenario list. + Scenarios->ItemsSource = ScenarioList; + Scenarios->ScrollIntoView(Scenarios->SelectedItem); +} + +/// +/// This method is responsible for loading the individual input and output sections for each scenario. This +/// is based on navigating a hidden Frame to the ScenarioX.xaml page and then extracting out the input +/// and output sections into the respective UserControl on the main page. +/// +/// +void MainPage::LoadScenario(String^ scenarioName) +{ + autoSizeInputSectionWhenSnapped = true; + + // Load the ScenarioX.xaml file into the Frame. + TypeName scenarioType = {scenarioName, TypeKind::Custom}; + HiddenFrame->Navigate(scenarioType, this); + + // Get the top element, the Page, so we can look up the elements + // that represent the input and output sections of the ScenarioX file. + Page^ hiddenPage = safe_cast(HiddenFrame->Content); + + // Get each element. + UIElement^ input = safe_cast(hiddenPage->FindName("Input")); + UIElement^ output = safe_cast(hiddenPage->FindName("Output")); + + if (input == nullptr) + { + // Malformed input section. + NotifyUser("Cannot load scenario input section for " + scenarioName + + " Make sure root of input section markup has x:Name of 'Input'", NotifyType::ErrorMessage); + return; + } + + if (output == nullptr) + { + // Malformed output section. + NotifyUser("Cannot load scenario output section for " + scenarioName + + " Make sure root of output section markup has x:Name of 'Output'", NotifyType::ErrorMessage); + return; + } + + // Find the LayoutRoot which parents the input and output sections in the main page. + Panel^ panel = safe_cast(hiddenPage->FindName("LayoutRoot")); + + if (panel != nullptr) + { + unsigned int index = 0; + UIElementCollection^ collection = panel->Children; + + // Get rid of the content that is currently in the intput and output sections. + collection->IndexOf(input, &index); + collection->RemoveAt(index); + + collection->IndexOf(output, &index); + collection->RemoveAt(index); + + // Populate the input and output sections with the newly loaded content. + InputSection->Content = input; + OutputSection->Content = output; + + ScenarioLoaded(this, nullptr); + } + else + { + // Malformed Scenario file. + NotifyUser("Cannot load scenario: " + scenarioName + ". Make sure root tag in the '" + + scenarioName + "' file has an x:Name of 'LayoutRoot'", NotifyType::ErrorMessage); + } +} + +void MainPage::Scenarios_SelectionChanged(Object^ sender, SelectionChangedEventArgs^ e) +{ + if (Scenarios->SelectedItem != nullptr) + { + NotifyUser("", NotifyType::StatusMessage); + + LoadScenario((safe_cast(Scenarios->SelectedItem))->Name); + InvalidateSize(); + } +} + +void MainPage::NotifyUser(String^ strMessage, NotifyType type) +{ + switch (type) + { + case NotifyType::StatusMessage: + // Use the status message style. + StatusBlock->Style = safe_cast(this->Resources->Lookup("StatusStyle")); + break; + case NotifyType::ErrorMessage: + // Use the error message style. + StatusBlock->Style = safe_cast(this->Resources->Lookup("ErrorStyle")); + break; + default: + break; + } + StatusBlock->Text = strMessage; + + // Collapsed the StatusBlock if it has no text to conserve real estate. + if (StatusBlock->Text != "") + { + StatusBlock->Visibility = Windows::UI::Xaml::Visibility::Visible; + } + else + { + StatusBlock->Visibility = Windows::UI::Xaml::Visibility::Collapsed; + } +} + +void MainPage::Footer_Click(Object^ sender, RoutedEventArgs^ e) +{ + auto uri = ref new Uri((String^)((HyperlinkButton^)sender)->Tag); + Windows::System::Launcher::LaunchUriAsync(uri); +} + + +/// +/// Populates the page with content passed during navigation. Any saved state is also +/// provided when recreating a page from a prior session. +/// +/// The parameter value passed to +/// when this page was initially requested. +/// +/// A map of state preserved by this page during an earlier +/// session. This will be null the first time a page is visited. +void MainPage::LoadState(Object^ navigationParameter, IMap^ pageState) +{ + (void) navigationParameter; // Unused parameter + + PopulateScenarios(); + + // Starting scenario is the first or based upon a previous state. + ListBoxItem^ startingScenario = nullptr; + int startingScenarioIndex = -1; + + if (pageState != nullptr && pageState->HasKey("SelectedScenarioIndex")) + { + startingScenarioIndex = safe_cast(pageState->Lookup("SelectedScenarioIndex")); + } + + Scenarios->SelectedIndex = startingScenarioIndex != -1 ? startingScenarioIndex : 0; + + InvalidateViewState(); +} + +/// +/// Preserves state associated with this page in case the application is suspended or the +/// page is discarded from the navigation cache. Values must conform to the serialization +/// requirements of . +/// +/// An empty map to be populated with serializable state. +void MainPage::SaveState(IMap^ pageState) +{ + int selectedListBoxItemIndex = Scenarios->SelectedIndex; + pageState->Insert("SelectedScenarioIndex", selectedListBoxItemIndex); +} diff --git a/samples/winrt/ImageManipulations/C++/MainPage.xaml.h b/samples/winrt/ImageManipulations/C++/MainPage.xaml.h new file mode 100644 index 0000000000..36fb7796a3 --- /dev/null +++ b/samples/winrt/ImageManipulations/C++/MainPage.xaml.h @@ -0,0 +1,105 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. +// +//********************************************************* + +// +// MainPage.xaml.h +// Declaration of the MainPage.xaml class. +// + +#pragma once + +#include "pch.h" +#include "MainPage.g.h" +#include "Common\LayoutAwarePage.h" // Required by generated header +#include "Constants.h" + +namespace SDKSample +{ + public enum class NotifyType + { + StatusMessage, + ErrorMessage + }; + + public ref class MainPageSizeChangedEventArgs sealed + { + public: + property Windows::UI::ViewManagement::ApplicationViewState ViewState + { + Windows::UI::ViewManagement::ApplicationViewState get() + { + return viewState; + } + + void set(Windows::UI::ViewManagement::ApplicationViewState value) + { + viewState = value; + } + } + + private: + Windows::UI::ViewManagement::ApplicationViewState viewState; + }; + + public ref class MainPage sealed + { + public: + MainPage(); + + protected: + virtual void LoadState(Platform::Object^ navigationParameter, + Windows::Foundation::Collections::IMap^ pageState) override; + virtual void SaveState(Windows::Foundation::Collections::IMap^ pageState) override; + + internal: + property bool AutoSizeInputSectionWhenSnapped + { + bool get() + { + return autoSizeInputSectionWhenSnapped; + } + + void set(bool value) + { + autoSizeInputSectionWhenSnapped = value; + } + } + + property Windows::ApplicationModel::Activation::LaunchActivatedEventArgs^ LaunchArgs + { + Windows::ApplicationModel::Activation::LaunchActivatedEventArgs^ get() + { + return safe_cast(App::Current)->LaunchArgs; + } + } + + void NotifyUser(Platform::String^ strMessage, NotifyType type); + void LoadScenario(Platform::String^ scenarioName); + event Windows::Foundation::EventHandler^ ScenarioLoaded; + event Windows::Foundation::EventHandler^ MainPageResized; + + private: + void PopulateScenarios(); + void InvalidateSize(); + void InvalidateViewState(); + + Platform::Collections::Vector^ ScenarioList; + Windows::UI::Xaml::Controls::Frame^ HiddenFrame; + void Footer_Click(Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e); + bool autoSizeInputSectionWhenSnapped; + + void MainPage_SizeChanged(Object^ sender, Windows::UI::Xaml::SizeChangedEventArgs^ e); + void Scenarios_SelectionChanged(Object^ sender, Windows::UI::Xaml::Controls::SelectionChangedEventArgs^ e); + + internal: + static MainPage^ Current; + + }; +} diff --git a/samples/winrt/ImageManipulations/C++/MediaCapture.sln b/samples/winrt/ImageManipulations/C++/MediaCapture.sln new file mode 100644 index 0000000000..7b99bce314 --- /dev/null +++ b/samples/winrt/ImageManipulations/C++/MediaCapture.sln @@ -0,0 +1,52 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 11 Express for Windows 8 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MediaCapture", "MediaCapture.vcxproj", "{C5B886A7-8300-46FF-B533-9613DE2AF637}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "GrayscaleTransform", "MediaExtensions\Grayscale\Grayscale.vcxproj", "{BA69218F-DA5C-4D14-A78D-21A9E4DEC669}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|ARM = Debug|ARM + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|ARM = Release|ARM + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {BA69218F-DA5C-4D14-A78D-21A9E4DEC669}.Debug|ARM.ActiveCfg = Debug|ARM + {BA69218F-DA5C-4D14-A78D-21A9E4DEC669}.Debug|ARM.Build.0 = Debug|ARM + {BA69218F-DA5C-4D14-A78D-21A9E4DEC669}.Debug|Win32.ActiveCfg = Debug|Win32 + {BA69218F-DA5C-4D14-A78D-21A9E4DEC669}.Debug|Win32.Build.0 = Debug|Win32 + {BA69218F-DA5C-4D14-A78D-21A9E4DEC669}.Debug|x64.ActiveCfg = Debug|x64 + {BA69218F-DA5C-4D14-A78D-21A9E4DEC669}.Debug|x64.Build.0 = Debug|x64 + {BA69218F-DA5C-4D14-A78D-21A9E4DEC669}.Release|ARM.ActiveCfg = Release|ARM + {BA69218F-DA5C-4D14-A78D-21A9E4DEC669}.Release|ARM.Build.0 = Release|ARM + {BA69218F-DA5C-4D14-A78D-21A9E4DEC669}.Release|Win32.ActiveCfg = Release|Win32 + {BA69218F-DA5C-4D14-A78D-21A9E4DEC669}.Release|Win32.Build.0 = Release|Win32 + {BA69218F-DA5C-4D14-A78D-21A9E4DEC669}.Release|x64.ActiveCfg = Release|x64 + {BA69218F-DA5C-4D14-A78D-21A9E4DEC669}.Release|x64.Build.0 = Release|x64 + {C5B886A7-8300-46FF-B533-9613DE2AF637}.Debug|ARM.ActiveCfg = Debug|ARM + {C5B886A7-8300-46FF-B533-9613DE2AF637}.Debug|ARM.Build.0 = Debug|ARM + {C5B886A7-8300-46FF-B533-9613DE2AF637}.Debug|ARM.Deploy.0 = Debug|ARM + {C5B886A7-8300-46FF-B533-9613DE2AF637}.Debug|Win32.ActiveCfg = Debug|Win32 + {C5B886A7-8300-46FF-B533-9613DE2AF637}.Debug|Win32.Build.0 = Debug|Win32 + {C5B886A7-8300-46FF-B533-9613DE2AF637}.Debug|Win32.Deploy.0 = Debug|Win32 + {C5B886A7-8300-46FF-B533-9613DE2AF637}.Debug|x64.ActiveCfg = Debug|x64 + {C5B886A7-8300-46FF-B533-9613DE2AF637}.Debug|x64.Build.0 = Debug|x64 + {C5B886A7-8300-46FF-B533-9613DE2AF637}.Debug|x64.Deploy.0 = Debug|x64 + {C5B886A7-8300-46FF-B533-9613DE2AF637}.Release|ARM.ActiveCfg = Release|ARM + {C5B886A7-8300-46FF-B533-9613DE2AF637}.Release|ARM.Build.0 = Release|ARM + {C5B886A7-8300-46FF-B533-9613DE2AF637}.Release|ARM.Deploy.0 = Release|ARM + {C5B886A7-8300-46FF-B533-9613DE2AF637}.Release|Win32.ActiveCfg = Release|Win32 + {C5B886A7-8300-46FF-B533-9613DE2AF637}.Release|Win32.Build.0 = Release|Win32 + {C5B886A7-8300-46FF-B533-9613DE2AF637}.Release|Win32.Deploy.0 = Release|Win32 + {C5B886A7-8300-46FF-B533-9613DE2AF637}.Release|x64.ActiveCfg = Release|x64 + {C5B886A7-8300-46FF-B533-9613DE2AF637}.Release|x64.Build.0 = Release|x64 + {C5B886A7-8300-46FF-B533-9613DE2AF637}.Release|x64.Deploy.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/samples/winrt/ImageManipulations/C++/MediaCapture.vcxproj b/samples/winrt/ImageManipulations/C++/MediaCapture.vcxproj new file mode 100644 index 0000000000..d2f255d1be --- /dev/null +++ b/samples/winrt/ImageManipulations/C++/MediaCapture.vcxproj @@ -0,0 +1,200 @@ + + + + + Debug + ARM + + + Debug + Win32 + + + Debug + x64 + + + Release + ARM + + + Release + Win32 + + + Release + x64 + + + + {C5B886A7-8300-46FF-B533-9613DE2AF637} + SDKSample + en-US + $(VCTargetsPath11) + 11.0 + true + MediaCapture + + + + Application + true + v110 + + + Application + true + v110 + + + Application + true + v110 + + + Application + false + true + v110 + + + Application + false + true + v110 + + + Application + false + true + v110 + + + + + + + + + + + + + + + + + + + + + + + + + + pch.h + + + + + AdvancedCapture.xaml + Code + + + AudioCapture.xaml + Code + + + BasicCapture.xaml + Code + + + + MainPage.xaml + + + + + + App.xaml + + + + + Designer + + + Designer + + + Designer + + + Designer + + + Designer + + + Designer + + + Designer + + + + + Designer + + + + + AdvancedCapture.xaml + Code + + + App.xaml + + + AudioCapture.xaml + Code + + + BasicCapture.xaml + Code + + + + + + MainPage.xaml + + + Create + Create + Create + Create + Create + Create + + + + + + + + + + + + + + + {ba69218f-da5c-4d14-a78d-21a9e4dec669} + + + + + + \ No newline at end of file diff --git a/samples/winrt/ImageManipulations/C++/MediaCapture.vcxproj.filters b/samples/winrt/ImageManipulations/C++/MediaCapture.vcxproj.filters new file mode 100644 index 0000000000..5f6124c2b3 --- /dev/null +++ b/samples/winrt/ImageManipulations/C++/MediaCapture.vcxproj.filters @@ -0,0 +1,88 @@ + + + + + Assets + + + Assets + + + Assets + + + Assets + + + Assets + + + Assets + + + Assets + + + Assets + + + + + + + + + + + + Common + + + Sample-Utils + + + + + + + + + + + Common + + + Common + + + + + + + + + + + + Common + + + Common + + + + + + + + + {132eec18-b164-4b15-a746-643880e9c5d9} + + + {476b4177-f316-4458-8e13-cab3dc2381c5} + + + {54f287f8-e4cb-4f47-97d0-4c469de6992e} + + + \ No newline at end of file diff --git a/samples/winrt/ImageManipulations/C++/MediaExtensions/Common/AsyncCB.h b/samples/winrt/ImageManipulations/C++/MediaExtensions/Common/AsyncCB.h new file mode 100644 index 0000000000..04ff69ed8a --- /dev/null +++ b/samples/winrt/ImageManipulations/C++/MediaExtensions/Common/AsyncCB.h @@ -0,0 +1,81 @@ +#pragma once + +////////////////////////////////////////////////////////////////////////// +// AsyncCallback [template] +// +// Description: +// Helper class that routes IMFAsyncCallback::Invoke calls to a class +// method on the parent class. +// +// Usage: +// Add this class as a member variable. In the parent class constructor, +// initialize the AsyncCallback class like this: +// m_cb(this, &CYourClass::OnInvoke) +// where +// m_cb = AsyncCallback object +// CYourClass = parent class +// OnInvoke = Method in the parent class to receive Invoke calls. +// +// The parent's OnInvoke method (you can name it anything you like) must +// have a signature that matches the InvokeFn typedef below. +////////////////////////////////////////////////////////////////////////// + +// T: Type of the parent object +template +class AsyncCallback : public IMFAsyncCallback +{ +public: + typedef HRESULT (T::*InvokeFn)(IMFAsyncResult *pAsyncResult); + + AsyncCallback(T *pParent, InvokeFn fn) : m_pParent(pParent), m_pInvokeFn(fn) + { + } + + // IUnknown + STDMETHODIMP_(ULONG) AddRef() { + // Delegate to parent class. + return m_pParent->AddRef(); + } + STDMETHODIMP_(ULONG) Release() { + // Delegate to parent class. + return m_pParent->Release(); + } + STDMETHODIMP QueryInterface(REFIID iid, void** ppv) + { + if (!ppv) + { + return E_POINTER; + } + if (iid == __uuidof(IUnknown)) + { + *ppv = static_cast(static_cast(this)); + } + else if (iid == __uuidof(IMFAsyncCallback)) + { + *ppv = static_cast(this); + } + else + { + *ppv = NULL; + return E_NOINTERFACE; + } + AddRef(); + return S_OK; + } + + + // IMFAsyncCallback methods + STDMETHODIMP GetParameters(DWORD*, DWORD*) + { + // Implementation of this method is optional. + return E_NOTIMPL; + } + + STDMETHODIMP Invoke(IMFAsyncResult* pAsyncResult) + { + return (m_pParent->*m_pInvokeFn)(pAsyncResult); + } + + T *m_pParent; + InvokeFn m_pInvokeFn; +}; diff --git a/samples/winrt/ImageManipulations/C++/MediaExtensions/Common/BufferLock.h b/samples/winrt/ImageManipulations/C++/MediaExtensions/Common/BufferLock.h new file mode 100644 index 0000000000..92de15eacc --- /dev/null +++ b/samples/winrt/ImageManipulations/C++/MediaExtensions/Common/BufferLock.h @@ -0,0 +1,102 @@ +// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A +// PARTICULAR PURPOSE. +// +// Copyright (c) Microsoft Corporation. All rights reserved + + +#pragma once + + +////////////////////////////////////////////////////////////////////////// +// VideoBufferLock +// +// Description: +// Locks a video buffer that might or might not support IMF2DBuffer. +// +////////////////////////////////////////////////////////////////////////// + +class VideoBufferLock +{ +public: + VideoBufferLock(IMFMediaBuffer *pBuffer) : m_p2DBuffer(NULL) + { + m_pBuffer = pBuffer; + m_pBuffer->AddRef(); + + // Query for the 2-D buffer interface. OK if this fails. + m_pBuffer->QueryInterface(IID_PPV_ARGS(&m_p2DBuffer)); + } + + ~VideoBufferLock() + { + UnlockBuffer(); + SafeRelease(&m_pBuffer); + SafeRelease(&m_p2DBuffer); + } + + // LockBuffer: + // Locks the buffer. Returns a pointer to scan line 0 and returns the stride. + + // The caller must provide the default stride as an input parameter, in case + // the buffer does not expose IMF2DBuffer. You can calculate the default stride + // from the media type. + + HRESULT LockBuffer( + LONG lDefaultStride, // Minimum stride (with no padding). + DWORD dwHeightInPixels, // Height of the image, in pixels. + BYTE **ppbScanLine0, // Receives a pointer to the start of scan line 0. + LONG *plStride // Receives the actual stride. + ) + { + HRESULT hr = S_OK; + + // Use the 2-D version if available. + if (m_p2DBuffer) + { + hr = m_p2DBuffer->Lock2D(ppbScanLine0, plStride); + } + else + { + // Use non-2D version. + BYTE *pData = NULL; + + hr = m_pBuffer->Lock(&pData, NULL, NULL); + if (SUCCEEDED(hr)) + { + *plStride = lDefaultStride; + if (lDefaultStride < 0) + { + // Bottom-up orientation. Return a pointer to the start of the + // last row *in memory* which is the top row of the image. + *ppbScanLine0 = pData + abs(lDefaultStride) * (dwHeightInPixels - 1); + } + else + { + // Top-down orientation. Return a pointer to the start of the + // buffer. + *ppbScanLine0 = pData; + } + } + } + return hr; + } + + HRESULT UnlockBuffer() + { + if (m_p2DBuffer) + { + return m_p2DBuffer->Unlock2D(); + } + else + { + return m_pBuffer->Unlock(); + } + } + +private: + IMFMediaBuffer *m_pBuffer; + IMF2DBuffer *m_p2DBuffer; +}; + diff --git a/samples/winrt/ImageManipulations/C++/MediaExtensions/Common/CritSec.h b/samples/winrt/ImageManipulations/C++/MediaExtensions/Common/CritSec.h new file mode 100644 index 0000000000..d5ea05bfd9 --- /dev/null +++ b/samples/winrt/ImageManipulations/C++/MediaExtensions/Common/CritSec.h @@ -0,0 +1,62 @@ +#pragma once + +////////////////////////////////////////////////////////////////////////// +// CritSec +// Description: Wraps a critical section. +////////////////////////////////////////////////////////////////////////// + +class CritSec +{ +public: + CRITICAL_SECTION m_criticalSection; +public: + CritSec() + { + InitializeCriticalSectionEx(&m_criticalSection, 100, 0); + } + + ~CritSec() + { + DeleteCriticalSection(&m_criticalSection); + } + + _Acquires_lock_(m_criticalSection) + void Lock() + { + EnterCriticalSection(&m_criticalSection); + } + + _Releases_lock_(m_criticalSection) + void Unlock() + { + LeaveCriticalSection(&m_criticalSection); + } +}; + + +////////////////////////////////////////////////////////////////////////// +// AutoLock +// Description: Provides automatic locking and unlocking of a +// of a critical section. +// +// Note: The AutoLock object must go out of scope before the CritSec. +////////////////////////////////////////////////////////////////////////// + +class AutoLock +{ +private: + CritSec *m_pCriticalSection; +public: + _Acquires_lock_(m_pCriticalSection) + AutoLock(CritSec& crit) + { + m_pCriticalSection = &crit; + m_pCriticalSection->Lock(); + } + + _Releases_lock_(m_pCriticalSection) + ~AutoLock() + { + m_pCriticalSection->Unlock(); + } +}; diff --git a/samples/winrt/ImageManipulations/C++/MediaExtensions/Common/LinkList.h b/samples/winrt/ImageManipulations/C++/MediaExtensions/Common/LinkList.h new file mode 100644 index 0000000000..c67c0f2ca9 --- /dev/null +++ b/samples/winrt/ImageManipulations/C++/MediaExtensions/Common/LinkList.h @@ -0,0 +1,516 @@ +//----------------------------------------------------------------------------- +// File: Linklist.h +// Desc: Linked list class. +// +// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A +// PARTICULAR PURPOSE. +// +// Copyright (C) Microsoft Corporation. All rights reserved. +//----------------------------------------------------------------------------- + +#pragma once + +// Notes: +// +// The List class template implements a simple double-linked list. +// It uses STL's copy semantics. + +// There are two versions of the Clear() method: +// Clear(void) clears the list w/out cleaning up the object. +// Clear(FN fn) takes a functor object that releases the objects, if they need cleanup. + +// The List class supports enumeration. Example of usage: +// +// List::POSIITON pos = list.GetFrontPosition(); +// while (pos != list.GetEndPosition()) +// { +// T item; +// hr = list.GetItemPos(&item); +// pos = list.Next(pos); +// } + +// The ComPtrList class template derives from List<> and implements a list of COM pointers. + +template +struct NoOp +{ + void operator()(T& t) + { + } +}; + +template +class List +{ +protected: + + // Nodes in the linked list + struct Node + { + Node *prev; + Node *next; + T item; + + Node() : prev(nullptr), next(nullptr) + { + } + + Node(T item) : prev(nullptr), next(nullptr) + { + this->item = item; + } + + T Item() const { return item; } + }; + +public: + + // Object for enumerating the list. + class POSITION + { + friend class List; + + public: + POSITION() : pNode(nullptr) + { + } + + bool operator==(const POSITION &p) const + { + return pNode == p.pNode; + } + + bool operator!=(const POSITION &p) const + { + return pNode != p.pNode; + } + + private: + const Node *pNode; + + POSITION(Node *p) : pNode(p) + { + } + }; + +protected: + Node m_anchor; // Anchor node for the linked list. + DWORD m_count; // Number of items in the list. + + Node* Front() const + { + return m_anchor.next; + } + + Node* Back() const + { + return m_anchor.prev; + } + + virtual HRESULT InsertAfter(T item, Node *pBefore) + { + if (pBefore == nullptr) + { + return E_POINTER; + } + + Node *pNode = new Node(item); + if (pNode == nullptr) + { + return E_OUTOFMEMORY; + } + + Node *pAfter = pBefore->next; + + pBefore->next = pNode; + pAfter->prev = pNode; + + pNode->prev = pBefore; + pNode->next = pAfter; + + m_count++; + + return S_OK; + } + + virtual HRESULT GetItem(const Node *pNode, T* ppItem) + { + if (pNode == nullptr || ppItem == nullptr) + { + return E_POINTER; + } + + *ppItem = pNode->item; + return S_OK; + } + + // RemoveItem: + // Removes a node and optionally returns the item. + // ppItem can be nullptr. + virtual HRESULT RemoveItem(Node *pNode, T *ppItem) + { + if (pNode == nullptr) + { + return E_POINTER; + } + + assert(pNode != &m_anchor); // We should never try to remove the anchor node. + if (pNode == &m_anchor) + { + return E_INVALIDARG; + } + + + T item; + + // The next node's previous is this node's previous. + pNode->next->prev = pNode->prev; + + // The previous node's next is this node's next. + pNode->prev->next = pNode->next; + + item = pNode->item; + delete pNode; + + m_count--; + + if (ppItem) + { + *ppItem = item; + } + + return S_OK; + } + +public: + + List() + { + m_anchor.next = &m_anchor; + m_anchor.prev = &m_anchor; + + m_count = 0; + } + + virtual ~List() + { + Clear(); + } + + // Insertion functions + HRESULT InsertBack(T item) + { + return InsertAfter(item, m_anchor.prev); + } + + + HRESULT InsertFront(T item) + { + return InsertAfter(item, &m_anchor); + } + + HRESULT InsertPos(POSITION pos, T item) + { + if (pos.pNode == nullptr) + { + return InsertBack(item); + } + + return InsertAfter(item, pos.pNode->prev); + } + + // RemoveBack: Removes the tail of the list and returns the value. + // ppItem can be nullptr if you don't want the item back. (But the method does not release the item.) + HRESULT RemoveBack(T *ppItem) + { + if (IsEmpty()) + { + return E_FAIL; + } + else + { + return RemoveItem(Back(), ppItem); + } + } + + // RemoveFront: Removes the head of the list and returns the value. + // ppItem can be nullptr if you don't want the item back. (But the method does not release the item.) + HRESULT RemoveFront(T *ppItem) + { + if (IsEmpty()) + { + return E_FAIL; + } + else + { + return RemoveItem(Front(), ppItem); + } + } + + // GetBack: Gets the tail item. + HRESULT GetBack(T *ppItem) + { + if (IsEmpty()) + { + return E_FAIL; + } + else + { + return GetItem(Back(), ppItem); + } + } + + // GetFront: Gets the front item. + HRESULT GetFront(T *ppItem) + { + if (IsEmpty()) + { + return E_FAIL; + } + else + { + return GetItem(Front(), ppItem); + } + } + + + // GetCount: Returns the number of items in the list. + DWORD GetCount() const { return m_count; } + + bool IsEmpty() const + { + return (GetCount() == 0); + } + + // Clear: Takes a functor object whose operator() + // frees the object on the list. + template + void Clear(FN& clear_fn) + { + Node *n = m_anchor.next; + + // Delete the nodes + while (n != &m_anchor) + { + clear_fn(n->item); + + Node *tmp = n->next; + delete n; + n = tmp; + } + + // Reset the anchor to point at itself + m_anchor.next = &m_anchor; + m_anchor.prev = &m_anchor; + + m_count = 0; + } + + // Clear: Clears the list. (Does not delete or release the list items.) + virtual void Clear() + { + NoOp clearOp; + Clear<>(clearOp); + } + + + // Enumerator functions + + POSITION FrontPosition() + { + if (IsEmpty()) + { + return POSITION(nullptr); + } + else + { + return POSITION(Front()); + } + } + + POSITION EndPosition() const + { + return POSITION(); + } + + HRESULT GetItemPos(POSITION pos, T *ppItem) + { + if (pos.pNode) + { + return GetItem(pos.pNode, ppItem); + } + else + { + return E_FAIL; + } + } + + POSITION Next(const POSITION pos) + { + if (pos.pNode && (pos.pNode->next != &m_anchor)) + { + return POSITION(pos.pNode->next); + } + else + { + return POSITION(nullptr); + } + } + + // Remove an item at a position. + // The item is returns in ppItem, unless ppItem is nullptr. + // NOTE: This method invalidates the POSITION object. + HRESULT Remove(POSITION& pos, T *ppItem) + { + if (pos.pNode) + { + // Remove const-ness temporarily... + Node *pNode = const_cast(pos.pNode); + + pos = POSITION(); + + return RemoveItem(pNode, ppItem); + } + else + { + return E_INVALIDARG; + } + } + +}; + + + +// Typical functors for Clear method. + +// ComAutoRelease: Releases COM pointers. +// MemDelete: Deletes pointers to new'd memory. + +class ComAutoRelease +{ +public: + void operator()(IUnknown *p) + { + if (p) + { + p->Release(); + } + } +}; + +class MemDelete +{ +public: + void operator()(void *p) + { + if (p) + { + delete p; + } + } +}; + + +// ComPtrList class +// Derived class that makes it safer to store COM pointers in the List<> class. +// It automatically AddRef's the pointers that are inserted onto the list +// (unless the insertion method fails). +// +// T must be a COM interface type. +// example: ComPtrList +// +// NULLABLE: If true, client can insert nullptr pointers. This means GetItem can +// succeed but return a nullptr pointer. By default, the list does not allow nullptr +// pointers. + +template +class ComPtrList : public List +{ +public: + + typedef T* Ptr; + + void Clear() + { + ComAutoRelease car; + List::Clear(car); + } + + ~ComPtrList() + { + Clear(); + } + +protected: + HRESULT InsertAfter(Ptr item, Node *pBefore) + { + // Do not allow nullptr item pointers unless NULLABLE is true. + if (item == nullptr && !NULLABLE) + { + return E_POINTER; + } + + if (item) + { + item->AddRef(); + } + + HRESULT hr = List::InsertAfter(item, pBefore); + if (FAILED(hr) && item != nullptr) + { + item->Release(); + } + return hr; + } + + HRESULT GetItem(const Node *pNode, Ptr* ppItem) + { + Ptr pItem = nullptr; + + // The base class gives us the pointer without AddRef'ing it. + // If we return the pointer to the caller, we must AddRef(). + HRESULT hr = List::GetItem(pNode, &pItem); + if (SUCCEEDED(hr)) + { + assert(pItem || NULLABLE); + if (pItem) + { + *ppItem = pItem; + (*ppItem)->AddRef(); + } + } + return hr; + } + + HRESULT RemoveItem(Node *pNode, Ptr *ppItem) + { + // ppItem can be nullptr, but we need to get the + // item so that we can release it. + + // If ppItem is not nullptr, we will AddRef it on the way out. + + Ptr pItem = nullptr; + + HRESULT hr = List::RemoveItem(pNode, &pItem); + + if (SUCCEEDED(hr)) + { + assert(pItem || NULLABLE); + if (ppItem && pItem) + { + *ppItem = pItem; + (*ppItem)->AddRef(); + } + + if (pItem) + { + pItem->Release(); + pItem = nullptr; + } + } + + return hr; + } +}; diff --git a/samples/winrt/ImageManipulations/C++/MediaExtensions/Common/OpQueue.h b/samples/winrt/ImageManipulations/C++/MediaExtensions/Common/OpQueue.h new file mode 100644 index 0000000000..dd0813be30 --- /dev/null +++ b/samples/winrt/ImageManipulations/C++/MediaExtensions/Common/OpQueue.h @@ -0,0 +1,222 @@ +////////////////////////////////////////////////////////////////////////// +// +// OpQueue.h +// Async operation queue. +// +// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A +// PARTICULAR PURPOSE. +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +////////////////////////////////////////////////////////////////////////// + +#pragma once + +#pragma warning( push ) +#pragma warning( disable : 4355 ) // 'this' used in base member initializer list + +/* + This header file defines an object to help queue and serialize + asynchronous operations. + + Background: + + To perform an operation asynchronously in Media Foundation, an object + does one of the following: + + 1. Calls MFPutWorkItem(Ex), using either a standard work queue + identifier or a caller-allocated work queue. The work-queue + thread invokes the object's callback. + + 2. Creates an async result object (IMFAsyncResult) and calls + MFInvokeCallback to invoke the object's callback. + + Ultimately, either of these cause the object's callback to be invoked + from a work-queue thread. The object can then complete the operation + inside the callback. + + However, the Media Foundation platform may dispatch async callbacks in + parallel on several threads. Putting an item on a work queue does NOT + guarantee that one operation will complete before the next one starts, + or even that work items will be dispatched in the same order they were + called. + + To serialize async operations that should not overlap, an object should + use a queue. While one operation is pending, subsequent operations are + put on the queue, and only dispatched after the previous operation is + complete. + + The granularity of a single "operation" depends on the requirements of + that particular object. A single operation might involve several + asynchronous calls before the object dispatches the next operation on + the queue. + + +*/ + + + +//------------------------------------------------------------------- +// OpQueue class template +// +// Base class for an async operation queue. +// +// TOperation: The class used to describe operations. This class must +// implement IUnknown. +// +// The OpQueue class is an abstract class. The derived class must +// implement the following pure-virtual methods: +// +// - IUnknown methods (AddRef, Release, QI) +// +// - DispatchOperation: +// +// Performs the asynchronous operation specified by pOp. +// +// At the end of each operation, the derived class must call +// ProcessQueue to process the next operation in the queue. +// +// NOTE: An operation is not required to complete inside the +// DispatchOperation method. A single operation might consist +// of several asynchronous method calls. +// +// - ValidateOperation: +// +// Checks whether the object can perform the operation specified +// by pOp at this time. +// +// If the object cannot perform the operation now (e.g., because +// another operation is still in progress) the method should +// return MF_E_NOTACCEPTING. +// +//------------------------------------------------------------------- +#include "linklist.h" +#include "AsyncCB.h" + +template +class OpQueue //: public IUnknown +{ +public: + + typedef ComPtrList OpList; + + HRESULT QueueOperation(TOperation *pOp); + +protected: + + HRESULT ProcessQueue(); + HRESULT ProcessQueueAsync(IMFAsyncResult *pResult); + + virtual HRESULT DispatchOperation(TOperation *pOp) = 0; + virtual HRESULT ValidateOperation(TOperation *pOp) = 0; + + OpQueue(CRITICAL_SECTION& critsec) + : m_OnProcessQueue(static_cast(this), &OpQueue::ProcessQueueAsync), + m_critsec(critsec) + { + } + + virtual ~OpQueue() + { + } + +protected: + OpList m_OpQueue; // Queue of operations. + CRITICAL_SECTION& m_critsec; // Protects the queue state. + AsyncCallback m_OnProcessQueue; // ProcessQueueAsync callback. +}; + + + +//------------------------------------------------------------------- +// Place an operation on the queue. +// Public method. +//------------------------------------------------------------------- + +template +HRESULT OpQueue::QueueOperation(TOperation *pOp) +{ + HRESULT hr = S_OK; + + EnterCriticalSection(&m_critsec); + + hr = m_OpQueue.InsertBack(pOp); + if (SUCCEEDED(hr)) + { + hr = ProcessQueue(); + } + + LeaveCriticalSection(&m_critsec); + return hr; +} + + +//------------------------------------------------------------------- +// Process the next operation on the queue. +// Protected method. +// +// Note: This method dispatches the operation to a work queue. +//------------------------------------------------------------------- + +template +HRESULT OpQueue::ProcessQueue() +{ + HRESULT hr = S_OK; + if (m_OpQueue.GetCount() > 0) + { + hr = MFPutWorkItem2( + MFASYNC_CALLBACK_QUEUE_STANDARD, // Use the standard work queue. + 0, // Default priority + &m_OnProcessQueue, // Callback method. + nullptr // State object. + ); + } + return hr; +} + + +//------------------------------------------------------------------- +// Process the next operation on the queue. +// Protected method. +// +// Note: This method is called from a work-queue thread. +//------------------------------------------------------------------- + +template +HRESULT OpQueue::ProcessQueueAsync(IMFAsyncResult *pResult) +{ + HRESULT hr = S_OK; + TOperation *pOp = nullptr; + + EnterCriticalSection(&m_critsec); + + if (m_OpQueue.GetCount() > 0) + { + hr = m_OpQueue.GetFront(&pOp); + + if (SUCCEEDED(hr)) + { + hr = ValidateOperation(pOp); + } + if (SUCCEEDED(hr)) + { + hr = m_OpQueue.RemoveFront(nullptr); + } + if (SUCCEEDED(hr)) + { + (void)DispatchOperation(pOp); + } + } + + if (pOp != nullptr) + { + pOp->Release(); + } + + LeaveCriticalSection(&m_critsec); + return hr; +} + +#pragma warning( pop ) \ No newline at end of file diff --git a/samples/winrt/ImageManipulations/C++/MediaExtensions/Grayscale/Grayscale.cpp b/samples/winrt/ImageManipulations/C++/MediaExtensions/Grayscale/Grayscale.cpp new file mode 100644 index 0000000000..687386ecee --- /dev/null +++ b/samples/winrt/ImageManipulations/C++/MediaExtensions/Grayscale/Grayscale.cpp @@ -0,0 +1,1783 @@ +// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A +// PARTICULAR PURPOSE. +// +// Copyright (c) Microsoft Corporation. All rights reserved. + +#include "Grayscale.h" +#include "bufferlock.h" + +#pragma comment(lib, "d2d1") + +using namespace Microsoft::WRL; + +/* + +This sample implements a video effect as a Media Foundation transform (MFT). + +The video effect manipulates chroma values in a YUV image. In the default setting, +the entire image is converted to grayscale. Optionally, the application may set any +of the following attributes: + +MFT_GRAYSCALE_DESTINATION_RECT (type = blob, UINT32[4] array) + + Sets the destination rectangle for the effect. Pixels outside the destination + rectangle are not altered. + +MFT_GRAYSCALE_SATURATION (type = double) + + Sets the saturation level. The nominal range is [0...1]. Values beyond 1.0f + result in supersaturated colors. Values below 0.0f create inverted colors. + +MFT_GRAYSCALE_CHROMA_ROTATION (type = double) + + Rotates the chroma values of each pixel. The attribue value is the angle of + rotation in degrees. The result is a shift in hue. + +The effect is implemented by treating the chroma value of each pixel as a vector [u,v], +and applying a transformation matrix to the vector. The saturation parameter is applied +as a scaling transform. + + +NOTES ON THE MFT IMPLEMENTATION + +1. The MFT has fixed streams: One input stream and one output stream. + +2. The MFT supports the following formats: UYVY, YUY2, NV12. + +3. If the MFT is holding an input sample, SetInputType and SetOutputType both fail. + +4. The input and output types must be identical. + +5. If both types are set, no type can be set until the current type is cleared. + +6. Preferred input types: + + (a) If the output type is set, that's the preferred type. + (b) Otherwise, the preferred types are partial types, constructed from the + list of supported subtypes. + +7. Preferred output types: As above. + +8. Streaming: + + The private BeingStreaming() method is called in response to the + MFT_MESSAGE_NOTIFY_BEGIN_STREAMING message. + + If the client does not send MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, the MFT calls + BeginStreaming inside the first call to ProcessInput or ProcessOutput. + + This is a good approach for allocating resources that your MFT requires for + streaming. + +9. The configuration attributes are applied in the BeginStreaming method. If the + client changes the attributes during streaming, the change is ignored until + streaming is stopped (either by changing the media types or by sending the + MFT_MESSAGE_NOTIFY_END_STREAMING message) and then restarted. + +*/ + + +// Video FOURCC codes. +const DWORD FOURCC_YUY2 = '2YUY'; +const DWORD FOURCC_UYVY = 'YVYU'; +const DWORD FOURCC_NV12 = '21VN'; + +// Static array of media types (preferred and accepted). +const GUID g_MediaSubtypes[] = +{ + MFVideoFormat_NV12, + MFVideoFormat_YUY2, + MFVideoFormat_UYVY +}; + +HRESULT GetImageSize(DWORD fcc, UINT32 width, UINT32 height, DWORD* pcbImage); +HRESULT GetDefaultStride(IMFMediaType *pType, LONG *plStride); +bool ValidateRect(const RECT& rc); + +template +inline T clamp(const T& val, const T& minVal, const T& maxVal) +{ + return (val < minVal ? minVal : (val > maxVal ? maxVal : val)); +} + + +// TransformChroma: +// Apply the transforms to calculate the output chroma values. + +void TransformChroma(const D2D1::Matrix3x2F& mat, BYTE *pu, BYTE *pv) +{ + // Normalize the chroma values to [-112, 112] range + + D2D1_POINT_2F pt = { static_cast(*pu) - 128, static_cast(*pv) - 128 }; + + pt = mat.TransformPoint(pt); + + // Clamp to valid range. + clamp(pt.x, -112.0f, 112.0f); + clamp(pt.y, -112.0f, 112.0f); + + // Map back to [16...240] range. + *pu = static_cast(pt.x + 128.0f); + *pv = static_cast(pt.y + 128.0f); +} + +//------------------------------------------------------------------- +// Functions to convert a YUV images to grayscale. +// +// In all cases, the same transformation is applied to the 8-bit +// chroma values, but the pixel layout in memory differs. +// +// The image conversion functions take the following parameters: +// +// mat Transfomation matrix for chroma values. +// rcDest Destination rectangle. +// pDest Pointer to the destination buffer. +// lDestStride Stride of the destination buffer, in bytes. +// pSrc Pointer to the source buffer. +// lSrcStride Stride of the source buffer, in bytes. +// dwWidthInPixels Frame width in pixels. +// dwHeightInPixels Frame height, in pixels. +//------------------------------------------------------------------- + +// Convert UYVY image. + +void TransformImage_UYVY( + const D2D1::Matrix3x2F& mat, + const D2D_RECT_U& rcDest, + _Inout_updates_(_Inexpressible_(lDestStride * dwHeightInPixels)) BYTE *pDest, + _In_ LONG lDestStride, + _In_reads_(_Inexpressible_(lSrcStride * dwHeightInPixels)) const BYTE* pSrc, + _In_ LONG lSrcStride, + _In_ DWORD dwWidthInPixels, + _In_ DWORD dwHeightInPixels) +{ + DWORD y = 0; + const DWORD y0 = min(rcDest.bottom, dwHeightInPixels); + + // Lines above the destination rectangle. + for ( ; y < rcDest.top; y++) + { + memcpy(pDest, pSrc, dwWidthInPixels * 2); + pSrc += lSrcStride; + pDest += lDestStride; + } + + // Lines within the destination rectangle. + for ( ; y < y0; y++) + { + WORD *pSrc_Pixel = (WORD*)pSrc; + WORD *pDest_Pixel = (WORD*)pDest; + + for (DWORD x = 0; (x + 1) < dwWidthInPixels; x += 2) + { + // Byte order is U0 Y0 V0 Y1 + // Each WORD is a byte pair (U/V, Y) + // Windows is little-endian so the order appears reversed. + + if (x >= rcDest.left && x < rcDest.right) + { + BYTE u = pSrc_Pixel[x] & 0x00FF; + BYTE v = pSrc_Pixel[x+1] & 0x00FF; + + TransformChroma(mat, &u, &v); + + pDest_Pixel[x] = (pSrc_Pixel[x] & 0xFF00) | u; + pDest_Pixel[x+1] = (pSrc_Pixel[x+1] & 0xFF00) | v; + } + else + { +#pragma warning(push) +#pragma warning(disable: 6385) +#pragma warning(disable: 6386) + pDest_Pixel[x] = pSrc_Pixel[x]; + pDest_Pixel[x+1] = pSrc_Pixel[x+1]; +#pragma warning(pop) + } + } + + pDest += lDestStride; + pSrc += lSrcStride; + } + + // Lines below the destination rectangle. + for ( ; y < dwHeightInPixels; y++) + { + memcpy(pDest, pSrc, dwWidthInPixels * 2); + pSrc += lSrcStride; + pDest += lDestStride; + } +} + + +// Convert YUY2 image. + +void TransformImage_YUY2( + const D2D1::Matrix3x2F& mat, + const D2D_RECT_U& rcDest, + _Inout_updates_(_Inexpressible_(lDestStride * dwHeightInPixels)) BYTE *pDest, + _In_ LONG lDestStride, + _In_reads_(_Inexpressible_(lSrcStride * dwHeightInPixels)) const BYTE* pSrc, + _In_ LONG lSrcStride, + _In_ DWORD dwWidthInPixels, + _In_ DWORD dwHeightInPixels) +{ + DWORD y = 0; + const DWORD y0 = min(rcDest.bottom, dwHeightInPixels); + + // Lines above the destination rectangle. + for ( ; y < rcDest.top; y++) + { + memcpy(pDest, pSrc, dwWidthInPixels * 2); + pSrc += lSrcStride; + pDest += lDestStride; + } + + // Lines within the destination rectangle. + for ( ; y < y0; y++) + { + WORD *pSrc_Pixel = (WORD*)pSrc; + WORD *pDest_Pixel = (WORD*)pDest; + + for (DWORD x = 0; (x + 1) < dwWidthInPixels; x += 2) + { + // Byte order is Y0 U0 Y1 V0 + // Each WORD is a byte pair (Y, U/V) + // Windows is little-endian so the order appears reversed. + + if (x >= rcDest.left && x < rcDest.right) + { + BYTE u = pSrc_Pixel[x] >> 8; + BYTE v = pSrc_Pixel[x+1] >> 8; + + TransformChroma(mat, &u, &v); + + pDest_Pixel[x] = (pSrc_Pixel[x] & 0x00FF) | (u<<8); + pDest_Pixel[x+1] = (pSrc_Pixel[x+1] & 0x00FF) | (v<<8); + } + else + { +#pragma warning(push) +#pragma warning(disable: 6385) +#pragma warning(disable: 6386) + pDest_Pixel[x] = pSrc_Pixel[x]; + pDest_Pixel[x+1] = pSrc_Pixel[x+1]; +#pragma warning(pop) + } + } + pDest += lDestStride; + pSrc += lSrcStride; + } + + // Lines below the destination rectangle. + for ( ; y < dwHeightInPixels; y++) + { + memcpy(pDest, pSrc, dwWidthInPixels * 2); + pSrc += lSrcStride; + pDest += lDestStride; + } +} + +// Convert NV12 image + +void TransformImage_NV12( + const D2D1::Matrix3x2F& mat, + const D2D_RECT_U& rcDest, + _Inout_updates_(_Inexpressible_(2 * lDestStride * dwHeightInPixels)) BYTE *pDest, + _In_ LONG lDestStride, + _In_reads_(_Inexpressible_(2 * lSrcStride * dwHeightInPixels)) const BYTE* pSrc, + _In_ LONG lSrcStride, + _In_ DWORD dwWidthInPixels, + _In_ DWORD dwHeightInPixels) +{ + // NV12 is planar: Y plane, followed by packed U-V plane. + + // Y plane + for (DWORD y = 0; y < dwHeightInPixels; y++) + { + CopyMemory(pDest, pSrc, dwWidthInPixels); + pDest += lDestStride; + pSrc += lSrcStride; + } + + // U-V plane + + // NOTE: The U-V plane has 1/2 the number of lines as the Y plane. + + // Lines above the destination rectangle. + DWORD y = 0; + const DWORD y0 = min(rcDest.bottom, dwHeightInPixels); + + for ( ; y < rcDest.top/2; y++) + { + memcpy(pDest, pSrc, dwWidthInPixels); + pSrc += lSrcStride; + pDest += lDestStride; + } + + // Lines within the destination rectangle. + for ( ; y < y0/2; y++) + { + for (DWORD x = 0; (x + 1) < dwWidthInPixels; x += 2) + { + if (x >= rcDest.left && x < rcDest.right) + { + BYTE u = pSrc[x]; + BYTE v = pSrc[x+1]; + + TransformChroma(mat, &u, &v); + + pDest[x] = u; + pDest[x+1] = v; + } + else + { + pDest[x] = pSrc[x]; + pDest[x+1] = pSrc[x+1]; + } + } + pDest += lDestStride; + pSrc += lSrcStride; + } + + // Lines below the destination rectangle. + for ( ; y < dwHeightInPixels/2; y++) + { + memcpy(pDest, pSrc, dwWidthInPixels); + pSrc += lSrcStride; + pDest += lDestStride; + } +} + +CGrayscale::CGrayscale() : + m_pSample(NULL), m_pInputType(NULL), m_pOutputType(NULL), m_pTransformFn(NULL), + m_imageWidthInPixels(0), m_imageHeightInPixels(0), m_cbImageSize(0), + m_transform(D2D1::Matrix3x2F::Identity()), m_rcDest(D2D1::RectU()), m_bStreamingInitialized(false), + m_pAttributes(NULL) +{ + InitializeCriticalSectionEx(&m_critSec, 3000, 0); +} + +CGrayscale::~CGrayscale() +{ + SafeRelease(&m_pInputType); + SafeRelease(&m_pOutputType); + SafeRelease(&m_pSample); + SafeRelease(&m_pAttributes); + DeleteCriticalSection(&m_critSec); +} + +// Initialize the instance. +STDMETHODIMP CGrayscale::RuntimeClassInitialize() +{ + // Create the attribute store. + return MFCreateAttributes(&m_pAttributes, 3); +} + +// IMediaExtension methods + +//------------------------------------------------------------------- +// SetProperties +// Sets the configuration of the effect +//------------------------------------------------------------------- +HRESULT CGrayscale::SetProperties(ABI::Windows::Foundation::Collections::IPropertySet *pConfiguration) +{ + return S_OK; +} + +// IMFTransform methods. Refer to the Media Foundation SDK documentation for details. + +//------------------------------------------------------------------- +// GetStreamLimits +// Returns the minimum and maximum number of streams. +//------------------------------------------------------------------- + +HRESULT CGrayscale::GetStreamLimits( + DWORD *pdwInputMinimum, + DWORD *pdwInputMaximum, + DWORD *pdwOutputMinimum, + DWORD *pdwOutputMaximum +) +{ + if ((pdwInputMinimum == NULL) || + (pdwInputMaximum == NULL) || + (pdwOutputMinimum == NULL) || + (pdwOutputMaximum == NULL)) + { + return E_POINTER; + } + + // This MFT has a fixed number of streams. + *pdwInputMinimum = 1; + *pdwInputMaximum = 1; + *pdwOutputMinimum = 1; + *pdwOutputMaximum = 1; + return S_OK; +} + + +//------------------------------------------------------------------- +// GetStreamCount +// Returns the actual number of streams. +//------------------------------------------------------------------- + +HRESULT CGrayscale::GetStreamCount( + DWORD *pcInputStreams, + DWORD *pcOutputStreams +) +{ + if ((pcInputStreams == NULL) || (pcOutputStreams == NULL)) + + { + return E_POINTER; + } + + // This MFT has a fixed number of streams. + *pcInputStreams = 1; + *pcOutputStreams = 1; + return S_OK; +} + + + +//------------------------------------------------------------------- +// GetStreamIDs +// Returns stream IDs for the input and output streams. +//------------------------------------------------------------------- + +HRESULT CGrayscale::GetStreamIDs( + DWORD dwInputIDArraySize, + DWORD *pdwInputIDs, + DWORD dwOutputIDArraySize, + DWORD *pdwOutputIDs +) +{ + // It is not required to implement this method if the MFT has a fixed number of + // streams AND the stream IDs are numbered sequentially from zero (that is, the + // stream IDs match the stream indexes). + + // In that case, it is OK to return E_NOTIMPL. + return E_NOTIMPL; +} + + +//------------------------------------------------------------------- +// GetInputStreamInfo +// Returns information about an input stream. +//------------------------------------------------------------------- + +HRESULT CGrayscale::GetInputStreamInfo( + DWORD dwInputStreamID, + MFT_INPUT_STREAM_INFO * pStreamInfo +) +{ + if (pStreamInfo == NULL) + { + return E_POINTER; + } + + EnterCriticalSection(&m_critSec); + + if (!IsValidInputStream(dwInputStreamID)) + { + LeaveCriticalSection(&m_critSec); + return MF_E_INVALIDSTREAMNUMBER; + } + + // NOTE: This method should succeed even when there is no media type on the + // stream. If there is no media type, we only need to fill in the dwFlags + // member of MFT_INPUT_STREAM_INFO. The other members depend on having a + // a valid media type. + + pStreamInfo->hnsMaxLatency = 0; + pStreamInfo->dwFlags = MFT_INPUT_STREAM_WHOLE_SAMPLES | MFT_INPUT_STREAM_SINGLE_SAMPLE_PER_BUFFER; + + if (m_pInputType == NULL) + { + pStreamInfo->cbSize = 0; + } + else + { + pStreamInfo->cbSize = m_cbImageSize; + } + + pStreamInfo->cbMaxLookahead = 0; + pStreamInfo->cbAlignment = 0; + + LeaveCriticalSection(&m_critSec); + return S_OK; +} + +//------------------------------------------------------------------- +// GetOutputStreamInfo +// Returns information about an output stream. +//------------------------------------------------------------------- + +HRESULT CGrayscale::GetOutputStreamInfo( + DWORD dwOutputStreamID, + MFT_OUTPUT_STREAM_INFO * pStreamInfo +) +{ + if (pStreamInfo == NULL) + { + return E_POINTER; + } + + EnterCriticalSection(&m_critSec); + + if (!IsValidOutputStream(dwOutputStreamID)) + { + LeaveCriticalSection(&m_critSec); + return MF_E_INVALIDSTREAMNUMBER; + } + + // NOTE: This method should succeed even when there is no media type on the + // stream. If there is no media type, we only need to fill in the dwFlags + // member of MFT_OUTPUT_STREAM_INFO. The other members depend on having a + // a valid media type. + + pStreamInfo->dwFlags = + MFT_OUTPUT_STREAM_WHOLE_SAMPLES | + MFT_OUTPUT_STREAM_SINGLE_SAMPLE_PER_BUFFER | + MFT_OUTPUT_STREAM_FIXED_SAMPLE_SIZE ; + + if (m_pOutputType == NULL) + { + pStreamInfo->cbSize = 0; + } + else + { + pStreamInfo->cbSize = m_cbImageSize; + } + + pStreamInfo->cbAlignment = 0; + + LeaveCriticalSection(&m_critSec); + return S_OK; +} + + +//------------------------------------------------------------------- +// GetAttributes +// Returns the attributes for the MFT. +//------------------------------------------------------------------- + +HRESULT CGrayscale::GetAttributes(IMFAttributes** ppAttributes) +{ + if (ppAttributes == NULL) + { + return E_POINTER; + } + + EnterCriticalSection(&m_critSec); + + *ppAttributes = m_pAttributes; + (*ppAttributes)->AddRef(); + + LeaveCriticalSection(&m_critSec); + return S_OK; +} + + +//------------------------------------------------------------------- +// GetInputStreamAttributes +// Returns stream-level attributes for an input stream. +//------------------------------------------------------------------- + +HRESULT CGrayscale::GetInputStreamAttributes( + DWORD dwInputStreamID, + IMFAttributes **ppAttributes +) +{ + // This MFT does not support any stream-level attributes, so the method is not implemented. + return E_NOTIMPL; +} + + +//------------------------------------------------------------------- +// GetOutputStreamAttributes +// Returns stream-level attributes for an output stream. +//------------------------------------------------------------------- + +HRESULT CGrayscale::GetOutputStreamAttributes( + DWORD dwOutputStreamID, + IMFAttributes **ppAttributes +) +{ + // This MFT does not support any stream-level attributes, so the method is not implemented. + return E_NOTIMPL; +} + + +//------------------------------------------------------------------- +// DeleteInputStream +//------------------------------------------------------------------- + +HRESULT CGrayscale::DeleteInputStream(DWORD dwStreamID) +{ + // This MFT has a fixed number of input streams, so the method is not supported. + return E_NOTIMPL; +} + + +//------------------------------------------------------------------- +// AddInputStreams +//------------------------------------------------------------------- + +HRESULT CGrayscale::AddInputStreams( + DWORD cStreams, + DWORD *adwStreamIDs +) +{ + // This MFT has a fixed number of output streams, so the method is not supported. + return E_NOTIMPL; +} + + +//------------------------------------------------------------------- +// GetInputAvailableType +// Returns a preferred input type. +//------------------------------------------------------------------- + +HRESULT CGrayscale::GetInputAvailableType( + DWORD dwInputStreamID, + DWORD dwTypeIndex, // 0-based + IMFMediaType **ppType +) +{ + if (ppType == NULL) + { + return E_INVALIDARG; + } + + EnterCriticalSection(&m_critSec); + + if (!IsValidInputStream(dwInputStreamID)) + { + LeaveCriticalSection(&m_critSec); + return MF_E_INVALIDSTREAMNUMBER; + } + + HRESULT hr = S_OK; + + // If the output type is set, return that type as our preferred input type. + if (m_pOutputType == NULL) + { + // The output type is not set. Create a partial media type. + hr = OnGetPartialType(dwTypeIndex, ppType); + } + else if (dwTypeIndex > 0) + { + hr = MF_E_NO_MORE_TYPES; + } + else + { + *ppType = m_pOutputType; + (*ppType)->AddRef(); + } + + LeaveCriticalSection(&m_critSec); + return hr; +} + + + +//------------------------------------------------------------------- +// GetOutputAvailableType +// Returns a preferred output type. +//------------------------------------------------------------------- + +HRESULT CGrayscale::GetOutputAvailableType( + DWORD dwOutputStreamID, + DWORD dwTypeIndex, // 0-based + IMFMediaType **ppType +) +{ + if (ppType == NULL) + { + return E_INVALIDARG; + } + + EnterCriticalSection(&m_critSec); + + if (!IsValidOutputStream(dwOutputStreamID)) + { + LeaveCriticalSection(&m_critSec); + return MF_E_INVALIDSTREAMNUMBER; + } + + HRESULT hr = S_OK; + + if (m_pInputType == NULL) + { + // The input type is not set. Create a partial media type. + hr = OnGetPartialType(dwTypeIndex, ppType); + } + else if (dwTypeIndex > 0) + { + hr = MF_E_NO_MORE_TYPES; + } + else + { + *ppType = m_pInputType; + (*ppType)->AddRef(); + } + + LeaveCriticalSection(&m_critSec); + return hr; +} + + +//------------------------------------------------------------------- +// SetInputType +//------------------------------------------------------------------- + +HRESULT CGrayscale::SetInputType( + DWORD dwInputStreamID, + IMFMediaType *pType, // Can be NULL to clear the input type. + DWORD dwFlags +) +{ + // Validate flags. + if (dwFlags & ~MFT_SET_TYPE_TEST_ONLY) + { + return E_INVALIDARG; + } + + EnterCriticalSection(&m_critSec); + + if (!IsValidInputStream(dwInputStreamID)) + { + LeaveCriticalSection(&m_critSec); + return MF_E_INVALIDSTREAMNUMBER; + } + + HRESULT hr = S_OK; + + // Does the caller want us to set the type, or just test it? + BOOL bReallySet = ((dwFlags & MFT_SET_TYPE_TEST_ONLY) == 0); + + // If we have an input sample, the client cannot change the type now. + if (HasPendingOutput()) + { + hr = MF_E_TRANSFORM_CANNOT_CHANGE_MEDIATYPE_WHILE_PROCESSING; + goto done; + } + + // Validate the type, if non-NULL. + if (pType) + { + hr = OnCheckInputType(pType); + if (FAILED(hr)) + { + goto done; + } + } + + // The type is OK. Set the type, unless the caller was just testing. + if (bReallySet) + { + OnSetInputType(pType); + + // When the type changes, end streaming. + hr = EndStreaming(); + } + +done: + LeaveCriticalSection(&m_critSec); + return hr; +} + + + +//------------------------------------------------------------------- +// SetOutputType +//------------------------------------------------------------------- + +HRESULT CGrayscale::SetOutputType( + DWORD dwOutputStreamID, + IMFMediaType *pType, // Can be NULL to clear the output type. + DWORD dwFlags +) +{ + // Validate flags. + if (dwFlags & ~MFT_SET_TYPE_TEST_ONLY) + { + return E_INVALIDARG; + } + + EnterCriticalSection(&m_critSec); + + if (!IsValidOutputStream(dwOutputStreamID)) + { + LeaveCriticalSection(&m_critSec); + return MF_E_INVALIDSTREAMNUMBER; + } + + HRESULT hr = S_OK; + + // Does the caller want us to set the type, or just test it? + BOOL bReallySet = ((dwFlags & MFT_SET_TYPE_TEST_ONLY) == 0); + + // If we have an input sample, the client cannot change the type now. + if (HasPendingOutput()) + { + hr = MF_E_TRANSFORM_CANNOT_CHANGE_MEDIATYPE_WHILE_PROCESSING; + goto done; + } + + // Validate the type, if non-NULL. + if (pType) + { + hr = OnCheckOutputType(pType); + if (FAILED(hr)) + { + goto done; + } + } + + // The type is OK. Set the type, unless the caller was just testing. + if (bReallySet) + { + OnSetOutputType(pType); + + // When the type changes, end streaming. + hr = EndStreaming(); + } + +done: + LeaveCriticalSection(&m_critSec); + return hr; +} + + +//------------------------------------------------------------------- +// GetInputCurrentType +// Returns the current input type. +//------------------------------------------------------------------- + +HRESULT CGrayscale::GetInputCurrentType( + DWORD dwInputStreamID, + IMFMediaType **ppType +) +{ + if (ppType == NULL) + { + return E_POINTER; + } + + HRESULT hr = S_OK; + + EnterCriticalSection(&m_critSec); + + if (!IsValidInputStream(dwInputStreamID)) + { + hr = MF_E_INVALIDSTREAMNUMBER; + } + else if (!m_pInputType) + { + hr = MF_E_TRANSFORM_TYPE_NOT_SET; + } + else + { + *ppType = m_pInputType; + (*ppType)->AddRef(); + } + LeaveCriticalSection(&m_critSec); + return hr; +} + + +//------------------------------------------------------------------- +// GetOutputCurrentType +// Returns the current output type. +//------------------------------------------------------------------- + +HRESULT CGrayscale::GetOutputCurrentType( + DWORD dwOutputStreamID, + IMFMediaType **ppType +) +{ + if (ppType == NULL) + { + return E_POINTER; + } + + HRESULT hr = S_OK; + + EnterCriticalSection(&m_critSec); + + if (!IsValidOutputStream(dwOutputStreamID)) + { + hr = MF_E_INVALIDSTREAMNUMBER; + } + else if (!m_pOutputType) + { + hr = MF_E_TRANSFORM_TYPE_NOT_SET; + } + else + { + *ppType = m_pOutputType; + (*ppType)->AddRef(); + } + + LeaveCriticalSection(&m_critSec); + return hr; +} + + +//------------------------------------------------------------------- +// GetInputStatus +// Query if the MFT is accepting more input. +//------------------------------------------------------------------- + +HRESULT CGrayscale::GetInputStatus( + DWORD dwInputStreamID, + DWORD *pdwFlags +) +{ + if (pdwFlags == NULL) + { + return E_POINTER; + } + + EnterCriticalSection(&m_critSec); + + if (!IsValidInputStream(dwInputStreamID)) + { + LeaveCriticalSection(&m_critSec); + return MF_E_INVALIDSTREAMNUMBER; + } + + // If an input sample is already queued, do not accept another sample until the + // client calls ProcessOutput or Flush. + + // NOTE: It is possible for an MFT to accept more than one input sample. For + // example, this might be required in a video decoder if the frames do not + // arrive in temporal order. In the case, the decoder must hold a queue of + // samples. For the video effect, each sample is transformed independently, so + // there is no reason to queue multiple input samples. + + if (m_pSample == NULL) + { + *pdwFlags = MFT_INPUT_STATUS_ACCEPT_DATA; + } + else + { + *pdwFlags = 0; + } + + LeaveCriticalSection(&m_critSec); + return S_OK; +} + + + +//------------------------------------------------------------------- +// GetOutputStatus +// Query if the MFT can produce output. +//------------------------------------------------------------------- + +HRESULT CGrayscale::GetOutputStatus(DWORD *pdwFlags) +{ + if (pdwFlags == NULL) + { + return E_POINTER; + } + + EnterCriticalSection(&m_critSec); + + // The MFT can produce an output sample if (and only if) there an input sample. + if (m_pSample != NULL) + { + *pdwFlags = MFT_OUTPUT_STATUS_SAMPLE_READY; + } + else + { + *pdwFlags = 0; + } + + LeaveCriticalSection(&m_critSec); + return S_OK; +} + + +//------------------------------------------------------------------- +// SetOutputBounds +// Sets the range of time stamps that the MFT will output. +//------------------------------------------------------------------- + +HRESULT CGrayscale::SetOutputBounds( + LONGLONG hnsLowerBound, + LONGLONG hnsUpperBound +) +{ + // Implementation of this method is optional. + return E_NOTIMPL; +} + + +//------------------------------------------------------------------- +// ProcessEvent +// Sends an event to an input stream. +//------------------------------------------------------------------- + +HRESULT CGrayscale::ProcessEvent( + DWORD dwInputStreamID, + IMFMediaEvent *pEvent +) +{ + // This MFT does not handle any stream events, so the method can + // return E_NOTIMPL. This tells the pipeline that it can stop + // sending any more events to this MFT. + return E_NOTIMPL; +} + + +//------------------------------------------------------------------- +// ProcessMessage +//------------------------------------------------------------------- + +HRESULT CGrayscale::ProcessMessage( + MFT_MESSAGE_TYPE eMessage, + ULONG_PTR ulParam +) +{ + EnterCriticalSection(&m_critSec); + + HRESULT hr = S_OK; + + switch (eMessage) + { + case MFT_MESSAGE_COMMAND_FLUSH: + // Flush the MFT. + hr = OnFlush(); + break; + + case MFT_MESSAGE_COMMAND_DRAIN: + // Drain: Tells the MFT to reject further input until all pending samples are + // processed. That is our default behavior already, so there is nothing to do. + // + // For a decoder that accepts a queue of samples, the MFT might need to drain + // the queue in response to this command. + break; + + case MFT_MESSAGE_SET_D3D_MANAGER: + // Sets a pointer to the IDirect3DDeviceManager9 interface. + + // The pipeline should never send this message unless the MFT sets the MF_SA_D3D_AWARE + // attribute set to TRUE. Because this MFT does not set MF_SA_D3D_AWARE, it is an error + // to send the MFT_MESSAGE_SET_D3D_MANAGER message to the MFT. Return an error code in + // this case. + + // NOTE: If this MFT were D3D-enabled, it would cache the IDirect3DDeviceManager9 + // pointer for use during streaming. + + hr = E_NOTIMPL; + break; + + case MFT_MESSAGE_NOTIFY_BEGIN_STREAMING: + hr = BeginStreaming(); + break; + + case MFT_MESSAGE_NOTIFY_END_STREAMING: + hr = EndStreaming(); + break; + + // The next two messages do not require any action from this MFT. + + case MFT_MESSAGE_NOTIFY_END_OF_STREAM: + break; + + case MFT_MESSAGE_NOTIFY_START_OF_STREAM: + break; + } + + LeaveCriticalSection(&m_critSec); + return hr; +} + + +//------------------------------------------------------------------- +// ProcessInput +// Process an input sample. +//------------------------------------------------------------------- + +HRESULT CGrayscale::ProcessInput( + DWORD dwInputStreamID, + IMFSample *pSample, + DWORD dwFlags +) +{ + // Check input parameters. + if (pSample == NULL) + { + return E_POINTER; + } + + if (dwFlags != 0) + { + return E_INVALIDARG; // dwFlags is reserved and must be zero. + } + + HRESULT hr = S_OK; + + EnterCriticalSection(&m_critSec); + + // Validate the input stream number. + if (!IsValidInputStream(dwInputStreamID)) + { + hr = MF_E_INVALIDSTREAMNUMBER; + goto done; + } + + // Check for valid media types. + // The client must set input and output types before calling ProcessInput. + if (!m_pInputType || !m_pOutputType) + { + hr = MF_E_NOTACCEPTING; + goto done; + } + + // Check if an input sample is already queued. + if (m_pSample != NULL) + { + hr = MF_E_NOTACCEPTING; // We already have an input sample. + goto done; + } + + // Initialize streaming. + hr = BeginStreaming(); + if (FAILED(hr)) + { + goto done; + } + + // Cache the sample. We do the actual work in ProcessOutput. + m_pSample = pSample; + pSample->AddRef(); // Hold a reference count on the sample. + +done: + LeaveCriticalSection(&m_critSec); + return hr; +} + + +//------------------------------------------------------------------- +// ProcessOutput +// Process an output sample. +//------------------------------------------------------------------- + +HRESULT CGrayscale::ProcessOutput( + DWORD dwFlags, + DWORD cOutputBufferCount, + MFT_OUTPUT_DATA_BUFFER *pOutputSamples, // one per stream + DWORD *pdwStatus +) +{ + // Check input parameters... + + // This MFT does not accept any flags for the dwFlags parameter. + + // The only defined flag is MFT_PROCESS_OUTPUT_DISCARD_WHEN_NO_BUFFER. This flag + // applies only when the MFT marks an output stream as lazy or optional. But this + // MFT has no lazy or optional streams, so the flag is not valid. + + if (dwFlags != 0) + { + return E_INVALIDARG; + } + + if (pOutputSamples == NULL || pdwStatus == NULL) + { + return E_POINTER; + } + + // There must be exactly one output buffer. + if (cOutputBufferCount != 1) + { + return E_INVALIDARG; + } + + // It must contain a sample. + if (pOutputSamples[0].pSample == NULL) + { + return E_INVALIDARG; + } + + HRESULT hr = S_OK; + + IMFMediaBuffer *pInput = NULL; + IMFMediaBuffer *pOutput = NULL; + + EnterCriticalSection(&m_critSec); + + // There must be an input sample available for processing. + if (m_pSample == NULL) + { + hr = MF_E_TRANSFORM_NEED_MORE_INPUT; + goto done; + } + + // Initialize streaming. + + hr = BeginStreaming(); + if (FAILED(hr)) + { + goto done; + } + + // Get the input buffer. + hr = m_pSample->ConvertToContiguousBuffer(&pInput); + if (FAILED(hr)) + { + goto done; + } + + // Get the output buffer. + hr = pOutputSamples[0].pSample->ConvertToContiguousBuffer(&pOutput); + if (FAILED(hr)) + { + goto done; + } + + hr = OnProcessOutput(pInput, pOutput); + if (FAILED(hr)) + { + goto done; + } + + // Set status flags. + pOutputSamples[0].dwStatus = 0; + *pdwStatus = 0; + + + // Copy the duration and time stamp from the input sample, if present. + + LONGLONG hnsDuration = 0; + LONGLONG hnsTime = 0; + + if (SUCCEEDED(m_pSample->GetSampleDuration(&hnsDuration))) + { + hr = pOutputSamples[0].pSample->SetSampleDuration(hnsDuration); + if (FAILED(hr)) + { + goto done; + } + } + + if (SUCCEEDED(m_pSample->GetSampleTime(&hnsTime))) + { + hr = pOutputSamples[0].pSample->SetSampleTime(hnsTime); + } + +done: + SafeRelease(&m_pSample); // Release our input sample. + SafeRelease(&pInput); + SafeRelease(&pOutput); + LeaveCriticalSection(&m_critSec); + return hr; +} + +// PRIVATE METHODS + +// All methods that follow are private to this MFT and are not part of the IMFTransform interface. + +// Create a partial media type from our list. +// +// dwTypeIndex: Index into the list of peferred media types. +// ppmt: Receives a pointer to the media type. + +HRESULT CGrayscale::OnGetPartialType(DWORD dwTypeIndex, IMFMediaType **ppmt) +{ + if (dwTypeIndex >= ARRAYSIZE(g_MediaSubtypes)) + { + return MF_E_NO_MORE_TYPES; + } + + IMFMediaType *pmt = NULL; + + HRESULT hr = MFCreateMediaType(&pmt); + if (FAILED(hr)) + { + goto done; + } + + hr = pmt->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video); + if (FAILED(hr)) + { + goto done; + } + + hr = pmt->SetGUID(MF_MT_SUBTYPE, g_MediaSubtypes[dwTypeIndex]); + if (FAILED(hr)) + { + goto done; + } + + *ppmt = pmt; + (*ppmt)->AddRef(); + +done: + SafeRelease(&pmt); + return hr; +} + + +// Validate an input media type. + +HRESULT CGrayscale::OnCheckInputType(IMFMediaType *pmt) +{ + assert(pmt != NULL); + + HRESULT hr = S_OK; + + // If the output type is set, see if they match. + if (m_pOutputType != NULL) + { + DWORD flags = 0; + hr = pmt->IsEqual(m_pOutputType, &flags); + + // IsEqual can return S_FALSE. Treat this as failure. + if (hr != S_OK) + { + hr = MF_E_INVALIDMEDIATYPE; + } + } + else + { + // Output type is not set. Just check this type. + hr = OnCheckMediaType(pmt); + } + return hr; +} + + +// Validate an output media type. + +HRESULT CGrayscale::OnCheckOutputType(IMFMediaType *pmt) +{ + assert(pmt != NULL); + + HRESULT hr = S_OK; + + // If the input type is set, see if they match. + if (m_pInputType != NULL) + { + DWORD flags = 0; + hr = pmt->IsEqual(m_pInputType, &flags); + + // IsEqual can return S_FALSE. Treat this as failure. + if (hr != S_OK) + { + hr = MF_E_INVALIDMEDIATYPE; + } + + } + else + { + // Input type is not set. Just check this type. + hr = OnCheckMediaType(pmt); + } + return hr; +} + + +// Validate a media type (input or output) + +HRESULT CGrayscale::OnCheckMediaType(IMFMediaType *pmt) +{ + BOOL bFoundMatchingSubtype = FALSE; + + // Major type must be video. + GUID major_type; + HRESULT hr = pmt->GetGUID(MF_MT_MAJOR_TYPE, &major_type); + if (FAILED(hr)) + { + goto done; + } + + if (major_type != MFMediaType_Video) + { + hr = MF_E_INVALIDMEDIATYPE; + goto done; + } + + // Subtype must be one of the subtypes in our global list. + + // Get the subtype GUID. + GUID subtype; + hr = pmt->GetGUID(MF_MT_SUBTYPE, &subtype); + if (FAILED(hr)) + { + goto done; + } + + // Look for the subtype in our list of accepted types. + for (DWORD i = 0; i < ARRAYSIZE(g_MediaSubtypes); i++) + { + if (subtype == g_MediaSubtypes[i]) + { + bFoundMatchingSubtype = TRUE; + break; + } + } + + if (!bFoundMatchingSubtype) + { + hr = MF_E_INVALIDMEDIATYPE; // The MFT does not support this subtype. + goto done; + } + + // Reject single-field media types. + UINT32 interlace = MFGetAttributeUINT32(pmt, MF_MT_INTERLACE_MODE, MFVideoInterlace_Progressive); + if (interlace == MFVideoInterlace_FieldSingleUpper || interlace == MFVideoInterlace_FieldSingleLower) + { + hr = MF_E_INVALIDMEDIATYPE; + } + +done: + return hr; +} + + +// Set or clear the input media type. +// +// Prerequisite: The input type was already validated. + +void CGrayscale::OnSetInputType(IMFMediaType *pmt) +{ + // if pmt is NULL, clear the type. + // if pmt is non-NULL, set the type. + + SafeRelease(&m_pInputType); + m_pInputType = pmt; + if (m_pInputType) + { + m_pInputType->AddRef(); + } + + // Update the format information. + UpdateFormatInfo(); +} + + +// Set or clears the output media type. +// +// Prerequisite: The output type was already validated. + +void CGrayscale::OnSetOutputType(IMFMediaType *pmt) +{ + // If pmt is NULL, clear the type. Otherwise, set the type. + + SafeRelease(&m_pOutputType); + m_pOutputType = pmt; + if (m_pOutputType) + { + m_pOutputType->AddRef(); + } +} + + +// Initialize streaming parameters. +// +// This method is called if the client sends the MFT_MESSAGE_NOTIFY_BEGIN_STREAMING +// message, or when the client processes a sample, whichever happens first. + +HRESULT CGrayscale::BeginStreaming() +{ + HRESULT hr = S_OK; + + if (!m_bStreamingInitialized) + { + // Get the configuration attributes. + + // Get the destination rectangle. + + RECT rcDest; + hr = m_pAttributes->GetBlob(MFT_GRAYSCALE_DESTINATION_RECT, (UINT8*)&rcDest, sizeof(rcDest), NULL); + if (hr == MF_E_ATTRIBUTENOTFOUND || !ValidateRect(rcDest)) + { + // The client did not set this attribute, or the client provided an invalid rectangle. + // Default to the entire image. + + m_rcDest = D2D1::RectU(0, 0, m_imageWidthInPixels, m_imageHeightInPixels); + hr = S_OK; + } + else if (SUCCEEDED(hr)) + { + m_rcDest = D2D1::RectU(rcDest.left, rcDest.top, rcDest.right, rcDest.bottom); + } + else + { + goto done; + } + + // Get the chroma transformations. + + float scale = (float)MFGetAttributeDouble(m_pAttributes, MFT_GRAYSCALE_SATURATION, 0.0f); + float angle = (float)MFGetAttributeDouble(m_pAttributes, MFT_GRAYSCALE_CHROMA_ROTATION, 0.0f); + + m_transform = D2D1::Matrix3x2F::Scale(scale, scale) * D2D1::Matrix3x2F::Rotation(angle); + + m_bStreamingInitialized = true; + } + +done: + return hr; +} + + +// End streaming. + +// This method is called if the client sends an MFT_MESSAGE_NOTIFY_END_STREAMING +// message, or when the media type changes. In general, it should be called whenever +// the streaming parameters need to be reset. + +HRESULT CGrayscale::EndStreaming() +{ + m_bStreamingInitialized = false; + return S_OK; +} + + + +// Generate output data. + +HRESULT CGrayscale::OnProcessOutput(IMFMediaBuffer *pIn, IMFMediaBuffer *pOut) +{ + BYTE *pDest = NULL; // Destination buffer. + LONG lDestStride = 0; // Destination stride. + + BYTE *pSrc = NULL; // Source buffer. + LONG lSrcStride = 0; // Source stride. + + // Helper objects to lock the buffers. + VideoBufferLock inputLock(pIn); + VideoBufferLock outputLock(pOut); + + // Stride if the buffer does not support IMF2DBuffer + LONG lDefaultStride = 0; + + HRESULT hr = GetDefaultStride(m_pInputType, &lDefaultStride); + if (FAILED(hr)) + { + goto done; + } + + // Lock the input buffer. + hr = inputLock.LockBuffer(lDefaultStride, m_imageHeightInPixels, &pSrc, &lSrcStride); + if (FAILED(hr)) + { + goto done; + } + + // Lock the output buffer. + hr = outputLock.LockBuffer(lDefaultStride, m_imageHeightInPixels, &pDest, &lDestStride); + if (FAILED(hr)) + { + goto done; + } + + // Invoke the image transform function. + assert (m_pTransformFn != NULL); + if (m_pTransformFn) + { + (*m_pTransformFn)(m_transform, m_rcDest, pDest, lDestStride, pSrc, lSrcStride, + m_imageWidthInPixels, m_imageHeightInPixels); + } + else + { + hr = E_UNEXPECTED; + goto done; + } + + + // Set the data size on the output buffer. + hr = pOut->SetCurrentLength(m_cbImageSize); + + // The VideoBufferLock class automatically unlocks the buffers. +done: + return hr; +} + + +// Flush the MFT. + +HRESULT CGrayscale::OnFlush() +{ + // For this MFT, flushing just means releasing the input sample. + SafeRelease(&m_pSample); + return S_OK; +} + + +// Update the format information. This method is called whenever the +// input type is set. + +HRESULT CGrayscale::UpdateFormatInfo() +{ + HRESULT hr = S_OK; + + GUID subtype = GUID_NULL; + + m_imageWidthInPixels = 0; + m_imageHeightInPixels = 0; + m_cbImageSize = 0; + + m_pTransformFn = NULL; + + if (m_pInputType != NULL) + { + hr = m_pInputType->GetGUID(MF_MT_SUBTYPE, &subtype); + if (FAILED(hr)) + { + goto done; + } + if (subtype == MFVideoFormat_YUY2) + { + m_pTransformFn = TransformImage_YUY2; + } + else if (subtype == MFVideoFormat_UYVY) + { + m_pTransformFn = TransformImage_UYVY; + } + else if (subtype == MFVideoFormat_NV12) + { + m_pTransformFn = TransformImage_NV12; + } + else + { + hr = E_UNEXPECTED; + goto done; + } + + hr = MFGetAttributeSize(m_pInputType, MF_MT_FRAME_SIZE, &m_imageWidthInPixels, &m_imageHeightInPixels); + if (FAILED(hr)) + { + goto done; + } + + // Calculate the image size (not including padding) + hr = GetImageSize(subtype.Data1, m_imageWidthInPixels, m_imageHeightInPixels, &m_cbImageSize); + } + +done: + return hr; +} + + +// Calculate the size of the buffer needed to store the image. + +// fcc: The FOURCC code of the video format. + +HRESULT GetImageSize(DWORD fcc, UINT32 width, UINT32 height, DWORD* pcbImage) +{ + HRESULT hr = S_OK; + + switch (fcc) + { + case FOURCC_YUY2: + case FOURCC_UYVY: + // check overflow + if ((width > MAXDWORD / 2) || (width * 2 > MAXDWORD / height)) + { + hr = E_INVALIDARG; + } + else + { + // 16 bpp + *pcbImage = width * height * 2; + } + break; + + case FOURCC_NV12: + // check overflow + if ((height/2 > MAXDWORD - height) || ((height + height/2) > MAXDWORD / width)) + { + hr = E_INVALIDARG; + } + else + { + // 12 bpp + *pcbImage = width * (height + (height/2)); + } + break; + + default: + hr = E_FAIL; // Unsupported type. + } + return hr; +} + +// Get the default stride for a video format. +HRESULT GetDefaultStride(IMFMediaType *pType, LONG *plStride) +{ + LONG lStride = 0; + + // Try to get the default stride from the media type. + HRESULT hr = pType->GetUINT32(MF_MT_DEFAULT_STRIDE, (UINT32*)&lStride); + if (FAILED(hr)) + { + // Attribute not set. Try to calculate the default stride. + GUID subtype = GUID_NULL; + + UINT32 width = 0; + UINT32 height = 0; + + // Get the subtype and the image size. + hr = pType->GetGUID(MF_MT_SUBTYPE, &subtype); + if (SUCCEEDED(hr)) + { + hr = MFGetAttributeSize(pType, MF_MT_FRAME_SIZE, &width, &height); + } + if (SUCCEEDED(hr)) + { + if (subtype == MFVideoFormat_NV12) + { + lStride = width; + } + else if (subtype == MFVideoFormat_YUY2 || subtype == MFVideoFormat_UYVY) + { + lStride = ((width * 2) + 3) & ~3; + } + else + { + hr = E_INVALIDARG; + } + } + + // Set the attribute for later reference. + if (SUCCEEDED(hr)) + { + (void)pType->SetUINT32(MF_MT_DEFAULT_STRIDE, UINT32(lStride)); + } + } + if (SUCCEEDED(hr)) + { + *plStride = lStride; + } + return hr; +} + + +// Validate that a rectangle meets the following criteria: +// +// - All coordinates are non-negative. +// - The rectangle is not flipped (top > bottom, left > right) +// +// These are the requirements for the destination rectangle. + +bool ValidateRect(const RECT& rc) +{ + if (rc.left < 0 || rc.top < 0) + { + return false; + } + if (rc.left > rc.right || rc.top > rc.bottom) + { + return false; + } + return true; +} diff --git a/samples/winrt/ImageManipulations/C++/MediaExtensions/Grayscale/Grayscale.def b/samples/winrt/ImageManipulations/C++/MediaExtensions/Grayscale/Grayscale.def new file mode 100644 index 0000000000..0b801908c5 --- /dev/null +++ b/samples/winrt/ImageManipulations/C++/MediaExtensions/Grayscale/Grayscale.def @@ -0,0 +1,4 @@ +EXPORTS + DllCanUnloadNow PRIVATE + DllGetActivationFactory PRIVATE + DllGetClassObject PRIVATE \ No newline at end of file diff --git a/samples/winrt/ImageManipulations/C++/MediaExtensions/Grayscale/Grayscale.h b/samples/winrt/ImageManipulations/C++/MediaExtensions/Grayscale/Grayscale.h new file mode 100644 index 0000000000..b83223bce5 --- /dev/null +++ b/samples/winrt/ImageManipulations/C++/MediaExtensions/Grayscale/Grayscale.h @@ -0,0 +1,266 @@ +// Defines the transform class. +// +// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A +// PARTICULAR PURPOSE. +// +// Copyright (c) Microsoft Corporation. All rights reserved. + +#ifndef GRAYSCALE_H +#define GRAYSCALE_H + +#include +#include +#include +#include +#include +#include +#include + +// Note: The Direct2D helper library is included for its 2D matrix operations. +#include + +#include +#include +#include + +#include "GrayscaleTransform.h" + +// CLSID of the MFT. +DEFINE_GUID(CLSID_GrayscaleMFT, +0x2f3dbc05, 0xc011, 0x4a8f, 0xb2, 0x64, 0xe4, 0x2e, 0x35, 0xc6, 0x7b, 0xf4); + +// +// * IMPORTANT: If you implement your own MFT, create a new GUID for the CLSID. * +// + + +// Configuration attributes + +// {7BBBB051-133B-41F5-B6AA-5AFF9B33A2CB} +DEFINE_GUID(MFT_GRAYSCALE_DESTINATION_RECT, +0x7bbbb051, 0x133b, 0x41f5, 0xb6, 0xaa, 0x5a, 0xff, 0x9b, 0x33, 0xa2, 0xcb); + + +// {14782342-93E8-4565-872C-D9A2973D5CBF} +DEFINE_GUID(MFT_GRAYSCALE_SATURATION, +0x14782342, 0x93e8, 0x4565, 0x87, 0x2c, 0xd9, 0xa2, 0x97, 0x3d, 0x5c, 0xbf); + +// {E0BADE5D-E4B9-4689-9DBA-E2F00D9CED0E} +DEFINE_GUID(MFT_GRAYSCALE_CHROMA_ROTATION, +0xe0bade5d, 0xe4b9, 0x4689, 0x9d, 0xba, 0xe2, 0xf0, 0xd, 0x9c, 0xed, 0xe); + + +template void SafeRelease(T **ppT) +{ + if (*ppT) + { + (*ppT)->Release(); + *ppT = NULL; + } +} + +// Function pointer for the function that transforms the image. +typedef void (*IMAGE_TRANSFORM_FN)( + const D2D1::Matrix3x2F& mat, // Chroma transform matrix. + const D2D_RECT_U& rcDest, // Destination rectangle for the transformation. + BYTE* pDest, // Destination buffer. + LONG lDestStride, // Destination stride. + const BYTE* pSrc, // Source buffer. + LONG lSrcStride, // Source stride. + DWORD dwWidthInPixels, // Image width in pixels. + DWORD dwHeightInPixels // Image height in pixels. + ); + +// CGrayscale class: +// Implements a grayscale video effect. + +class CGrayscale + : public Microsoft::WRL::RuntimeClass< + Microsoft::WRL::RuntimeClassFlags< Microsoft::WRL::RuntimeClassType::WinRtClassicComMix >, + ABI::Windows::Media::IMediaExtension, + IMFTransform > +{ + InspectableClass(RuntimeClass_GrayscaleTransform_GrayscaleEffect, BaseTrust) + +public: + CGrayscale(); + + ~CGrayscale(); + + STDMETHOD(RuntimeClassInitialize)(); + + // IMediaExtension + STDMETHODIMP SetProperties(ABI::Windows::Foundation::Collections::IPropertySet *pConfiguration); + + // IMFTransform + STDMETHODIMP GetStreamLimits( + DWORD *pdwInputMinimum, + DWORD *pdwInputMaximum, + DWORD *pdwOutputMinimum, + DWORD *pdwOutputMaximum + ); + + STDMETHODIMP GetStreamCount( + DWORD *pcInputStreams, + DWORD *pcOutputStreams + ); + + STDMETHODIMP GetStreamIDs( + DWORD dwInputIDArraySize, + DWORD *pdwInputIDs, + DWORD dwOutputIDArraySize, + DWORD *pdwOutputIDs + ); + + STDMETHODIMP GetInputStreamInfo( + DWORD dwInputStreamID, + MFT_INPUT_STREAM_INFO * pStreamInfo + ); + + STDMETHODIMP GetOutputStreamInfo( + DWORD dwOutputStreamID, + MFT_OUTPUT_STREAM_INFO * pStreamInfo + ); + + STDMETHODIMP GetAttributes(IMFAttributes** pAttributes); + + STDMETHODIMP GetInputStreamAttributes( + DWORD dwInputStreamID, + IMFAttributes **ppAttributes + ); + + STDMETHODIMP GetOutputStreamAttributes( + DWORD dwOutputStreamID, + IMFAttributes **ppAttributes + ); + + STDMETHODIMP DeleteInputStream(DWORD dwStreamID); + + STDMETHODIMP AddInputStreams( + DWORD cStreams, + DWORD *adwStreamIDs + ); + + STDMETHODIMP GetInputAvailableType( + DWORD dwInputStreamID, + DWORD dwTypeIndex, // 0-based + IMFMediaType **ppType + ); + + STDMETHODIMP GetOutputAvailableType( + DWORD dwOutputStreamID, + DWORD dwTypeIndex, // 0-based + IMFMediaType **ppType + ); + + STDMETHODIMP SetInputType( + DWORD dwInputStreamID, + IMFMediaType *pType, + DWORD dwFlags + ); + + STDMETHODIMP SetOutputType( + DWORD dwOutputStreamID, + IMFMediaType *pType, + DWORD dwFlags + ); + + STDMETHODIMP GetInputCurrentType( + DWORD dwInputStreamID, + IMFMediaType **ppType + ); + + STDMETHODIMP GetOutputCurrentType( + DWORD dwOutputStreamID, + IMFMediaType **ppType + ); + + STDMETHODIMP GetInputStatus( + DWORD dwInputStreamID, + DWORD *pdwFlags + ); + + STDMETHODIMP GetOutputStatus(DWORD *pdwFlags); + + STDMETHODIMP SetOutputBounds( + LONGLONG hnsLowerBound, + LONGLONG hnsUpperBound + ); + + STDMETHODIMP ProcessEvent( + DWORD dwInputStreamID, + IMFMediaEvent *pEvent + ); + + STDMETHODIMP ProcessMessage( + MFT_MESSAGE_TYPE eMessage, + ULONG_PTR ulParam + ); + + STDMETHODIMP ProcessInput( + DWORD dwInputStreamID, + IMFSample *pSample, + DWORD dwFlags + ); + + STDMETHODIMP ProcessOutput( + DWORD dwFlags, + DWORD cOutputBufferCount, + MFT_OUTPUT_DATA_BUFFER *pOutputSamples, // one per stream + DWORD *pdwStatus + ); + + +private: + // HasPendingOutput: Returns TRUE if the MFT is holding an input sample. + BOOL HasPendingOutput() const { return m_pSample != NULL; } + + // IsValidInputStream: Returns TRUE if dwInputStreamID is a valid input stream identifier. + BOOL IsValidInputStream(DWORD dwInputStreamID) const + { + return dwInputStreamID == 0; + } + + // IsValidOutputStream: Returns TRUE if dwOutputStreamID is a valid output stream identifier. + BOOL IsValidOutputStream(DWORD dwOutputStreamID) const + { + return dwOutputStreamID == 0; + } + + HRESULT OnGetPartialType(DWORD dwTypeIndex, IMFMediaType **ppmt); + HRESULT OnCheckInputType(IMFMediaType *pmt); + HRESULT OnCheckOutputType(IMFMediaType *pmt); + HRESULT OnCheckMediaType(IMFMediaType *pmt); + void OnSetInputType(IMFMediaType *pmt); + void OnSetOutputType(IMFMediaType *pmt); + HRESULT BeginStreaming(); + HRESULT EndStreaming(); + HRESULT OnProcessOutput(IMFMediaBuffer *pIn, IMFMediaBuffer *pOut); + HRESULT OnFlush(); + HRESULT UpdateFormatInfo(); + + CRITICAL_SECTION m_critSec; + + // Transformation parameters + D2D1::Matrix3x2F m_transform; // Chroma transform matrix. + D2D_RECT_U m_rcDest; // Destination rectangle for the effect. + + // Streaming + bool m_bStreamingInitialized; + IMFSample *m_pSample; // Input sample. + IMFMediaType *m_pInputType; // Input media type. + IMFMediaType *m_pOutputType; // Output media type. + + // Fomat information + UINT32 m_imageWidthInPixels; + UINT32 m_imageHeightInPixels; + DWORD m_cbImageSize; // Image size, in bytes. + + IMFAttributes *m_pAttributes; + + // Image transform function. (Changes based on the media type.) + IMAGE_TRANSFORM_FN m_pTransformFn; +}; +#endif \ No newline at end of file diff --git a/samples/winrt/ImageManipulations/C++/MediaExtensions/Grayscale/Grayscale.vcxproj b/samples/winrt/ImageManipulations/C++/MediaExtensions/Grayscale/Grayscale.vcxproj new file mode 100644 index 0000000000..8af8f2c7d9 --- /dev/null +++ b/samples/winrt/ImageManipulations/C++/MediaExtensions/Grayscale/Grayscale.vcxproj @@ -0,0 +1,313 @@ + + + + + Debug + ARM + + + Debug + Win32 + + + Debug + x64 + + + Release + ARM + + + Release + Win32 + + + Release + x64 + + + + $(VCTargetsPath11) + {BA69218F-DA5C-4D14-A78D-21A9E4DEC669} + Win32Proj + GrayscaleTransform + GrayscaleTransform + 11.0 + true + + + + DynamicLibrary + true + v110 + + + DynamicLibrary + true + v110 + + + DynamicLibrary + true + v110 + + + DynamicLibrary + false + true + v110 + + + DynamicLibrary + false + true + v110 + + + DynamicLibrary + false + true + v110 + + + + en-US + + + + + + + + + + + + + + + + + + + + + + + false + $(Configuration)\$(MSBuildProjectName)\ + + + false + + + false + + + false + $(Configuration)\$(MSBuildProjectName)\ + + + false + + + false + + + + NotUsing + _WINRT_DLL;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + + + + + $(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories) + false + $(ProjectDir);$(IntermediateOutputPath);%(AdditionalIncludeDirectories);$(ProjectDir)\..\Common; + + + Console + runtimeobject.lib;%(AdditionalDependencies);mf.lib;mfuuid.lib;mfplat.lib + false + Grayscale.def + + + mdmerge -metadata_dir "$(WindowsSDK_MetadataPath)" -o "$(ProjectDir)$(Configuration)\$(MSBuildProjectName)" -i "$(MSBuildProjectDirectory)" -v -partial + $(ProjectDir)$(Configuration)\$(MSBuildProjectName)\$(ProjectName).winmd + + + + + NotUsing + _WINRT_DLL;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + + + + + $(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories) + false + $(ProjectDir);$(IntermediateOutputPath);%(AdditionalIncludeDirectories);$(ProjectDir)\..\Common; + + + Console + runtimeobject.lib;%(AdditionalDependencies);mf.lib;mfuuid.lib;mfplat.lib + false + Grayscale.def + + + mdmerge -metadata_dir "$(WindowsSDK_MetadataPath)" -o "$(SolutionDir)$(Platform)\$(Configuration)\$(MSBuildProjectName)" -i "$(MSBuildProjectDirectory)" -v -partial + $(SolutionDir)$(Platform)\$(Configuration)\$(MSBuildProjectName)\$(ProjectName).winmd + + + + + NotUsing + _WINRT_DLL;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + + + + + $(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories) + false + $(ProjectDir);$(IntermediateOutputPath);%(AdditionalIncludeDirectories);$(ProjectDir)\..\Common; + + + Console + runtimeobject.lib;%(AdditionalDependencies);mf.lib;mfuuid.lib;mfplat.lib + false + Grayscale.def + + + mdmerge -metadata_dir "$(WindowsSDK_MetadataPath)" -o "$(SolutionDir)$(Platform)\$(Configuration)\$(MSBuildProjectName)" -i "$(MSBuildProjectDirectory)" -v -partial + $(SolutionDir)$(Platform)\$(Configuration)\$(MSBuildProjectName)\$(ProjectName).winmd + + + + + NotUsing + _WINRT_DLL;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + + + + + $(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories) + false + $(ProjectDir);$(IntermediateOutputPath);%(AdditionalIncludeDirectories);$(ProjectDir)\..\Common; + + + Console + runtimeobject.lib;%(AdditionalDependencies);mf.lib;mfuuid.lib;mfplat.lib + false + Grayscale.def + + + mdmerge -metadata_dir "$(WindowsSDK_MetadataPath)" -o "$(ProjectDir)$(Configuration)\$(MSBuildProjectName)" -i "$(MSBuildProjectDirectory)" -v -partial + $(ProjectDir)$(Configuration)\$(MSBuildProjectName)\$(ProjectName).winmd + + + + + NotUsing + _WINRT_DLL;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + + + + + $(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories) + false + $(ProjectDir);$(IntermediateOutputPath);%(AdditionalIncludeDirectories);$(ProjectDir)\..\Common; + + + Console + runtimeobject.lib;%(AdditionalDependencies);mf.lib;mfuuid.lib;mfplat.lib + false + Grayscale.def + + + mdmerge -metadata_dir "$(WindowsSDK_MetadataPath)" -o "$(SolutionDir)$(Platform)\$(Configuration)\$(MSBuildProjectName)" -i "$(MSBuildProjectDirectory)" -v -partial + $(SolutionDir)$(Platform)\$(Configuration)\$(MSBuildProjectName)\$(ProjectName).winmd + + + + + NotUsing + _WINRT_DLL;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + + + + + $(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories) + false + $(ProjectDir);$(IntermediateOutputPath);%(AdditionalIncludeDirectories);$(ProjectDir)\..\Common; + + + Console + runtimeobject.lib;%(AdditionalDependencies);mf.lib;mfuuid.lib;mfplat.lib + false + Grayscale.def + + + mdmerge -metadata_dir "$(WindowsSDK_MetadataPath)" -o "$(SolutionDir)$(Platform)\$(Configuration)\$(MSBuildProjectName)" -i "$(MSBuildProjectDirectory)" -v -partial + $(SolutionDir)$(Platform)\$(Configuration)\$(MSBuildProjectName)\$(ProjectName).winmd + + + + + + + + + + + + + + + + + + + + + + + + + + + $(WindowsSDK_MetadataPath) + $(WindowsSDK_MetadataPath) + $(WindowsSDK_MetadataPath) + $(WindowsSDK_MetadataPath) + $(WindowsSDK_MetadataPath) + $(WindowsSDK_MetadataPath) + true + true + true + true + true + true + %(Filename).h + %(Filename).h + %(Filename).h + %(Filename).h + %(Filename).h + %(Filename).h + + + + + <_MdMergeOutput Condition="'$(Platform)' == 'Win32'" Include="$(ProjectDir)$(Configuration)\$(MSBuildProjectName)\$(ProjectName).winmd" /> + <_MdMergeOutput Condition="'$(Platform)' != 'Win32'" Include="$(SolutionDir)$(Platform)\$(Configuration)\$(MSBuildProjectName)\$(ProjectName).winmd" /> + + + + + $(ProjectName).winmd + $(TargetName)$(TargetExt) + + + + + + \ No newline at end of file diff --git a/samples/winrt/ImageManipulations/C++/MediaExtensions/Grayscale/Grayscale.vcxproj.filters b/samples/winrt/ImageManipulations/C++/MediaExtensions/Grayscale/Grayscale.vcxproj.filters new file mode 100644 index 0000000000..92c2c9cfec --- /dev/null +++ b/samples/winrt/ImageManipulations/C++/MediaExtensions/Grayscale/Grayscale.vcxproj.filters @@ -0,0 +1,22 @@ + + + + + bdc52ff6-58cb-464b-bf4f-0c1804b135ff + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/winrt/ImageManipulations/C++/MediaExtensions/Grayscale/GrayscaleTransform.idl b/samples/winrt/ImageManipulations/C++/MediaExtensions/Grayscale/GrayscaleTransform.idl new file mode 100644 index 0000000000..de81380eca --- /dev/null +++ b/samples/winrt/ImageManipulations/C++/MediaExtensions/Grayscale/GrayscaleTransform.idl @@ -0,0 +1,11 @@ +import "Windows.Media.idl"; + +#include + +namespace GrayscaleTransform +{ + [version(NTDDI_WIN8)] + runtimeclass GrayscaleEffect + { + } +} \ No newline at end of file diff --git a/samples/winrt/ImageManipulations/C++/MediaExtensions/Grayscale/dllmain.cpp b/samples/winrt/ImageManipulations/C++/MediaExtensions/Grayscale/dllmain.cpp new file mode 100644 index 0000000000..ad67670110 --- /dev/null +++ b/samples/winrt/ImageManipulations/C++/MediaExtensions/Grayscale/dllmain.cpp @@ -0,0 +1,58 @@ +////////////////////////////////////////////////////////////////////////// +// +// dllmain.cpp +// +// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A +// PARTICULAR PURPOSE. +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +////////////////////////////////////////////////////////////////////////// + +#include +#include "Grayscale.h" + +using namespace Microsoft::WRL; + +namespace Microsoft { namespace Samples { + ActivatableClass(CGrayscale); +}} + +BOOL WINAPI DllMain( _In_ HINSTANCE hInstance, _In_ DWORD dwReason, _In_opt_ LPVOID lpReserved ) +{ + if( DLL_PROCESS_ATTACH == dwReason ) + { + // + // Don't need per-thread callbacks + // + DisableThreadLibraryCalls( hInstance ); + + Module::GetModule().Create(); + } + else if( DLL_PROCESS_DETACH == dwReason ) + { + Module::GetModule().Terminate(); + } + + return TRUE; +} + +HRESULT WINAPI DllGetActivationFactory( _In_ HSTRING activatibleClassId, _Outptr_ IActivationFactory** factory ) +{ + auto &module = Microsoft::WRL::Module< Microsoft::WRL::InProc >::GetModule(); + return module.GetActivationFactory( activatibleClassId, factory ); +} + +HRESULT WINAPI DllCanUnloadNow() +{ + auto &module = Microsoft::WRL::Module::GetModule(); + return (module.Terminate()) ? S_OK : S_FALSE; +} + +STDAPI DllGetClassObject( _In_ REFCLSID rclsid, _In_ REFIID riid, _Outptr_ LPVOID FAR* ppv ) +{ + auto &module = Microsoft::WRL::Module::GetModule(); + return module.GetClassObject( rclsid, riid, ppv ); +} diff --git a/samples/winrt/ImageManipulations/C++/Package.appxmanifest b/samples/winrt/ImageManipulations/C++/Package.appxmanifest new file mode 100644 index 0000000000..c72258b0c4 --- /dev/null +++ b/samples/winrt/ImageManipulations/C++/Package.appxmanifest @@ -0,0 +1,39 @@ + + + + + MediaCapture CPP sample + Microsoft Corporation + Assets\storeLogo-sdk.png + + + 6.2.1 + 6.2.1 + + + + + + + + + + + + + + + + + + + + + + + GrayscaleTransform.dll + + + + + \ No newline at end of file diff --git a/samples/winrt/ImageManipulations/C++/assets/microsoft-sdk.png b/samples/winrt/ImageManipulations/C++/assets/microsoft-sdk.png new file mode 100644 index 0000000000000000000000000000000000000000..1a1aec25b67aa1dc55663bdc58da2083107eac0d GIT binary patch literal 1583 zcmV+~2GIG5P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGmbN~PnbOGLGA9w%&02y>eSaefwW^{L9 za%BKeVQFr3E>1;MAa*k@H7+-&gq_R)00o~(L_t(oN9~visFhU^$9--EX8GC_#TFqg zGsMa^)1tx*B!f1x#mt4MtcW-#S%BUan9FL&TqXX@G&Z||Ep!@D z+1O23l5Bln^t!^c!t;TdO&cXe2h?ylf!crt^xI$fp@!Vy;*S+hGz@8aWB)@1vTyg~ zg9k;A3j6(FPi%VoQhH%YUEk&vBGx6E9r@~hvn7zcm zKq$ItKZo&^0P6b#)BmWx9|;=%U;$py`oD{Rsn9L-4AS=tCHDNPvE$9*eMR4%_V4T5 zsIi_xM~Tj&eZhNN*g=>sOl>GIn!stK=+{!dmBwb5jNhxV{nPl>qSq0gsl%8llA)>a z^*zWhX^i0e_;;cStn+3?V4gBQdhQgs0lyd@ds~G80!@$W;ShbFc;}1$qz+rM56Lq4 zZ<4T2qk*BD$_m{vyJ`IMl1-6Law?oE-mPg4H(sJx zY@GFWO6#KYHG%w3Iw2+Y5V{K!l5Y0c%TgSjrtfK_XimQK=7voK&G}>N4nQqIDZC{q zT_l>mytaQf)?T3v{;*Hu4qnguZuS$@n05AtLU<{ zk6wHU?4~6LqqoL?G9AnHO;<(1^`-%X*Rm5$V?kPCZ4Ssi0ylwqrwOFCY(tf%GkhbB z4>jFgHHN)@IH^Z38jm3Ni@-R4MN>fug+&ibYRb|AqqwfLsctOSowtkg+LBQi;Ez;t z?_@)+FV*ybyO}l!OkPqMe;=Pyl-Pd%FPh;HZwFPfD z-V)t{ZHeFhZv-ZHQ=Cv7vN&yw)5U3Ak@y7bj>H=$+H*fat8^s#Jwou*NTGPMzd~Vl$Y0Tv~?T3WHpPF3dVVdelQCh9)!ilHE znj`f6Mai1X;V_&P^3;)=OXQKAC-e;Yg-Lc~@cA!CKCsLFaZo}jZ7Ox5iV?v-#(3j{ z$0>7JYRmA%n{G~}2p=cVO5=QrF-~q1`iD;546buZHb~=iP`rKk5BAprL8a-Swyg5+ z717j~a|LPww}cyt(^d+c{Lv}!1m{qJw8mSFhb0G*zfnncPTFfFU}48-jq#YI2J>Dz hxT(Obp8Vf?;4d8suiatp%?AJg002ovPDHLkV1jTD8!rF= literal 0 HcmV?d00001 diff --git a/samples/winrt/ImageManipulations/C++/assets/placeholder-sdk.png b/samples/winrt/ImageManipulations/C++/assets/placeholder-sdk.png new file mode 100644 index 0000000000000000000000000000000000000000..01b3138cf8c040c54f3a200b2052f94a10240ded GIT binary patch literal 8991 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 LTB?Ottseaktfbe2 literal 0 HcmV?d00001 diff --git a/samples/winrt/ImageManipulations/C++/assets/smallTile-sdk.png b/samples/winrt/ImageManipulations/C++/assets/smallTile-sdk.png new file mode 100644 index 0000000000000000000000000000000000000000..5546e8b24a69738114bc17b2b2d493a6872d67ad GIT binary patch literal 1248 zcmeAS@N?(olHy`uVBq!ia0vp^av;pX1|+Qw)-3{3k|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+m{T%CB1$5BeXNr6bM+EIYV;~{3xK*A7;Nk-3KEmEQ%e+* zQqwc@Y?a>c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxKsVXI%s|1+P|wiV z#N6CmN5ROz&_Lh7NZ-&%*U;R`*vQJjKmiJrfVLH-q*(>IxIyg#@@$ndN=gc>^!3Zj z%k|2Q_413-^$jg8EkR}&8R-I5=oVMzl_XZ^<`pZ$OmImpPA){ffi_eM3D1{oGuTzrd=COM+4n&cLd=IHa;5RX-@T zIKQ+g85kdF$}r8qu)}W=NFmTQR{lkqz(`5Vami0E%}vcK@pQ3O0?O#6WTse|n3$Sa zTDmy9nj4$D8X6i}IGH%PI2*Va8n{>*8k#!7%)qAC)y34v)Xdz{*vZAn(9qS;($vJl z)xyQx%*e^f(b2>Zrq?sCxFj(zITdDaCeU7}UJJZ>t(=Qe6HD@oLh|!-U@0IVBfliS zI3vG6!8zDeAv`lLCBM8F6gd#Tx}+9mmZhe+73JqDfJ4_R6N~M}u1=N)CT<3nuErMF zeGSo@LQaV310ACeN*YK>1ttVce;_72;R8AFtdp7t%r8a2jPa$hZ3Y7aqmZYIV@SoV zH(|GVn+U)puN1)>GjWDYb(7@t?Y2iuo7SC^e(wK3>@~xg zMI8?7PRhyOSNeDN&u*(nlZ&MfRzH8rq}_NqyH&<=^8B|I4LQ+k9H%eLW)>+)vZ|V=`jsDXVcc>oTy0n3v?M%&Q3sa2+aW|74&4eCtU0$WI7BOyoPGRM@CzI)G{Td{9My9)pICP(!TIQd&%E34(}N-e_yy&h_tq=Dpr8?}sTnpr6>ge0Hke>!%qVM5&;0f2Jn_m;*}^(UWfQgfpiQ(a>V(NLXiYC;HWpw3k|fv zB7M=P(Mazw{BLLz06@|k}+8u999Rp=A%Q#28p8q026a^5EA8&CIP+BzL-E$ z5U;WZ1jKlof}9QPAa+5PXg|zx3IXj%v3EjI{87fAFNQ9A1 zK_78p;`_B_T@di23(4OU^obP0?j+C>M?eD&bRgO&h#>@M2-Sfe(l;_P0t59RP>3!> zPZz4E4K;!t(t|-Dz|S9$I2*y+2X-26^*NW=G6ngONI@`N-O$ibozO!%ID)S()Y#Z~ z4MR^)TkN4t3=1S7$=ZQLjdcV#nusD`f=C!#AaD&4>4gg>nS#VjKb3$Dva|aWIFR_c zQR0^Al955WP#uUa7Q0s0M{gqOH2S}3eAS!i6c&WmJ&h*ff(a<`JbX0PgT=l3`$TJw zVm2@b0!BO(WB?q83dW)XNj7j(kobv?H^v*LZ)BivYz((NW^~Bt7!(RUY@u%fw}il< z5V$cEYH)NN<7=@7`bK)jhI)pF;D&lo=rQO~OSpjr#K6GP$k4#T0197^wFxAWkbx-l zdR~k;@8?*9zm0`i63|Ezj^KpD1+4FYlYTf7j_8LA0$Ms60G%;`-ndZW+HgMA3P%$# zA!u(a0uBrO*j*UrE7||K>QAxW|C^5Kipl7%ZH+Is%KDOc|E=Brx$(uupGOBBC_XO) z@xhS4pqLB*>{zjZA9W&g#@{61hY*Sv+m5)T0+b}=Bvd41E{U%VaThHi>Y1|RvbI|+ zw@k=ZJvsko2$u+`1`HY(#4C0`53{LTETb$>3x&4;fIUU4nHvFs7-;~Yb`t>D0|3a1 ze+9M}el#z7O8#kXR06D-KRn&=(MxQu1+ANFLH`+J&1((ve~j^EVll1t#Qz!NOY)z2 z{5{5(2tgdVsvny!d+2N)KOgOxdVkw-BraF?;GNO za9OEniGGuBu)16 z$>uEcspvWKb+0@@sF06pX&NeVHU5rX6F5-wvIBVp@ zXDRl~j_4e_Tl^-YaYilOeAiD*|K{A7WFK{->m?vKBL~rCZb32)S%{f#^HRvUdFrq( zF0A_!TEVVQ%bto6a@0<*o(eD9Z+(q`Yx}O|?v9-S398A59#s`S^kVXz!=CBA<#efc zsh*F5mcKvl*k8a87xt2+cFx_95oNe*csmMV5oS9BAV0eeD^a=;)cksx&SxE1;xJex z3wOC8&0-3NQYL47c*lZlZ|>N#8YC}AlMrn;e9b7ds0 zO=3?fGo`f!A>THu9iij=VC8rhBir(2m&*Q)G18kB4f{i%{Q-Im3^7&J+DvUTXz<&` z$QthwWf3$J|BwodRn6LgnC8PlO<8#n^%8z&Sb8y zQU82iy-(NBbaZCNYb9~*MYZXxKbI;D#^-uf&J@SvUb9JulO3szm|X#>5Xw)KrOB$j zVE(B{%LyRk{LX3n_Y3se%3-NtmJ9(j84SmsymYJRBCNpecmXlOe>fn z-V&UdsI)Q4u0Qn{UlT7kT2cO{cVd~p>h99dE2$bdxKxv4u*!QqQC`(5x;5Yup=t|T z37oE2SurUjFOR9RY2?BTh)Pn})*}D*p~z0tfyL(V#qIpi{waKAbK?*l%qvy(Jom7F zWKfVKtgQDB0WPQ8z+!qJ)-vc1zPo#b)TZssqlgaQ8mv> zNr(42u_MpFy7LOPAHSLV!OylId_>z?Ha@2>J1_EFl8J4SiM6syQ!Z{G{5t(i4zEWu zeq=C=A7LDtROB`+=u4`2wHc9~l=z{ey0vP_o`pFwyW&KJJjvji$(Y=aY7iyS=^|vLO=7Bu6BRl!*};Rl)2P-*Ypn~=+y3I%H7uAD&Encu{-I9<0~hp z6!VcCa|I~|rA0x+-!}+gb#$Du9$_4KX`v;uZ3u6sbGVKU#!*Qt+sE;H{f*v7FSZ!RZKar(0Rb1AwH~z2Bvw?@iEVL%fBVvE5Pr(^8w6 zqDY5&m9Q{vb~?+YN;%_%y?W2v22NH#Y9|BR{lvR8O&*_`HmB(&$0lW?ORD@txg7$}W<9QLg5|L$UsO*#y?2d=r0jJkz76iRoo$<`Ze(f9^@2+< zkRs}<#-+pJ!@_fxsUx!5hni{nXODp!C9UEG)1en?!or(t4Y%=m2g)V3#z_f4 zUzU6NS2(yTyH&pb!zN=-&zZBX1)w}!kYVC)!xt}6of)aJm)u5CO9Je$>7B}fa=RhX zcOKstJ|PzkwDWemc%%)|7eLW+W)~Q)mpm^k`4etER6a0TDD3@yWkizMc=G7Ck-zXY z5SHKgvQuc>ifW|H*prLq>r9QV^A{{eSPX!Xj& zjbjL_{JknQo-Z^K62j)Qet25D|DLwWIR(SsD`!gUvIU59+vu0Mi4xPVM_N#uWwJOg z%xoqMuZ{9s&Nw`nzR^%xgMda48S@$-dh$E6xq3s&J79=-!;MO>-CcjPcYt>>hEbhN zj|`j$6<=be^{Y;+QTY(RByl0KrOHH}g8zli6&;A;-MxNyPoVI*B0EJ}!-4zM7$5cS zscT1b20hB@9yU$PswIGp+~|u3^>7*n9@B294gJyNvUq&L0UYko>g=}n^y2dE9J};u z%BWO1L0?FOY4G5Dc9PmOMJsuqnL~HSJge)0AB!EK&5;cVatYQzv1MvQ0&>dok5E@# zUSIHdoKGb=xXrzFC95c*m4>8tLthj2m?c9v{edPqzL*{jT20(A(zK2%9g`THSn=|F z2OL37>n!%H^i@;$iQ7`3!$)5|8K7rgnl=oyT6!R9o%D_9c|DEvp(OGV{6*$?{cHuZ zcJretn4zS$V8>wO6ZYX9fDN?`8M~9GvYgLOVr%X?TkqcMvA-u)(HNr`fxqbw`;|!F zO0S4iqPph&JUOeHQKo#Z%BlMSyM3t-T>qS`Or}KZt|}E7NySJPnt)EKC~y_e;Rcfz z@6l)s!Pu?_xz>4wxeJQB3+8pl7qjei6XkcvAe_>#P27?f^`)RO;g^K?N6SmM84UkP ziIcXMO*m!czt2Y9hg0RoKs|4phJLK!Gy5y8&MMz2Z(#NpX-@w7CfGdtSVGy=>1X5R z1lL&kunb|Q^}ncD*1DTqOdifoc+8xOA62dzAu?iHCz7{W`Kgx6VYe8Tow~kV{#>Vg z$fAYTnBA5ldagS&9(#wASn*!&rM;sdvLu^5IOFA@GK!)m(7UxyWMrND8P7r>gX zoo!?PdTi=Ccu2O@c;DLmj^X8cXrodE6iRY3siuH`pCZNM85^i+vvPN`I|2vpB~OXm z>^&=_`21otc*Kr|t>3SE!cE7z%jbK%LVnxttnPy)1I@5)eBbavbkuE`-Ps;L{zN(6 z-Md|>)!697yCYMQ8lr(mRs4^O3qx3yUWzl0xQ{$ec11uVRs|-cAvhA z714Q^UzNf3b6XKHA{c7i3Q;o?-P9?&r`p_3g7K5OhCg+3^~0vd_iW{=*t9vlWxfe9 ztfmNqs5Q+Cq$U$?K5lBvDyTP$?Mx%R{&xA4f;5+A3x47Psqt0!#Uky*9y$Z61r$hz=1>QzG{9d0Z^ zW;>GYm&C3>T=2&Vb?eonS=roU>(8h^m6LA`gpjizS!|0;ZqDalbgDVS5OZxH) z7xqdS3rAA(= z2C_=Yb1{1xy*PSgpEKpjnjB+*HmiIRWM2r7wFjS7%U2ZG-z%SWG4%z;KPaDnS73ip zK7Y|2ANAr_^v@UMzowr49b?VwbK-SH`u7mp!LQZc9$K@p>LCn)az@bsMBUpvp4neYAPeV_OG zoin*nt3zyNI?cr4a5kJ!kc+(!ns3YL*wg>BjSsQc0u?(}6$2-!G-3q8F{N-Kgy+b` z>me>AmZopK4*BD77E&26Ruvl&&XB-zlGv<6N|P%wYm6o^O(~Y7LMnVBv|gqNAPhD% z67Vu<0D<)tejG8aN&H6p?Kk>oOIjMeVX9hnueXc&=*3~P#b0|-mha!8@# zfB^*T3rQ-IGAK;84@jZ2*iPVL;(Q`NCyBP<|N0bY-zzv7By%&(~AXq z0RS6d0>M;_jYbLb@ecM{?mcPCQJ^ZZLIO?tm0^BA*wX*hmJx(NVik<=V0gpi1Vkmn zDi}?MmH40-A3R^Ckir_&+?@Bhf)FCx1WAJtSdO2lE<^SS?<|TJ)0@p^(y=fHktOG8PQkJR1KoDwA8-{F#>@2OkqY9vw)5ofiZ<81CJbt~lJZJ`Tv_rFA|n&f8e# zZ@W@WORzY%u6mJLVvFA)i~7r>AmGH>_)+3VyyBxWv!Q_^*Yg+TjCkuY*?EXJ%C&ZLc_TFUhB@tlPKZ ztuQIYLNMqid6%PJV$8LS%K7SL_)v88<3F-zz=d?{D?YBDk z(@kr0US82Vnfi$6EjP7mr9NXVqdWecyFu9bQ+-vz8~*Jvx9S@}PI#nNetuSS!i8wk z+2%t+i}JEQOZ7g#c_LBGC{o4ST9^^)NT7?J5)0N#T*h;_Wx;TqK{z%zf-s~q+YwB9u^OgKBG`gUUdml{92j!Tihu~5eK)(TydZIShZuokRiT}2N0 zcDc2}bHhff=9HgJPtLf-jEF`G_WFA6JJQ)tb$hmVhTVW;aR_-}cG$zM?sa9##irpc z<&R9Eq<CAi4DOoQ%=KG!OUUvWW#nHkpQ#kO{GHv}x)0)lUe%p1v zL%`CXz;=(464l$UN(Pd~u6Y!+_QyDFdT=0p(Y!NmdYhq;8m)2hzSOXmU)tVLhkyGc zF~;%vBmOe}1}F9bBmR~m!XbgLKWR8P+QBib-tiabEv)6`w%^YW{6}_dhW_G+^FC7K zkvB!3J8O+ct8^=>yBb0}b`Ge|l+VpAZvSFftI+ibddotU*X!`{FA_jPVWDxa^xdUq zq+T0wnf!A6Rlfo5wU>UwtMlr*`CVPU#q2q2kjo0e5r6pT543r02EiydC0Asaw!_BW zB(j9N38zY zVSnnVYg@*N=YGSlDqVZC^-JyRY6^JHUNANN_oKml)nKDbi%`78&Lwkq>3n8lOaK(M z%XIY)`(*v_R>j{Wk6lNV(-IeGdy{*z&OsHogMSnZ|3Tg#&#R$#Tys~HmZY1|0$u&~ z8{Z!cZXd0F3%?F04BZXB^SFofI5YqCEdel3^w)^1&PVosU-?Y zsp*+{wo31J?^jaDOtDo8H}y5}EpSfF$n>ZxN)4{^3rViZPPR-@vbR&PsjvbXkegbP zs8ErclUHn2VXFi-*9yo63F|8Xy|HaX=q~T zXkuhyoy6KF3~uM1wiR?bDKi6!|(A^G_^uoMuGkzbNu zoRMFk;2dnK5T2Qrl3!j7iX4bvT~doO%TiO^it=+6z@clEiN$tTLlY-cQ*#SPb7Kqa zzJ};cAt%K2fsWA!B@Lvc0uutJKM)h1@PQn7)=A9+=9eO1#>h2t<6vN5a`1F<45_&F zCgNglvw^_UQ1K63?>IoPgvE$SQc9`RFR@5oEMEQcuh2HP zp6FYXzh@K|KXK{P|2wZ_Iz!UOKY}UZOO97`J6F2O-)1bnXSiIfU8r#i)0E?L&d-t2 z)c7~uKsr%c^Nr5aQ$j_iEzTP2CUULicAD~e(?KcrHI`iRGF9`Xqr9aHn7pUHd@)Un zYj5;f1(o7sYHzMT6tnTW>+t@;Wxnk!*_3;D;aQ7@>=7RYwcvWU3HaL8rD zamEmP#-v^oIY;GJrcGr_YyWP$|7PL>Nd~R3E+*rLwl`M(?ya};313mc{QT!#{=kD~ z*_z*~*>3GM0X=d#Wzp$P!Xu`&Pv literal 0 HcmV?d00001 diff --git a/samples/winrt/ImageManipulations/C++/assets/tile-sdk.png b/samples/winrt/ImageManipulations/C++/assets/tile-sdk.png new file mode 100644 index 0000000000000000000000000000000000000000..cdec0dbdccad7ae5dd0e92e3c3015449fffb7450 GIT binary patch literal 2665 zcma)8dpwj`AD^(gN@_z1&pSR_@0Ph|CX;bDv1_Dpuf%xfF$Ocwm>HL$5^q~u7S+3& zTD-Ok5}6Q6nB-Pe79nQGbi)dHH^jK^u&Le8=e6%Xf1Kx>^ZS0kzu!6M_xYW3%GG6$ z)~d~`5D0{plcPNyfmlXV_0O-+RJHCMS4UL8O+p86p&J}1jAZa3gbfo8fB+{Bg9Xtc z22&LF5VAlZ)B(1;x6qsBOa@^NnlX<-M{#&6HUeQ`6~$wKA&?LVfLLrU1vz|IiUin9 z3ewArhNbcBpdhwmG#_${c5w%zLqKyT(#jIBh$5>5IFOJ5L~%m70&)}uxu8o{_2;KC zNMHdX451)@56YY73fRGX2rxrqQ6QFx1&BB_j|#1#k`To7`yryx~)G@H#N6A9)HW;k;^k$|?KuS)BlTqYX-%Ae=h|P1LEg9mWlnv{$CwipSRw5W!7=Gl{Lv|$c&&wI(fkRs zU|z&5m=`hsM?z)(;O1{jWK<1uq7yUx#%3OaYE=>Oc0k_VAnz%Ys)BE!w}l;RWk)8z z(VENsO!gwpvfivAyHe0KY8rXrP=K=c_{hkX#>B^dA*a(?jrF?TX05mq{A2T$rv-;I z$g)GLQthe-50+`?nAorF8~K&f1xcDT?^m3L8awoS^t+|B+_)o2M|S0bvx#3RHK)Q97{jhg}3G8uH@@X&CJqU`E#W{P30#``^{%(OwJD-=-tyauopL0;VGqI3c5q4 zr*$ObhOaD=K;y>j@OB(*Y&F$>fHn1Sym(M#EK#3=slAk%n1|!amZGMfQyrkraP~!> z)-W|mGF&kTS}6x41?jgc3I-VXbg1n0kWp%YDJoLz5y0_884jFCy2t_@V*^{WqK&Vj z#j#K7hFadS*9^0nR!#e8^3SKrl7nC8{Bpi&c{jLWmq)wKWPA5!c}o(GC~7L-m_Cc3yhuqVprfBOTg1$Tmq++%`0NgQG)Qb0okCcS zk9B*@D2`=mr`HE}>8<&1fBcQj`m7M-x`@t+M|s|8?Qg!+3NgcXlQ8jD%v&B1)xof_ zvtup#a{1X2R~F;h=%WFHf!+0#aD@pljC}qB!EZ!qJ$7e9@Zl7H8MCd!Rj(*Pv;3cG z%mXw7edEMLDMZOdby>zc2ah&@JVyxK?m{O#8snd zB#U9q_hK$7E?2vRqqBV^4jt&zytD1ruf;jKdWR&rv&!4_1MZjmDJ7le!AZhu7>vQR ziRvEGNi$i|(y(#Xj$-A#llvm;X70rrh34!dIf$7uw@y^y^HeXtkYnZ^naWn(qCtz- zRfkyezQXmBx(s)(K7(yCY7o(?6Gf}9*p%~{3*O1hsxMwH+d6PUivpeTLWS#Ui1`h) zOGVoG;&%DQ#y)xL)3(9rUc+2ZFQ0p;CVlP^Kc4g1q`SM{{bQW6-Q`7Hn^&lZr_L>t z#!LgqOV2XP*X;0WYFjJkQF7Q(Tf|d!5BC1jHIkC(IW;IO;F=`B;PJKz%Ko~rjCVcv zvZ7<@FI+af-v2L@hhJkH+H-cbpZ4+lUtE7MhDOPlqpA&Q6Tso6^`10k( zB-VRlVRfHXfnwSz-fc%$z3I=vkI#gxDJkFIxg}>XP@`eQuV@r|{P(a>u6% zbshAL;`_fWk*`fVne`@i^!iZ$+wJrA7$N>k51Z_K`RLmbsqYJ5Ae zpDHirz501}!eNS?kO8L1^C!)ktV)`Jj^`)VJh@*TSEK%sN(#lz}UAsoG zRciR3+%kN&g+^=xSy?_x3snMB(~e+cy^QWP;%#Cq>n)f`f3<94`odnN$+D3+?MvMI zW0MyJYI6zx>iRu%7OIN=he92%^@l>eWF=PB=s(r^zbEmR*TvEjf4XsIX4m^8zOJ*{ V^V80WL-UV6CkGe%d|Utc-vFTWTn+#L literal 0 HcmV?d00001 diff --git a/samples/winrt/ImageManipulations/C++/assets/windows-sdk.png b/samples/winrt/ImageManipulations/C++/assets/windows-sdk.png new file mode 100644 index 0000000000000000000000000000000000000000..67268021df6fbd0dfdd5220f669db5e07d83f620 GIT binary patch literal 2997 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 literal 0 HcmV?d00001 diff --git a/samples/winrt/ImageManipulations/C++/common/LayoutAwarePage.cpp b/samples/winrt/ImageManipulations/C++/common/LayoutAwarePage.cpp new file mode 100644 index 0000000000..9449fbead3 --- /dev/null +++ b/samples/winrt/ImageManipulations/C++/common/LayoutAwarePage.cpp @@ -0,0 +1,452 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. +// +//********************************************************* + +#include "pch.h" +#include "LayoutAwarePage.h" +#include "SuspensionManager.h" + +using namespace SDKSample::Common; + +using namespace Platform; +using namespace Platform::Collections; +using namespace Windows::Foundation; +using namespace Windows::Foundation::Collections; +using namespace Windows::System; +using namespace Windows::UI::Core; +using namespace Windows::UI::ViewManagement; +using namespace Windows::UI::Xaml; +using namespace Windows::UI::Xaml::Controls; +using namespace Windows::UI::Xaml::Interop; +using namespace Windows::UI::Xaml::Navigation; + +/// +/// Initializes a new instance of the class. +/// +LayoutAwarePage::LayoutAwarePage() +{ + if (Windows::ApplicationModel::DesignMode::DesignModeEnabled) + { + return; + } + + // Create an empty default view model + DefaultViewModel = ref new Map(std::less()); + + // When this page is part of the visual tree make two changes: + // 1) Map application view state to visual state for the page + // 2) Handle keyboard and mouse navigation requests + Loaded += ref new RoutedEventHandler(this, &LayoutAwarePage::OnLoaded); + + // Undo the same changes when the page is no longer visible + Unloaded += ref new RoutedEventHandler(this, &LayoutAwarePage::OnUnloaded); +} + +static DependencyProperty^ _defaultViewModelProperty = + DependencyProperty::Register("DefaultViewModel", + TypeName(IObservableMap::typeid), TypeName(LayoutAwarePage::typeid), nullptr); + +/// +/// Identifies the dependency property. +/// +DependencyProperty^ LayoutAwarePage::DefaultViewModelProperty::get() +{ + return _defaultViewModelProperty; +} + +/// +/// Gets an implementation of designed to be +/// used as a trivial view model. +/// +IObservableMap^ LayoutAwarePage::DefaultViewModel::get() +{ + return safe_cast^>(GetValue(DefaultViewModelProperty)); +} + +/// +/// Sets an implementation of designed to be +/// used as a trivial view model. +/// +void LayoutAwarePage::DefaultViewModel::set(IObservableMap^ value) +{ + SetValue(DefaultViewModelProperty, value); +} + +/// +/// Invoked when the page is part of the visual tree +/// +/// Instance that triggered the event. +/// Event data describing the conditions that led to the event. +void LayoutAwarePage::OnLoaded(Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e) +{ + this->StartLayoutUpdates(sender, e); + + // Keyboard and mouse navigation only apply when occupying the entire window + if (this->ActualHeight == Window::Current->Bounds.Height && + this->ActualWidth == Window::Current->Bounds.Width) + { + // Listen to the window directly so focus isn't required + _acceleratorKeyEventToken = Window::Current->CoreWindow->Dispatcher->AcceleratorKeyActivated += + ref new TypedEventHandler(this, + &LayoutAwarePage::CoreDispatcher_AcceleratorKeyActivated); + _pointerPressedEventToken = Window::Current->CoreWindow->PointerPressed += + ref new TypedEventHandler(this, + &LayoutAwarePage::CoreWindow_PointerPressed); + _navigationShortcutsRegistered = true; + } +} + +/// +/// Invoked when the page is removed from visual tree +/// +/// Instance that triggered the event. +/// Event data describing the conditions that led to the event. +void LayoutAwarePage::OnUnloaded(Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e) +{ + if (_navigationShortcutsRegistered) + { + Window::Current->CoreWindow->Dispatcher->AcceleratorKeyActivated -= _acceleratorKeyEventToken; + Window::Current->CoreWindow->PointerPressed -= _pointerPressedEventToken; + _navigationShortcutsRegistered = false; + } + StopLayoutUpdates(sender, e); +} + +#pragma region Navigation support + +/// +/// Invoked as an event handler to navigate backward in the page's associated +/// until it reaches the top of the navigation stack. +/// +/// Instance that triggered the event. +/// Event data describing the conditions that led to the event. +void LayoutAwarePage::GoHome(Object^ sender, RoutedEventArgs^ e) +{ + (void) sender; // Unused parameter + (void) e; // Unused parameter + + // Use the navigation frame to return to the topmost page + if (Frame != nullptr) + { + while (Frame->CanGoBack) + { + Frame->GoBack(); + } + } +} + +/// +/// Invoked as an event handler to navigate backward in the navigation stack +/// associated with this page's . +/// +/// Instance that triggered the event. +/// Event data describing the conditions that led to the event. +void LayoutAwarePage::GoBack(Object^ sender, RoutedEventArgs^ e) +{ + (void) sender; // Unused parameter + (void) e; // Unused parameter + + // Use the navigation frame to return to the previous page + if (Frame != nullptr && Frame->CanGoBack) + { + Frame->GoBack(); + } +} + +/// +/// Invoked as an event handler to navigate forward in the navigation stack +/// associated with this page's . +/// +/// Instance that triggered the event. +/// Event data describing the conditions that led to the event. +void LayoutAwarePage::GoForward(Object^ sender, RoutedEventArgs^ e) +{ + (void) sender; // Unused parameter + (void) e; // Unused parameter + + // Use the navigation frame to advance to the next page + if (Frame != nullptr && Frame->CanGoForward) + { + Frame->GoForward(); + } +} + +/// +/// Invoked on every keystroke, including system keys such as Alt key combinations, when +/// this page is active and occupies the entire window. Used to detect keyboard navigation +/// between pages even when the page itself doesn't have focus. +/// +/// Instance that triggered the event. +/// Event data describing the conditions that led to the event. +void LayoutAwarePage::CoreDispatcher_AcceleratorKeyActivated(CoreDispatcher^ sender, AcceleratorKeyEventArgs^ args) +{ + auto virtualKey = args->VirtualKey; + + // Only investigate further when Left, Right, or the dedicated Previous or Next keys + // are pressed + if ((args->EventType == CoreAcceleratorKeyEventType::SystemKeyDown || + args->EventType == CoreAcceleratorKeyEventType::KeyDown) && + (virtualKey == VirtualKey::Left || virtualKey == VirtualKey::Right || + (int)virtualKey == 166 || (int)virtualKey == 167)) + { + auto coreWindow = Window::Current->CoreWindow; + auto downState = Windows::UI::Core::CoreVirtualKeyStates::Down; + bool menuKey = (coreWindow->GetKeyState(VirtualKey::Menu) & downState) == downState; + bool controlKey = (coreWindow->GetKeyState(VirtualKey::Control) & downState) == downState; + bool shiftKey = (coreWindow->GetKeyState(VirtualKey::Shift) & downState) == downState; + bool noModifiers = !menuKey && !controlKey && !shiftKey; + bool onlyAlt = menuKey && !controlKey && !shiftKey; + + if (((int)virtualKey == 166 && noModifiers) || + (virtualKey == VirtualKey::Left && onlyAlt)) + { + // When the previous key or Alt+Left are pressed navigate back + args->Handled = true; + GoBack(this, ref new RoutedEventArgs()); + } + else if (((int)virtualKey == 167 && noModifiers) || + (virtualKey == VirtualKey::Right && onlyAlt)) + { + // When the next key or Alt+Right are pressed navigate forward + args->Handled = true; + GoForward(this, ref new RoutedEventArgs()); + } + } +} + +/// +/// Invoked on every mouse click, touch screen tap, or equivalent interaction when this +/// page is active and occupies the entire window. Used to detect browser-style next and +/// previous mouse button clicks to navigate between pages. +/// +/// Instance that triggered the event. +/// Event data describing the conditions that led to the event. +void LayoutAwarePage::CoreWindow_PointerPressed(CoreWindow^ sender, PointerEventArgs^ args) +{ + auto properties = args->CurrentPoint->Properties; + + // Ignore button chords with the left, right, and middle buttons + if (properties->IsLeftButtonPressed || properties->IsRightButtonPressed || + properties->IsMiddleButtonPressed) return; + + // If back or foward are pressed (but not both) navigate appropriately + bool backPressed = properties->IsXButton1Pressed; + bool forwardPressed = properties->IsXButton2Pressed; + if (backPressed ^ forwardPressed) + { + args->Handled = true; + if (backPressed) GoBack(this, ref new RoutedEventArgs()); + if (forwardPressed) GoForward(this, ref new RoutedEventArgs()); + } +} + +#pragma endregion + +#pragma region Visual state switching + +/// +/// Invoked as an event handler, typically on the event of a +/// within the page, to indicate that the sender should start receiving +/// visual state management changes that correspond to application view state changes. +/// +/// Instance of that supports visual state management +/// corresponding to view states. +/// Event data that describes how the request was made. +/// The current view state will immediately be used to set the corresponding visual state +/// when layout updates are requested. A corresponding event handler +/// connected to is strongly encouraged. Instances of +/// automatically invoke these handlers in their Loaded and Unloaded +/// events. +/// +/// +void LayoutAwarePage::StartLayoutUpdates(Object^ sender, RoutedEventArgs^ e) +{ + (void) e; // Unused parameter + + auto control = safe_cast(sender); + if (_layoutAwareControls == nullptr) + { + // Start listening to view state changes when there are controls interested in updates + _layoutAwareControls = ref new Vector(); + _windowSizeEventToken = Window::Current->SizeChanged += ref new WindowSizeChangedEventHandler(this, &LayoutAwarePage::WindowSizeChanged); + + // Page receives notifications for children. Protect the page until we stopped layout updates for all controls. + _this = this; + } + _layoutAwareControls->Append(control); + + // Set the initial visual state of the control + VisualStateManager::GoToState(control, DetermineVisualState(ApplicationView::Value), false); +} + +void LayoutAwarePage::WindowSizeChanged(Object^ sender, Windows::UI::Core::WindowSizeChangedEventArgs^ e) +{ + (void) sender; // Unused parameter + (void) e; // Unused parameter + + InvalidateVisualState(); +} + +/// +/// Invoked as an event handler, typically on the event of a +/// , to indicate that the sender should start receiving visual state +/// management changes that correspond to application view state changes. +/// +/// Instance of that supports visual state management +/// corresponding to view states. +/// Event data that describes how the request was made. +/// The current view state will immediately be used to set the corresponding visual state +/// when layout updates are requested. +/// +void LayoutAwarePage::StopLayoutUpdates(Object^ sender, RoutedEventArgs^ e) +{ + (void) e; // Unused parameter + + auto control = safe_cast(sender); + unsigned int index; + if (_layoutAwareControls != nullptr && _layoutAwareControls->IndexOf(control, &index)) + { + _layoutAwareControls->RemoveAt(index); + if (_layoutAwareControls->Size == 0) + { + // Stop listening to view state changes when no controls are interested in updates + Window::Current->SizeChanged -= _windowSizeEventToken; + _layoutAwareControls = nullptr; + // Last control has received the Unload notification. + _this = nullptr; + } + } +} + +/// +/// Translates values into strings for visual state management +/// within the page. The default implementation uses the names of enum values. Subclasses may +/// override this method to control the mapping scheme used. +/// +/// View state for which a visual state is desired. +/// Visual state name used to drive the +/// +String^ LayoutAwarePage::DetermineVisualState(ApplicationViewState viewState) +{ + switch (viewState) + { + case ApplicationViewState::Filled: + return "Filled"; + case ApplicationViewState::Snapped: + return "Snapped"; + case ApplicationViewState::FullScreenPortrait: + return "FullScreenPortrait"; + case ApplicationViewState::FullScreenLandscape: + default: + return "FullScreenLandscape"; + } +} + +/// +/// Updates all controls that are listening for visual state changes with the correct visual +/// state. +/// +/// +/// Typically used in conjunction with overriding to +/// signal that a different value may be returned even though the view state has not changed. +/// +void LayoutAwarePage::InvalidateVisualState() +{ + if (_layoutAwareControls != nullptr) + { + String^ visualState = DetermineVisualState(ApplicationView::Value); + auto controlIterator = _layoutAwareControls->First(); + while (controlIterator->HasCurrent) + { + auto control = controlIterator->Current; + VisualStateManager::GoToState(control, visualState, false); + controlIterator->MoveNext(); + } + } +} + +#pragma endregion + +#pragma region Process lifetime management + +/// +/// Invoked when this page is about to be displayed in a Frame. +/// +/// Event data that describes how this page was reached. The Parameter +/// property provides the group to be displayed. +void LayoutAwarePage::OnNavigatedTo(NavigationEventArgs^ e) +{ + // Returning to a cached page through navigation shouldn't trigger state loading + if (_pageKey != nullptr) return; + + auto frameState = SuspensionManager::SessionStateForFrame(Frame); + _pageKey = "Page-" + Frame->BackStackDepth; + + if (e->NavigationMode == NavigationMode::New) + { + // Clear existing state for forward navigation when adding a new page to the + // navigation stack + auto nextPageKey = _pageKey; + int nextPageIndex = Frame->BackStackDepth; + while (frameState->HasKey(nextPageKey)) + { + frameState->Remove(nextPageKey); + nextPageIndex++; + nextPageKey = "Page-" + nextPageIndex; + } + + // Pass the navigation parameter to the new page + LoadState(e->Parameter, nullptr); + } + else + { + // Pass the navigation parameter and preserved page state to the page, using + // the same strategy for loading suspended state and recreating pages discarded + // from cache + LoadState(e->Parameter, safe_cast^>(frameState->Lookup(_pageKey))); + } +} + +/// +/// Invoked when this page will no longer be displayed in a Frame. +/// +/// Event data that describes how this page was reached. The Parameter +/// property provides the group to be displayed. +void LayoutAwarePage::OnNavigatedFrom(NavigationEventArgs^ e) +{ + auto frameState = SuspensionManager::SessionStateForFrame(Frame); + auto pageState = ref new Map(); + SaveState(pageState); + frameState->Insert(_pageKey, pageState); +} + +/// +/// Populates the page with content passed during navigation. Any saved state is also +/// provided when recreating a page from a prior session. +/// +/// The parameter value passed to +/// when this page was initially requested. +/// +/// A map of state preserved by this page during an earlier +/// session. This will be null the first time a page is visited. +void LayoutAwarePage::LoadState(Object^ navigationParameter, IMap^ pageState) +{ +} + +/// +/// Preserves state associated with this page in case the application is suspended or the +/// page is discarded from the navigation cache. Values must conform to the serialization +/// requirements of . +/// +/// An empty map to be populated with serializable state. +void LayoutAwarePage::SaveState(IMap^ pageState) +{ +} + +#pragma endregion diff --git a/samples/winrt/ImageManipulations/C++/common/LayoutAwarePage.h b/samples/winrt/ImageManipulations/C++/common/LayoutAwarePage.h new file mode 100644 index 0000000000..bd71062fe9 --- /dev/null +++ b/samples/winrt/ImageManipulations/C++/common/LayoutAwarePage.h @@ -0,0 +1,88 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. +// +//********************************************************* + +#pragma once + +#include + +namespace SDKSample +{ + namespace Common + { + /// + /// Typical implementation of Page that provides several important conveniences: + /// + /// + /// Application view state to visual state mapping + /// + /// + /// GoBack, GoForward, and GoHome event handlers + /// + /// + /// Mouse and keyboard shortcuts for navigation + /// + /// + /// State management for navigation and process lifetime management + /// + /// + /// A default view model + /// + /// + /// + [Windows::Foundation::Metadata::WebHostHidden] + public ref class LayoutAwarePage : Windows::UI::Xaml::Controls::Page + { + internal: + LayoutAwarePage(); + + public: + void StartLayoutUpdates(Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e); + void StopLayoutUpdates(Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e); + void InvalidateVisualState(); + static property Windows::UI::Xaml::DependencyProperty^ DefaultViewModelProperty + { + Windows::UI::Xaml::DependencyProperty^ get(); + }; + property Windows::Foundation::Collections::IObservableMap^ DefaultViewModel + { + Windows::Foundation::Collections::IObservableMap^ get(); + void set(Windows::Foundation::Collections::IObservableMap^ value); + } + + protected: + virtual void GoHome(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e); + virtual void GoBack(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e); + virtual void GoForward(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e); + virtual Platform::String^ DetermineVisualState(Windows::UI::ViewManagement::ApplicationViewState viewState); + virtual void OnNavigatedTo(Windows::UI::Xaml::Navigation::NavigationEventArgs^ e) override; + virtual void OnNavigatedFrom(Windows::UI::Xaml::Navigation::NavigationEventArgs^ e) override; + virtual void LoadState(Platform::Object^ navigationParameter, + Windows::Foundation::Collections::IMap^ pageState); + virtual void SaveState(Windows::Foundation::Collections::IMap^ pageState); + + private: + Platform::String^ _pageKey; + bool _navigationShortcutsRegistered; + Platform::Collections::Map^ _defaultViewModel; + Windows::Foundation::EventRegistrationToken _windowSizeEventToken, + _acceleratorKeyEventToken, _pointerPressedEventToken; + Platform::Collections::Vector^ _layoutAwareControls; + void WindowSizeChanged(Platform::Object^ sender, Windows::UI::Core::WindowSizeChangedEventArgs^ e); + void OnLoaded(Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e); + void OnUnloaded(Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e); + + void CoreDispatcher_AcceleratorKeyActivated(Windows::UI::Core::CoreDispatcher^ sender, + Windows::UI::Core::AcceleratorKeyEventArgs^ args); + void CoreWindow_PointerPressed(Windows::UI::Core::CoreWindow^ sender, + Windows::UI::Core::PointerEventArgs^ args); + LayoutAwarePage^ _this; // Strong reference to self, cleaned up in OnUnload + }; + } +} diff --git a/samples/winrt/ImageManipulations/C++/common/StandardStyles.xaml b/samples/winrt/ImageManipulations/C++/common/StandardStyles.xaml new file mode 100644 index 0000000000..7c3d238776 --- /dev/null +++ b/samples/winrt/ImageManipulations/C++/common/StandardStyles.xaml @@ -0,0 +1,978 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Mouse + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/winrt/ImageManipulations/C++/common/suspensionmanager.cpp b/samples/winrt/ImageManipulations/C++/common/suspensionmanager.cpp new file mode 100644 index 0000000000..c1ecf11cfd --- /dev/null +++ b/samples/winrt/ImageManipulations/C++/common/suspensionmanager.cpp @@ -0,0 +1,481 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. +// +//********************************************************* + +// +// SuspensionManager.cpp +// Implementation of the SuspensionManager class +// + +#include "pch.h" +#include "SuspensionManager.h" + +#include +#include + +using namespace SDKSample::Common; + +using namespace Concurrency; +using namespace Platform; +using namespace Platform::Collections; +using namespace Windows::Foundation; +using namespace Windows::Foundation::Collections; +using namespace Windows::Storage; +using namespace Windows::Storage::FileProperties; +using namespace Windows::Storage::Streams; +using namespace Windows::UI::Xaml; +using namespace Windows::UI::Xaml::Controls; +using namespace Windows::UI::Xaml::Interop; + +namespace +{ + Map^ _sessionState = ref new Map(); + String^ sessionStateFilename = "_sessionState.dat"; + + // Forward declarations for object object read / write support + void WriteObject(Windows::Storage::Streams::DataWriter^ writer, Platform::Object^ object); + Platform::Object^ ReadObject(Windows::Storage::Streams::DataReader^ reader); +} + +/// +/// Provides access to global session state for the current session. This state is serialized by +/// and restored by which require values to be +/// one of the following: boxed values including integers, floating-point singles and doubles, +/// wide characters, boolean, Strings and Guids, or Map where map values are +/// subject to the same constraints. Session state should be as compact as possible. +/// +IMap^ SuspensionManager::SessionState::get(void) +{ + return _sessionState; +} + +/// +/// Wrap a WeakReference as a reference object for use in a collection. +/// +private ref class WeakFrame sealed +{ +private: + WeakReference _frameReference; + +internal: + WeakFrame(Frame^ frame) { _frameReference = frame; } + property Frame^ ResolvedFrame + { + Frame^ get(void) { return _frameReference.Resolve(); } + }; +}; + +namespace +{ + std::vector _registeredFrames; + DependencyProperty^ FrameSessionStateKeyProperty = + DependencyProperty::RegisterAttached("_FrameSessionStateKeyProperty", + TypeName(String::typeid), TypeName(SuspensionManager::typeid), nullptr); + DependencyProperty^ FrameSessionStateProperty = + DependencyProperty::RegisterAttached("_FrameSessionStateProperty", + TypeName(IMap::typeid), TypeName(SuspensionManager::typeid), nullptr); +} + +/// +/// Registers a instance to allow its navigation history to be saved to +/// and restored from . Frames should be registered once +/// immediately after creation if they will participate in session state management. Upon +/// registration if state has already been restored for the specified key +/// the navigation history will immediately be restored. Subsequent invocations of +/// will also restore navigation history. +/// +/// An instance whose navigation history should be managed by +/// +/// A unique key into used to +/// store navigation-related information. +void SuspensionManager::RegisterFrame(Frame^ frame, String^ sessionStateKey) +{ + if (frame->GetValue(FrameSessionStateKeyProperty) != nullptr) + { + throw ref new FailureException("Frames can only be registered to one session state key"); + } + + if (frame->GetValue(FrameSessionStateProperty) != nullptr) + { + throw ref new FailureException("Frames must be either be registered before accessing frame session state, or not registered at all"); + } + + // Use a dependency property to associate the session key with a frame, and keep a list of frames whose + // navigation state should be managed + frame->SetValue(FrameSessionStateKeyProperty, sessionStateKey); + _registeredFrames.insert(_registeredFrames.begin(), ref new WeakFrame(frame)); + + // Check to see if navigation state can be restored + RestoreFrameNavigationState(frame); +} + +/// +/// Disassociates a previously registered by +/// from . Any navigation state previously captured will be +/// removed. +/// +/// An instance whose navigation history should no longer be +/// managed. +void SuspensionManager::UnregisterFrame(Frame^ frame) +{ + // Remove session state and remove the frame from the list of frames whose navigation + // state will be saved (along with any weak references that are no longer reachable) + auto key = safe_cast(frame->GetValue(FrameSessionStateKeyProperty)); + if (SessionState->HasKey(key)) SessionState->Remove(key); + _registeredFrames.erase( + std::remove_if(_registeredFrames.begin(), _registeredFrames.end(), [=](WeakFrame^& e) + { + auto testFrame = e->ResolvedFrame; + return testFrame == nullptr || testFrame == frame; + }), + _registeredFrames.end() + ); +} + +/// +/// Provides storage for session state associated with the specified . +/// Frames that have been previously registered with have +/// their session state saved and restored automatically as a part of the global +/// . Frames that are not registered have transient state +/// that can still be useful when restoring pages that have been discarded from the +/// navigation cache. +/// +/// Apps may choose to rely on to manage +/// page-specific state instead of working with frame session state directly. +/// The instance for which session state is desired. +/// A collection of state subject to the same serialization mechanism as +/// . +IMap^ SuspensionManager::SessionStateForFrame(Frame^ frame) +{ + auto frameState = safe_cast^>(frame->GetValue(FrameSessionStateProperty)); + + if (frameState == nullptr) + { + auto frameSessionKey = safe_cast(frame->GetValue(FrameSessionStateKeyProperty)); + if (frameSessionKey != nullptr) + { + // Registered frames reflect the corresponding session state + if (!_sessionState->HasKey(frameSessionKey)) + { + _sessionState->Insert(frameSessionKey, ref new Map()); + } + frameState = safe_cast^>(_sessionState->Lookup(frameSessionKey)); + } + else + { + // Frames that aren't registered have transient state + frameState = ref new Map(); + } + frame->SetValue(FrameSessionStateProperty, frameState); + } + return frameState; +} + +void SuspensionManager::RestoreFrameNavigationState(Frame^ frame) +{ + auto frameState = SessionStateForFrame(frame); + if (frameState->HasKey("Navigation")) + { + frame->SetNavigationState(safe_cast(frameState->Lookup("Navigation"))); + } +} + +void SuspensionManager::SaveFrameNavigationState(Frame^ frame) +{ + auto frameState = SessionStateForFrame(frame); + frameState->Insert("Navigation", frame->GetNavigationState()); +} + +/// +/// Save the current . Any instances +/// registered with will also preserve their current +/// navigation stack, which in turn gives their active an opportunity +/// to save its state. +/// +/// An asynchronous task that reflects when session state has been saved. +task SuspensionManager::SaveAsync(void) +{ + // Save the navigation state for all registered frames + for (auto&& weakFrame : _registeredFrames) + { + auto frame = weakFrame->ResolvedFrame; + if (frame != nullptr) SaveFrameNavigationState(frame); + } + + // Serialize the session state synchronously to avoid asynchronous access to shared + // state + auto sessionData = ref new InMemoryRandomAccessStream(); + auto sessionDataWriter = ref new DataWriter(sessionData->GetOutputStreamAt(0)); + WriteObject(sessionDataWriter, _sessionState); + + // Once session state has been captured synchronously, begin the asynchronous process + // of writing the result to disk + return task(sessionDataWriter->StoreAsync()).then([=](unsigned int) + { + return sessionDataWriter->FlushAsync(); + }).then([=](bool flushSucceeded) + { + (void)flushSucceeded; // Unused parameter + return ApplicationData::Current->LocalFolder->CreateFileAsync(sessionStateFilename, + CreationCollisionOption::ReplaceExisting); + }).then([=](StorageFile^ createdFile) + { + return createdFile->OpenAsync(FileAccessMode::ReadWrite); + }).then([=](IRandomAccessStream^ newStream) + { + return RandomAccessStream::CopyAndCloseAsync( + sessionData->GetInputStreamAt(0), newStream->GetOutputStreamAt(0)); + }).then([=](UINT64 copiedBytes) + { + (void)copiedBytes; // Unused parameter + return; + }); +} + +/// +/// Restores previously saved . Any instances +/// registered with will also restore their prior navigation +/// state, which in turn gives their active an opportunity restore its +/// state. +/// +/// A version identifer compared to the session state to prevent +/// incompatible versions of session state from reaching app code. Saved state with a +/// different version will be ignored, resulting in an empty +/// dictionary. +/// An asynchronous task that reflects when session state has been read. The +/// content of should not be relied upon until this task +/// completes. +task SuspensionManager::RestoreAsync(void) +{ + _sessionState->Clear(); + + task getFileTask(ApplicationData::Current->LocalFolder->GetFileAsync(sessionStateFilename)); + return getFileTask.then([=](StorageFile^ stateFile) + { + task getBasicPropertiesTask(stateFile->GetBasicPropertiesAsync()); + return getBasicPropertiesTask.then([=](BasicProperties^ stateFileProperties) + { + auto size = unsigned int(stateFileProperties->Size); + if (size != stateFileProperties->Size) throw ref new FailureException("Session state larger than 4GB"); + task openReadTask(stateFile->OpenReadAsync()); + return openReadTask.then([=](IRandomAccessStreamWithContentType^ stateFileStream) + { + auto stateReader = ref new DataReader(stateFileStream); + return task(stateReader->LoadAsync(size)).then([=](unsigned int bytesRead) + { + (void)bytesRead; // Unused parameter + // Deserialize the Session State + Object^ content = ReadObject(stateReader); + _sessionState = (Map^)content; + + // Restore any registered frames to their saved state + for (auto&& weakFrame : _registeredFrames) + { + auto frame = weakFrame->ResolvedFrame; + if (frame != nullptr) + { + frame->ClearValue(FrameSessionStateProperty); + RestoreFrameNavigationState(frame); + } + } + }, task_continuation_context::use_current()); + }); + }); + }); +} + +#pragma region Object serialization for a known set of types + +namespace +{ + // Codes used for identifying serialized types + enum StreamTypes { + NullPtrType = 0, + + // Supported IPropertyValue types + UInt8Type, UInt16Type, UInt32Type, UInt64Type, Int16Type, Int32Type, Int64Type, + SingleType, DoubleType, BooleanType, Char16Type, GuidType, StringType, + + // Additional supported types + StringToObjectMapType, + + // Marker values used to ensure stream integrity + MapEndMarker + }; + + void WriteString(DataWriter^ writer, String^ string) + { + writer->WriteByte(StringType); + writer->WriteUInt32(writer->MeasureString(string)); + writer->WriteString(string); + } + + void WriteProperty(DataWriter^ writer, IPropertyValue^ propertyValue) + { + switch (propertyValue->Type) + { + case PropertyType::UInt8: + writer->WriteByte(UInt8Type); + writer->WriteByte(propertyValue->GetUInt8()); + return; + case PropertyType::UInt16: + writer->WriteByte(UInt16Type); + writer->WriteUInt16(propertyValue->GetUInt16()); + return; + case PropertyType::UInt32: + writer->WriteByte(UInt32Type); + writer->WriteUInt32(propertyValue->GetUInt32()); + return; + case PropertyType::UInt64: + writer->WriteByte(UInt64Type); + writer->WriteUInt64(propertyValue->GetUInt64()); + return; + case PropertyType::Int16: + writer->WriteByte(Int16Type); + writer->WriteUInt16(propertyValue->GetInt16()); + return; + case PropertyType::Int32: + writer->WriteByte(Int32Type); + writer->WriteUInt32(propertyValue->GetInt32()); + return; + case PropertyType::Int64: + writer->WriteByte(Int64Type); + writer->WriteUInt64(propertyValue->GetInt64()); + return; + case PropertyType::Single: + writer->WriteByte(SingleType); + writer->WriteSingle(propertyValue->GetSingle()); + return; + case PropertyType::Double: + writer->WriteByte(DoubleType); + writer->WriteDouble(propertyValue->GetDouble()); + return; + case PropertyType::Boolean: + writer->WriteByte(BooleanType); + writer->WriteBoolean(propertyValue->GetBoolean()); + return; + case PropertyType::Char16: + writer->WriteByte(Char16Type); + writer->WriteUInt16(propertyValue->GetChar16()); + return; + case PropertyType::Guid: + writer->WriteByte(GuidType); + writer->WriteGuid(propertyValue->GetGuid()); + return; + case PropertyType::String: + WriteString(writer, propertyValue->GetString()); + return; + default: + throw ref new InvalidArgumentException("Unsupported property type"); + } + } + + void WriteStringToObjectMap(DataWriter^ writer, IMap^ map) + { + writer->WriteByte(StringToObjectMapType); + writer->WriteUInt32(map->Size); + for (auto&& pair : map) + { + WriteObject(writer, pair->Key); + WriteObject(writer, pair->Value); + } + writer->WriteByte(MapEndMarker); + } + + void WriteObject(DataWriter^ writer, Object^ object) + { + if (object == nullptr) + { + writer->WriteByte(NullPtrType); + return; + } + + auto propertyObject = dynamic_cast(object); + if (propertyObject != nullptr) + { + WriteProperty(writer, propertyObject); + return; + } + + auto mapObject = dynamic_cast^>(object); + if (mapObject != nullptr) + { + WriteStringToObjectMap(writer, mapObject); + return; + } + + throw ref new InvalidArgumentException("Unsupported data type"); + } + + String^ ReadString(DataReader^ reader) + { + int length = reader->ReadUInt32(); + String^ string = reader->ReadString(length); + return string; + } + + IMap^ ReadStringToObjectMap(DataReader^ reader) + { + auto map = ref new Map(); + auto size = reader->ReadUInt32(); + for (unsigned int index = 0; index < size; index++) + { + auto key = safe_cast(ReadObject(reader)); + auto value = ReadObject(reader); + map->Insert(key, value); + } + if (reader->ReadByte() != MapEndMarker) + { + throw ref new InvalidArgumentException("Invalid stream"); + } + return map; + } + + Object^ ReadObject(DataReader^ reader) + { + auto type = reader->ReadByte(); + switch (type) + { + case NullPtrType: + return nullptr; + case UInt8Type: + return reader->ReadByte(); + case UInt16Type: + return reader->ReadUInt16(); + case UInt32Type: + return reader->ReadUInt32(); + case UInt64Type: + return reader->ReadUInt64(); + case Int16Type: + return reader->ReadInt16(); + case Int32Type: + return reader->ReadInt32(); + case Int64Type: + return reader->ReadInt64(); + case SingleType: + return reader->ReadSingle(); + case DoubleType: + return reader->ReadDouble(); + case BooleanType: + return reader->ReadBoolean(); + case Char16Type: + return (char16_t)reader->ReadUInt16(); + case GuidType: + return reader->ReadGuid(); + case StringType: + return ReadString(reader); + case StringToObjectMapType: + return ReadStringToObjectMap(reader); + default: + throw ref new InvalidArgumentException("Unsupported property type"); + } + } +} + +#pragma endregion diff --git a/samples/winrt/ImageManipulations/C++/common/suspensionmanager.h b/samples/winrt/ImageManipulations/C++/common/suspensionmanager.h new file mode 100644 index 0000000000..65e1180a06 --- /dev/null +++ b/samples/winrt/ImageManipulations/C++/common/suspensionmanager.h @@ -0,0 +1,50 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. +// +//********************************************************* + +// +// SuspensionManager.h +// Declaration of the SuspensionManager class +// + +#pragma once + +#include + +namespace SDKSample +{ + namespace Common + { + /// + /// SuspensionManager captures global session state to simplify process lifetime management + /// for an application. Note that session state will be automatically cleared under a variety + /// of conditions and should only be used to store information that would be convenient to + /// carry across sessions, but that should be disacarded when an application crashes or is + /// upgraded. + /// + ref class SuspensionManager sealed + { + internal: + static void RegisterFrame(Windows::UI::Xaml::Controls::Frame^ frame, Platform::String^ sessionStateKey); + static void UnregisterFrame(Windows::UI::Xaml::Controls::Frame^ frame); + static Concurrency::task SaveAsync(void); + static Concurrency::task RestoreAsync(void); + static property Windows::Foundation::Collections::IMap^ SessionState + { + Windows::Foundation::Collections::IMap^ get(void); + }; + static Windows::Foundation::Collections::IMap^ SessionStateForFrame( + Windows::UI::Xaml::Controls::Frame^ frame); + + private: + static void RestoreFrameNavigationState(Windows::UI::Xaml::Controls::Frame^ frame); + static void SaveFrameNavigationState(Windows::UI::Xaml::Controls::Frame^ frame); + }; + } +} diff --git a/samples/winrt/ImageManipulations/C++/pch.cpp b/samples/winrt/ImageManipulations/C++/pch.cpp new file mode 100644 index 0000000000..97389d94cc --- /dev/null +++ b/samples/winrt/ImageManipulations/C++/pch.cpp @@ -0,0 +1,16 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. +// +//********************************************************* + +// +// pch.cpp +// Include the standard header and generate the precompiled header. +// + +#include "pch.h" diff --git a/samples/winrt/ImageManipulations/C++/pch.h b/samples/winrt/ImageManipulations/C++/pch.h new file mode 100644 index 0000000000..13f9bc34c7 --- /dev/null +++ b/samples/winrt/ImageManipulations/C++/pch.h @@ -0,0 +1,23 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. +// +//********************************************************* + +// +// pch.h +// Header for standard system include files. +// + +#pragma once + +#include +#include +#include +#include "Common\LayoutAwarePage.h" +#include "Common\SuspensionManager.h" +#include "App.xaml.h" diff --git a/samples/winrt/ImageManipulations/C++/sample-utils/SampleTemplateStyles.xaml b/samples/winrt/ImageManipulations/C++/sample-utils/SampleTemplateStyles.xaml new file mode 100644 index 0000000000..ec2c1a7132 --- /dev/null +++ b/samples/winrt/ImageManipulations/C++/sample-utils/SampleTemplateStyles.xaml @@ -0,0 +1,51 @@ + + + + + + + + + + + + diff --git a/samples/winrt/ImageManipulations/description.html b/samples/winrt/ImageManipulations/description.html new file mode 100644 index 0000000000..ad1df7d563 --- /dev/null +++ b/samples/winrt/ImageManipulations/description.html @@ -0,0 +1,238 @@ + + + + + + + Media capture using capture device sample + + + + + + + + + diff --git a/samples/winrt/ImageManipulations/description/4ee0dda0-3e7e-46df-b80b-1692acc1c812Combined.css b/samples/winrt/ImageManipulations/description/4ee0dda0-3e7e-46df-b80b-1692acc1c812Combined.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/samples/winrt/ImageManipulations/description/Brand.css b/samples/winrt/ImageManipulations/description/Brand.css new file mode 100644 index 0000000000..98415561e5 --- /dev/null +++ b/samples/winrt/ImageManipulations/description/Brand.css @@ -0,0 +1,3629 @@ +#BodyBackground +{ + min-width:0px; +} + +#JelloExpander, .IE7 #JelloExpander, .IE8 #JelloExpander, .IE9 #JelloExpander +{ + padding-left:0px; + padding-right:0px; +} + +#JelloSizer +{ + margin: 0 auto; + max-width: 0px; + padding: 0; + width: 100%; +} + +/* Global Styles */ +body +{ + font-size:0.75em; +} + +h1 +{ + font-size: 3em; + font-family: 'Segoe UI Light','Segoe UI', 'Lucida Grande', Verdana, Arial, Helvetica, sans-serif; + color: #707070; + font-weight: normal; + padding-top:4px; + margin-bottom: 17px; + line-height: 1.3; + font-weight:100; +} + +h2, h3, h4, h5, h6 +{ + font-family: 'Segoe UI', 'Lucida Grande', Verdana, Arial, Helvetica, sans-serif; + color:#2a2a2a; + font-weight:normal; +} + +a, a:link, a:visited { + color: #00749E; +} +a:hover { + color: #0095c4; + text-decoration:none; +} + +/*---------- Masthead -----------*/ + +.NetworkLogo a { + display: none; +} + +/*-------- Start Advertisment --------*/ + +.advertisment { + padding-top:5px; +} + +/*-------- End Advertisment --------*/ + + +/*-------- Start LocalTabs Page --------*/ +#GalleriesNavigation { + float: right; +} + +.LocalNavigation +{ + display:block; + overflow: auto; + width: auto; +} + .LocalNavigation .HeaderTabs { + width: auto; + } + .LocalNavigation .TabOff a { + color:#333; + } + + .LocalNavigation .TabOff a:hover { + background-color:#E0E7EC; + margin-top: 1px; + padding: 4px 6px; + } + + .LocalNavigation #notificationLink span { + color:Red; + font-weight:bold; + } + + +/*-------- End LocalTabs Page --------*/ + +/*-------- Start SubMenu Page --------*/ + +.subMenu +{ + margin: 0 0 10px 0; + color: #FFFFFF; + overflow: hidden; +} + +.subMenu a, .subMenu span +{ + margin: 5px 5px 0 5px; +} + +.subMenu > h2 +{ + float: left; + margin: 7px 15px 0 0; + vertical-align: middle; + color: #666; + padding-bottom: 5px; +} + +.subMenu .advancedSearchLink +{ + float: left; + margin: 9px 0 0 15px; +} + + + +.subMenu .uploadLink +{ + float: right; + margin: 5px 5px 0 0; + padding: 0 0 0 30px; + height: 24px; + background: url('../Common/smalluploadicon.png') no-repeat 10px 0; + color: #0066E1; + cursor: pointer; +} + +.subMenu #searchBoxContainer +{ + float: left; + width: 400px; + padding: 3px 50px; +} + +/*-------- End SubMenu Page --------*/ + +/*-------- Start SearchBox --------*/ + +div.searchbox +{ + overflow:auto; + width:450px; + margin:26px 0 14px 0; +} + +.srchbox +{ + width: 100%; + background: #FFFFFF; + padding: 0px 2px; + height: 25px; + border: 1px solid #ccc; + table-layout: auto; + margin-bottom:5px; +} + + + +.srchbox #searchImageCell input +{ + background: transparent url('searchButton.png') no-repeat 0 0; + width: 20px; + height: 20px; + padding-left: 1px; + margin-top: 3px; +} + + +.srchbox #searchImageCell +{ + text-align: right; + padding: 0px; + vertical-align: middle; +} + +.IE7 .srchbox #searchImageCell +{ + padding:2px 2px 0 0; +} + +.srchbox #searchImageCell input +{ +} + + +table.srchbox +{ + table-layout: fixed; +} + +.srchbox .stxtcell +{ + padding-right: 4px; + width:90%; +} + +.srchbox .stxtcell > input +{ + margin-right: 4px; + height: 26px; + line-height:26px; + width: 100%; + padding: 0px; + padding-left: 4px; + padding-top: 2px; + border: none; + font-style: normal !important; + outline: none; +} + +.IE7 .srchbox .stxtcell > input +{ + height: 20px; +} + +.srchbox .stxtcell > input.stxtinptpassive +{ + color: #AAA; + font-style: normal !important; +} + +/*-------- End SearchBox --------*/ + + +/*-------- Start Search Page --------*/ + + +#searchPage #mainContentContainer +{ + margin: 0 0 0 243px; +} + +#searchPage .browseFilterBar, #dashboardPage .browseFilterBar +{ + padding: 5px 0 6px 0; + +} + + #searchPage .browseFilterBar a, #dashboardPage .browseFilterBar a + { + font-weight:normal; + } + + #searchPage .browseFilterBar .browseSort, #dashboardPage .browseFilterBar .browseSort + { + float:right; + } + + #searchPage .browseBreadcrumb + { + padding-top:3px; + } + + #searchPage .browseFilterBar .browseSort select, #dashboardPage .browseFilterBar .browseSort select + { + height:20px; + } + + #searchPage .browseFilterBar .browseSort img, #dashboardPage .browseFilterBar .browseSort img + { + vertical-align:text-top; + } + +#searchPage h2, #searchPage h3 +{ + font-size: 1.25em; + padding: 2px 0 3px 3px; +} + +#searchPage h2 +{ + display:inline; + clear:none; +} + + #dashboardsearchbox + { + width:420px; + margin:0; + float:left; + } + +/*-------- End Search Page --------*/ + +/*-------- Begin Requests Page --------*/ + +#requestsPage { + color:#666; +} + + #requestsPage h1 { + clear:none; + } + #requestsPage h2, #requestsPage h3 + { + font-size: 1.25em; + } + + #requestsPage h2 + { + display:inline; + clear:none; + } + + #requestsPage h3 + { + padding: 2px 0 3px 3px; + } + + #requestsPage #mainContentContainer + { + margin: 0 0 0 243px; + } + + #requestsPage #mainContentWrapper { + float:left; + width:100%; + } + #requestsPage #mainContent { + position:relative; + } + + #requestsPage #mainContent .subtitle{ + margin-bottom:5px; + } + + #requestsPage #requestsFilterBar { + margin-top: 10px; + overflow: auto; + } + + #requestsPage #requestsFilterBar .requestsCrumb { + float: left; + margin:10px 0 10px 10px; + } + #requestsPage #requestsFilterBar .requestsSort { + float: right; + margin:10px 0 10px 0; + } + + #requestsPage #Pager { + text-align: center; + padding-top: .75em; + padding-bottom: .75em; + } + + #requestsPage #requestsRss { + float: right; + margin: 2px 0 0 3px; + cursor: pointer; + } + + .IE7 #requestsPage #requestsList { + padding-top: 10px; + margin-top:5px; + } + + #requestsPage #requestsList .noResults { + padding: 10px 0; + } + + +/*-------- End Requests Page --------*/ + +/*-------- Begin Request List Item --------*/ +.requestlistitem td +{ + padding: .9em 0 .9em .5em; +} + .requestlistitem .votebox + { + + text-align:center; + } + + .requestlistitem .voteboxcontainer + { + vertical-align:top; + width: 75px; + } + + .requestlistitem #votelabel + { + border-width:1px; + border-bottom-style:solid; + } + + #myRequestsTab #votenumber, + #requestsPage #votenumber + { + background-color: #777; + padding: 6px 10px; + color: #fff; + margin-bottom: 5px; + font-size: 1.6em; + } + + #myRequestsTab .votelink, + #requestsPage .votelink + { + font-weight:bold; + background-color: #eee; + padding: 5px 0; + width: 75px; + float: left; + } + + #myRequestsTab .needvote, + #requestsPage .needvote + { + font-weight:bold; + } + + #myRequestsTab .alreadyvoted, #myRequestsTab .resolved + #requestsPage .alreadyvoted, #requestsPage .resolved + { + font-weight:bold; + color: #666; + } + + .requestlistitem .linkform + { + width:100%; + float:right; + margin-top:15px; + } + + .requestlistitem .requesttitle + { + font-weight:600; + margin-bottom:3px; + } + + .requestlistitem .requester + { + margin-bottom:3px; + } + + .requestlistitem .hidden + { + display:none; + } + + .requestlistitem .requestSummary div + { + margin-bottom:0.25em; + } + + .requestlistitem .requestSummary + { + line-height: 1.45em; + width: 80%; + } + .requestlistitem .requestInfo + { + padding:1.2em 0 0 2.5em; + vertical-align:top; + width:15%; + line-height: 1.45em; + } + + .requestlinks > td + { + padding-bottom: 16px; + } + + .requestlinks div + { + float:right; + } + + .requestlistitem .textbox + { + width:95%; + } + + + +/*-------- End Request List Item --------*/ + + +/*-------- Begin New Request Form --------*/ +#newrequest { + margin-top:5px; + background:#F8F8F8; + border-width:1px; + border-style:solid; + border-color:#E8E8E8; + padding:5px; +} + +#newrequest > div:first-child { + font-weight:bold; +} + +#newrequest .itemheading { + margin-top:5px; +} + +#newrequest textarea { + width:95%; +} + +#newrequest .field-validation-error { + display:block; + color:Red; + font-weight:bold; +} +#newrequest #submit { + margin-top:5px; +} + +/*-------- End New Request Form --------*/ + +/*-------- Request Description Page ------*/ + +.reqDescPage #mainContent { + overflow: auto; +} +.reqDescPage #header { + float: left; + width: 100%; +} + +.reqDescPage { + color: #000 !important; +} + +.reqDescPage #sideNav { + background-color: #fff; +} + +.reqDescPage #header td { + vertical-align: bottom; +} + +.reqDescPage #header #votenumber { + width: 50px; + padding: 6px 12px; + text-align: center; + float: left; +} + +.reqDescPage #header .requestTitle { + margin: 0 0 5px 10px; + width: 85%; +} + +.reqDescPage #header .requestTitle h1 { + margin-bottom: 0px; + font-size: 2em; +} + +.reqDescPage #headerLinks { + +} + +.reqDescPage .votelink +{ + text-align: center; + float: left; +} + +#requestsPage #headerLinks .assignlink +{ + font-weight:bold; + background-color: #eee; + padding: 5px 10px; + float: left; + margin-left: 10px; +} + +.reqDescPage #projectInfo { + clear: both; +} + +.reqDescPage #solutions { + clear: both; + padding-top: 30px; +} + +.reqDescPage .solutionItems { + clear: both; +} + +.reqDescPage .solutionHeader { + border-bottom: 1px solid #ccc; +} + +.reqDescPage .solutionAddContainer { + padding-top: 15px; + float: left; +} + +.reqDescPage #SubmittedProjectLinkUrl { + width: 400px; +} + +.reqDescPage .solutionItem { + margin-top: 25px; + padding: 0 5px 5px 0; + float: left; +} +.reqDescPage .solutionItemDetails { + float: left; + width: 535px; +} + +.reqDescPage .solutionItemLinks { + margin-top: 10px; + clear: both; + float: left; + width: 100%; +} + +.reqDescPage .solutionItemLinks a { + float: left; + margin-right: 10px; + font-weight:bold; + background-color: #eee; + padding: 5px 10px; +} + +.reqDescPage .solutionItem .itemTitle { + font-size: 1.2em; + padding-bottom: 5px; +} + +.reqDescPage .tagsContainer label { + display: none; +} + +.reqDescPage .solutionItem .summaryBox { + padding: .25em 0 .25em 0; + clear: both; + line-height: 1.45; +} + +.reqDescPage .solutionsection { + float: left; + margin-left: 20px; +} +.reqDescPage .completedSolution { + font-size: 1.25em; + padding-top: 4px; +} + +.reqDescPage .requestDescriptionCont, .reqDescPage .requestDicussions { + padding-top: 30px; + clear: both; + overflow: auto; +} + +.reqDescPage .requestDescription { + padding-top: 10px; + line-height: 1.45; +} + +.reqDescPage .requestDicussionsAsk { + padding: 10px 0; +} + +.reqDescPage .watermark { + color:Gray; + } + + +/*-------- Begin Extra Actions Section --------*/ +#extraActions +{ + float: right; + width: 300px; + vertical-align: top; +} + + #extraActions .section + { + padding: 0 0 10px 0; + overflow:auto; + } + + #extraActions .section a + { + font-weight:bold; + } +/*-------- End Extra Actions Section --------*/ + +/*-------- Begin Contribute --------*/ + + +#contributeSection a +{ + font-size:1.1em; +} + +#contributeSection, #sideNav #contributeSection h3, .sidebar #contributeSection h3, #contributeSection h3 +{ + background-color:#fff; + margin: 0 0 9px 0; + padding-left: 0px; +} + +#sideNav #contributeSection h3, .sidebar #contributeSection h3, #contributeSection h3 +{ + font-size:1.65em; + margin-top:42px; +} + +#sideNav #contributeSection, #contributeSection +{ + padding:0 0 41px 0; +} + +#projectPage .sidebar #contributeSection +{ + margin: 10px 0 10px 0; + padding:0 0 5px 0; + +} + +#sideNav #contributeSection > div, .sidebar #contributeSection > div, #contributeSection > div +{ + padding: 0 0 2px 0; + overflow:auto; +} + +#sideNav #contributeSection img, #contributeSection img +{ + float: left; + width: 25px; +} + +#sideNav #contributeSection .contributeAction, .sidebar #contributeSection .contributeAction, #contributeSection .contributeAction +{ + padding:17px 0 0 0; +} + + #contributeSection .contributeAction img + { + background-color:#00749e; + margin-right:9px; + } + + #contributeSection .contributeAction img:hover + { + background-color:#0095C4; + } + + #contributeSection .contributeAction a + { + display:block; + line-height: 1.8; + } + +#uploadLink, #exclamationLink, #myContributionsLink +{ + display:block; + line-height: 1.8; +} + +#myContributionsLink span +{ + color: red; +} + +#feedbackLink +{ + background: url("FeedbackIcon.png") no-repeat; + width: 40px; + height: 40px; +} + +.itemRow .affiliationLink, #editorPicksSection .affiliationLink +{ + background: url("../common/MicrosoftLogo.png") no-repeat; + width: 82px; + height: 18px; +} + + +#contributeSection a+div +{ + +} + +.IE7 #contributeSection a+div +{ + float:left; +} +/*-------- End Contribute --------*/ + + +/*-------- Begin Directory List Footer --------*/ + +#directoryListFooter { + padding:5px; + margin-top:10px; + margin-bottom:10px; + border:1px solid #E6E6E6; + background-color:#F8F8F8; + width:438px; +} + +#directoryListFooter h4 +{ + font-size:1em; +} + +/*-------- End Directory List Footer --------*/ + +/*-------- Begin Editors Picks --------*/ +#editorPicksSection ul +{ + padding-left:0px; + line-height:135%; +} + + #editorPicksSection ul li + { + clear: left; + padding-top: 10px; + list-style:none; + } + #editorPicksSection ul li:first-child { + padding-top: 0; + } + + #editorPicksSection a { + font-weight: normal !important; + } + + #editorPicksSection ul li .thumbnail + { + float: left; + padding: 3px; + max-width: 60px; + } + + #editorPicksSection ul li .thumbnail img { + max-width: 60px; + } +/*-------- End Editors Picks --------*/ + +/*-------- Begin Side Nav Section --------*/ + +#sideNav +{ + width: 215px; + margin-left: 0; + vertical-align: top; + float: left; +} + + #sideNav #sideNavHeader + { + margin-bottom: 10px; + padding-left: 12px; + } + + #sideNav #sideNavHeader a + { + margin-right:3px; + } + + #sideNav .section + { + padding: 0 10px 18px 10px; + } + #sideNav .section h3 + { + + padding: 2px 0 3px 3px; + } + + #sideNav .section ul + { + } + + #sideNav .section ul li + { + padding: 4px 0 2px; + margin-left: 3px; + margin-right: 3px; + } + + + #sideNav .section form > div + { + padding: 4px 0 0 0; + border-bottom: none; + margin: 0 3px 0 3px; + color: #3A3E43; + } + .IE8 #sideNav .section ul li > div.itemText + { + word-wrap: break-word; + width: 225px; + } + #sideNav .section ul li > div.itemCount + { + color: #3A3E43; + } + #sideNav .section a + { + font-weight: normal; + } + + #sideNav .section a:hover + { + text-decoration: underline; + } + +/*-------- End Side Nav Section --------*/ + + + +/*-------- Start Dashboard Page --------*/ + +#dashboardPage { + padding-top:5px; + clear:both; +} +#dashboardPage .contributions .tabContents { + padding: 5px 5px 10px 0; + clear:both; +} + #dashboardPage .contributions .noContributions { + clear:both; + } +#dashboardPage #mainContentWrapper { + float:left; + width:100%; +} + +#dashboardPage #detailsSection { + float:left; + margin-left:-100%; + width:240px; + padding: 0 0 18px 10px; +} + +.IE7 #dashboardPage #detailsSection { + width:auto; + min-width: 300px; + max-width: 300px; +} +.IE7 #dashboardPage #detailsSection .itemBar { + width: 270px; +} + +#dashboardPage #detailsSection h3 { + word-wrap:break-word; +} + +#dashboardPage #mainContent { + margin:0 0 0 250px; + position:relative; +} + +#dashboardPage .dashboardEvenRow +{ + background-color: #ececec; +} +#dashboardPage .dashboardPadding +{ + padding-bottom: 4px; +} +#dashboardPage .dashboardCell +{ + width: 100%; + vertical-align: top; + padding: 1em 0.5em 0.5em 0.5em; + word-wrap: break-word; +} +#dashboardPage .projectManagement +{ + width: 24em; + padding: 0em 1em 0em 1em; + vertical-align: top; + text-align: right; + float: right; +} + +#dashboardPage #subscriptionsLink +{ + position:absolute; + right:5px; +} + +#dashboardPage .itemDelete +{ + vertical-align:top; + padding-top:.8em; + width:30px; +} + +#dashboardPage .itemDeleteText +{ + border-style:solid; + border-width:thin; + border-collapse:collapse; + padding-bottom:2px; + padding-left:4px; + padding-right:4px; + color:Gray +} + +#dashboardPage #myRequestsTab .requestTabHeaders +{ + font-weight:normal; + border-bottom: solid 1px #CCC; + padding-bottom: 10px; + padding-top: 10px; + float: left; + width: 100%; +} + +#dashboardPage #myRequestsTab .requestTabHeaders div:first-child +{ + border: 0; +} + +#dashboardPage #myRequestsTab .requestTabHeaders div +{ + padding: 2px 9px 3px 9px; + font-size: 0.9em; + line-height: 125%; + color:#00749E; + border-left: solid 1px #555; + cursor: pointer; + float: left; + text-align: center; +} + +#dashboardPage #myRequestsTab .requestTabHeaders div.currentRequest +{ + background-color:#fff; + cursor: default; + border-bottom:none; + margin-bottom:-2px; + color:#000; +} + +#dashboardPage #myRequestsTab .requestTabHeaders div.currentRequest a { + color: #000; +} + + +#dashboardPage #myRequestsTab .requestTabHeaders div a +{ + text-decoration:none; +} + +#dashboardPage #myRequestsTab .requestTabContents +{ + clear: both; +} + +#dashboardPage #myRequestsTab .requestTabContents .noResults { + padding-top: 20px; +} + +/*-------- End Dashboard Page --------*/ + +/*-------- Start Upload Page --------*/ + +#UploadPage +{ + margin-left:10px; + max-width:925px; +} + + #UploadPage .watermark { + color:Gray; + } + + #UploadPage .projectTypeChoice { + } + + #UploadPage .projectTypeChoice > div { + padding: 20px 10px 20px 10px; + border: 1px solid darkgrey; + cursor: pointer; + height: 200px; + float: left; + } + + #UploadPage .projectTypeChoice > div + div + { + margin: 0 0 0 5px; + } + + #UploadPage .projectTypeChoice div.current + { + background-color: #E9E9E9; + cursor: default; + } + + #UploadPage .projectTypeChoice div.choice { + font-size: large; + font-weight: bold; + padding: 0 0 10px 0; + } + + #UploadPage .projectTypeChoice div.description { + padding: 0 0 0 17px; + } + + #UploadPage .projectTypeChoice #genericSampleUploadDescription { + padding-top: 5px; + } + + #UploadPage .projectTypeChoice #genericSampleUploadDescription .logos { + overflow: auto; + } + + #UploadPage .projectTypeChoice #genericSampleUploadDescription .vslogo { + background: url(../samples/vslogo.png) no-repeat; + width: 125px; + height: 18px; + } + + #UploadPage .projectTypeChoice #genericSampleUploadDescription .javalogo { + background: url(../samples/javalogo.png) no-repeat; + width: 33px; + height: 60px; + float: left; + } + + #UploadPage .projectTypeChoice #genericSampleUploadDescription .phplogo { + background: url(../samples/phplogo.png) no-repeat; + width: 65px; + height: 35px; + float: left; + margin: 15px 0 0 10px; + } + + #UploadPage .projectTypeChoice #genericSampleUploadDescription .nodejslogo { + background: url(../samples/nodejslogo.png) no-repeat; + width: 90px; + height: 25px; + float: left; + margin: 18px 0 0 10px; + } + + #UploadPage .projectTypeChoice #genericSampleUploadDescription > div+div { + margin-top: 10px; + } + + #UploadPage .projectTypeContents { + clear: left; + padding: 10px 0 0 0; + } + + #UploadPage .projectTypeContents .instruction > div+div { + padding-top: 5px; + } + #UploadPage #libraryContainer { + margin: 5px 0 0 0; + display: none; + } + #UploadPage fieldset + { + margin: 10px 0 30px 5px; + } + + #UploadPage fieldset > * + { + margin-left:10px; + } + + #UploadPage .fieldsetStyleContainer { + margin: 10px 0 0 5px; + } + + #UploadPage fieldset h2, + #UploadPage .fieldsetStyleContainer h2 + { + font-family: 'Segoe UI Semibold','Segoe UI','Lucida Grande',Verdana,Arial,Helvetica,sans-serif; + font-size: 17px; + font-weight: bold; + color: #3A3E43; + border-bottom: 2px solid #EFEFEF; + width: 100%; + padding-bottom: 3px; + margin-left:0px; + margin-bottom:8px; + } + + .IE7 #UploadPage fieldset h2, + .IE7 #UploadPage .fieldsetStyleContainer h2 + { + margin-left:-10px; + } + + #UploadPage fieldset .field-validation-error + { + clear:left; + display:block; + margin-top:4px; + } + + #UploadPage fieldset .required + { + margin-left: 3px; + } + + #UploadPage fieldset .instruction, + #UploadPage .fieldsetStyleContainer .description, + #UploadPage .fieldsetStyleContainer .instruction + { + color: #3A3E43; + margin:0 0 10px 0; + } + + #UploadPage fieldset .faqLink + { + margin: 0 0 10px 0; + } + + #UploadPage fieldset label + { + display:block; + } + + #UploadPage fieldset input[type=text] + { + width:60%; + } + + #UploadPage fieldset input[type=checkbox] + { + float:left; + clear:left; + margin-right:5px; + } + + #UploadPage fieldset input[type=radio] + { + float:left; + clear:left; + margin-right:5px; + } + + #UploadPage fieldset#richDescription textarea + { + width:70%; + height:600px; + } + + #UploadPage fieldset#summary textarea + { + width:60%; + height:100px; + margin-top: 10px; + margin-left: -30px; + } + + .IE #UploadPage fieldset#summary textarea, .IE9 #UploadPage fieldset#summary textarea + { + margin-left: -30px; + overflow: auto; + } + + .FF #UploadPage fieldset#summary textarea + { + margin-left: -30px; + } + + #UploadPage fieldset#summary #SummaryReadOnly + { + width:60%; + margin-top: 10px; + padding-top: 5px; + color: #909082; + } + + #UploadPage fieldset#summary #SummaryCharCount + { + width:60%; + text-align: right; + } + + #UploadPage fieldset#options label + { + margin-bottom:10px; + } + + #UploadPage fieldset#license label + { + margin-bottom:10px; + } + + #UploadPage input[type="text"].tagInput, #UploadPage input[type="text"].listInput + { + width:40%; + float:left; + } + + #UploadPage .addedTags, #UploadPage .addedProjects + { + margin-bottom:15px; + width: 500px; + + } + #UploadPage .addedTags .tag, #UploadPage .addedProjects .projectTitle + { + position:relative; + overflow:hidden; + } + + #UploadPage .addedTags .tag label, #UploadPage .addedProjects .projectTitle label + { + float:left; + width: 450px; + } + + #UploadPage .addedTags .tag a, #UploadPage .addedProjects .projectTitle a + { + position:absolute; + text-align:right; + right:0px; + } + + .fileManager .fileUploadProgressIndicator + { + width: 500px; + } + + .fileManager .uploadProcessingWarning { + margin-top: 5px; + } + + .fileManager .fileUploadProgressIndicator .throbber + { + font-weight: bold; + background: url('./progressIndicatorWhite.gif') no-repeat 10px 0; + padding: 7px 10px 5px 60px; + height: 25px; + margin-left: -10px; + } + + .fileManager #uploadFrame, .fileManager .uploadFrame + { + width:100%; + } + + .fileManager .addLabel + a + { + margin-left:25px; + } + + .fileManager fieldset label { + display: block; + } + + .fileManager .unlocalizedFiles { + color:#808080; + } + + .fileManager .filesContainer + { + margin-bottom:15px; + width: 500px; + + } + + .fileManager .filesContainer .file + { + position:relative; + overflow:hidden; + } + + .fileManager .filesContainer .file .title { + border-bottom: 1px solid #000000; + padding-bottom: 3px; + margin-top: 10px; + margin-bottom: 10px; + } + + .fileManager .filesContainer .file .title .manageLinks + { + float: right; + } + + .fileManager .filesContainer .file .version { + padding: 0 0 20px 10px; + } + + .fileManager .filesContainer .file .version label { + float: left; + font-weight: bold; + } + + .fileManager .filesContainer .file .version span { + float: left; + margin-left: 5px; + } + + .fileManager .filesContainer .file .language { + clear: left; + padding: 0 0 5px 10px; + } + + .fileManager .filesContainer .file .language label { + font-weight: bold; + } + + .fileManager .filesContainer .file .language label + label { + font-weight: normal; + padding-left: 10px; + } + + .fileManager .filesContainer .file .language div { + padding-left: 20px; + } + + .file .requirements { + clear: left; + padding: 0 0 0 10px; + } + + .file .requirements label { + font-weight: bold; + } + + .file .requirements > div { + padding-left: 20px; + } + + .file .requirements .requirementsContent { + padding-top: 5px; + padding-bottom: 10px; + } + + .file .requirements .requirementsContent label { + font-style: italic; + font-weight: normal; + } + + .file .requirements .requirementsContent > div + { + margin-bottom:4px; + position:relative; + } + + .requirementsContent .requirementsRemove { + float:right; + position:absolute; + right:0px; + } + + .requirements .requirementsAddError { + color:#ff0000; + } + + .requirements .reqBrowseButton { + margin-left : 10px; + } + + #UploadPage fieldset .requirements input[type="text"] { + margin-right: 10px; + width: 70%; + } + + .reqBrowsefade + { + position: absolute; + background-color: #aaaaaa; + } + .reqBrowse + { + background-color: #f4f4f4; + border:1px solid #000000; + -border-radius: 5px; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + padding:15px; + position: absolute; + display: block; + } + + .reqBrowsebuttons + { + text-align:right; + position:absolute; + right:10px; + bottom:5px; + } + .reqBrowsebuttons > button + { + color: Blue; + border: none; + background: none; + font-weight: bold; + cursor:pointer; + } + + .reqBrowseclose + { + display:none; + } + + .reqBrowseDialog + { + width: 700px; + clear:both; + overflow:hidden; + padding-bottom: 20px; + } + + .reqBrowseDialog > h2 + { + border-bottom: 1px solid #000000; + padding-bottom: 5px; + } + .reqBrowseDialog > p + { + margin: 15px 0 15px 0; + } + + .reqBrowseSearchCont + { + width: 100%; + background: white; + padding: 0px 2px; + -moz-border-radius: 2px; + border-radius: 2px; + border: 1px solid #888; + margin-bottom: 5px; + height: 29px; + } + .reqBrowseSearchCont .rbboxcont + { + width:90%; + float:left; + margin-top: 2px; + } + + .reqBrowseSearchCont input + { + border:none; + outline:none; + border-color: transparent; + } + .reqBrowseSearchBox + { + margin-right: 4px; + height: 20px; + line-height: 20px; + width: 100%; + padding-left: 4px; + padding-top: 2px; + } + + .reqBrowseSearchBoxDefault + { + color: #AAA; + } + .reqBrowseSearchCont .rbbtncont + { + float: right; + margin-top: 4px; + } + .reqBrowseSearchBtn + { + background: transparent url('searchButton.png') no-repeat 0 0; + width: 22px; + height: 22px; + float:left; + cursor:pointer; + } + + .reqBrowseTabs + { + border-bottom: 5px solid #E8E8E8; + margin:3px 0; + overflow:auto; + } + + .reqBrowseTabs .reqBrowseTabsR + { + color:#fff !important; + } + + .reqBrowseTabs .reqBrowseTabsHighlight + { + color:#000 !important; + background-color:#E8E8E8; + } + + .reqBrowseTabs a + { + padding:5px 12px; + color:#fff; + background-color:#888; + font-weight:bold; + float:left; + margin: 0 4px 0px 0; + } + + .reqBrowsePager td + { + text-align:center; + } + .reqBrowseDialog #Pager + { + margin: 5px 0 15px 0; + } + + .reqBrowseContent + { + height:310px; + overflow:auto; + clear:both; + position:relative; + } + + .reqBrowsePager + { + width:700px; + margin:0 auto; + } + + .reqBrowseContentError + { + color:#ff0000; + margin:5px; + } + .reqBrowseContent .requirementItem + { + border-bottom: 2px solid #E8E8E8; + padding: 4px 0 6px 0; + overflow:auto; + } + .reqBrowseContent .section1 + { + float:left; + width:75%; + padding-left:25px; + position:relative; + } + + .reqBrowseContent .section1 input + { + position:absolute; + left:0px; + } + + .reqBrowseContent .title + { + font-weight:bold; + } + .reqBrowseContent .section2 + { + float:right; + } + + + .progressIndicatorfade + { + position: absolute; + background-color: #FFFFFF; + } + .progressIndicator + { + background-color: #f4f4f4; + border: 1px solid #000000; + -border-radius: 5px; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + padding: 15px; + position: absolute; + display: block; + max-width: 70%; + max-height: 70%; + } + #progressIndicator .progressIndicatorclose + { + display: none; + } + + #progressIndicatorContent + { + font-weight: bold; + padding: 7px 10px 0 10px; + height: 25px; + } + + +/*-------- End Upload Page --------*/ + +/* --- + --- Homepage --- + --- */ +p +{ + margin: 0 0 1px 0; +} +#homePageHeader +{ + float: left; + margin-bottom: 1em; + width: 100%; +} + +.tagline { + font-size: x-small; + position: relative; + top: -11px; +} + +.logo +{ + float: left; + width: 455px; + height: 70px; + font-weight: bold; + font-size: 22px; + margin: 0 10px 0 0; + color: #3A3E43; +} +.logo > img +{ + float: right; +} +.logo > div +{ + color: #3A3E43; + font-weight: bold; + font-size: 22px; +} + +.welcomeInfo +{ + float: left; + width: 700px; +} +.welcomeInfo h2 +{ + font-size: 16px; +} +.welcomeInfo p +{ + margin: 5px 0 0 0; +} + +#siteActions > div +{ + border: 1px solid #BBBBBB; + padding: 15px 5px 0 65px; + height: 55px; + background-repeat: no-repeat; + background-position: 10px 10px; +} +#siteActions a, #siteActions p +{ + margin-top: 5px; +} + +#siteActions .label a +{ + font-size: 1.25em; + font-weight: bold; + margin-bottom: 3px; +} + +#myGalleryBox +{ + background-image: url("MyGalleryIcon.png"); +} + +#findActions +{ + float: right; + padding: 10px; + width: 225px; + height: 50px; + border: 1px solid #BBBBBB; + margin: 0 10px 0 0; +} + +#findBox div:first-child +{ + margin: 0 0 5px 0; + font-weight: bold; +} + +#legalDisclaimer +{ + margin: 0 0 10px; + color: #798072; + font-size: 0.8em; +} + +#siteActions +{ + float: right; + width: 200px; +} +#siteActions > div +{ + margin-bottom: 10px; +} +.homePageProjects +{ + width: 100%; + float: left; +} +.homePageProjects > div +{ + padding-left: 1.5em; +} +.homePageProjects > div:first-child +{ + padding-left: 0; +} + +.homePageProjects .projectList ul +{ + padding: 0; + margin: 0; +} +.homePageProjects li +{ + margin-top: .5em; + padding-bottom: 0.5em; + list-style-type: none; +} +.homePageProjects .itemRow a, .homePageProjects .itemRow a:hover +{ + color: #0054A6; +} + +.projectList > a:first-child +{ + margin-left: 1px; + float: right; +} +.projectList > a, .projectList > a:hover +{ + color: #5BAEDB; +} +.projectListTitle +{ + height: 27px; + font-size: 16px; + font-weight: bold; + line-height: 125%; + background: #E8E8E8; + padding: 5px 5px 5px 25px; +} +.homePageListPager +{ + text-align: right; + margin-bottom: -.5em; +} + +.recentlyAddedProjects +{ + float: left; + width: 32%; +} +.mostPopularProjects +{ + float: right; + width: 32%; +} +.highestRankedProjects +{ + overflow: hidden; +} +* html .highestRankedProjects +{ + float: left; +} +* html .highestRankedProjects > div +{ + width: 100%; +} + +#Pager +{ + text-align:left; +} + +/* Impromptu warning style */ +.ipWarningfade +{ + position: absolute; + background-color: #aaaaaa; +} +div.ipWarning +{ + width: 400px; + position: absolute; + border-radius: 10px; + -moz-border-radius: 10px; + -webkit-border-radius: 10px; + padding: 20px 0 20px 20px; + background-color: #FCE5E6; + border: solid 2px #EE1F25; +} +.ipWarningcontainer +{ + font-weight: bold; +} +.ipWarningclose +{ + display: none; +} +.ipWarningmessage div +{ + position: relative; +} +.ipWarningmessage div div + div +{ + text-align: center; + padding-right: 20px; +} +.ipWarningmessage div div:first-child div +{ + margin-left: 50px; +} +.ipWarningmessage div div:first-child img +{ + position: absolute; + margin: -20px 0 0 0; + top: 35%; +} +.ipWarningbuttons +{ + text-align: center; + margin: 20px 0 0 0; +} +.ipWarning button +{ + padding: 4px 8px 4px 8px; + margin: 2px; + font-weight: bold; + border: solid 1px #A6A3A6; + color: #FFFFFF; + background: #B8BABC; + filter: progid:DXImageTransform.Microsoft.Gradient(GradientType=0,StartColorStr='#B8BABC',EndColorStr='#949699'); +} + +#eyebrow +{ + width: 100%; +} +#siteMessage .unsupportedLocale { + margin: 10px 0 0 243px; + border: solid 1px #CCC; + background: #FCFEC5; + padding: 5px; +} + +#Footer +{ + width: 100%; + height: auto; +} + +.clear +{ + clear: both; +} + +#buildVersion +{ + clear: both; + margin-left: auto; + margin-right: auto; + padding-right: 26px; + padding-top: 8px; + text-align: right; +} + +#page +{ + clear: both; + padding-top: 10px; +} + +#page h1 +{ + padding: 10px 0px; +} + +#ownerBar +{ + background: #EFEFEF; + border: 2px solid #7FCBF5; + text-align: left; + color: Black; + margin: 10px 0 20px 0; + padding: 5px; + word-spacing: 0px; + font-size: medium; +} + +#ownerBar a +{ + color: Blue; + padding: 0 5px 0 5px; +} + + + + +/*-------- Start Tab Control --------*/ + +.tabHeaders +{ + font-weight:normal; + text-transform: uppercase; + border-bottom: solid 1px #CCC; + float: left; + width: 100%; +} + +.tabHeaders div +{ + padding: 7px 19px 8px 19px; + font-size: 0.9em; + line-height: 125%; + color:#00749E; + cursor: pointer; + float: left; + text-align: center; +} + +.tabHeaders div.current +{ + background-color:#fff; + cursor: default; + border: 1px solid #CCC; + border-bottom:none; + margin-bottom:-2px; + color:#000; +} + +.tabHeaders div a +{ + text-decoration:none; +} + +.tabContents +{ + clear: both; +} + +#MainContent .tabHeaders div.current a +{ + color:#000; +} + +.tabContents div.current +{ + display: block; +} +/*-------- End Tab Control --------*/ + +.itemContainer +{ + width: 100%; +} +.itemRow .itemTitle +{ + padding-bottom: 5px; + font-size:1.1em; +} + +.itemRow .itemBody, .itemRow .itemInfo +{ + padding:15px 17px 16px 0; +} +.itemRow .itemDescription +{ + overflow: hidden; + max-height: 80px; +} +.itemRow .itemBody +{ + vertical-align: top; + line-height: 1.4; +} +.itemRow .itemBody a.officialMicrosoftLabel +{ + color: #ACACAC; +} +.itemRow .itemInfo +{ + vertical-align: top; + padding-left: .5em; + line-height: 1.4; + width: 10em; + text-align:right; +} + +.IE7 .itemRow .itemInfo +{ + width:11em; +} + +.itemRow .itemInfo .ratingStars +{ + float: left; +} +.itemRow .itemInfo .ratingCount +{ + padding: 0 0 0 5px; + float: left; +} +.itemRow .ratingInfo +{ + text-align: center; +} + +.itemRow .affiliationLink, #editorPicksSection .affiliationLink +{ + position: relative; + top: 3px; +} + +#editorPicksSection a.officialMicrosoftLabel +{ + color: #ACACAC; +} + +.itemRow .tagsContainer label { + display:none; +} + +.editorPickedItem +{ + background-color:#F8F8F8; +} + +.editorPickedText +{ + font-size:1.25em; + padding-bottom:2px; +} +.editorPickedItem > td +{ + border-top:6px solid #fff; +} + +.dirSubHeading +{ + margin-bottom:15px; +} + +#searchPage .dirSubHeading h2 +{ + line-height:1.4; + font-size:1.1em; +} + +#searchPage .dirSubHeading h2 span +{ + padding-top:5px; + display:block; +} + +#searchPage .dirSubHeading h1, #searchPage .dirSubHeading h2 +{ + clear:none; + padding-left:0px; +} + +.dirSubHeading .dirSubLinks +{ + font-size:1.2em; + padding-top:5px; +} + + +.summaryBox +{ + padding: .25em 0 .25em 0; + clear: both; + line-height:1.45; +} + +/*-------- Start Rating Stars --------*/ + +.RatingStar +{ + width: 11px; + height: 11px; + padding: 0 8px 0 0; + background-position: center; + float: left; +} + +.FilledRatingStar, .HalfRatingStar, .EmptyRatingStar, .FilledRatingStarHover +{ + width: 11px; + height: 11px; + padding: 0px 1px 0px 0px; + margin-top: 2px; +} + +.FilledRatingStar +{ + background: url(../samples/fullStar.png) no-repeat; +} + +.ownerRating .FilledRatingStar +{ + background: url(../samples/fullStar.png) no-repeat; +} + +.FilledRatingStarHover +{ + background: url(../samples/fullStarHover.png) no-repeat; + +} + +.HalfRatingStar +{ + background: url(../samples/halfStar.png) no-repeat; + +} + +.EmptyRatingStar +{ + background: url(../samples/emptyStar.png) no-repeat; + +} + +.EditStarMode .RatingStar +{ + cursor: pointer; +} + + + +/*-------- End Rating Stars --------*/ + +.discussionFormTable +{ + width: 100%; + table-layout: fixed; +} + + +#ReviewsTabPane .seeAllLink, #DiscussionsTabPane .seeAllLink +{ + margin-top: 10px; + text-align: center; +} + +/*-------- Start DiscussionsTab --------*/ + +.threadActions +{ + text-align: right; + margin-top: 10px; + float: right; +} + +#DiscussionsTabPane .reply, #DiscussionsTabPane .toggleDiscussion +{ + cursor: pointer; +} + + +#defaultDicussionText, #newDiscussion +{ + padding-top: 10px; +} +#DiscussionsTabPane .posts +{ + display: block; +} +#DiscussionsTabPane .threadHeader .left +{ + float: left; +} +#DiscussionsTabPane .threadHeader .right +{ + float: right; +} +#DiscussionsTabPane .normal +{ + font-weight: normal; +} + +#DiscussionsTabPane .threadHeader +{ + position: relative; + background-color: #ECECEC; + padding: 4px 10px 4px 10px; +} + +#DiscussionsTabPane .threadHeader .title +{ + color: #000000; + font-weight: bold; + margin-bottom: .7em; +} +#DiscussionsTabPane .threadHeader .label +{ + color: #000000; +} +#DiscussionsTabPane .postMeta +{ + color: #666666; +} +#DiscussionsTabPane .threadHeader .value +{ + font-weight: bold; + color: #000000; +} + +#DiscussionsTabPane .reply +{ + font-weight: normal; + cursor: hand; +} + +#DiscussionsTabPane ul li +{ + list-style-type: none; + list-style-image: none; + padding-bottom: 10px; +} + +#DiscussionsTabPane ul +{ + padding-left: 0px; + margin-left: 0px; + padding-right: 2px; +} + +.IE #reviewList .right +{ + margin-right: -1em; +} + +#newDiscussion +{ + margin: 0 0 15px 0; +} +#newDiscussion #Title +{ + width: 50%; +} +#newDiscussion textarea +{ + width: 99%; + height: 10em; +} + +#DiscussionsTabPane +{ + margin-left: 0px; + padding: 0 1em 1em 1em; +} + + +.postMeta +{ + float: right; +} + +.postReply +{ + cursor: hand; + float: right; + font-weight: bold; +} + +.postSaveReply +{ + display: none; +} + +.postSaveReply textarea +{ + width: 99%; + margin-bottom: 4px; + height: 8em; +} + +.toggleDiscussion +{ + cursor: hand; +} + +.saveReplyErrorMessage +{ + display: none; + margin: 0 0 4px 0; + color: Red; + font-weight: bold; +} + +#discussionListItem .avatar +{ + float: left; + padding: 5px; + vertical-align: middle; +} + +#discussionListItem .discussion +{ + margin-left: 55px; + padding: 0 5px 5px 5px; + vertical-align: top; +} + +.IE7 #discussionListItem .avatar +{ + margin-top: 15px; +} + + +/*-------- End DiscussionsTab --------*/ + + +.flotChart +{ + height: 300px; +} + +#projectMenuBarTop +{ + padding: 10px 0 20px 0; + font-weight: bold; + font-size: 25px; +} +.dayHeader +{ + font-weight: bold; +} + +/*-------- Start StatsPage --------*/ +#statsPage +{ + border: none; + background-color: Transparent; + margin-top: 1em; +} + +#statsPage .rangeBox +{ + padding: 5px; + background-color: #ECECEC; + border: solid 1px #C2C2C2; + float: left; +} +#statsPage .statBox +{ + margin-top: 1em; + margin-bottom: 10px; + overflow: hidden; + display: none; +} +#statsPage .statBox h3 +{ + font-size: 1.1em; + display: inline; +} + +#statsPage #statMessage +{ + margin-top: 1em; + display: none; +} + +#statsPage #statDownloadBox img { + float: left; +} + +#statsPage .statDownloadLink { + padding-left: 5px; + vertical-align: middle; +} + +#pointTooltip +{ + border: solid #000000 1px; + height: 35px; + background-color: #EEEEEE; + position: absolute; + display: none; + text-align: center; + padding: 9px; + border-radius: 4px; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + z-index: 1000; + white-space: nowrap; +} + +.flotChart +{ + height: 300px; +} + +/*-------- End StatsPage --------*/ + + +/***************************************************************/ +/* TagAutoComplete Styles */ +/***************************************************************/ +.AutoCompletePanel +{ + font-size: 95%; + border: solid .1em #999; + background-color: #f0f0f0; + padding: .15em; +} + +.AutoCompletePanel div.Row +{ + color: #000; + cursor: pointer; + background-color: transparent; + padding: .15em .25em; + text-align: left; +} + +.AutoCompletePanel div.Selected +{ + color: #fff; + background-color: #6D6D6D; +} + + +/*-------- Start Subscription Form --------*/ + +#subscribeForm +{ + background-color: #D3D3D1; + border: 1px solid #000000; + -border-radius: 5px; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + padding: 15px; + position: absolute; + display: block; +} + +#subscribeForm .subscribeFormbuttons +{ + text-align: right; + margin-top: 10px; +} +#subscribeForm .subscribeFormbuttons > button +{ + color: Blue; + border: none; + background: none; + font-weight: bold; + cursor: pointer; +} + +#subscribeForm .subscribeFormclose +{ + display: none; +} + +#subscribeForm table +{ + margin-bottom: 15px; +} + +#subscribeForm table th +{ + text-align: left; + font-weight: bold; + border-bottom: 1px solid #000000; +} + +#subscribeForm table td +{ + padding: 5px 10px 5px 20px; +} + +#subscribeForm .rowHeading td +{ + font-weight: bold; + border-bottom: 1px solid #FFFFFF; +} + +#subscribeForm table tr td:first-child +{ + padding: 5px 40px 5px 0; +} + + +/*-------- End Subscription Form --------*/ + +/*-------- Start Tag Browser --------*/ + +.tagBrowserfade +{ + position: absolute; + background-color: #aaaaaa; +} +#tagBrowser +{ + background-color: #f4f4f4; + border:1px solid #000000; + -border-radius: 5px; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + padding:15px; + position: absolute; + display: block; +} + +#tagBrowser .tagBrowserbuttons +{ + text-align:right; + margin-top: 10px; +} +#tagBrowser .tagBrowserbuttons > button +{ + color: Blue; + border: none; + background: none; + font-weight: bold; + cursor:pointer; +} + +#tagBrowser .tagBrowserclose +{ + display:none; +} + +.tagBrowserContainer { + width: 450px; +} + +.tagBrowserContainer h2 { + border-bottom: 1px solid #000000; + padding-bottom: 5px; +} +.tagBrowserContainer > p { + margin: 15px 0 15px 0; +} + +.tagBrowserContainer .tags { + margin: 5px; + height: 225px; + overflow-y: scroll; +} + + +/*-------- End Tag Browser --------*/ + +/*-------- Start List Filter Box --------*/ + +div#filterInputBox +{ + overflow:auto; + min-width:225px; +} + +.filterBox +{ + width: 100%; + background: #FFFFFF; + padding: 0px 2px; + height: 25px; + -moz-border-radius: 2px; + border-radius: 2px; + border: 1px solid #888888; + table-layout: auto; + margin-bottom:5px; +} + + .filterBox #filterImageCell + { + text-align: right; + padding: 0px; + vertical-align: middle; + } + + .IE7 .filterBox #filterImageCell + { + padding:2px 2px 0 0; + } + + .filterBox #filterImg + { + background: transparent url('searchButton.png') no-repeat 0 0; + width: 22px; + height: 22px; + } + +table.filterBox +{ + table-layout: fixed; +} + + .filterBox .stxtcell + { + padding-right: 4px; + width:90%; + } + + .filterBox .stxtcell > input + { + margin-right: 4px; + height: 26px; + line-height:26px; + width: 100%; + padding: 0px; + padding-left: 4px; + padding-top: 2px; + border: none; + } + + .IE7 .filterBox .stxtcell > input + { + height: 20px; + } + + .filterBox .stxtcell > input.stxtinptpassive + { + color: #ACACAC; + } + +/*-------- End List Filter Box --------*/ + +/*-------- Start Notifications --------*/ + +#notificationsSummaryBox +{ + font-weight: bold; + padding: .5em; +} + + +.notificationsByDay, .notifications +{ + /*list-style: none;*/ +} + +.dayHeader +{ + border-bottom: 1px solid silver; + margin-top: 1em; + padding-bottom: .5em; +} + +.notifications +{ + margin-top: .5em; +} + +ul.notifications li +{ + line-height: 1.5em; +} + +ul.notifications li.unread +{ + font-weight: bold; +} + +ul.notifications li ol, ul.notifications li ul +{ + margin-left: 1.5em; +} + +/*-------- End Notifications --------*/ + +/*-------- Start ProjectDetailsPage --------*/ + +#projectPage #projectInfo +{ + position: relative; + margin-top: 5px; + height: 100%; +} +#projectInfo .section +{ + float: left; + margin-bottom: 0px; + height: 100%; +} + + #projectInfo .section .itemBar, #projectInfo .section .itemBarLong + { + clear: both; + padding: 7px 0 7px 0; + overflow:auto; + } + + #projectInfo .section .itemBarLong:first-child + { + padding-top:3px; + } + + .IE7 #projectInfo .section .itemBar, .IE7 #projectInfo .section .itemBarLong + { + padding-top: 5px; + } + + #projectInfo .section .itemBar > label, #projectInfo .section .itemBarLong > label + { + width: 130px; + float: left; + text-transform: capitalize; + } + + #projectInfo .section .itemBar div#yourRating { + float:left; + } + + #projectInfo .section .itemBar div.RatingStar { + margin:2px 1px 0 0; + } + + #projectInfo .section .itemBar div#RatingCount { + padding: 0 0 0 3px; + } + + #projectInfo .section .itemBar .ratingsWithCountContainer img { + vertical-align:top; + float:left; + padding-right: 4px; + } + + #projectInfo .section .itemBar > span, #projectInfo .section .itemBarLong > span, #projectPage .section .itemBar > div + { + float: left; + } + + #projectInfo .section .itemBar, #projectInfo .section .itemBarLong { + width: 100%; + } + + #projectInfo .section .itemBar > span { + float: none; + } + + #projectInfo .section .itemBar > span .shareThisItem { + white-space: nowrap; + } + + #projectInfo .section .itemBarLong div + { + margin-left: 130px; + padding: 0; + } + + #projectInfo .section .viewonlinecont + { + background-color:#d3d3d3; + padding:5px 10px; + margin-top:10px; + float:left; + font-weight:bold; + } + +#projectInfo #sectionLeft +{ + width: 50%; +} +#projectInfo #sectionRight +{ + width: 50%; +} +.IE7 #projectInfo #sectionRight +{ + width: auto; +} + +#projectPage h2.projectSummary +{ + font-weight:normal; + font-size: 1.1em; + margin-bottom: 11px; + line-height:1.4; + word-wrap: break-word; +} + +.IE7 #projectPage h2.projectSummary +{ + font-family: 'Segoe UI' , 'Lucida Grande' ,Verdana,Arial,Helvetica,sans-serif; +} + +.IE #projectPage .projectTitle, .IE9 #projectPage .projectTitle +{ + width: 100%; +} + +#projectPage #reportAbuse +{ + float: left; + font-size: x-small; +} + +#projectPage .hiddenSidebar { + display: none; +} + +#projectPage .fullProjectBody { + margin-left:-275px; +} + +.IE8 #projectPage #userCard { + float: left; + height: auto; +} + +#projectPage #userCard .avatar img { + max-width: 100px; + max-height: 100px; +} + +#projectDetails +{ + overflow:hidden; +} + +#projectBody +{ + width: 100%; + overflow:hidden; +} + + #projectDetails > div:first-child + { + margin: 5px 0 0 260px; + } + + #projectBody > div:first-child + { + margin: 20px 0 0 260px; + } + + .IE7 #projectContent .tabHeaders + { + overflow:hidden; + margin-bottom:-20px; + } + +#projectPage .sidebar +{ + float: left; + width: 215px; + margin-right: -250px; +} + + #projectPage .sidebar .section + { + margin: 20px 0 10px 0; + } + + #projectPage .sidebar .section .titleBar h3 + { + padding: 0 0 2px 0; + } + + #projectPage .sidebar .section .itemBarRight + { + min-height: 21px; + position: relative; + padding-top: 5px; + } + + #projectPage .sidebar .section .titleBar + { + margin-bottom: 5px; + } + + #projectPage .sidebar .section .authorItem + { + padding: 0 0 5px 10px; + } + + #projectPage .sidebar .section .authorItem a + { + display:block; + float:left; + max-width:170px; + } + + #projectPage .sidebar .section .authorItem > div + { + float:right; + } + + #projectPage .sidebar #advertisement + { + margin-top: 20px; + } + + #projectPage .sidebar #advertisement .label + { + text-align: center; + } + + #projectPage .sidebar #moreFromAuthor + { + width:225px; + margin: 20px 0 10px 0; + float:left; + } + + #projectPage .sidebar #moreFromAuthor .bottomBar { + padding: 5px 0px 5px 25px; + text-align: right; + } + +#projectPage #Collections { + min-height:22px; + min-width:169px; +} + +#projectPage #Collections .bevelButton { + background-color: #8CC63F; +} + +#projectPage .bevelButton +{ + font-weight: bold; + border-radius: 3px; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + color: white; + padding: 2px 10px 3px; + text-align: center; +} + +#projectPage #DiscussionsTabPane .bevelButton +{ + font-weight: normal; + color: black; + padding: 1px 10px 1px; +} + +#projectPage #Downloads { + padding: 0 0 8px 0; + float: left; +} + #projectPage #Downloads > div:first-child { + float: left; + margin: 15px 0 0 0; + height:35px; + line-height:1.6; + width: 130px; + } + #projectPage #Downloads label + { + font-size:1.25em; + } + #projectPage #Downloads input + { + min-width: 100px; + padding: 3px 10px 3px 10px; + margin: 3px 10px 0 10px; + font-weight: bold; + float: left; + } + + #projectPage #Downloads .button + { + background-color:#007494; + color:#fff; + padding:5px 15px; + margin: 15px 15px 0 0; + float:left; + } + + #projectPage #Downloads .button:hover + { + background-color:#0095c4; + text-decoration:none; + } + +#projectPage #projectBody .attachments { + margin: 0 0 15px 0; +} + + #projectPage #projectBody .attachments label { + float: left; + } + + #projectPage #projectBody .attachments .files a, #projectPage #projectBody .attachments .files span { + float: left; + padding: 0 5px 0 5px; + } + +#publishBar +{ + border: 1px solid #707070; + background-color: #F8F8F8; + margin-top: 10px; +} + +#sourceItem { + height: 610px; +} + #sourceItem > div:first-child { + padding: 20px 5px 0 15px; + font-weight: bold; + } + #sourceItem > div+div { + height: 560px; + padding: 10px; + overflow: auto; + } + #sourceItem .copyCode { + font-weight: normal; + margin: 0 15px 0 0; + float: right; + } + +.sourceList { + height: 600px; + padding: 5px; + border-top: 1px solid #CCC; + margin-top: -1px; +} + + .sourceList .sourceListTabHeader + { + margin:20px 10px; + } + .sourceList .sourceListTabs + { + margin-bottom:20px; + border-bottom: 1px solid #CCC; + float:left; + width:100%; + } + .sourceList .sourceListTabs .languageTab { + padding:5px 10px 5px 10px; + font-weight: bold; + float: left; + margin: 0 3px 0px 0; + color:#00749E; + } + .sourceList .sourceListTabs .languageTab:hover + { + color: #0095C4; + } + + .sourceList .sourceListTabs .selectedLanguage { + background-color: #fff; + color: #000; + border: 1px solid #ccc; + border-bottom:none; + margin-bottom:-2px; + } + + .sourceList .sourceListTabs .unselectedLanguage { + cursor: pointer; + } + + .sourceList .endTabs { + clear: both; + } + + .sourceList .sourceListContent { + padding-top: 5px; + } + + +.sbfc, +.sbfe +{ + white-space: nowrap; + text-indent: 20px; + cursor: pointer; + padding: .2em 0em .2em 0em; + background-repeat: no-repeat; + background-position: left center; + font-weight: bold; + text-decoration: none !important; +} + +.sbfc +{ + background-image: url(../samples/node_closed.gif); +} + +.sbfe +{ + white-space: nowrap; + background-image: url(../samples/node_opened.gif); +} + +.ndbf +{ + white-space: nowrap; + text-indent:20px; +} + +.sbf +{ + white-space: nowrap; + background: url(../samples/bullet.gif) no-repeat 4px -1px; + cursor: pointer; + text-indent: 20px; + white-space: nowrap; + padding: .1em 0em .1em 0em; +} + +.sbsf, +.sbf:hover +{ + color: #000; + text-decoration: none !important; + +} + +.sbsf +{ + color: #000 !important; + background-color: rgb(232, 232, 232); +} + +.sbf:hover +{ + color: #ce8b10; +} + +/*-------- Translate --------*/ +.translatePage { + width: 900px; +} +.translatePage .fileManager { + margin-bottom:20px; +} +.translatePage .fileManager h4 { + margin-top:10px; +} +.translatePage #formContainer { + width: 100%; +} +.translatePage .formLabel { + width: 300px; + padding: 5px; +} + .translatePage .textInput { + width: 425px; +} +.translatePage TEXTAREA.richText { + height: 600px; + width: 620px; +} +.translatePage TEXTAREA.unadornedEditor { + height: 600px; +} +.translatePage .formWideLabel, .translatePage .richText { + padding: 5px; +} +.translatePage .formWideLabel, .translatePage .unadornedEditor { + width: 750px; + padding: 5px; +} +.translatePage #languageSelection { + margin: 15px; + display: inline-block; +} + +.translateTab, .translateTabSelected { + font-weight: bold; + float: left; + text-align: center; + margin: 10px; + padding: 7px; + background: #E8E8E8; + white-space: nowrap; + cursor: pointer; +} + +.translateTabSelected +{ + color: White; + background: Gray; +} + +.translateTabSelected .translateLabel, .translateTab .translateLabel +{ + white-space: nowrap; + float: left; + padding: 0 5px 0 5px; +} + +.translateTab #deleteLanguage, .translateTab #moreLanguages +{ + padding-left: 10px; +} + +.translateLabel #deleteLanguage { + color: #FFFFFF; +} +/*-------- End Translate --------*/ +/*-------- Begin Eula --------*/ + + +#eulaPagefade +{ + position: absolute; + background-color: #FFFFFF; +} + +#eulaPage +{ + background-color: #f4f4f4; + border: 1px solid #000000; + -border-radius: 5px; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + padding: 15px; + display: block; + max-width: 70%; +} + +#eulaPage .eulaPageclose +{ + text-align: right; +} + +#eulaPage .eulaPagemessage +{ + overflow: auto; + max-height: 350px; +} + + #eulaPage .eulaPagemessage h1 + { + margin: 0 0 5px 0; + } + +#eulaPage .eulaPagebuttons +{ + text-align: right; + margin-top: 10px; +} +#eulaPage .eulaPagebuttons > button +{ + color: Blue; + border: none; + background: none; + font-weight: bold; + cursor: pointer; +} + + + #eulaPage #eula #documentText + { + line-height: normal; + } + +/*-------- End DocumentView --------*/ +/*-------- Begin FAQ --------*/ + +#FAQPage #TableOfContents h2 +{ + padding: 5px; + border-bottom: 2px solid #EFEFEF; + margin: 0 0 10px 0; + max-width: 70%; +} + +#FAQPage .FAQSection +{ + padding: 10px 0px; + width: 70%; +} + + #FAQPage .FAQSection h2 + { + padding: 5px; + border-bottom: 2px solid #EFEFEF; + } + + #FAQPage .FAQSection h3 + { + padding: 5px; + } + + #FAQPage .FAQSection ul, #FAQPage .FAQSection ol + { + margin: 0; + } + + #FAQPage .FAQSection #description > div + { + overflow: auto; + padding-left: 10px; + } + + #FAQPage .FAQSection #description img + { + float: left; + } + + #FAQPage .FAQSection #description div > div + { + padding-top: 10px; + float: left; + } + + #FAQPage .FAQSection > div > div + { + padding: 0 15px; + } + + #FAQPage #Reputation th, #FAQPage #Reputation td + { + padding-left: 20px; + } + +/*-------- End FAQ --------*/ +/*-------- Begin DocumentView --------*/ + +#documentView #documentText +{ + line-height: normal; +} + +/*-------- End DocumentView --------*/ + +.Opera wbr:after +{ + content: "\00200B"; +} + + +.IE9 wbr:after +{ + content: "\00200B"; +} + +.IE8 wbr { + width: 0px; + display: inline-block; + overflow: hidden; +} + +/*-------- Begin FileManager --------*/ + +.uploadControlNoError + { + height:30px + } +.uploadControlWithError + { + height:80px; + } +/*-------- End FileManager --------*/ + +/*-------- Begin User Card --------*/ +#userCard .titleBar { + border-bottom: solid 5px #E8E8E8; + margin: 10px 0 5px 0; +} + #userCard .titleBar h3 + { + font-size: 1.0em; + font-weight: bold; + line-height: 125%; + padding: 0 0 2px 0; + } + +#userCard .userFeed { + float: right; +} + +#userCard .bio { + max-width:300px; + white-space: normal; +} + +#userCard .avatar +{ + padding: 5px 5px 10px 5px; + margin: 0 0 3px 0; + text-align: center; +} + +#userCard .itemBar { + padding: 5px; +} + + #userCard .itemBar label + { + float: left; + text-transform: capitalize; + } + + #userCard .itemBar label+span { + display: block; + margin-left: 100px; + } + +#userCard .collapsableSidebar { + clear:both; + width:100%; + margin-top: 5px; +} + +/* Profile Overrides */ +#userCard +{ + padding: 0 0 10px 0 +} + +#userCard .profile-usercard +{ + width:225px; +} + +#userCard .profile-usercard, #userCard .profile-inline, #userCard .profile-inline-header +{ + border:0px; + background-color:#fff; +} + +#userCard .profile-userimage-large +{ + margin-bottom:10px; + border:none; + width:auto; + height:auto; +} + +#userCard .profile-inline .profile-inline-header-details +{ + width:100%; + display:block; + clear:both; + margin-left:0px; +} + + + +/*-------- End User Card --------*/ +/*-------- Begin Description Progress Meter --------*/ + +#descriptionProgressMeter { + float: right; + border-top: 1px solid #DADADA; + border-left: 1px solid #DADADA; + width: 210px; + padding-left: 7px; +} + + #descriptionProgressMeter h4 { + font-weight: bold; + } + + #descriptionProgressMeter #progressGraphic { + border: 1px solid #888888; + width: 205px; + margin: 10px 0; + background-color: #E9E9E9; + padding: 1px 0 0 1px; + } + + #descriptionProgressMeter #progressGraphic div { + background-image: url("../common/progress_meter.png"); + padding-left: 5px; + } + + #descriptionProgressMeter #progressText { + font-weight: bold; + margin: 5px 0; + + } + + #descriptionProgressMeter #goodDescriptionText p+p { + padding: 5px 0; + } + + #descriptionProgressMeter #goodDescriptionText > div { + margin-top: 5px; + width: 150px; + border-radius: 5px; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + overflow: auto; + } + + #descriptionProgressMeter #goodDescriptionText div img { + float: left; + } + + #descriptionProgressMeter #goodDescriptionText div div { + margin: 7px 0 5px 10px; + } + +/*-------- End Description Progress Indicator --------*/ + +/*-------- Start Sample Pack Tab View --------*/ +.SamplePackTab #headerBar +{ + padding: 15px 5px 20px 5px; + font-weight: bold +} + .SamplePackTab #headerBar .reportCount + { + padding-top: 2px; + } + .SamplePackTab #headerBar .samplePackSort + { + float: right; + color: #666; + } +/*-------- End Sample Pack Tab View --------*/ \ No newline at end of file diff --git a/samples/winrt/ImageManipulations/description/Combined.css b/samples/winrt/ImageManipulations/description/Combined.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/samples/winrt/ImageManipulations/description/Galleries.css b/samples/winrt/ImageManipulations/description/Galleries.css new file mode 100644 index 0000000000..ac2e94ee84 --- /dev/null +++ b/samples/winrt/ImageManipulations/description/Galleries.css @@ -0,0 +1,418 @@ +/* *************************************************** +Galleries.css - Common Structure + +This is where we define common layout for structures that are truly close to common across the different +Galleries sites. To make sure this works we need to follow certain conventions. + +1. Define each logical structure in its own section with its own comment block that gives the section +a Name, Description and defines the root element if one exists (i.e #someElement). Also, mark the closing block. + +2. Indent styles in a section to represent if it is a child of a previous element. +i.e. #someDiv{ + } + #someDiv ul { + } + +3. Do not include brand specific information here like colors and fonts unless they are *really* common. + +4. If there is an element that you know will be overridden in each brand stylesheet still include it here with an empty definition. +This will aid in knowing what section to override and what selectors to use. + +i.e. #someSction a { + } + +5. When you add a new section also update the Table of Contents below so that we have a quick overview of the sections + + *****************************************************/ + +/**************************************************** +Table of Contents + + Global - global classes + + FileAttachmentDisplay - The list of attached files under the editor + Eyebrow - The breadcrumb control at the top of the master page + Pager - The common paging control, used for browsing pages of search results + Profile User Card - Elements in the profile usercard control + SideNav - The navigation side bar that contains the search filters + + +*****************************************************/ + +/******************************** +Name: Global +Root: none +Description: truly global classes +********************************/ +body { + text-align: left; + direction: ltr; +} + +img.rss { + background: url(../../../GlobalResources/Images/Rss.png) no-repeat; + background-position: 0px 0px; + height: 17px; + width: 17px; +} +/* End Global Section */ + +/******************************** +Name: FileAttachmentDisplay +Root: #fileAttachmentDisplay +Description: The list of attached files under the editor +********************************/ +#fileAttachmentDisplay { +} + #fileAttachmentDisplay .attachment { + margin-right: 10px; + float: left; + } + + #fileAttachmentDisplay .attachment .displayAttachment { + padding: 0px 0 13px 0; + float: left; + } + + #fileAttachmentDisplay .attachment .removeAttachment { + background-image: url('/Content/Common/delete.png'); + display: block; + width: 16px; + height: 16px; + float: left; + } +/* End FileAttachmentDisplay Section */ + + +/******************************** +Name: Eyebrow +Root: .EyebrowContainer +Description: The breadcrumb control at the top of the master page +********************************/ +.EyebrowContainer { +} + .EyebrowContainer div.EyebrowElement{ + display:inline; + } + + .EyebrowContainer .EyebrowElement{ + font-weight:normal + } + .EyebrowContainer .EyebrowLeafLink{ + color:#000; + } +/* End Eyebrow Section */ + +/******************************** +Name: Pager +Root: #Pager +Description: The common paging control, used for browsing pages of search results +********************************/ +#Pager { +} + #Pager div{ + display:inline; + } +/* End Pager Section */ + +/******************************** + +Name: Profile User Card +Root: #dashboardPage #userCard +Description: Elements in the profile usercard control + +********************************/ + #dashboardPage #userCard .profile-usercard-inline { + margin: 5px 0 10px 0; + } + + /* #dashboardPage #userCard .profile-usercard { + width: 288px; + } +/* End Profile User Card Section */ + +/******************************** + +Name: Discussion +Root: #DiscussionsTabPane +Description: Defines the layout of the dicussion + + +********************************/ +#DiscussionsTabPane { +} + + #DiscussionsTabPane .itemHidden + { + background: lightgrey; + } + + #discussionListItem { + } + + .discussion .postActions + { + float: right; + } + + #discussionListItem .postItem + { + white-space: pre-wrap; + word-wrap: break-word; + font-size:1em; + } + +/* End Discussion Section */ + + +/******************************** + +Name: SearchDefaultLocale +Root: .searchDefaultLocale +Description: Defines the layout of the include english result checkbox on the Browse Page + + +********************************/ +.searchDefaultLocale +{ + float: right; + margin: 20px 0 0 5px; +} + .searchDefaultLocale input + { + vertical-align:top; + } + .searchDefaultLocale span + { + margin-left: -3px; + } +/*-------- End SearchDefaultLocale --------*/ + + +/******************************** + +Name: SideNav +Root: #sideNav +Description: Defines the layout of the naviation elements on the side of the Browse Page + These represent the different filters like Code Language, Category and Tag + + +********************************/ + +#sideNav { + width: 250px; + vertical-align:top; + background-color:#eee; +} + #sideNav h3 { + } + + #sideNav .section { + padding: 0 0 10px 0; + position: relative; + } + + #sideNav .section a { + } + + #sideNav .section a:hover { + } + + #sideNav .section > div { + padding:5px 5px 5px 0; + line-height:150%; + } + + #sideNav .section ul { + list-style-type:none; + padding:0px; + margin:0px; + } + + #sideNav .section ul li { + position: relative; + padding: 5px 5px 5px 0; + } + + #sideNav .section ul li .selectedFilter { + float: left; + padding-right: 5px; + } + + #sideNav .section div.itemCount { + float: right; + } + + #sideNav .section form input[ type = "checkbox"] { + margin: 0px 4px 0px 0px; + vertical-align: middle; + } +/* End SideNav Section */ + +/*----------- Contribution Logos *******/ +.contributionLogo { + float: left; + position: relative; + margin-right: 6px; +} + +.logo_visualstudio { + background: transparent url('../common/logos/visualstudio.png') no-repeat; + width: 23px; + height: 12px; + margin-top: 3px; +} +.logo_allinonecode { + background: transparent url('../common/logos/1code.png') no-repeat; + width: 14px; + height: 16px; +} +.logo_exchange { + background: transparent url('../common/logos/exchange.png') no-repeat; + width: 14px; + height: 16px; +} +.logo_ie { + background: transparent url('../common/logos/ie.png') no-repeat; + width: 16px; + height: 16px; +} +.logo_office { + background: transparent url('../common/logos/office.png') no-repeat; + width: 17px; + height: 16px; +} +.logo_windows { + background: transparent url('../common/logos/windows.png') no-repeat; + width: 17px; + height: 16px; + } +.logo_azure { + background: transparent url('../common/logos/windowsazuredark.png') no-repeat; + width: 16px; + height: 16px; +} + +.logo_windowsphone { + background: transparent url('../common/logos/windowsphone.png') no-repeat; + width: 16px; + height: 16px; + } + + .contributionLogoTip { + position: absolute; + display: none; + border: solid 1px #CCC; + color: #333; + background-color: #F0F0F0; + font-size: 11px; + font-family: "Segoe UI",Sans-Serif; + box-shadow: 3px 3px 5px #888; + -moz-box-shadow: 3px 3px 5px #888; + z-index: 1003; + padding: 5px; + min-width: 250px; + } + +/*----------- End Contribution Logos *******/ + +.clear +{ + clear: both; +} + +.customcontributionLogoTip { + position: absolute; + display: none; + border: solid 1px #CCC; + background-color: white; + color: #333; + font-size: 11px; + font-family: "Segoe UI",Sans-Serif; + box-shadow: 3px 3px 5px #888; + -moz-box-shadow: 3px 3px 5px #888; + z-index: 1004; + padding: 5px; + min-width: 250px; +} + +.customcontributionTittle { + font-size: 14px; + margin-left: 90px; +} + +.customcontributionDiscription { + font-size: 13px; + margin: 10px 5px; + text-align: justify; +} + +.customcontribution { + float: left; + position: relative; + margin-right: 6px; +} + +.customcontributionLink { + margin-left: 5px; +} + +.customcontributionlogo { + float: left; + padding: 0 10px; + margin: 0; + width: 70px; + height: 70px; + background-repeat: no-repeat; +} + + +.logo_azure_large { + background-image: url('../common/logos/windowsazure_large.png'); +} +.logo_visualstudio_large { + background-image: url('../common/logos/visualstudio_large.png'); +} +.logo_exchange_large { + background-image: url('../common/logos/exchange_large.png'); +} +.logo_ie_large { + background-image: url('../common/logos/ie_large.png'); +} +.logo_office_large { + background-image: url('../common/logos/office_large.png'); +} +.logo_windows_large { + background-image: url('../common/logos/windows_large.png'); +} +.logo_windowsphone_large { + background-image: url('../common/logos/windowsphone_large.png'); +} + +/* Custome Header */ +.dirSubHeading .windowssdk .container +{ + background: #FF3300 url('wpappsbackground.png') no-repeat; + color: white; + padding: 8px 10px 18px 170px; +} + +.dirSubHeading .windowssdk .container h1, .dirSubHeading .windowssdk .container h2 { + color: white !important; +} + +.dirSubHeading .windowssdk .container p { + margin: 20px 0 0 0 !important; +} + +.dirSubHeading .windowssdk .container a { + background-color:#ffd800; + color: #2a2a2a !important; + cursor:pointer; + font-size:13px; + font-family:'Segoe UI Semibold','Segoe UI','Lucida Grande',Verdana,Arial,Helvetica,sans-serif; + padding:4px 12px 6px; +} + + + diff --git a/samples/winrt/ImageManipulations/description/Layout.css b/samples/winrt/ImageManipulations/description/Layout.css new file mode 100644 index 0000000000..625f777637 --- /dev/null +++ b/samples/winrt/ImageManipulations/description/Layout.css @@ -0,0 +1,147 @@ +#container { + min-height: 768px; +} + +#leftSubHeaderContainer +{ + margin-top:20px; +} + +#title h1 +{ + font-size:25px; +} + +#subtitle h2 +{ + font-size:15px; +} + +#subtitle +{ + margin-left:10px; +} + + +#formContainer +{ + margin-left:10px; + margin-top:30px; +} + +.formLabel +{ + float:left; + width: 250px; +} + +.formRow +{ + clear:both; + padding: 10px 0 10px 10px; +} + + +.formRecaptchaRow +{ + clear:both; + float:left; + margin-top:20px; + margin-left:10px; + margin-bottom:20px; +} + +.formSubmitRow +{ + clear:both; + margin-top:20px; + margin-left:300px; + margin-bottom:20px; +} + +.formControl { + width:300px; + float:left; +} +.formControl .textInput +{ + width:300px; +} + +.formControl textarea +{ + width:425px; + height:100px; +} + +.formControl .tag +{ + width:425px; +} + +.formControl .richText +{ + margin-top:10px; + width:500px; + height:440px; +} + +.formWideLabel +{ + width:500px; +} + +.formBigLabel +{ + margin-top:20px; + font-size:20px; +} + +.formControlBelow +{ + clear:both; + margin-top:10px; + width:500px; +} + +.required +{ + color: Red; +} +.helpText +{ + color: #9D9D9D; + font-style: italic; +} + +#agreementSummary +{ + clear:both; + margin-top:10px; + width:800px; +} + +.field-validation-error, .validation-summary-errors +{ + color: #FF0000; + font-weight: bold; +} + +.tinyMCETemplate { + position: relative; + left: 400px; + width: 300px; + max-height: 300px; + overflow: auto; +} + +.IE6 .tinyMCETemplate { + left: 25px; +} + +.ownerBar { + padding: 5px; +} +.ownerBar .ownerBarOptions { + float: right; +} diff --git a/samples/winrt/ImageManipulations/description/c2e69f54-1c43-4037-b90b-5f775f1d945fBrand.css b/samples/winrt/ImageManipulations/description/c2e69f54-1c43-4037-b90b-5f775f1d945fBrand.css new file mode 100644 index 0000000000..e3f039dfb4 --- /dev/null +++ b/samples/winrt/ImageManipulations/description/c2e69f54-1c43-4037-b90b-5f775f1d945fBrand.css @@ -0,0 +1,303 @@ +/*Global*/ +h1 { + font-size: 36px; + font-family: 'Segoe UI Light'; + color: #707070; + font-weight: normal; + margin-bottom: 17px !important; +} + +h2, h3, h4, h5, h6, #searchPage h3 { + font-family: 'Segoe UI', 'Lucida Grande', Verdana, Arial, Helvetica, sans-serif !important; + font-weight:normal; + color: #2A2A2A !important; +} + +a, a:link, a:visited { + color: #0095c4; +} + +body { + color:#707070; +} + +.profile-usercard { + color:#707070 !important; +} + +/*temporary setting to override msdn_windows.css +can remove once conflicting settings are removed from that file*/ + + +.LocalNavigation, .LocalNavigation .TabOn, .LocalNavigation .TabOn:hover, .LocalNavigation .TabOff, .LocalNavigation .TabOff a:hover { + display: block; + background-color:transparent !important; + color: #0095c4; +} + +.LocalNavigation .TabOff a { +color:#707070 ; +} + +/*End Global*/ + +.EyebrowContainer +{ + margin-bottom: 0 !important; +} + +#sideNav +{ + width: 215px !important; +} + +#searchPage #mainContentContainer +{ + margin-right:0 !important; + margin-left:243px !important; +} + +#searchPage .dirSubHeading h2 +{ + font-size: 14px !important; + font-weight: normal !important; + color: #454545 !important; + line-height: 1.45; +} + +#searchPage #directoryListFooter, #searchPage #Pager { + font-size: 14px; +} + +#searchPage h2, #searchPage h3 +{ + font-size: 1.25em !important; +} + +#sideNav #contributeSection h3, .sidebar #contributeSection h3, #contributeSection h3 +{ + font-size: 1.65em !important; +} + +.subMenu > h2 +{ + font-family: 'Segoe UI Light','Segoe UI', 'Lucida Grande', Verdana, Arial, Helvetica, sans-serif !important; + font-weight:normal; + font-size:30px; + margin: 15px 10px 5px 0; + padding-bottom:0px; +} + +.itemRow { +} + .itemRow .itemBody, .itemRow .itemInfo { + padding: 18px 17px 20px 0; + font-size: 14px; + line-height: 1.45em; + } + + .itemRow .itemTitle { + font-weight: normal; + } + + .itemRow .summaryBox{ + color: #454545; + } + + .Samples #MainContent .itemRow .itemTitle a { + font-weight: 600 !important; + line-height: 1.45; + } + #MainContent a.officialMicrosoftLabel + { + color: #ACACAC; + } + + +.tabContents { + border-top-width:0px; +} + +#UploadPage { + margin: 0px 0px 0px 10px; +} + #UploadPage h1 { + padding: 0; + font-size: 22px; + } + #UploadPage h2 { + color:#F39700 !important; + } + + #UploadPage #uploadPageInstruction { + margin-top:10px; + } + + #UploadPage fieldset { + margin-left:0px; + } + + #UploadPage fieldset h2 { + font-weight:normal; + } + + #UploadPage fieldset#uploadsForm{ + margin-top:25px; + } + + #UploadPage fieldset#summary textarea { + margin-left:0px; + } + + #UploadPage .projectTypeChoice > div { + height: 250px; + } + +#sideNav { +} + + #sideNav .section h3 { + background-color: transparent; + + } + + #sideNav .section UL LI { + border-bottom-width: 0px; + } + + #sideNav .section form > div { + border-bottom: none; + color: #707070; + } + #sideNav .section ul li > div.itemCount + { + color: #707070; + } + + +#searchPage { +} + + #searchPage h2, #searchPage h3 { + text-transform:none; + background-color:transparent; + font-weight:normal; + font-size:1.2em; + } + + #searchPage .browseFilterBar { + background-color:transparent; + border-width:0px; + font-weight:normal; + } + +#requestsPage { + padding-top:15px; +} + #requestsPage .tabHeaders { + overflow: visible; + } + + #requestsPage #requestsList { + border: none; + } + + + #requestsPage h2, #requestsPage h3 { + text-transform:none; + background-color:transparent; + font-weight:normal; + font-size:1.2em; + } + + .reqBrowseContent .title { + font-weight: bold !important; + color:#000 !important; + font-family: 'Segoe UI', 'Lucida Grande', Verdana, Arial, Helvetica, sans-serif !important; + } + + .reqDescPage #header #votenumber { + height: 30px; + padding: 9px 12px 3px 12px; + } + +#extraActions { +} + #extraActions .section + { + margin-bottom: 10px; + } + #extraActions .section a + { + font-weight:normal; + } + + #extraActions #contributeSection div img { + width:0px; + } + + + +#projectPage { +} + + #projectPage .projectTitle { + color: #707070; + margin: 5px 0px 15px 0px; + } + + #projectPage h2.projectSummary, #projectPage #projectInfo, #projectPage .tabHeaders { + font-size: 14px !important; + line-height: 1.45em; + color: #454545 !important; + font-weight: normal !important; + } + + #projectPage #projectInfo a { + color: #00749e; + } + + #projectPage #Downloads a, #projectPage #Downloads label { + font-size: 14px; + } + + #projectPage #reportAbuse { + font-size: 1em; + } + + #projectPage #publishBar a, #projectPage #publishBar a:visited { + color: #0095c4; + font-weight: normal; + } + + #projectPage #Collections .bevelButton{ + background-color: #F8F8F8; + color: #0095C4; + border: 1px solid #707070; + } + + #projectPage #DiscussionsTabPane .threadHeader .title { + font-weight:bold !important; + color:Black !important;#F8F8F8; + font-family: 'Segoe UI', 'Lucida Grande', Verdana, Arial, Helvetica, sans-serif !important; + } + + #projectPage .sidebar .section .titleBar h3 { + font-weight:normal; + font-size:1.2em; + } + +#LocalNav { +} + + #LocalNav.HeaderTabs { + margin-left:11px; + } + + +#searchPage .dirSubHeading h1 +{ + margin-bottom:17px !important; +} + + diff --git a/samples/winrt/ImageManipulations/description/iframedescription.css b/samples/winrt/ImageManipulations/description/iframedescription.css new file mode 100644 index 0000000000..9abc9cdb3f --- /dev/null +++ b/samples/winrt/ImageManipulations/description/iframedescription.css @@ -0,0 +1,179 @@ +body { + color: #000000; + font-family: 'Segoe UI',Verdana,Arial; + font-size: 0.813em; + font-style: normal; + word-wrap: break-word; +} + +/*BEGIN HEADERS*/ +.h1, h1 { + color: #3A3E43; + font-family: 'Segoe UI',Verdana,Arial; + font-size: 1.4em; + font-weight: bold; + margin: 0; +} + +.h2, h2 { + color: #3A3E43; + font-family: 'Segoe UI',Verdana,Arial; + font-size: 1.2em; + font-weight: bold; +} +.h3, h3 { + color: #3A3E43; + font-family: 'Segoe UI',Verdana,Arial; + font-size: 1.077em; + font-weight: bold; +} +.h4, h4 { + color: #3A3E43; + font-family: 'Segoe UI',Verdana,Arial; + font-size: 1em; + font-weight: bold; +} +h4.subHeading { + margin-bottom: 7px; + margin-top: 13px; +} +/*END HEADERS*/ + +/*BEGIN LINKS*/ +a:link { + color: #00749E; + text-decoration: none; +} +a:hover { + text-decoration: underline; +} +a:visited { + color: #960BB4; + text-decoration: none; +} +a:focus { + outline: 1px dotted #000000; +} + +a.libraryLink:link { + text-decoration:none; + border-bottom:1px dotted; +} + +/*END LINKS*/ + +/*BEGIN IMAGES*/ +img { + border: 0 none; +} +/*END IMAGES*/ + +/*BEGIN TABLE*/ +.title table { + color: #000000; + font-family: 'Segoe UI',Verdana,Arial; + font-size: 1.077em; + font-style: normal; +} +table { + border-collapse: collapse; +} + +table, table th, table td { + border:1px solid #BBBBBB; +} +/*END TABLE*/ + +/*BEGIN LIST*/ +ul { + list-style-type: disc; + margin-left:40px; + padding-left: 0; +} +ul li { + padding-bottom: 10px; +} +ol { + margin-left:40px; + padding-left: 0; +} +ol li { + padding-bottom: 10px; +} +/*END LIST*/ + +.scriptcode { + position: relative; + padding: 8px 8px 8px 8px; + background: #FFFFFF; + font-size: 12px; + line-height: 125%; + font-weight:normal; +} +.scriptcode pre +{ + white-space: pre-wrap !important; /* css-3 */ + word-wrap: break-word !important; /* Internet Explorer 5.5+ */ + margin:0 0 10px 0 !important; + padding: 10px; + border-top: solid 2px #D0D2D2; + border-bottom: solid 2px #D0D2D2; + border-left: solid 1px #D0D2D2; + border-right: solid 1px #D0D2D2; +} + +.scriptcode .title { + color:#E66A38; + font-size: 12px; + font-weight:bold; + margin: 0; + min-height: 23px; +} +.scriptcode .title > span:first-child { + border-left: solid 1px #D0D2D2; +} +.scriptcode .title > span { + padding: 4px 8px 4px 8px; + display: inline-block; + border-top: 1px solid #D0D2D2; + border-right: 1px solid #D0D2D2; + border-collapse: collapse; + text-align: center; + background: white; +} +.scriptcode .title > span.otherTab { + color: #1364C4; + background: #EFF5FF; + cursor: pointer; +} + +.scriptcode .hidden { + display: none !important; + visibility: hidden !important; +} + +.scriptcode .copyCode { + padding: 8px 2px 0 2px !important; + margin-right: 15px; + position: absolute !important; + right: 0 !important; + top: 17px; + display:block !important; + background: #FFFFFF; +} +.scriptcode .pluginLinkHolder { + display: none; +} +.scriptcode .pluginEditHolderLink { + display: none; +} + +.Opera wbr +{ + display: inline-block; +} + +.IE9 wbr:after +{ +content: "\00200B"; +} diff --git a/samples/winrt/ImageManipulations/description/offline.js b/samples/winrt/ImageManipulations/description/offline.js new file mode 100644 index 0000000000..f5d07c8af1 --- /dev/null +++ b/samples/winrt/ImageManipulations/description/offline.js @@ -0,0 +1,52 @@ +var Galleries = Galleries || { }; + +(function() { + + function findElem(parent, tagName, className) { + var elemToSearch = (parent) ? parent : document.body; + var tagMatch = elemToSearch.getElementsByTagName(tagName); + var evaluator = function(elem) { + return (className) ? (elem.className.indexOf(className) > -1) : true; + }; + + return findArrayElem(tagMatch, evaluator); + } + + function findArrayElem(array, evaluator) { + var newArray = new Array(); + for (var count = 0; count < array.length; count++) { + if (evaluator(array[count])) { + newArray.push(array[count]); + } + } + return newArray; + } + + function iterateElem(elems, delegate) { + for(var count = 0; count < elems.length; count++) { + delegate(count, elems[count]); + } + } + + function isHidden(elem) { + return (elem.offsetHeight === 0 && elem.offsetWidth === 0) || elem.style && elem.style.display === "none"; + } + + function onWindowLoad(callback) { + attachEventHandler(null, 'load', callback); + } + + function attachEventHandler(elem, event, callback) { + var elemToAttach = (elem) ? elem : window; + if (document.addEventListener) { + elemToAttach.addEventListener(event, callback, false); + } else if ( document.attachEvent ) { + elemToAttach.attachEvent('on' + event, callback); + } + } + + Galleries.findElem = findElem; + Galleries.iterateElem = iterateElem; + Galleries.attachEventHandler = attachEventHandler; + Galleries.onWindowLoad = onWindowLoad; +})(); \ No newline at end of file diff --git a/samples/winrt/ImageManipulations/license.rtf b/samples/winrt/ImageManipulations/license.rtf new file mode 100644 index 0000000000..690a7ad071 --- /dev/null +++ b/samples/winrt/ImageManipulations/license.rtf @@ -0,0 +1,25 @@ +{\rtf1\ansi\ansicpg1252\uc1\htmautsp\deff2{\fonttbl{\f0\fcharset0 Times New Roman;}{\f2\fcharset0 MS Shell Dlg;}}{\colortbl\red0\green0\blue0;\red255\green255\blue255;}\loch\hich\dbch\pard\plain\ltrpar\itap0{\lang1033\fs16\f2\cf0 \cf0\ql{\f2 \line \li0\ri0\sa0\sb0\fi0\ql\par} +{\fs40\f2 {\ltrch MICROSOFT LIMITED PUBLIC LICENSE version 1.1}\li0\ri0\sa0\sb0\fi0\ql\par} +{\f2 \line {\ltrch ----------------------}\line \li0\ri0\sa0\sb0\fi0\ql\par} +{\f2 {\ltrch This license governs use of code marked as \ldblquote sample\rdblquote or \ldblquote example\rdblquote available on this web site without a license agreement, as provided under the section above titled \ldblquote NOTICE SPECIFIC TO SOFTWARE AVAILABLE ON THIS WEB SITE.\rdblquote If you use such code (the \ldblquote software\rdblquote ), you accept this license. If you do not accept the license, do not use the software.}\li0\ri0\sa0\sb0\fi0\ql\par} +{\f2 \line \li0\ri0\sa0\sb0\fi0\ql\par} +{\f2 {\ltrch 1. Definitions}\li0\ri0\sa0\sb0\fi0\ql\par} +{\f2 {\ltrch The terms \ldblquote reproduce,\rdblquote \ldblquote reproduction,\rdblquote \ldblquote derivative works,\rdblquote and \ldblquote distribution\rdblquote have the same meaning here as under U.S. copyright law. }\li0\ri0\sa0\sb0\fi0\ql\par} +{\f2 {\ltrch A \ldblquote contribution\rdblquote is the original software, or any additions or changes to the software.}\li0\ri0\sa0\sb0\fi0\ql\par} +{\f2 {\ltrch A \ldblquote contributor\rdblquote is any person that distributes its contribution under this license.}\li0\ri0\sa0\sb0\fi0\ql\par} +{\f2 {\ltrch \ldblquote Licensed patents\rdblquote are a contributor\rquote s patent claims that read directly on its contribution.}\li0\ri0\sa0\sb0\fi0\ql\par} +{\f2 \line \li0\ri0\sa0\sb0\fi0\ql\par} +{\f2 {\ltrch 2. Grant of Rights}\li0\ri0\sa0\sb0\fi0\ql\par} +{\f2 {\ltrch (A) Copyright Grant - Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create.}\li0\ri0\sa0\sb0\fi0\ql\par} +{\f2 {\ltrch (B) Patent Grant - Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software.}\li0\ri0\sa0\sb0\fi0\ql\par} +{\f2 \line \li0\ri0\sa0\sb0\fi0\ql\par} +{\f2 {\ltrch 3. Conditions and Limitations}\li0\ri0\sa0\sb0\fi0\ql\par} +{\f2 {\ltrch (A) No Trademark License- This license does not grant you rights to use any contributors\rquote name, logo, or trademarks.}\li0\ri0\sa0\sb0\fi0\ql\par} +{\f2 {\ltrch (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, your patent license from such contributor to the software ends automatically.}\li0\ri0\sa0\sb0\fi0\ql\par} +{\f2 {\ltrch (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution notices that are present in the software.}\li0\ri0\sa0\sb0\fi0\ql\par} +{\f2 {\ltrch (D) If you distribute any portion of the software in source code form, you may do so only under this license by including a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object code form, you may only do so under a license that complies with this license.}\li0\ri0\sa0\sb0\fi0\ql\par} +{\f2 {\ltrch (E) The software is licensed \ldblquote as-is.\rdblquote You bear the risk of using it. The contributors give no express warranties, guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular purpose and non-infringement.}\li0\ri0\sa0\sb0\fi0\ql\par} +{\f2 {\ltrch (F) Platform Limitation - The licenses granted in sections 2(A) and 2(B) extend only to the software or derivative works that you create that run directly on a Microsoft Windows operating system product, Microsoft run-time technology (such as the .NET Framework or Silverlight), or Microsoft application platform (such as Microsoft Office or Microsoft Dynamics).}\li0\ri0\sa0\sb0\fi0\ql\par} +{\f2 \line \li0\ri0\sa0\sb0\fi0\ql\par} +} +} \ No newline at end of file From 9e06287121155f1312cc64a23a5e56bf08ffcfad Mon Sep 17 00:00:00 2001 From: Alexander Smorkalov Date: Mon, 24 Jun 2013 02:32:57 -0700 Subject: [PATCH 164/178] Windows RT sample updated. Unused scenarious removed. Grey scale convertion replaced with cv::Canny call. --- platforms/scripts/cmake_winrt.cmd | 2 +- platforms/winrt/arm.winrt.toolchain.cmake | 13 +- .../ImageManipulations/C++/AudioCapture.xaml | 62 -- .../C++/AudioCapture.xaml.cpp | 366 ------------ .../C++/AudioCapture.xaml.h | 70 --- .../ImageManipulations/C++/BasicCapture.xaml | 87 --- .../C++/BasicCapture.xaml.cpp | 535 ------------------ .../C++/BasicCapture.xaml.h | 88 --- .../ImageManipulations/C++/Constants.cpp | 2 - .../winrt/ImageManipulations/C++/Constants.h | 2 +- .../ImageManipulations/C++/MainPage.xaml | 6 +- .../C++/MediaCapture.vcxproj | 160 +++++- .../C++/MediaCapture.vcxproj.filters | 25 +- .../MediaExtensions/Grayscale/Grayscale.cpp | 250 +------- .../C++/MediaExtensions/Grayscale/Grayscale.h | 34 +- .../Grayscale/Grayscale.vcxproj | 20 +- 16 files changed, 221 insertions(+), 1501 deletions(-) delete mode 100644 samples/winrt/ImageManipulations/C++/AudioCapture.xaml delete mode 100644 samples/winrt/ImageManipulations/C++/AudioCapture.xaml.cpp delete mode 100644 samples/winrt/ImageManipulations/C++/AudioCapture.xaml.h delete mode 100644 samples/winrt/ImageManipulations/C++/BasicCapture.xaml delete mode 100644 samples/winrt/ImageManipulations/C++/BasicCapture.xaml.cpp delete mode 100644 samples/winrt/ImageManipulations/C++/BasicCapture.xaml.h diff --git a/platforms/scripts/cmake_winrt.cmd b/platforms/scripts/cmake_winrt.cmd index aafed7d09d..c6d8cb8e03 100644 --- a/platforms/scripts/cmake_winrt.cmd +++ b/platforms/scripts/cmake_winrt.cmd @@ -3,4 +3,4 @@ cd build rem call "C:\Program Files\Microsoft Visual Studio 11.0\VC\bin\x86_arm\vcvarsx86_arm.bat" -cmake.exe -GNinja -DCMAKE_BUILD_TYPE=Release -DWITH_FFMPEG=OFF -DBUILD_opencv_gpu=OFF -DBUILD_opencv_python=OFF -DCMAKE_TOOLCHAIN_FILE=..\..\winrt\arm.winrt.toolchain.cmake ..\..\.. +cmake.exe -GNinja -DWITH_TBB=ON -DBUILD_TBB=ON -DCMAKE_BUILD_TYPE=Release -DWITH_FFMPEG=OFF -DBUILD_opencv_gpu=OFF -DBUILD_opencv_python=OFF -DCMAKE_TOOLCHAIN_FILE=..\..\winrt\arm.winrt.toolchain.cmake ..\..\.. diff --git a/platforms/winrt/arm.winrt.toolchain.cmake b/platforms/winrt/arm.winrt.toolchain.cmake index b34056cd5e..ac9af117df 100644 --- a/platforms/winrt/arm.winrt.toolchain.cmake +++ b/platforms/winrt/arm.winrt.toolchain.cmake @@ -3,4 +3,15 @@ set(CMAKE_SYSTEM_PROCESSOR "arm-v7a") set(CMAKE_FIND_ROOT_PATH "${CMAKE_SOURCE_DIR}/platforms/winrt") set(CMAKE_REQUIRED_DEFINITIONS -D_ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE) -add_definitions(-D_ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE) \ No newline at end of file +add_definitions(-D_ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE) + +set(CMAKE_CXX_FLAGS "" CACHE STRING "c++ flags") +set(CMAKE_C_FLAGS "" CACHE STRING "c flags") + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ZW -EHsc -GS") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -GS") + + +set(CMAKE_SHARED_LINKER_FLAGS "/r:System.Runtime.WindowsRuntime.dll /r:System.Threading.Tasks.dll" CACHE STRING "shared linker flags") +set(CMAKE_MODULE_LINKER_FLAGS "/r:System.Runtime.WindowsRuntime.dll /r:System.Threading.Tasks.dll" CACHE STRING "module linker flags") +set(CMAKE_EXE_LINKER_FLAGS "/r:System.Runtime.WindowsRuntime.dll /r:System.Threading.Tasks.dll" CACHE STRING "executable linker flags") \ No newline at end of file diff --git a/samples/winrt/ImageManipulations/C++/AudioCapture.xaml b/samples/winrt/ImageManipulations/C++/AudioCapture.xaml deleted file mode 100644 index be65bcd8c1..0000000000 --- a/samples/winrt/ImageManipulations/C++/AudioCapture.xaml +++ /dev/null @@ -1,62 +0,0 @@ - - - - - - - - - - - - - - - - This scenario shows how to do an audio only capture using the default microphone. Click on StartRecord to start recording. - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/samples/winrt/ImageManipulations/C++/AudioCapture.xaml.cpp b/samples/winrt/ImageManipulations/C++/AudioCapture.xaml.cpp deleted file mode 100644 index 37fc379d38..0000000000 --- a/samples/winrt/ImageManipulations/C++/AudioCapture.xaml.cpp +++ /dev/null @@ -1,366 +0,0 @@ -//********************************************************* -// -// Copyright (c) Microsoft. All rights reserved. -// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF -// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY -// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR -// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. -// -//********************************************************* - -// -// AudioCapture.xaml.cpp -// Implementation of the AudioCapture class -// - -#include "pch.h" -#include "AudioCapture.xaml.h" -#include -using namespace concurrency; - -using namespace SDKSample::MediaCapture; - -using namespace Windows::UI::Xaml; -using namespace Windows::UI::Xaml::Navigation; -using namespace Windows::UI::Xaml::Data; -using namespace Windows::System; -using namespace Windows::Foundation; -using namespace Platform; -using namespace Windows::UI; -using namespace Windows::UI::Core; -using namespace Windows::UI::Xaml; -using namespace Windows::UI::Xaml::Controls; -using namespace Windows::UI::Xaml::Data; -using namespace Windows::UI::Xaml::Media; -using namespace Windows::Storage; -using namespace Windows::Media::MediaProperties; -using namespace Windows::Storage::Streams; -using namespace Windows::System; -using namespace Windows::UI::Xaml::Media::Imaging; - - -AudioCapture::AudioCapture() -{ - InitializeComponent(); - ScenarioInit(); -} - -/// -/// Invoked when this page is about to be displayed in a Frame. -/// -/// Event data that describes how this page was reached. The Parameter -/// property is typically used to configure the page. -void AudioCapture::OnNavigatedTo(NavigationEventArgs^ e) -{ - // A pointer back to the main page. This is needed if you want to call methods in MainPage such - // as NotifyUser() - rootPage = MainPage::Current; - m_eventRegistrationToken = Windows::Media::MediaControl::SoundLevelChanged += ref new EventHandler(this, &AudioCapture::SoundLevelChanged); -} - -void AudioCapture::OnNavigatedFrom(NavigationEventArgs^ e) -{ - // A pointer back to the main page. This is needed if you want to call methods in MainPage such - // as NotifyUser() - Windows::Media::MediaControl::SoundLevelChanged -= m_eventRegistrationToken; -} - -void AudioCapture::ScenarioInit() -{ - try - { - rootPage = MainPage::Current; - btnStartDevice3->IsEnabled = true; - btnStartStopRecord3->IsEnabled = false; - m_bRecording = false; - playbackElement3->Source = nullptr; - m_bSuspended = false; - ShowStatusMessage(""); - } - catch (Exception ^e) - { - ShowExceptionMessage(e); - } - -} - -void AudioCapture::ScenarioReset() -{ - ScenarioInit(); -} - - -void AudioCapture::SoundLevelChanged(Object^ sender, Object^ e) -{ - create_task(Dispatcher->RunAsync(Windows::UI::Core::CoreDispatcherPriority::High, ref new Windows::UI::Core::DispatchedHandler([this]() - { - if(Windows::Media::MediaControl::SoundLevel != Windows::Media::SoundLevel::Muted) - { - ScenarioReset(); - } - else - { - if (m_bRecording) - { - ShowStatusMessage("Stopping Record on invisibility"); - - create_task(m_mediaCaptureMgr->StopRecordAsync()).then([this](task recordTask) - { - try - { - recordTask.get(); - m_bRecording = false; - }catch (Exception ^e) - { - ShowExceptionMessage(e); - } - }); - } - } - }))); -} - -void AudioCapture::RecordLimitationExceeded(Windows::Media::Capture::MediaCapture ^currentCaptureObject) -{ - try - { - if (m_bRecording) - { - create_task(Dispatcher->RunAsync(Windows::UI::Core::CoreDispatcherPriority::High, ref new Windows::UI::Core::DispatchedHandler([this](){ - try - { - ShowStatusMessage("Stopping Record on exceeding max record duration"); - EnableButton(false, "StartStopRecord"); - create_task(m_mediaCaptureMgr->StopRecordAsync()).then([this](task recordTask) - { - try - { - recordTask.get(); - m_bRecording = false; - SwitchRecordButtonContent(); - EnableButton(true, "StartStopRecord"); - ShowStatusMessage("Stopped record on exceeding max record duration:" + m_recordStorageFile->Path); - } - catch (Exception ^e) - { - ShowExceptionMessage(e); - m_bRecording = false; - SwitchRecordButtonContent(); - EnableButton(true, "StartStopRecord"); - } - }); - - } - catch (Exception ^e) - { - m_bRecording = false; - SwitchRecordButtonContent(); - EnableButton(true, "StartStopRecord"); - ShowExceptionMessage(e); - } - - }))); - } - } - catch (Exception ^e) - { - m_bRecording = false; - SwitchRecordButtonContent(); - EnableButton(true, "StartStopRecord"); - ShowExceptionMessage(e); - } -} - -void AudioCapture::Failed(Windows::Media::Capture::MediaCapture ^currentCaptureObject, Windows::Media::Capture::MediaCaptureFailedEventArgs^ currentFailure) -{ - String ^message = "Fatal error: " + currentFailure->Message; - create_task(Dispatcher->RunAsync(Windows::UI::Core::CoreDispatcherPriority::High, - ref new Windows::UI::Core::DispatchedHandler([this, message]() - { - ShowStatusMessage(message); - }))); -} - -void AudioCapture::btnStartDevice_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e) -{ - try - { - EnableButton(false, "StartDevice"); - ShowStatusMessage("Starting device"); - auto mediaCapture = ref new Windows::Media::Capture::MediaCapture(); - m_mediaCaptureMgr = mediaCapture; - auto settings = ref new Windows::Media::Capture::MediaCaptureInitializationSettings(); - settings->StreamingCaptureMode = Windows::Media::Capture::StreamingCaptureMode::Audio; - create_task(mediaCapture->InitializeAsync()).then([this](task initTask) - { - try - { - initTask.get(); - - auto mediaCapture = m_mediaCaptureMgr.Get(); - EnableButton(true, "StartPreview"); - EnableButton(true, "StartStopRecord"); - EnableButton(true, "TakePhoto"); - ShowStatusMessage("Device initialized successful"); - mediaCapture->RecordLimitationExceeded += ref new Windows::Media::Capture::RecordLimitationExceededEventHandler(this, &AudioCapture::RecordLimitationExceeded); - mediaCapture->Failed += ref new Windows::Media::Capture::MediaCaptureFailedEventHandler(this, &AudioCapture::Failed); - } - catch (Exception ^ e) - { - ShowExceptionMessage(e); - } - }); - } - catch (Platform::Exception^ e) - { - ShowExceptionMessage(e); - } -} - -void AudioCapture::btnStartStopRecord_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e) -{ - try - { - String ^fileName; - EnableButton(false, "StartStopRecord"); - - if (!m_bRecording) - { - ShowStatusMessage("Starting Record"); - - fileName = AUDIO_FILE_NAME; - - task(KnownFolders::VideosLibrary->CreateFileAsync(fileName, Windows::Storage::CreationCollisionOption::GenerateUniqueName)).then([this](task fileTask) - { - try - { - this->m_recordStorageFile = fileTask.get(); - ShowStatusMessage("Create record file successful"); - - MediaEncodingProfile^ recordProfile= nullptr; - recordProfile = MediaEncodingProfile::CreateM4a(Windows::Media::MediaProperties::AudioEncodingQuality::Auto); - - create_task(m_mediaCaptureMgr->StartRecordToStorageFileAsync(recordProfile, this->m_recordStorageFile)).then([this](task recordTask) - { - try - { - recordTask.get(); - m_bRecording = true; - SwitchRecordButtonContent(); - EnableButton(true, "StartStopRecord"); - - ShowStatusMessage("Start Record successful"); - - - }catch (Exception ^e) - { - ShowExceptionMessage(e); - m_bRecording = false; - SwitchRecordButtonContent(); - EnableButton(true, "StartStopRecord"); - } - }); - } - catch (Exception ^e) - { - m_bRecording = false; - SwitchRecordButtonContent(); - EnableButton(true, "StartStopRecord"); - ShowExceptionMessage(e); - } - } - ); - } - else - { - ShowStatusMessage("Stopping Record"); - - create_task(m_mediaCaptureMgr->StopRecordAsync()).then([this](task) - { - try - { - m_bRecording = false; - EnableButton(true, "StartStopRecord"); - SwitchRecordButtonContent(); - - ShowStatusMessage("Stop record successful"); - if (!m_bSuspended) - { - task(this->m_recordStorageFile->OpenAsync(FileAccessMode::Read)).then([this](task streamTask) - { - try - { - ShowStatusMessage("Record file opened"); - auto stream = streamTask.get(); - ShowStatusMessage(this->m_recordStorageFile->Path); - playbackElement3->AutoPlay = true; - playbackElement3->SetSource(stream, this->m_recordStorageFile->FileType); - playbackElement3->Play(); - } - catch (Exception ^e) - { - ShowExceptionMessage(e); - m_bRecording = false; - SwitchRecordButtonContent(); - EnableButton(true, "StartStopRecord"); - } - }); - } - } - catch (Exception ^e) - { - m_bRecording = false; - SwitchRecordButtonContent(); - EnableButton(true, "StartStopRecord"); - ShowExceptionMessage(e); - } - }); - } - } - catch (Platform::Exception^ e) - { - EnableButton(true, "StartStopRecord"); - ShowExceptionMessage(e); - m_bRecording = false; - SwitchRecordButtonContent(); - } -} - - -void AudioCapture::ShowStatusMessage(Platform::String^ text) -{ - rootPage->NotifyUser(text, NotifyType::StatusMessage); -} - -void AudioCapture::ShowExceptionMessage(Platform::Exception^ ex) -{ - rootPage->NotifyUser(ex->Message, NotifyType::ErrorMessage); -} - -void AudioCapture::SwitchRecordButtonContent() -{ - { - if (m_bRecording) - { - btnStartStopRecord3->Content="StopRecord"; - } - else - { - btnStartStopRecord3->Content="StartRecord"; - } - } -} -void AudioCapture::EnableButton(bool enabled, String^ name) -{ - if (name->Equals("StartDevice")) - { - btnStartDevice3->IsEnabled = enabled; - } - - else if (name->Equals("StartStopRecord")) - { - btnStartStopRecord3->IsEnabled = enabled; - } - -} - diff --git a/samples/winrt/ImageManipulations/C++/AudioCapture.xaml.h b/samples/winrt/ImageManipulations/C++/AudioCapture.xaml.h deleted file mode 100644 index b75efdc728..0000000000 --- a/samples/winrt/ImageManipulations/C++/AudioCapture.xaml.h +++ /dev/null @@ -1,70 +0,0 @@ -//********************************************************* -// -// Copyright (c) Microsoft. All rights reserved. -// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF -// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY -// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR -// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. -// -//********************************************************* - -// -// AudioCapture.xaml.h -// Declaration of the AudioCapture class -// - -#pragma once - -#include "pch.h" -#include "AudioCapture.g.h" -#include "MainPage.xaml.h" - -#define AUDIO_FILE_NAME "audio.mp4" - -namespace SDKSample -{ - namespace MediaCapture - { - /// - /// An empty page that can be used on its own or navigated to within a Frame. - /// - [Windows::Foundation::Metadata::WebHostHidden] - public ref class AudioCapture sealed - { - public: - AudioCapture(); - - protected: - virtual void OnNavigatedTo(Windows::UI::Xaml::Navigation::NavigationEventArgs^ e) override; - virtual void OnNavigatedFrom(Windows::UI::Xaml::Navigation::NavigationEventArgs^ e) override; - private: - MainPage^ rootPage; - - void ScenarioInit(); - void ScenarioReset(); - - void SoundLevelChanged(Object^ sender, Object^ e); - void RecordLimitationExceeded(Windows::Media::Capture::MediaCapture ^ mediaCapture); - void Failed(Windows::Media::Capture::MediaCapture ^ mediaCapture, Windows::Media::Capture::MediaCaptureFailedEventArgs ^ args); - - void btnStartDevice_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e); - - void btnStartPreview_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e); - - void btnStartStopRecord_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e); - - void ShowStatusMessage(Platform::String^ text); - void ShowExceptionMessage(Platform::Exception^ ex); - - void EnableButton(bool enabled, Platform::String ^name); - void SwitchRecordButtonContent(); - - Platform::Agile m_mediaCaptureMgr; - Windows::Storage::StorageFile^ m_photoStorageFile; - Windows::Storage::StorageFile^ m_recordStorageFile; - bool m_bRecording; - bool m_bSuspended; - Windows::Foundation::EventRegistrationToken m_eventRegistrationToken; - }; - } -} diff --git a/samples/winrt/ImageManipulations/C++/BasicCapture.xaml b/samples/winrt/ImageManipulations/C++/BasicCapture.xaml deleted file mode 100644 index 2cc0b0a5ff..0000000000 --- a/samples/winrt/ImageManipulations/C++/BasicCapture.xaml +++ /dev/null @@ -1,87 +0,0 @@ - - - - - - - - - - - - - - - - - This scenario demonstrates how to use the MediaCapture API to preview the camera stream, record a video, and take a picture using default initialization settings. - You can also adjust the brightness and contrast. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/samples/winrt/ImageManipulations/C++/BasicCapture.xaml.cpp b/samples/winrt/ImageManipulations/C++/BasicCapture.xaml.cpp deleted file mode 100644 index f385fa9a74..0000000000 --- a/samples/winrt/ImageManipulations/C++/BasicCapture.xaml.cpp +++ /dev/null @@ -1,535 +0,0 @@ -//********************************************************* -// -// Copyright (c) Microsoft. All rights reserved. -// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF -// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY -// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR -// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. -// -//********************************************************* - -// -// BasicCapture.xaml.cpp -// Implementation of the BasicCapture class -// - -#include "pch.h" -#include "BasicCapture.xaml.h" -#include "ppl.h" - -using namespace Windows::System; -using namespace Windows::Foundation; -using namespace Platform; -using namespace Windows::UI; -using namespace Windows::UI::Core; -using namespace Windows::UI::Xaml; -using namespace Windows::UI::Xaml::Controls; -using namespace Windows::UI::Xaml::Navigation; -using namespace Windows::UI::Xaml::Data; -using namespace Windows::UI::Xaml::Media; -using namespace Windows::Storage; -using namespace Windows::Media::MediaProperties; -using namespace Windows::Storage::Streams; -using namespace Windows::System; -using namespace Windows::UI::Xaml::Media::Imaging; - -using namespace SDKSample::MediaCapture; -using namespace concurrency; - - -BasicCapture::BasicCapture() -{ - InitializeComponent(); - ScenarioInit(); -} - -/// -/// Invoked when this page is about to be displayed in a Frame. -/// -/// Event data that describes how this page was reached. The Parameter -/// property is typically used to configure the page. -void BasicCapture::OnNavigatedTo(NavigationEventArgs^ e) -{ - // A pointer back to the main page. This is needed if you want to call methods in MainPage such - // as NotifyUser() - rootPage = MainPage::Current; - m_eventRegistrationToken = Windows::Media::MediaControl::SoundLevelChanged += ref new EventHandler(this, &BasicCapture::SoundLevelChanged); -} - -void BasicCapture::OnNavigatedFrom(NavigationEventArgs^ e) -{ - // A pointer back to the main page. This is needed if you want to call methods in MainPage such - // as NotifyUser() - - Windows::Media::MediaControl::SoundLevelChanged -= m_eventRegistrationToken; - m_currentScenarioLoaded = false; -} - - -void BasicCapture::ScenarioInit() -{ - try - { - btnStartDevice1->IsEnabled = true; - btnStartPreview1->IsEnabled = false; - btnStartStopRecord1->IsEnabled = false; - m_bRecording = false; - m_bPreviewing = false; - btnStartStopRecord1->Content = "StartRecord"; - btnTakePhoto1->IsEnabled = false; - previewElement1->Source = nullptr; - playbackElement1->Source = nullptr; - imageElement1->Source= nullptr; - sldBrightness->IsEnabled = false; - sldContrast->IsEnabled = false; - m_bSuspended = false; - previewCanvas1->Visibility = Windows::UI::Xaml::Visibility::Collapsed; - - } - catch (Exception ^e) - { - ShowExceptionMessage(e); - } - -} - -void BasicCapture::ScenarioReset() -{ - previewCanvas1->Visibility = Windows::UI::Xaml::Visibility::Collapsed; - ScenarioInit(); -} - -void BasicCapture::SoundLevelChanged(Object^ sender, Object^ e) -{ - create_task(Dispatcher->RunAsync(Windows::UI::Core::CoreDispatcherPriority::High, ref new Windows::UI::Core::DispatchedHandler([this]() - { - if(Windows::Media::MediaControl::SoundLevel != Windows::Media::SoundLevel::Muted) - { - ScenarioReset(); - } - else - { - if (m_bRecording) - { - ShowStatusMessage("Stopping Record on invisibility"); - - create_task(m_mediaCaptureMgr->StopRecordAsync()).then([this](task recordTask) - { - m_bRecording = false; - }); - } - if (m_bPreviewing) - { - ShowStatusMessage("Stopping Preview on invisibility"); - - create_task(m_mediaCaptureMgr->StopPreviewAsync()).then([this](task previewTask) - { - try - { - previewTask.get(); - m_bPreviewing = false; - } - catch (Exception ^e) - { - ShowExceptionMessage(e); - } - }); - } - } - }))); -} - -void BasicCapture::RecordLimitationExceeded(Windows::Media::Capture::MediaCapture ^currentCaptureObject) -{ - try - { - if (m_bRecording) - { - create_task(Dispatcher->RunAsync(Windows::UI::Core::CoreDispatcherPriority::High, ref new Windows::UI::Core::DispatchedHandler([this](){ - try - { - ShowStatusMessage("Stopping Record on exceeding max record duration"); - EnableButton(false, "StartStopRecord"); - create_task(m_mediaCaptureMgr->StopRecordAsync()).then([this](task recordTask) - { - try - { - recordTask.get(); - m_bRecording = false; - SwitchRecordButtonContent(); - EnableButton(true, "StartStopRecord"); - ShowStatusMessage("Stopped record on exceeding max record duration:" + m_recordStorageFile->Path); - } - catch (Exception ^e) - { - ShowExceptionMessage(e); - m_bRecording = false; - SwitchRecordButtonContent(); - EnableButton(true, "StartStopRecord"); - } - }); - - } - catch (Exception ^e) - { - m_bRecording = false; - SwitchRecordButtonContent(); - EnableButton(true, "StartStopRecord"); - ShowExceptionMessage(e); - } - - }))); - } - } - catch (Exception ^e) - { - m_bRecording = false; - SwitchRecordButtonContent(); - EnableButton(true, "StartStopRecord"); - ShowExceptionMessage(e); - } -} - -void BasicCapture::Failed(Windows::Media::Capture::MediaCapture ^currentCaptureObject, Windows::Media::Capture::MediaCaptureFailedEventArgs^ currentFailure) -{ - String ^message = "Fatal error: " + currentFailure->Message; - create_task(Dispatcher->RunAsync(Windows::UI::Core::CoreDispatcherPriority::High, - ref new Windows::UI::Core::DispatchedHandler([this, message]() - { - ShowStatusMessage(message); - }))); -} - -void BasicCapture::btnStartDevice_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e) -{ - try - { - EnableButton(false, "StartDevice"); - ShowStatusMessage("Starting device"); - auto mediaCapture = ref new Windows::Media::Capture::MediaCapture(); - m_mediaCaptureMgr = mediaCapture; - create_task(mediaCapture->InitializeAsync()).then([this](task initTask) - { - try - { - initTask.get(); - - auto mediaCapture = m_mediaCaptureMgr.Get(); - EnableButton(true, "StartPreview"); - EnableButton(true, "StartStopRecord"); - EnableButton(true, "TakePhoto"); - ShowStatusMessage("Device initialized successful"); - mediaCapture->RecordLimitationExceeded += ref new Windows::Media::Capture::RecordLimitationExceededEventHandler(this, &BasicCapture::RecordLimitationExceeded); - mediaCapture->Failed += ref new Windows::Media::Capture::MediaCaptureFailedEventHandler(this, &BasicCapture::Failed); - } - catch (Exception ^ e) - { - ShowExceptionMessage(e); - } - } - ); - } - catch (Platform::Exception^ e) - { - ShowExceptionMessage(e); - } -} - -void BasicCapture::btnStartPreview_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e) -{ - m_bPreviewing = false; - try - { - ShowStatusMessage("Starting preview"); - EnableButton(false, "StartPreview"); - auto mediaCapture = m_mediaCaptureMgr.Get(); - - previewCanvas1->Visibility = Windows::UI::Xaml::Visibility::Visible; - previewElement1->Source = mediaCapture; - create_task(mediaCapture->StartPreviewAsync()).then([this](task previewTask) - { - try - { - previewTask.get(); - auto mediaCapture = m_mediaCaptureMgr.Get(); - m_bPreviewing = true; - ShowStatusMessage("Start preview successful"); - if(mediaCapture->VideoDeviceController->Brightness) - { - SetupVideoDeviceControl(mediaCapture->VideoDeviceController->Brightness, sldBrightness); - } - if(mediaCapture->VideoDeviceController->Contrast) - { - SetupVideoDeviceControl(mediaCapture->VideoDeviceController->Contrast, sldContrast); - } - - }catch (Exception ^e) - { - ShowExceptionMessage(e); - } - }); - } - catch (Platform::Exception^ e) - { - m_bPreviewing = false; - previewElement1->Source = nullptr; - EnableButton(true, "StartPreview"); - ShowExceptionMessage(e); - } -} - -void BasicCapture::btnTakePhoto_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e) -{ - try - { - ShowStatusMessage("Taking photo"); - EnableButton(false, "TakePhoto"); - - task(KnownFolders::PicturesLibrary->CreateFileAsync(PHOTO_FILE_NAME, Windows::Storage::CreationCollisionOption::GenerateUniqueName)).then([this](task getFileTask) - { - try - { - this->m_photoStorageFile = getFileTask.get(); - ShowStatusMessage("Create photo file successful"); - ImageEncodingProperties^ imageProperties = ImageEncodingProperties::CreateJpeg(); - - create_task(m_mediaCaptureMgr->CapturePhotoToStorageFileAsync(imageProperties, this->m_photoStorageFile)).then([this](task photoTask) - { - try - { - photoTask.get(); - EnableButton(true, "TakePhoto"); - ShowStatusMessage("Photo taken"); - - task(this->m_photoStorageFile->OpenAsync(FileAccessMode::Read)).then([this](task getStreamTask) - { - try - { - auto photoStream = getStreamTask.get(); - ShowStatusMessage("File open successful"); - auto bmpimg = ref new BitmapImage(); - - bmpimg->SetSource(photoStream); - imageElement1->Source = bmpimg; - } - catch (Exception^ e) - { - ShowExceptionMessage(e); - EnableButton(true, "TakePhoto"); - } - }); - } - catch (Platform::Exception ^ e) - { - ShowExceptionMessage(e); - EnableButton(true, "TakePhoto"); - } - }); - } - catch (Exception^ e) - { - ShowExceptionMessage(e); - EnableButton(true, "TakePhoto"); - } - }); - } - catch (Platform::Exception^ e) - { - ShowExceptionMessage(e); - EnableButton(true, "TakePhoto"); - } -} - -void BasicCapture::btnStartStopRecord_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e) -{ - try - { - String ^fileName; - EnableButton(false, "StartStopRecord"); - - if (!m_bRecording) - { - ShowStatusMessage("Starting Record"); - - fileName = VIDEO_FILE_NAME; - - task(KnownFolders::VideosLibrary->CreateFileAsync(fileName,Windows::Storage::CreationCollisionOption::GenerateUniqueName )).then([this](task fileTask) - { - try - { - this->m_recordStorageFile = fileTask.get(); - ShowStatusMessage("Create record file successful"); - - MediaEncodingProfile^ recordProfile= nullptr; - recordProfile = MediaEncodingProfile::CreateMp4(Windows::Media::MediaProperties::VideoEncodingQuality::Auto); - - create_task(m_mediaCaptureMgr->StartRecordToStorageFileAsync(recordProfile, this->m_recordStorageFile)).then([this](task recordTask) - { - try - { - recordTask.get(); - m_bRecording = true; - SwitchRecordButtonContent(); - EnableButton(true, "StartStopRecord"); - - ShowStatusMessage("Start Record successful"); - } - catch (Exception ^e) - { - ShowExceptionMessage(e); - } - }); - } - catch (Exception ^e) - { - m_bRecording = false; - SwitchRecordButtonContent(); - EnableButton(true, "StartStopRecord"); - ShowExceptionMessage(e); - } - } - ); - } - else - { - ShowStatusMessage("Stopping Record"); - - create_task(m_mediaCaptureMgr->StopRecordAsync()).then([this](task recordTask) - { - try - { - recordTask.get(); - m_bRecording = false; - EnableButton(true, "StartStopRecord"); - SwitchRecordButtonContent(); - - ShowStatusMessage("Stop record successful"); - if (!m_bSuspended) - { - task(this->m_recordStorageFile->OpenAsync(FileAccessMode::Read)).then([this](task streamTask) - { - try - { - auto stream = streamTask.get(); - ShowStatusMessage("Record file opened"); - ShowStatusMessage(this->m_recordStorageFile->Path); - playbackElement1->AutoPlay = true; - playbackElement1->SetSource(stream, this->m_recordStorageFile->FileType); - playbackElement1->Play(); - } - catch (Exception ^e) - { - ShowExceptionMessage(e); - m_bRecording = false; - EnableButton(true, "StartStopRecord"); - SwitchRecordButtonContent(); - } - }); - } - } - catch (Exception ^e) - { - m_bRecording = false; - EnableButton(true, "StartStopRecord"); - SwitchRecordButtonContent(); - ShowExceptionMessage(e); - } - }); - } - } - catch (Platform::Exception^ e) - { - EnableButton(true, "StartStopRecord"); - ShowExceptionMessage(e); - SwitchRecordButtonContent(); - m_bRecording = false; - } -} - -void BasicCapture::SetupVideoDeviceControl(Windows::Media::Devices::MediaDeviceControl^ videoDeviceControl, Slider^ slider) -{ - try - { - if ((videoDeviceControl->Capabilities)->Supported) - { - slider->IsEnabled = true; - slider->Maximum = videoDeviceControl->Capabilities->Max; - slider->Minimum = videoDeviceControl->Capabilities->Min; - slider->StepFrequency = videoDeviceControl->Capabilities->Step; - double controlValue = 0; - if (videoDeviceControl->TryGetValue(&controlValue)) - { - slider->Value = controlValue; - } - } - else - { - slider->IsEnabled = false; - } - } - catch (Platform::Exception^ e) - { - ShowExceptionMessage(e); - } -} - -// VideoDeviceControllers -void BasicCapture::sldBrightness_ValueChanged(Platform::Object^ sender, Windows::UI::Xaml::Controls::Primitives::RangeBaseValueChangedEventArgs^ e) -{ - bool succeeded = m_mediaCaptureMgr->VideoDeviceController->Brightness->TrySetValue(sldBrightness->Value); - if (!succeeded) - { - ShowStatusMessage("Set Brightness failed"); - } -} - -void BasicCapture::sldContrast_ValueChanged(Platform::Object^ sender, Windows::UI::Xaml::Controls::Primitives::RangeBaseValueChangedEventArgs ^e) -{ - bool succeeded = m_mediaCaptureMgr->VideoDeviceController->Contrast->TrySetValue(sldContrast->Value); - if (!succeeded) - { - ShowStatusMessage("Set Contrast failed"); - } -} - -void BasicCapture::ShowStatusMessage(Platform::String^ text) -{ - rootPage->NotifyUser(text, NotifyType::StatusMessage); -} - -void BasicCapture::ShowExceptionMessage(Platform::Exception^ ex) -{ - rootPage->NotifyUser(ex->Message, NotifyType::ErrorMessage); -} - -void BasicCapture::SwitchRecordButtonContent() -{ - if (m_bRecording) - { - btnStartStopRecord1->Content="StopRecord"; - } - else - { - btnStartStopRecord1->Content="StartRecord"; - } -} -void BasicCapture::EnableButton(bool enabled, String^ name) -{ - if (name->Equals("StartDevice")) - { - btnStartDevice1->IsEnabled = enabled; - } - else if (name->Equals("StartPreview")) - { - btnStartPreview1->IsEnabled = enabled; - } - else if (name->Equals("StartStopRecord")) - { - btnStartStopRecord1->IsEnabled = enabled; - } - else if (name->Equals("TakePhoto")) - { - btnTakePhoto1->IsEnabled = enabled; - } -} - diff --git a/samples/winrt/ImageManipulations/C++/BasicCapture.xaml.h b/samples/winrt/ImageManipulations/C++/BasicCapture.xaml.h deleted file mode 100644 index 28129efb72..0000000000 --- a/samples/winrt/ImageManipulations/C++/BasicCapture.xaml.h +++ /dev/null @@ -1,88 +0,0 @@ -//********************************************************* -// -// Copyright (c) Microsoft. All rights reserved. -// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF -// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY -// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR -// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. -// -//********************************************************* - -// -// BasicCapture.xaml.h -// Declaration of the BasicCapture class -// - -#pragma once - -#include "pch.h" -#include "BasicCapture.g.h" -#include "MainPage.xaml.h" - -using namespace Windows::UI::Xaml; -using namespace Windows::UI::Xaml::Controls; -using namespace Windows::Graphics::Display; -using namespace Windows::UI::ViewManagement; -using namespace Windows::Devices::Enumeration; -#define VIDEO_FILE_NAME "video.mp4" -#define PHOTO_FILE_NAME "photo.jpg" -namespace SDKSample -{ - namespace MediaCapture - { - /// - /// An empty page that can be used on its own or navigated to within a Frame. - /// - [Windows::Foundation::Metadata::WebHostHidden] - public ref class BasicCapture sealed - { - public: - BasicCapture(); - - protected: - virtual void OnNavigatedTo(Windows::UI::Xaml::Navigation::NavigationEventArgs^ e) override; - virtual void OnNavigatedFrom(Windows::UI::Xaml::Navigation::NavigationEventArgs^ e) override; - - private: - MainPage^ rootPage; - void ScenarioInit(); - void ScenarioReset(); - - void Suspending(Object^ sender, Windows::ApplicationModel::SuspendingEventArgs^ e); - void Resuming(Object^ sender, Object^ e); - - void btnStartDevice_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e); - void SoundLevelChanged(Object^ sender, Object^ e); - void RecordLimitationExceeded(Windows::Media::Capture::MediaCapture ^ mediaCapture); - void Failed(Windows::Media::Capture::MediaCapture ^ mediaCapture, Windows::Media::Capture::MediaCaptureFailedEventArgs ^ args); - - - void btnStartPreview_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e); - - void btnStartStopRecord_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e); - - void btnTakePhoto_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e); - - void SetupVideoDeviceControl(Windows::Media::Devices::MediaDeviceControl^ videoDeviceControl, Slider^ slider); - void sldBrightness_ValueChanged(Platform::Object^ sender, Windows::UI::Xaml::Controls::Primitives::RangeBaseValueChangedEventArgs^ e); - void sldContrast_ValueChanged(Platform::Object^ sender, Windows::UI::Xaml::Controls::Primitives::RangeBaseValueChangedEventArgs^ e); - - void ShowStatusMessage(Platform::String^ text); - void ShowExceptionMessage(Platform::Exception^ ex); - - void EnableButton(bool enabled, Platform::String ^name); - void SwitchRecordButtonContent(); - - Platform::Agile m_mediaCaptureMgr; - Windows::Storage::StorageFile^ m_photoStorageFile; - Windows::Storage::StorageFile^ m_recordStorageFile; - bool m_bRecording; - bool m_bEffectAdded; - bool m_bSuspended; - bool m_bPreviewing; - Windows::UI::Xaml::WindowVisibilityChangedEventHandler ^m_visbilityHandler; - Windows::Foundation::EventRegistrationToken m_eventRegistrationToken; - bool m_currentScenarioLoaded; - }; - } -} diff --git a/samples/winrt/ImageManipulations/C++/Constants.cpp b/samples/winrt/ImageManipulations/C++/Constants.cpp index 873b983819..a26634272b 100644 --- a/samples/winrt/ImageManipulations/C++/Constants.cpp +++ b/samples/winrt/ImageManipulations/C++/Constants.cpp @@ -18,7 +18,5 @@ Platform::Array^ MainPage::scenariosInner = ref new Platform::Array - + @@ -47,7 +47,7 @@ - + @@ -92,7 +92,7 @@ - + diff --git a/samples/winrt/ImageManipulations/C++/MediaCapture.vcxproj b/samples/winrt/ImageManipulations/C++/MediaCapture.vcxproj index d2f255d1be..84b6d3df5c 100644 --- a/samples/winrt/ImageManipulations/C++/MediaCapture.vcxproj +++ b/samples/winrt/ImageManipulations/C++/MediaCapture.vcxproj @@ -101,14 +101,6 @@ AdvancedCapture.xaml Code - - AudioCapture.xaml - Code - - - BasicCapture.xaml - Code - MainPage.xaml @@ -127,12 +119,6 @@ Designer - - Designer - - - Designer - Designer @@ -156,14 +142,6 @@ App.xaml - - AudioCapture.xaml - Code - - - BasicCapture.xaml - Code - @@ -194,6 +172,144 @@ {ba69218f-da5c-4d14-a78d-21a9e4dec669} + + + true + true + true + true + true + true + + + true + true + true + true + true + true + + + true + true + true + true + true + true + + + true + true + true + true + true + true + + + true + true + true + true + true + true + + + true + true + true + true + true + true + + + true + true + true + true + true + true + + + true + true + true + true + true + true + + + true + true + true + true + true + true + + + true + true + true + true + true + true + + + true + true + true + true + true + true + + + true + true + true + true + true + true + + + true + true + true + true + true + true + + + true + true + true + true + true + true + + + true + true + true + true + true + true + + + true + true + true + true + true + true + + + true + true + true + true + true + true + + diff --git a/samples/winrt/ImageManipulations/C++/MediaCapture.vcxproj.filters b/samples/winrt/ImageManipulations/C++/MediaCapture.vcxproj.filters index 5f6124c2b3..403e5ea1f1 100644 --- a/samples/winrt/ImageManipulations/C++/MediaCapture.vcxproj.filters +++ b/samples/winrt/ImageManipulations/C++/MediaCapture.vcxproj.filters @@ -40,9 +40,7 @@ Sample-Utils - - @@ -56,8 +54,6 @@ - - @@ -71,8 +67,6 @@ - - @@ -85,4 +79,23 @@ {54f287f8-e4cb-4f47-97d0-4c469de6992e} + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/winrt/ImageManipulations/C++/MediaExtensions/Grayscale/Grayscale.cpp b/samples/winrt/ImageManipulations/C++/MediaExtensions/Grayscale/Grayscale.cpp index 687386ecee..d41fa34815 100644 --- a/samples/winrt/ImageManipulations/C++/MediaExtensions/Grayscale/Grayscale.cpp +++ b/samples/winrt/ImageManipulations/C++/MediaExtensions/Grayscale/Grayscale.cpp @@ -8,6 +8,9 @@ #include "Grayscale.h" #include "bufferlock.h" +#include "opencv2\core\core.hpp" +#include "opencv2\imgproc\imgproc.hpp" + #pragma comment(lib, "d2d1") using namespace Microsoft::WRL; @@ -80,16 +83,12 @@ NOTES ON THE MFT IMPLEMENTATION // Video FOURCC codes. -const DWORD FOURCC_YUY2 = '2YUY'; -const DWORD FOURCC_UYVY = 'YVYU'; const DWORD FOURCC_NV12 = '21VN'; // Static array of media types (preferred and accepted). const GUID g_MediaSubtypes[] = { - MFVideoFormat_NV12, - MFVideoFormat_YUY2, - MFVideoFormat_UYVY + MFVideoFormat_NV12 }; HRESULT GetImageSize(DWORD fcc, UINT32 width, UINT32 height, DWORD* pcbImage); @@ -102,27 +101,6 @@ inline T clamp(const T& val, const T& minVal, const T& maxVal) return (val < minVal ? minVal : (val > maxVal ? maxVal : val)); } - -// TransformChroma: -// Apply the transforms to calculate the output chroma values. - -void TransformChroma(const D2D1::Matrix3x2F& mat, BYTE *pu, BYTE *pv) -{ - // Normalize the chroma values to [-112, 112] range - - D2D1_POINT_2F pt = { static_cast(*pu) - 128, static_cast(*pv) - 128 }; - - pt = mat.TransformPoint(pt); - - // Clamp to valid range. - clamp(pt.x, -112.0f, 112.0f); - clamp(pt.y, -112.0f, 112.0f); - - // Map back to [16...240] range. - *pu = static_cast(pt.x + 128.0f); - *pv = static_cast(pt.y + 128.0f); -} - //------------------------------------------------------------------- // Functions to convert a YUV images to grayscale. // @@ -141,144 +119,6 @@ void TransformChroma(const D2D1::Matrix3x2F& mat, BYTE *pu, BYTE *pv) // dwHeightInPixels Frame height, in pixels. //------------------------------------------------------------------- -// Convert UYVY image. - -void TransformImage_UYVY( - const D2D1::Matrix3x2F& mat, - const D2D_RECT_U& rcDest, - _Inout_updates_(_Inexpressible_(lDestStride * dwHeightInPixels)) BYTE *pDest, - _In_ LONG lDestStride, - _In_reads_(_Inexpressible_(lSrcStride * dwHeightInPixels)) const BYTE* pSrc, - _In_ LONG lSrcStride, - _In_ DWORD dwWidthInPixels, - _In_ DWORD dwHeightInPixels) -{ - DWORD y = 0; - const DWORD y0 = min(rcDest.bottom, dwHeightInPixels); - - // Lines above the destination rectangle. - for ( ; y < rcDest.top; y++) - { - memcpy(pDest, pSrc, dwWidthInPixels * 2); - pSrc += lSrcStride; - pDest += lDestStride; - } - - // Lines within the destination rectangle. - for ( ; y < y0; y++) - { - WORD *pSrc_Pixel = (WORD*)pSrc; - WORD *pDest_Pixel = (WORD*)pDest; - - for (DWORD x = 0; (x + 1) < dwWidthInPixels; x += 2) - { - // Byte order is U0 Y0 V0 Y1 - // Each WORD is a byte pair (U/V, Y) - // Windows is little-endian so the order appears reversed. - - if (x >= rcDest.left && x < rcDest.right) - { - BYTE u = pSrc_Pixel[x] & 0x00FF; - BYTE v = pSrc_Pixel[x+1] & 0x00FF; - - TransformChroma(mat, &u, &v); - - pDest_Pixel[x] = (pSrc_Pixel[x] & 0xFF00) | u; - pDest_Pixel[x+1] = (pSrc_Pixel[x+1] & 0xFF00) | v; - } - else - { -#pragma warning(push) -#pragma warning(disable: 6385) -#pragma warning(disable: 6386) - pDest_Pixel[x] = pSrc_Pixel[x]; - pDest_Pixel[x+1] = pSrc_Pixel[x+1]; -#pragma warning(pop) - } - } - - pDest += lDestStride; - pSrc += lSrcStride; - } - - // Lines below the destination rectangle. - for ( ; y < dwHeightInPixels; y++) - { - memcpy(pDest, pSrc, dwWidthInPixels * 2); - pSrc += lSrcStride; - pDest += lDestStride; - } -} - - -// Convert YUY2 image. - -void TransformImage_YUY2( - const D2D1::Matrix3x2F& mat, - const D2D_RECT_U& rcDest, - _Inout_updates_(_Inexpressible_(lDestStride * dwHeightInPixels)) BYTE *pDest, - _In_ LONG lDestStride, - _In_reads_(_Inexpressible_(lSrcStride * dwHeightInPixels)) const BYTE* pSrc, - _In_ LONG lSrcStride, - _In_ DWORD dwWidthInPixels, - _In_ DWORD dwHeightInPixels) -{ - DWORD y = 0; - const DWORD y0 = min(rcDest.bottom, dwHeightInPixels); - - // Lines above the destination rectangle. - for ( ; y < rcDest.top; y++) - { - memcpy(pDest, pSrc, dwWidthInPixels * 2); - pSrc += lSrcStride; - pDest += lDestStride; - } - - // Lines within the destination rectangle. - for ( ; y < y0; y++) - { - WORD *pSrc_Pixel = (WORD*)pSrc; - WORD *pDest_Pixel = (WORD*)pDest; - - for (DWORD x = 0; (x + 1) < dwWidthInPixels; x += 2) - { - // Byte order is Y0 U0 Y1 V0 - // Each WORD is a byte pair (Y, U/V) - // Windows is little-endian so the order appears reversed. - - if (x >= rcDest.left && x < rcDest.right) - { - BYTE u = pSrc_Pixel[x] >> 8; - BYTE v = pSrc_Pixel[x+1] >> 8; - - TransformChroma(mat, &u, &v); - - pDest_Pixel[x] = (pSrc_Pixel[x] & 0x00FF) | (u<<8); - pDest_Pixel[x+1] = (pSrc_Pixel[x+1] & 0x00FF) | (v<<8); - } - else - { -#pragma warning(push) -#pragma warning(disable: 6385) -#pragma warning(disable: 6386) - pDest_Pixel[x] = pSrc_Pixel[x]; - pDest_Pixel[x+1] = pSrc_Pixel[x+1]; -#pragma warning(pop) - } - } - pDest += lDestStride; - pSrc += lSrcStride; - } - - // Lines below the destination rectangle. - for ( ; y < dwHeightInPixels; y++) - { - memcpy(pDest, pSrc, dwWidthInPixels * 2); - pSrc += lSrcStride; - pDest += lDestStride; - } -} - // Convert NV12 image void TransformImage_NV12( @@ -307,7 +147,8 @@ void TransformImage_NV12( // Lines above the destination rectangle. DWORD y = 0; - const DWORD y0 = min(rcDest.bottom, dwHeightInPixels); + + const DWORD y0 = rcDest.bottom < dwHeightInPixels ? rcDest.bottom : dwHeightInPixels; for ( ; y < rcDest.top/2; y++) { @@ -323,13 +164,8 @@ void TransformImage_NV12( { if (x >= rcDest.left && x < rcDest.right) { - BYTE u = pSrc[x]; - BYTE v = pSrc[x+1]; - - TransformChroma(mat, &u, &v); - - pDest[x] = u; - pDest[x+1] = v; + pDest[x] = 0; + pDest[x+1] = 0; } else { @@ -351,9 +187,9 @@ void TransformImage_NV12( } CGrayscale::CGrayscale() : - m_pSample(NULL), m_pInputType(NULL), m_pOutputType(NULL), m_pTransformFn(NULL), + m_pSample(NULL), m_pInputType(NULL), m_pOutputType(NULL), m_imageWidthInPixels(0), m_imageHeightInPixels(0), m_cbImageSize(0), - m_transform(D2D1::Matrix3x2F::Identity()), m_rcDest(D2D1::RectU()), m_bStreamingInitialized(false), + m_TransformType(Preview), m_rcDest(D2D1::RectU()), m_bStreamingInitialized(false), m_pAttributes(NULL) { InitializeCriticalSectionEx(&m_critSec, 3000, 0); @@ -1516,10 +1352,8 @@ HRESULT CGrayscale::BeginStreaming() // Get the chroma transformations. - float scale = (float)MFGetAttributeDouble(m_pAttributes, MFT_GRAYSCALE_SATURATION, 0.0f); - float angle = (float)MFGetAttributeDouble(m_pAttributes, MFT_GRAYSCALE_CHROMA_ROTATION, 0.0f); - - m_transform = D2D1::Matrix3x2F::Scale(scale, scale) * D2D1::Matrix3x2F::Rotation(angle); + // float scale = (float)MFGetAttributeDouble(m_pAttributes, MFT_GRAYSCALE_SATURATION, 0.0f); + // float angle = (float)MFGetAttributeDouble(m_pAttributes, MFT_GRAYSCALE_CHROMA_ROTATION, 0.0f); m_bStreamingInitialized = true; } @@ -1563,42 +1397,36 @@ HRESULT CGrayscale::OnProcessOutput(IMFMediaBuffer *pIn, IMFMediaBuffer *pOut) HRESULT hr = GetDefaultStride(m_pInputType, &lDefaultStride); if (FAILED(hr)) { - goto done; + return hr; } // Lock the input buffer. hr = inputLock.LockBuffer(lDefaultStride, m_imageHeightInPixels, &pSrc, &lSrcStride); if (FAILED(hr)) { - goto done; + return hr; } // Lock the output buffer. hr = outputLock.LockBuffer(lDefaultStride, m_imageHeightInPixels, &pDest, &lDestStride); if (FAILED(hr)) { - goto done; - } - - // Invoke the image transform function. - assert (m_pTransformFn != NULL); - if (m_pTransformFn) - { - (*m_pTransformFn)(m_transform, m_rcDest, pDest, lDestStride, pSrc, lSrcStride, - m_imageWidthInPixels, m_imageHeightInPixels); - } - else - { - hr = E_UNEXPECTED; - goto done; + return hr; } + //(*m_pTransformFn)(m_transform, m_rcDest, pDest, lDestStride, pSrc, lSrcStride, + // m_imageWidthInPixels, m_imageHeightInPixels); + cv::Mat InputFrame(m_imageHeightInPixels + m_imageHeightInPixels/2, m_imageWidthInPixels, CV_8UC1, pSrc, lSrcStride); + cv::Mat InputGreyScale(InputFrame, cv::Range(0, m_imageHeightInPixels), cv::Range(0, m_imageWidthInPixels)); + cv::Mat OutputFrame(m_imageHeightInPixels + m_imageHeightInPixels/2, m_imageWidthInPixels, CV_8UC1, pDest, lDestStride); + OutputFrame.setTo(cv::Scalar(128)); + cv::Mat OutputGreyScale(OutputFrame, cv::Range(0, m_imageHeightInPixels), cv::Range(0, m_imageWidthInPixels)); + cv::Canny(InputGreyScale, OutputGreyScale, 80, 90); + // Set the data size on the output buffer. hr = pOut->SetCurrentLength(m_cbImageSize); - // The VideoBufferLock class automatically unlocks the buffers. -done: return hr; } @@ -1626,8 +1454,6 @@ HRESULT CGrayscale::UpdateFormatInfo() m_imageHeightInPixels = 0; m_cbImageSize = 0; - m_pTransformFn = NULL; - if (m_pInputType != NULL) { hr = m_pInputType->GetGUID(MF_MT_SUBTYPE, &subtype); @@ -1635,19 +1461,7 @@ HRESULT CGrayscale::UpdateFormatInfo() { goto done; } - if (subtype == MFVideoFormat_YUY2) - { - m_pTransformFn = TransformImage_YUY2; - } - else if (subtype == MFVideoFormat_UYVY) - { - m_pTransformFn = TransformImage_UYVY; - } - else if (subtype == MFVideoFormat_NV12) - { - m_pTransformFn = TransformImage_NV12; - } - else + if (subtype != MFVideoFormat_NV12) { hr = E_UNEXPECTED; goto done; @@ -1678,20 +1492,6 @@ HRESULT GetImageSize(DWORD fcc, UINT32 width, UINT32 height, DWORD* pcbImage) switch (fcc) { - case FOURCC_YUY2: - case FOURCC_UYVY: - // check overflow - if ((width > MAXDWORD / 2) || (width * 2 > MAXDWORD / height)) - { - hr = E_INVALIDARG; - } - else - { - // 16 bpp - *pcbImage = width * height * 2; - } - break; - case FOURCC_NV12: // check overflow if ((height/2 > MAXDWORD - height) || ((height + height/2) > MAXDWORD / width)) diff --git a/samples/winrt/ImageManipulations/C++/MediaExtensions/Grayscale/Grayscale.h b/samples/winrt/ImageManipulations/C++/MediaExtensions/Grayscale/Grayscale.h index b83223bce5..96b4b1b965 100644 --- a/samples/winrt/ImageManipulations/C++/MediaExtensions/Grayscale/Grayscale.h +++ b/samples/winrt/ImageManipulations/C++/MediaExtensions/Grayscale/Grayscale.h @@ -42,15 +42,14 @@ DEFINE_GUID(CLSID_GrayscaleMFT, DEFINE_GUID(MFT_GRAYSCALE_DESTINATION_RECT, 0x7bbbb051, 0x133b, 0x41f5, 0xb6, 0xaa, 0x5a, 0xff, 0x9b, 0x33, 0xa2, 0xcb); - -// {14782342-93E8-4565-872C-D9A2973D5CBF} -DEFINE_GUID(MFT_GRAYSCALE_SATURATION, -0x14782342, 0x93e8, 0x4565, 0x87, 0x2c, 0xd9, 0xa2, 0x97, 0x3d, 0x5c, 0xbf); - -// {E0BADE5D-E4B9-4689-9DBA-E2F00D9CED0E} -DEFINE_GUID(MFT_GRAYSCALE_CHROMA_ROTATION, -0xe0bade5d, 0xe4b9, 0x4689, 0x9d, 0xba, 0xe2, 0xf0, 0xd, 0x9c, 0xed, 0xe); - +enum ProcessingType +{ + Preview, + GrayScale, + Canny, + Zoom, + Sepia +}; template void SafeRelease(T **ppT) { @@ -61,18 +60,6 @@ template void SafeRelease(T **ppT) } } -// Function pointer for the function that transforms the image. -typedef void (*IMAGE_TRANSFORM_FN)( - const D2D1::Matrix3x2F& mat, // Chroma transform matrix. - const D2D_RECT_U& rcDest, // Destination rectangle for the transformation. - BYTE* pDest, // Destination buffer. - LONG lDestStride, // Destination stride. - const BYTE* pSrc, // Source buffer. - LONG lSrcStride, // Source stride. - DWORD dwWidthInPixels, // Image width in pixels. - DWORD dwHeightInPixels // Image height in pixels. - ); - // CGrayscale class: // Implements a grayscale video effect. @@ -244,7 +231,7 @@ private: CRITICAL_SECTION m_critSec; // Transformation parameters - D2D1::Matrix3x2F m_transform; // Chroma transform matrix. + ProcessingType m_TransformType; D2D_RECT_U m_rcDest; // Destination rectangle for the effect. // Streaming @@ -259,8 +246,5 @@ private: DWORD m_cbImageSize; // Image size, in bytes. IMFAttributes *m_pAttributes; - - // Image transform function. (Changes based on the media type.) - IMAGE_TRANSFORM_FN m_pTransformFn; }; #endif \ No newline at end of file diff --git a/samples/winrt/ImageManipulations/C++/MediaExtensions/Grayscale/Grayscale.vcxproj b/samples/winrt/ImageManipulations/C++/MediaExtensions/Grayscale/Grayscale.vcxproj index 8af8f2c7d9..c7e905936d 100644 --- a/samples/winrt/ImageManipulations/C++/MediaExtensions/Grayscale/Grayscale.vcxproj +++ b/samples/winrt/ImageManipulations/C++/MediaExtensions/Grayscale/Grayscale.vcxproj @@ -123,13 +123,14 @@ $(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories) false - $(ProjectDir);$(IntermediateOutputPath);%(AdditionalIncludeDirectories);$(ProjectDir)\..\Common; + $(OPENCV_DIR)\include;$(ProjectDir);$(IntermediateOutputPath);%(AdditionalIncludeDirectories);$(ProjectDir)\..\Common Console - runtimeobject.lib;%(AdditionalDependencies);mf.lib;mfuuid.lib;mfplat.lib + runtimeobject.lib;%(AdditionalDependencies);mf.lib;mfuuid.lib;mfplat.lib;opencv_core245.lib;opencv_imgproc245.lib false Grayscale.def + $(OPENCV_DIR)\lib;%(AdditionalLibraryDirectories) mdmerge -metadata_dir "$(WindowsSDK_MetadataPath)" -o "$(ProjectDir)$(Configuration)\$(MSBuildProjectName)" -i "$(MSBuildProjectDirectory)" -v -partial @@ -146,13 +147,14 @@ $(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories) false - $(ProjectDir);$(IntermediateOutputPath);%(AdditionalIncludeDirectories);$(ProjectDir)\..\Common; + $(OPENCV_DIR)\include;$(ProjectDir);$(IntermediateOutputPath);%(AdditionalIncludeDirectories);$(ProjectDir)\..\Common Console runtimeobject.lib;%(AdditionalDependencies);mf.lib;mfuuid.lib;mfplat.lib false Grayscale.def + $(OPENCV_DIR)\lib;%(AdditionalLibraryDirectories) mdmerge -metadata_dir "$(WindowsSDK_MetadataPath)" -o "$(SolutionDir)$(Platform)\$(Configuration)\$(MSBuildProjectName)" -i "$(MSBuildProjectDirectory)" -v -partial @@ -169,13 +171,14 @@ $(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories) false - $(ProjectDir);$(IntermediateOutputPath);%(AdditionalIncludeDirectories);$(ProjectDir)\..\Common; + $(OPENCV_DIR)\include;$(ProjectDir);$(IntermediateOutputPath);%(AdditionalIncludeDirectories);$(ProjectDir)\..\Common Console runtimeobject.lib;%(AdditionalDependencies);mf.lib;mfuuid.lib;mfplat.lib false Grayscale.def + $(OPENCV_DIR)\lib;%(AdditionalLibraryDirectories) mdmerge -metadata_dir "$(WindowsSDK_MetadataPath)" -o "$(SolutionDir)$(Platform)\$(Configuration)\$(MSBuildProjectName)" -i "$(MSBuildProjectDirectory)" -v -partial @@ -192,13 +195,14 @@ $(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories) false - $(ProjectDir);$(IntermediateOutputPath);%(AdditionalIncludeDirectories);$(ProjectDir)\..\Common; + $(OPENCV_DIR)\include;$(ProjectDir);$(IntermediateOutputPath);%(AdditionalIncludeDirectories);$(ProjectDir)\..\Common Console runtimeobject.lib;%(AdditionalDependencies);mf.lib;mfuuid.lib;mfplat.lib false Grayscale.def + $(OPENCV_DIR)\lib;%(AdditionalLibraryDirectories) mdmerge -metadata_dir "$(WindowsSDK_MetadataPath)" -o "$(ProjectDir)$(Configuration)\$(MSBuildProjectName)" -i "$(MSBuildProjectDirectory)" -v -partial @@ -215,13 +219,14 @@ $(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories) false - $(ProjectDir);$(IntermediateOutputPath);%(AdditionalIncludeDirectories);$(ProjectDir)\..\Common; + $(OPENCV_DIR)\include;$(ProjectDir);$(IntermediateOutputPath);%(AdditionalIncludeDirectories);$(ProjectDir)\..\Common Console runtimeobject.lib;%(AdditionalDependencies);mf.lib;mfuuid.lib;mfplat.lib false Grayscale.def + $(OPENCV_DIR)\lib;%(AdditionalLibraryDirectories) mdmerge -metadata_dir "$(WindowsSDK_MetadataPath)" -o "$(SolutionDir)$(Platform)\$(Configuration)\$(MSBuildProjectName)" -i "$(MSBuildProjectDirectory)" -v -partial @@ -238,13 +243,14 @@ $(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories) false - $(ProjectDir);$(IntermediateOutputPath);%(AdditionalIncludeDirectories);$(ProjectDir)\..\Common; + $(OPENCV_DIR)\include;$(ProjectDir);$(IntermediateOutputPath);%(AdditionalIncludeDirectories);$(ProjectDir)\..\Common Console runtimeobject.lib;%(AdditionalDependencies);mf.lib;mfuuid.lib;mfplat.lib false Grayscale.def + $(OPENCV_DIR)\lib;%(AdditionalLibraryDirectories) mdmerge -metadata_dir "$(WindowsSDK_MetadataPath)" -o "$(SolutionDir)$(Platform)\$(Configuration)\$(MSBuildProjectName)" -i "$(MSBuildProjectDirectory)" -v -partial From de9f659f1e37d798ff22c8b9a1c3a13f68bdde12 Mon Sep 17 00:00:00 2001 From: Alexander Smorkalov Date: Mon, 10 Jun 2013 11:48:53 -0700 Subject: [PATCH 165/178] Several transforms added to sample IMFTransform. --- .../C++/AdvancedCapture.xaml | 3 +- .../C++/AdvancedCapture.xaml.cpp | 45 +++--- .../C++/AdvancedCapture.xaml.h | 1 + .../ImageManipulations/C++/MainPage.xaml | 12 +- .../MediaExtensions/Grayscale/Grayscale.cpp | 153 ++++++++++++------ .../C++/MediaExtensions/Grayscale/Grayscale.h | 22 +-- 6 files changed, 146 insertions(+), 90 deletions(-) diff --git a/samples/winrt/ImageManipulations/C++/AdvancedCapture.xaml b/samples/winrt/ImageManipulations/C++/AdvancedCapture.xaml index 4e6ebfd301..e6266adb9d 100644 --- a/samples/winrt/ImageManipulations/C++/AdvancedCapture.xaml +++ b/samples/winrt/ImageManipulations/C++/AdvancedCapture.xaml @@ -40,7 +40,8 @@ - + + diff --git a/samples/winrt/ImageManipulations/C++/AdvancedCapture.xaml.cpp b/samples/winrt/ImageManipulations/C++/AdvancedCapture.xaml.cpp index dc59acc2e3..15890ba6b1 100644 --- a/samples/winrt/ImageManipulations/C++/AdvancedCapture.xaml.cpp +++ b/samples/winrt/ImageManipulations/C++/AdvancedCapture.xaml.cpp @@ -122,7 +122,7 @@ void AdvancedCapture::ScenarioReset() void AdvancedCapture::SoundLevelChanged(Object^ sender, Object^ e) { create_task(Dispatcher->RunAsync(Windows::UI::Core::CoreDispatcherPriority::High, ref new Windows::UI::Core::DispatchedHandler([this]() - { + { if(Windows::Media::MediaControl::SoundLevel != Windows::Media::SoundLevel::Muted) { ScenarioReset(); @@ -220,7 +220,7 @@ void AdvancedCapture::RecordLimitationExceeded(Windows::Media::Capture::MediaCap void AdvancedCapture::Failed(Windows::Media::Capture::MediaCapture ^currentCaptureObject, Windows::Media::Capture::MediaCaptureFailedEventArgs^ currentFailure) { String ^message = "Fatal error" + currentFailure->Message; - create_task(Dispatcher->RunAsync(Windows::UI::Core::CoreDispatcherPriority::High, + create_task(Dispatcher->RunAsync(Windows::UI::Core::CoreDispatcherPriority::High, ref new Windows::UI::Core::DispatchedHandler([this, message]() { ShowStatusMessage(message); @@ -325,7 +325,7 @@ void AdvancedCapture::btnTakePhoto_Click(Platform::Object^ sender, Windows::UI:: EnableButton(false, "TakePhoto"); auto currentRotation = GetCurrentPhotoRotation(); - task(KnownFolders::PicturesLibrary->CreateFileAsync(TEMP_PHOTO_FILE_NAME, Windows::Storage::CreationCollisionOption::GenerateUniqueName)).then([this, currentRotation](task getFileTask) + task(KnownFolders::PicturesLibrary->CreateFileAsync(TEMP_PHOTO_FILE_NAME, Windows::Storage::CreationCollisionOption::GenerateUniqueName)).then([this, currentRotation](task getFileTask) { try { @@ -520,7 +520,7 @@ void AdvancedCapture::lstEnumedDevices_SelectionChanged(Platform::Object^ sender } }); } - + btnStartDevice2->IsEnabled = true; btnStartPreview2->IsEnabled = false; btnStartStopRecord2->IsEnabled = false; @@ -581,12 +581,12 @@ void AdvancedCapture::EnumerateWebcamsAsync() } void AdvancedCapture::AddEffectToImageStream() -{ +{ auto mediaCapture = m_mediaCaptureMgr.Get(); Windows::Media::Capture::VideoDeviceCharacteristic charecteristic = mediaCapture->MediaCaptureSettings->VideoDeviceCharacteristic; if((charecteristic != Windows::Media::Capture::VideoDeviceCharacteristic::AllStreamsIdentical) && - (charecteristic != Windows::Media::Capture::VideoDeviceCharacteristic::PreviewPhotoStreamsIdentical) && + (charecteristic != Windows::Media::Capture::VideoDeviceCharacteristic::PreviewPhotoStreamsIdentical) && (charecteristic != Windows::Media::Capture::VideoDeviceCharacteristic::RecordPhotoStreamsIdentical)) { Windows::Media::MediaProperties::IMediaEncodingProperties ^props = mediaCapture->VideoDeviceController->GetMediaStreamProperties(Windows::Media::Capture::MediaStreamType::Photo); @@ -596,13 +596,13 @@ void AdvancedCapture::AddEffectToImageStream() Windows::Foundation::Collections::IVectorView^ supportedPropsList = mediaCapture->VideoDeviceController->GetAvailableMediaStreamProperties(Windows::Media::Capture::MediaStreamType::Photo); { unsigned int i = 0; - while (i< supportedPropsList->Size) + while (i < supportedPropsList->Size) { Windows::Media::MediaProperties::IMediaEncodingProperties^ props = supportedPropsList->GetAt(i); String^ s = props->Type; if(props->Type->Equals("Video")) - { + { task(mediaCapture->VideoDeviceController->SetMediaStreamPropertiesAsync(Windows::Media::Capture::MediaStreamType::Photo,props)).then([this](task changeTypeTask) { try @@ -616,7 +616,7 @@ void AdvancedCapture::AddEffectToImageStream() { effectTask3.get(); m_bEffectAddedToPhoto = true; - ShowStatusMessage("Adding effect to photo stream successful"); + ShowStatusMessage("Adding effect to photo stream successful"); chkAddRemoveEffect->IsEnabled = true; } @@ -633,8 +633,7 @@ void AdvancedCapture::AddEffectToImageStream() { ShowExceptionMessage(e); chkAddRemoveEffect->IsEnabled = true; - chkAddRemoveEffect->IsChecked = false; - + chkAddRemoveEffect->IsChecked = false; } }); @@ -686,8 +685,8 @@ void AdvancedCapture::chkAddRemoveEffect_Checked(Platform::Object^ sender, Windo auto mediaCapture = m_mediaCaptureMgr.Get(); Windows::Media::Capture::VideoDeviceCharacteristic charecteristic = mediaCapture->MediaCaptureSettings->VideoDeviceCharacteristic; - ShowStatusMessage("Add effect successful to preview stream successful"); - if((charecteristic != Windows::Media::Capture::VideoDeviceCharacteristic::AllStreamsIdentical) && + ShowStatusMessage("Add effect successful to preview stream successful"); + if((charecteristic != Windows::Media::Capture::VideoDeviceCharacteristic::AllStreamsIdentical) && (charecteristic != Windows::Media::Capture::VideoDeviceCharacteristic::PreviewRecordStreamsIdentical)) { Windows::Media::MediaProperties::IMediaEncodingProperties ^props = mediaCapture->VideoDeviceController->GetMediaStreamProperties(Windows::Media::Capture::MediaStreamType::VideoRecord); @@ -703,14 +702,14 @@ void AdvancedCapture::chkAddRemoveEffect_Checked(Platform::Object^ sender, Windo m_bEffectAddedToRecord = true; AddEffectToImageStream(); chkAddRemoveEffect->IsEnabled = true; - } + } catch(Exception ^e) { ShowExceptionMessage(e); chkAddRemoveEffect->IsEnabled = true; chkAddRemoveEffect->IsChecked = false; } - }); + }); } else { @@ -718,7 +717,7 @@ void AdvancedCapture::chkAddRemoveEffect_Checked(Platform::Object^ sender, Windo chkAddRemoveEffect->IsEnabled = true; } - } + } else { AddEffectToImageStream(); @@ -777,7 +776,7 @@ void AdvancedCapture::chkAddRemoveEffect_Unchecked(Platform::Object^ sender, Win { ShowExceptionMessage(e); chkAddRemoveEffect->IsEnabled = true; - chkAddRemoveEffect->IsChecked = true; + chkAddRemoveEffect->IsChecked = true; } }); @@ -791,7 +790,7 @@ void AdvancedCapture::chkAddRemoveEffect_Unchecked(Platform::Object^ sender, Win { ShowExceptionMessage(e); chkAddRemoveEffect->IsEnabled = true; - chkAddRemoveEffect->IsChecked = true; + chkAddRemoveEffect->IsChecked = true; } @@ -813,7 +812,7 @@ void AdvancedCapture::chkAddRemoveEffect_Unchecked(Platform::Object^ sender, Win { ShowExceptionMessage(e); chkAddRemoveEffect->IsEnabled = true; - chkAddRemoveEffect->IsChecked = true; + chkAddRemoveEffect->IsChecked = true; } }); @@ -821,7 +820,7 @@ void AdvancedCapture::chkAddRemoveEffect_Unchecked(Platform::Object^ sender, Win else { chkAddRemoveEffect->IsEnabled = true; - chkAddRemoveEffect->IsChecked = true; + chkAddRemoveEffect->IsChecked = true; } } catch (Exception ^e) @@ -1032,3 +1031,9 @@ Windows::Media::Capture::VideoRotation AdvancedCapture::VideoRotationLookup( } } + + +void SDKSample::MediaCapture::AdvancedCapture::EffectType_SelectionChanged(Platform::Object^ sender, Windows::UI::Xaml::Controls::SelectionChangedEventArgs^ e) +{ + +} diff --git a/samples/winrt/ImageManipulations/C++/AdvancedCapture.xaml.h b/samples/winrt/ImageManipulations/C++/AdvancedCapture.xaml.h index 83556b95e2..4784900d71 100644 --- a/samples/winrt/ImageManipulations/C++/AdvancedCapture.xaml.h +++ b/samples/winrt/ImageManipulations/C++/AdvancedCapture.xaml.h @@ -98,6 +98,7 @@ namespace SDKSample bool m_bRotateVideoOnOrientationChange; bool m_bReversePreviewRotation; Windows::Foundation::EventRegistrationToken m_orientationChangedEventToken; + void EffectType_SelectionChanged(Platform::Object^ sender, Windows::UI::Xaml::Controls::SelectionChangedEventArgs^ e); }; } } diff --git a/samples/winrt/ImageManipulations/C++/MainPage.xaml b/samples/winrt/ImageManipulations/C++/MainPage.xaml index 82d5494b62..e0ed0d79c1 100644 --- a/samples/winrt/ImageManipulations/C++/MainPage.xaml +++ b/samples/winrt/ImageManipulations/C++/MainPage.xaml @@ -116,17 +116,7 @@ - - - - - - - - - + diff --git a/samples/winrt/ImageManipulations/C++/MediaExtensions/Grayscale/Grayscale.cpp b/samples/winrt/ImageManipulations/C++/MediaExtensions/Grayscale/Grayscale.cpp index d41fa34815..e853d46272 100644 --- a/samples/winrt/ImageManipulations/C++/MediaExtensions/Grayscale/Grayscale.cpp +++ b/samples/winrt/ImageManipulations/C++/MediaExtensions/Grayscale/Grayscale.cpp @@ -30,9 +30,9 @@ MFT_GRAYSCALE_DESTINATION_RECT (type = blob, UINT32[4] array) MFT_GRAYSCALE_SATURATION (type = double) - Sets the saturation level. The nominal range is [0...1]. Values beyond 1.0f + Sets the saturation level. The nominal range is [0...1]. Values beyond 1.0f result in supersaturated colors. Values below 0.0f create inverted colors. - + MFT_GRAYSCALE_CHROMA_ROTATION (type = double) Rotates the chroma values of each pixel. The attribue value is the angle of @@ -45,7 +45,7 @@ as a scaling transform. NOTES ON THE MFT IMPLEMENTATION -1. The MFT has fixed streams: One input stream and one output stream. +1. The MFT has fixed streams: One input stream and one output stream. 2. The MFT supports the following formats: UYVY, YUY2, NV12. @@ -56,34 +56,34 @@ NOTES ON THE MFT IMPLEMENTATION 5. If both types are set, no type can be set until the current type is cleared. 6. Preferred input types: - + (a) If the output type is set, that's the preferred type. - (b) Otherwise, the preferred types are partial types, constructed from the + (b) Otherwise, the preferred types are partial types, constructed from the list of supported subtypes. - + 7. Preferred output types: As above. -8. Streaming: - - The private BeingStreaming() method is called in response to the - MFT_MESSAGE_NOTIFY_BEGIN_STREAMING message. +8. Streaming: + + The private BeingStreaming() method is called in response to the + MFT_MESSAGE_NOTIFY_BEGIN_STREAMING message. If the client does not send MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, the MFT calls - BeginStreaming inside the first call to ProcessInput or ProcessOutput. + BeginStreaming inside the first call to ProcessInput or ProcessOutput. This is a good approach for allocating resources that your MFT requires for - streaming. - -9. The configuration attributes are applied in the BeginStreaming method. If the - client changes the attributes during streaming, the change is ignored until - streaming is stopped (either by changing the media types or by sending the + streaming. + +9. The configuration attributes are applied in the BeginStreaming method. If the + client changes the attributes during streaming, the change is ignored until + streaming is stopped (either by changing the media types or by sending the MFT_MESSAGE_NOTIFY_END_STREAMING message) and then restarted. - + */ // Video FOURCC codes. -const DWORD FOURCC_NV12 = '21VN'; +const DWORD FOURCC_NV12 = '21VN'; // Static array of media types (preferred and accepted). const GUID g_MediaSubtypes[] = @@ -124,11 +124,11 @@ inline T clamp(const T& val, const T& minVal, const T& maxVal) void TransformImage_NV12( const D2D1::Matrix3x2F& mat, const D2D_RECT_U& rcDest, - _Inout_updates_(_Inexpressible_(2 * lDestStride * dwHeightInPixels)) BYTE *pDest, - _In_ LONG lDestStride, + _Inout_updates_(_Inexpressible_(2 * lDestStride * dwHeightInPixels)) BYTE *pDest, + _In_ LONG lDestStride, _In_reads_(_Inexpressible_(2 * lSrcStride * dwHeightInPixels)) const BYTE* pSrc, - _In_ LONG lSrcStride, - _In_ DWORD dwWidthInPixels, + _In_ LONG lSrcStride, + _In_ DWORD dwWidthInPixels, _In_ DWORD dwHeightInPixels) { // NV12 is planar: Y plane, followed by packed U-V plane. @@ -189,7 +189,7 @@ void TransformImage_NV12( CGrayscale::CGrayscale() : m_pSample(NULL), m_pInputType(NULL), m_pOutputType(NULL), m_imageWidthInPixels(0), m_imageHeightInPixels(0), m_cbImageSize(0), - m_TransformType(Preview), m_rcDest(D2D1::RectU()), m_bStreamingInitialized(false), + m_TransformType(Preview), m_rcDest(D2D1::RectU()), m_bStreamingInitialized(false), m_pAttributes(NULL) { InitializeCriticalSectionEx(&m_critSec, 3000, 0); @@ -786,12 +786,12 @@ HRESULT CGrayscale::GetInputStatus( return MF_E_INVALIDSTREAMNUMBER; } - // If an input sample is already queued, do not accept another sample until the + // If an input sample is already queued, do not accept another sample until the // client calls ProcessOutput or Flush. - // NOTE: It is possible for an MFT to accept more than one input sample. For - // example, this might be required in a video decoder if the frames do not - // arrive in temporal order. In the case, the decoder must hold a queue of + // NOTE: It is possible for an MFT to accept more than one input sample. For + // example, this might be required in a video decoder if the frames do not + // arrive in temporal order. In the case, the decoder must hold a queue of // samples. For the video effect, each sample is transformed independently, so // there is no reason to queue multiple input samples. @@ -902,12 +902,12 @@ HRESULT CGrayscale::ProcessMessage( case MFT_MESSAGE_SET_D3D_MANAGER: // Sets a pointer to the IDirect3DDeviceManager9 interface. - // The pipeline should never send this message unless the MFT sets the MF_SA_D3D_AWARE + // The pipeline should never send this message unless the MFT sets the MF_SA_D3D_AWARE // attribute set to TRUE. Because this MFT does not set MF_SA_D3D_AWARE, it is an error // to send the MFT_MESSAGE_SET_D3D_MANAGER message to the MFT. Return an error code in // this case. - // NOTE: If this MFT were D3D-enabled, it would cache the IDirect3DDeviceManager9 + // NOTE: If this MFT were D3D-enabled, it would cache the IDirect3DDeviceManager9 // pointer for use during streaming. hr = E_NOTIMPL; @@ -972,7 +972,7 @@ HRESULT CGrayscale::ProcessInput( // The client must set input and output types before calling ProcessInput. if (!m_pInputType || !m_pOutputType) { - hr = MF_E_NOTACCEPTING; + hr = MF_E_NOTACCEPTING; goto done; } @@ -1016,7 +1016,7 @@ HRESULT CGrayscale::ProcessOutput( // This MFT does not accept any flags for the dwFlags parameter. - // The only defined flag is MFT_PROCESS_OUTPUT_DISCARD_WHEN_NO_BUFFER. This flag + // The only defined flag is MFT_PROCESS_OUTPUT_DISCARD_WHEN_NO_BUFFER. This flag // applies only when the MFT marks an output stream as lazy or optional. But this // MFT has no lazy or optional streams, so the flag is not valid. @@ -1266,7 +1266,7 @@ HRESULT CGrayscale::OnCheckMediaType(IMFMediaType *pmt) goto done; } - // Reject single-field media types. + // Reject single-field media types. UINT32 interlace = MFGetAttributeUINT32(pmt, MF_MT_INTERLACE_MODE, MFVideoInterlace_Progressive); if (interlace == MFVideoInterlace_FieldSingleUpper || interlace == MFVideoInterlace_FieldSingleLower) { @@ -1350,10 +1350,13 @@ HRESULT CGrayscale::BeginStreaming() goto done; } - // Get the chroma transformations. + // Get the effect type + UINT32 effect = MFGetAttributeUINT32(m_pAttributes, MFT_IMAGE_EFFECT, 1); - // float scale = (float)MFGetAttributeDouble(m_pAttributes, MFT_GRAYSCALE_SATURATION, 0.0f); - // float angle = (float)MFGetAttributeDouble(m_pAttributes, MFT_GRAYSCALE_CHROMA_ROTATION, 0.0f); + if ((effect >= 0) && (effect < InvalidEffect)) + { + m_TransformType = (ProcessingType)effect; + } m_bStreamingInitialized = true; } @@ -1363,7 +1366,7 @@ done: } -// End streaming. +// End streaming. // This method is called if the client sends an MFT_MESSAGE_NOTIFY_END_STREAMING // message, or when the media type changes. In general, it should be called whenever @@ -1414,16 +1417,72 @@ HRESULT CGrayscale::OnProcessOutput(IMFMediaBuffer *pIn, IMFMediaBuffer *pOut) return hr; } - //(*m_pTransformFn)(m_transform, m_rcDest, pDest, lDestStride, pSrc, lSrcStride, - // m_imageWidthInPixels, m_imageHeightInPixels); + cv::Mat InputFrame(m_imageHeightInPixels + m_imageHeightInPixels/2, m_imageWidthInPixels, CV_8UC1, pSrc, lSrcStride); + cv::Mat InputGreyScale(InputFrame, cv::Range(0, m_imageHeightInPixels), cv::Range(0, m_imageWidthInPixels)); + cv::Mat OutputFrame(m_imageHeightInPixels + m_imageHeightInPixels/2, m_imageWidthInPixels, CV_8UC1, pDest, lDestStride); + + switch (m_TransformType) + { + case Preview: + { + InputFrame.copyTo(OutputFrame); + } break; + case GrayScale: + { + OutputFrame.setTo(cv::Scalar(128)); + cv::Mat OutputGreyScale(OutputFrame, cv::Range(0, m_imageHeightInPixels), cv::Range(0, m_imageWidthInPixels)); + InputGreyScale.copyTo(OutputGreyScale); + } break; + case Canny: + { + OutputFrame.setTo(cv::Scalar(128)); + cv::Mat OutputGreyScale(OutputFrame, cv::Range(0, m_imageHeightInPixels), cv::Range(0, m_imageWidthInPixels)); + cv::Canny(InputGreyScale, OutputGreyScale, 80, 90); + + } break; + case Sobel: + { + OutputFrame.setTo(cv::Scalar(128)); + cv::Mat OutputGreyScale(OutputFrame, cv::Range(0, m_imageHeightInPixels), cv::Range(0, m_imageWidthInPixels)); + cv::Sobel(InputGreyScale, OutputGreyScale, CV_8U, 1, 1); + } break; + case Histogram: + { + const int mHistSizeNum = 25; + const int channels[3][1] = {{0}, {1}, {2}}; + const int mHistSize[] = {25}; + const float baseRabge[] = {0.f,256.f}; + const float* ranges[] = {baseRabge}; + const cv::Scalar mColorsRGB[] = { cv::Scalar(200, 0, 0, 255), cv::Scalar(0, 200, 0, 255), + cv::Scalar(0, 0, 200, 255) }; + + cv::Mat BgrFrame; + cv::cvtColor(InputFrame, BgrFrame, cv::COLOR_YUV420sp2BGR); + int thikness = (int) (BgrFrame.cols / (mHistSizeNum + 10) / 5); + if(thikness > 5) thikness = 5; + int offset = (int) ((BgrFrame.cols - (5*mHistSizeNum + 4*10)*thikness)/2); + + // RGB + for (int c=0; c<3; c++) + { + std::vector hist; + cv::calcHist(&BgrFrame, 1, channels[c], cv::Mat(), hist, 1, mHistSize, ranges); + cv::normalize(hist, hist, BgrFrame.rows/2, 0, cv::NORM_INF); + for(int h=0; hSetCurrentLength(m_cbImageSize); @@ -1461,7 +1520,7 @@ HRESULT CGrayscale::UpdateFormatInfo() { goto done; } - if (subtype != MFVideoFormat_NV12) + if (subtype != MFVideoFormat_NV12) { hr = E_UNEXPECTED; goto done; @@ -1511,7 +1570,7 @@ HRESULT GetImageSize(DWORD fcc, UINT32 width, UINT32 height, DWORD* pcbImage) return hr; } -// Get the default stride for a video format. +// Get the default stride for a video format. HRESULT GetDefaultStride(IMFMediaType *pType, LONG *plStride) { LONG lStride = 0; diff --git a/samples/winrt/ImageManipulations/C++/MediaExtensions/Grayscale/Grayscale.h b/samples/winrt/ImageManipulations/C++/MediaExtensions/Grayscale/Grayscale.h index 96b4b1b965..a6a6aa2f13 100644 --- a/samples/winrt/ImageManipulations/C++/MediaExtensions/Grayscale/Grayscale.h +++ b/samples/winrt/ImageManipulations/C++/MediaExtensions/Grayscale/Grayscale.h @@ -37,18 +37,18 @@ DEFINE_GUID(CLSID_GrayscaleMFT, // Configuration attributes +// {698649BE-8EAE-4551-A4CB-3EC98FBD3D86} +DEFINE_GUID(MFT_IMAGE_EFFECT, +0x698649be, 0x8eae, 0x4551, 0xa4, 0xcb, 0x3e, 0xc9, 0x8f, 0xbd, 0x3d, 0x86); -// {7BBBB051-133B-41F5-B6AA-5AFF9B33A2CB} -DEFINE_GUID(MFT_GRAYSCALE_DESTINATION_RECT, -0x7bbbb051, 0x133b, 0x41f5, 0xb6, 0xaa, 0x5a, 0xff, 0x9b, 0x33, 0xa2, 0xcb); enum ProcessingType { - Preview, - GrayScale, - Canny, - Zoom, - Sepia + GrayScale, + Canny, + Sobel, + Histogram, + InvalidEffect }; template void SafeRelease(T **ppT) @@ -63,9 +63,9 @@ template void SafeRelease(T **ppT) // CGrayscale class: // Implements a grayscale video effect. -class CGrayscale +class CGrayscale : public Microsoft::WRL::RuntimeClass< - Microsoft::WRL::RuntimeClassFlags< Microsoft::WRL::RuntimeClassType::WinRtClassicComMix >, + Microsoft::WRL::RuntimeClassFlags< Microsoft::WRL::RuntimeClassType::WinRtClassicComMix >, ABI::Windows::Media::IMediaExtension, IMFTransform > { @@ -231,7 +231,7 @@ private: CRITICAL_SECTION m_critSec; // Transformation parameters - ProcessingType m_TransformType; + ProcessingType m_TransformType; D2D_RECT_U m_rcDest; // Destination rectangle for the effect. // Streaming From bf22567c09ce2eba16cc5ac179a44b9822ef4283 Mon Sep 17 00:00:00 2001 From: Alexander Smorkalov Date: Fri, 14 Jun 2013 15:01:09 -0700 Subject: [PATCH 166/178] Transform selection implemented in sample GUI. Gistogram output does not work propertly due color conversion problems. --- .../C++/AdvancedCapture.xaml | 39 +- .../C++/AdvancedCapture.xaml.cpp | 505 ++++-------------- .../C++/AdvancedCapture.xaml.h | 10 +- .../winrt/ImageManipulations/C++/App.xaml.cpp | 2 + .../ImageManipulations/C++/MainPage.xaml.cpp | 12 +- .../C++/MediaCapture.vcxproj | 4 +- .../MediaExtensions/Grayscale/Grayscale.cpp | 66 ++- .../C++/MediaExtensions/Grayscale/Grayscale.h | 2 +- .../C++/Package.appxmanifest | 3 - 9 files changed, 149 insertions(+), 494 deletions(-) diff --git a/samples/winrt/ImageManipulations/C++/AdvancedCapture.xaml b/samples/winrt/ImageManipulations/C++/AdvancedCapture.xaml index e6266adb9d..07db96f275 100644 --- a/samples/winrt/ImageManipulations/C++/AdvancedCapture.xaml +++ b/samples/winrt/ImageManipulations/C++/AdvancedCapture.xaml @@ -33,38 +33,31 @@ - - - - - - - - - + +
+

Media capture using capture device sample

+
+
+ + + +
+ +
+

This sample demonstrates how to use the +MediaCapture API to capture video, audio, and pictures from a capture device, such as a webcam. +

+

Specifically, this sample covers:

+
    +
  • Previewing video from a capture device, such as a webcam, connected to the computer. +
  • Capturing video from a capture device, such as a webcam, connected to the computer. +
  • Taking a picture from a capture device, such as a webcam, connected to the computer. +
  • Enumerating cameras connected to the computer.
  • Adding a video effect to a video.
  • Recording audio from a capture device connected to the computer.
+

+

For more information on capturing video in your app, see +Quickstart: capturing a photo or video using the camera dialog and +Quickstart: capturing video using the MediaCapture API.

+

Important  

+

This sample uses the Media Extension feature of Windows 8 to add functionality to the Microsoft Media Foundation pipeline. A Media Extension consists of a hybrid object that implements both Component Object Model (COM) and Windows Runtime + interfaces. The COM interfaces interact with the Media Foundation pipeline. The Windows Runtime interfaces activate the component and interact with the Windows Store app. +

+

In most situations, it is recommended that you use Visual C++ with Component Extensions (C++/CX ) to interact with the Windows Runtime. But in the case of hybrid components that implement both COM and Windows Runtime interfaces, such as Media + Extensions, this is not possible. C++/CX can only create Windows Runtime objects. So, for hybrid objects it is recommended that you use +Windows Runtime C++ Template Library to interact with the Windows Runtime. Be aware that Windows Runtime C++ Template Library has limited support for implementing COM interfaces.

+

+

To obtain an evaluation copy of Windows 8, go to +Windows 8.

+

To obtain an evaluation copy of Microsoft Visual Studio 2012, go to +Visual Studio 2012.

+

Related topics

+
Windows 8 app samples +
Roadmaps
Adding multimedia +
Capturing or rendering audio, video, and images +
Designing UX for apps +
Roadmap for apps using C# and Visual Basic +
Roadmap for apps using C++ +
Roadmap for apps using JavaScript +
Tasks
Quickstart: capturing a photo or video using the camera dialog +
Quickstart: capturing video using the MediaCapture API +
Reference
AddEffectAsync +
ClearEffectsAsync +
MediaCapture +
MediaCaptureSettings +
MediaEncodingProfile +
StartRecordToStorageFileAsync +
Windows.Media.Capture
+

Operating system requirements

+ + + + + + + + + + + +
Client
Windows 8
Server
Windows Server 2012
+

Build the sample

+

+
    +
  1. Start Visual Studio Express 2012 for Windows 8 and select File > +Open > Project/Solution.
  2. Go to the directory in which you unzipped the sample. Go to the directory named for the sample, and double-click the Visual Studio Express 2012 for Windows 8 Solution (.sln) file. +
  3. Press F7 or use Build > Build Solution to build the sample.
+

+

Run the sample

+

To debug the app and then run it, press F5 or use Debug > Start Debugging. To run the app without debugging, press Ctrl+F5 or use +Debug > Start Without Debugging.

+
+ +
+ + +