From 416fb50594003dba822a59fbc77b2d5b4e99f1a5 Mon Sep 17 00:00:00 2001 From: Andrey Kamaev Date: Thu, 18 Apr 2013 15:03:50 +0400 Subject: [PATCH] Revert "Merge pull request #836 from jet47:gpu-modules" This reverts commit fba72cb60d27905cce9f1390250adf13d5355c03, reversing changes made to 02131ffb620f349617a65da334591eca5d3cb23b. --- cmake/OpenCVDetectCUDA.cmake | 3 - .../opencv2/core/cuda/border_interpolate.hpp | 58 +- .../core/include/opencv2/core/cuda/common.hpp | 9 + .../include/opencv2/core/cuda/emulation.hpp | 125 +- .../include/opencv2/core/cuda/filters.hpp | 10 +- .../include/opencv2/core/cuda/functional.hpp | 26 +- .../core/include/opencv2/core/gpu_private.hpp | 4 + modules/core/src/gpumat.cpp | 30 + modules/gpu/CMakeLists.txt | 95 +- modules/gpu/app/nv_perf_test/CMakeLists.txt | 10 + modules/gpu/app/nv_perf_test/im1_1280x800.jpg | Bin 0 -> 143316 bytes modules/gpu/app/nv_perf_test/im2_1280x800.jpg | Bin 0 -> 143366 bytes modules/gpu/app/nv_perf_test/main.cpp | 486 +++ modules/gpu/doc/calib3d.rst | 36 - ...era_calibration_and_3d_reconstruction.rst} | 36 +- .../doc/feature_detection_and_description.rst | 0 modules/gpu/doc/gpu.rst | 9 +- .../doc/image_filtering.rst} | 0 modules/gpu/doc/image_processing.rst | 1087 +++++++ .../doc/matrix_reductions.rst} | 86 +- modules/gpu/doc/operations_on_matrices.rst | 274 ++ .../doc/per_element_operations.rst} | 131 +- modules/gpu/doc/video.rst | 1142 +++++++ modules/gpu/include/opencv2/gpu.hpp | 2253 +++++++++++++- .../include/opencv2/gpu/devmem2d.hpp} | 2 +- .../include/opencv2/gpu/gpumat.hpp} | 2 +- modules/gpu/perf/perf_calib3d.cpp | 236 ++ .../perf/perf_core.cpp} | 762 ++++- .../perf/perf_denoising.cpp} | 61 +- .../perf/perf_features2d.cpp | 10 +- .../{gpufilters => gpu}/perf/perf_filters.cpp | 18 +- modules/gpu/perf/perf_imgproc.cpp | 1904 ++++++++++++ modules/gpu/perf/perf_precomp.hpp | 11 +- modules/gpu/perf/perf_video.cpp | 1107 +++++++ modules/gpu/perf4au/CMakeLists.txt | 3 +- modules/gpu/perf4au/main.cpp | 13 +- modules/gpu/src/arithm.cpp | 565 ++++ .../src/gmg.cpp => gpu/src/bgfg_gmg.cpp} | 0 .../src/mog.cpp => gpu/src/bgfg_mog.cpp} | 0 .../src/bilateral_filter.cpp} | 0 modules/{gpuimgproc => gpu}/src/blend.cpp | 3 - .../src/brute_force_matcher.cpp | 0 modules/gpu/src/calib3d.cpp | 14 +- modules/gpu/src/cascadeclassifier.cpp | 275 +- modules/{gpuimgproc => gpu}/src/color.cpp | 93 +- modules/gpu/src/cuda/NV12ToARGB.cu | 201 ++ .../src/cuda/bf_knnmatch.cu | 0 .../src/cuda/bf_match.cu | 0 .../src/cuda/bf_radius_match.cu | 0 .../cuda/gmg.cu => gpu/src/cuda/bgfg_gmg.cu} | 0 .../cuda/mog.cu => gpu/src/cuda/bgfg_mog.cu} | 0 .../src/cuda/bilateral_filter.cu | 4 +- modules/{gpuimgproc => gpu}/src/cuda/blend.cu | 0 modules/{gpuimgproc => gpu}/src/cuda/canny.cu | 4 +- modules/{gpuimgproc => gpu}/src/cuda/clahe.cu | 0 modules/{gpuimgproc => gpu}/src/cuda/color.cu | 0 .../src/cuda/column_filter.0.cu} | 2 +- .../src/cuda/column_filter.1.cu} | 2 +- .../src/cuda/column_filter.10.cu} | 2 +- .../src/cuda/column_filter.11.cu} | 2 +- .../src/cuda/column_filter.12.cu} | 2 +- .../src/cuda/column_filter.13.cu} | 2 +- .../src/cuda/column_filter.14.cu} | 2 +- .../src/cuda/column_filter.2.cu} | 2 +- .../src/cuda/column_filter.3.cu} | 2 +- .../src/cuda/column_filter.4.cu} | 2 +- .../src/cuda/column_filter.5.cu} | 2 +- .../src/cuda/column_filter.6.cu} | 2 +- .../src/cuda/column_filter.7.cu} | 2 +- .../src/cuda/column_filter.8.cu} | 2 +- .../src/cuda/column_filter.9.cu} | 2 +- .../src/cuda/column_filter.h} | 134 +- .../src/cuda/copy_make_border.cu | 6 +- .../{gpuimgproc => gpu}/src/cuda/debayer.cu | 0 .../src/cuda/disp_bilateral_filter.cu} | 0 modules/gpu/src/cuda/element_operations.cu | 2636 +++++++++++++++++ .../{gpufeatures2d => gpu}/src/cuda/fast.cu | 0 .../cuda/fgd.cu => gpu/src/cuda/fgd_bgfg.cu} | 2 +- .../src/cuda/fgd_bgfg_common.hpp} | 0 modules/{gpuimgproc => gpu}/src/cuda/gftt.cu | 0 modules/{gpuimgproc => gpu}/src/cuda/hist.cu | 0 modules/{gpuimgproc => gpu}/src/cuda/hough.cu | 0 modules/gpu/src/cuda/imgproc.cu | 1008 +++++++ .../src/cuda/integral_image.cu} | 0 .../src/cuda/internal_shared.hpp} | 50 +- .../src/cuda/match_template.cu | 0 .../src/cuda/mathfunc.cu} | 0 modules/gpu/src/cuda/matrix_reductions.cu | 1366 +++++++++ modules/{photo => gpu}/src/cuda/nlm.cu | 9 +- .../bm_fast.cu => gpu/src/cuda/optflowbm.cu} | 119 + .../src/cuda/optical_flow.cu} | 0 .../src/cuda/optical_flow_farneback.cu} | 15 +- .../{gpufeatures2d => gpu}/src/cuda/orb.cu | 0 .../{gpuwarping => gpu}/src/cuda/pyr_down.cu | 0 .../{gpuwarping => gpu}/src/cuda/pyr_up.cu | 0 modules/{gpuoptflow => gpu}/src/cuda/pyrlk.cu | 0 modules/{gpuwarping => gpu}/src/cuda/remap.cu | 18 +- .../{gpuwarping => gpu}/src/cuda/resize.cu | 0 modules/gpu/src/cuda/rgb_to_yv12.cu | 175 ++ .../src/cuda/row_filter.0.cu} | 2 +- .../src/cuda/row_filter.1.cu} | 2 +- .../src/cuda/row_filter.10.cu} | 2 +- .../src/cuda/row_filter.11.cu} | 2 +- .../src/cuda/row_filter.12.cu} | 2 +- .../src/cuda/row_filter.13.cu} | 2 +- .../src/cuda/row_filter.14.cu} | 2 +- .../src/cuda/row_filter.2.cu} | 2 +- .../src/cuda/row_filter.3.cu} | 2 +- .../src/cuda/row_filter.4.cu} | 2 +- .../src/cuda/row_filter.5.cu} | 2 +- .../src/cuda/row_filter.6.cu} | 2 +- .../src/cuda/row_filter.7.cu} | 2 +- .../src/cuda/row_filter.8.cu} | 2 +- .../src/cuda/row_filter.9.cu} | 2 +- .../src/cuda/row_filter.h} | 134 +- .../src/cuda/safe_call.hpp} | 81 +- .../src/cuda/split_merge.cu | 0 .../{gpustereo => gpu}/src/cuda/stereobm.cu | 0 .../{gpustereo => gpu}/src/cuda/stereobp.cu | 0 .../{gpustereo => gpu}/src/cuda/stereocsbp.cu | 0 .../src/cuda/texture_binder.hpp} | 77 +- .../{gpuoptflow => gpu}/src/cuda/tvl1flow.cu | 0 modules/{gpuwarping => gpu}/src/cuda/warp.cu | 18 +- .../src/cuvid_video_source.cpp | 11 +- .../src/cuvid_video_source.h | 66 +- .../src/cvt_color_internal.h | 0 .../src/denoising.cpp} | 70 +- .../src/element_operations.cpp | 367 +-- modules/gpu/src/error.cpp | 188 ++ modules/{gpufeatures2d => gpu}/src/fast.cpp | 0 .../src/ffmpeg_video_source.cpp | 22 +- .../src/ffmpeg_video_source.h | 56 +- .../src/fgd.cpp => gpu/src/fgd_bgfg.cpp} | 2 +- modules/{gpufilters => gpu}/src/filtering.cpp | 39 +- modules/{gpucodec => gpu}/src/frame_queue.cpp | 10 +- .../needle_map.cpp => gpu/src/frame_queue.h} | 103 +- modules/{gpuimgproc => gpu}/src/gftt.cpp | 9 +- modules/{gpuimgproc => gpu}/src/hough.cpp | 0 modules/gpu/src/imgproc.cpp | 1675 +++++++++++ .../src/match_template.cpp | 46 +- .../src/matrix_reductions.cpp} | 196 +- .../src/mssegmentation.cpp | 0 .../src/nvidia}/NCVBroxOpticalFlow.cu | 9 +- .../src/nvidia}/NCVBroxOpticalFlow.hpp | 4 +- .../src/nvidia}/NCVHaarObjectDetection.cu | 228 +- .../src/nvidia}/NCVHaarObjectDetection.hpp | 18 +- .../src/nvidia/NPP_staging}/NPP_staging.cu | 4 +- .../src/nvidia/NPP_staging}/NPP_staging.hpp | 110 +- .../NCV.cpp => gpu/src/nvidia/core/NCV.cu} | 292 +- .../gpulegacy => gpu/src/nvidia/core}/NCV.hpp | 42 +- .../cuda => gpu/src/nvidia/core}/NCVAlg.hpp | 2 +- .../src/nvidia/core}/NCVColorConversion.hpp | 0 .../src/nvidia/core}/NCVPixelOperations.hpp | 2 +- .../src/nvidia/core}/NCVPyramid.cu | 67 +- .../src/nvidia/core}/NCVPyramid.hpp | 16 +- .../src/nvidia/core}/NCVRuntimeTemplates.hpp | 0 .../src/bm.cpp => gpu/src/optflowbm.cpp} | 70 +- .../src/brox.cpp => gpu/src/optical_flow.cpp} | 109 +- .../src/optical_flow_farneback.cpp} | 51 +- modules/{gpufeatures2d => gpu}/src/orb.cpp | 12 +- modules/gpu/src/precomp.hpp | 70 +- modules/{gpuwarping => gpu}/src/pyramids.cpp | 63 +- modules/{gpuoptflow => gpu}/src/pyrlk.cpp | 14 +- modules/{gpuwarping => gpu}/src/remap.cpp | 5 +- modules/{gpuwarping => gpu}/src/resize.cpp | 13 +- modules/gpu/src/speckle_filtering.cpp | 168 ++ modules/gpu/src/split_merge.cpp | 171 ++ modules/{gpustereo => gpu}/src/stereobm.cpp | 0 modules/{gpustereo => gpu}/src/stereobp.cpp | 0 modules/{gpustereo => gpu}/src/stereocsbp.cpp | 2 +- modules/gpu/src/thread_wrappers.cpp | 254 ++ .../private.hpp => gpu/src/thread_wrappers.h} | 94 +- modules/{gpuoptflow => gpu}/src/tvl1flow.cpp | 0 .../{gpucodec => gpu}/src/video_decoder.cpp | 7 +- modules/{gpucodec => gpu}/src/video_decoder.h | 121 +- .../{gpucodec => gpu}/src/video_parser.cpp | 6 +- modules/{gpucodec => gpu}/src/video_parser.h | 84 +- .../{gpucodec => gpu}/src/video_reader.cpp | 57 +- .../{gpucodec => gpu}/src/video_writer.cpp | 44 +- modules/{gpuwarping => gpu}/src/warp.cpp | 208 +- .../test/interpolation.hpp | 0 .../test/test_main.cpp => gpu/test/main.cpp} | 0 .../test/main_test_nvidia.h | 0 .../test/nvidia}/NCVAutoTestLister.hpp | 8 +- .../test => gpu/test/nvidia}/NCVTest.hpp | 3 +- .../test/nvidia}/NCVTestSourceProvider.hpp | 4 +- .../test => gpu/test/nvidia}/TestCompact.cpp | 7 +- .../test => gpu/test/nvidia}/TestCompact.h | 0 .../test/nvidia}/TestDrawRects.cpp | 7 +- .../test => gpu/test/nvidia}/TestDrawRects.h | 0 .../nvidia}/TestHaarCascadeApplication.cpp | 14 +- .../test/nvidia}/TestHaarCascadeApplication.h | 0 .../test/nvidia}/TestHaarCascadeLoader.cpp | 7 +- .../test/nvidia}/TestHaarCascadeLoader.h | 0 .../test/nvidia}/TestHypothesesFilter.cpp | 7 +- .../test/nvidia}/TestHypothesesFilter.h | 0 .../test/nvidia}/TestHypothesesGrow.cpp | 7 +- .../test/nvidia}/TestHypothesesGrow.h | 0 .../test/nvidia}/TestIntegralImage.cpp | 7 +- .../test/nvidia}/TestIntegralImage.h | 0 .../test/nvidia}/TestIntegralImageSquared.cpp | 6 +- .../test/nvidia}/TestIntegralImageSquared.h | 0 .../test/nvidia}/TestRectStdDev.cpp | 8 +- .../test => gpu/test/nvidia}/TestRectStdDev.h | 0 .../test => gpu/test/nvidia}/TestResize.cpp | 8 +- .../test => gpu/test/nvidia}/TestResize.h | 0 .../test/nvidia}/TestTranspose.cpp | 8 +- .../test => gpu/test/nvidia}/TestTranspose.h | 0 .../test => gpu/test/nvidia}/main_nvidia.cpp | 33 +- .../test/test_bgfg.cpp} | 8 +- modules/gpu/test/test_calib3d.cpp | 158 + .../{gpuimgproc => gpu}/test/test_color.cpp | 0 .../test/test_copy_make_border.cpp} | 72 +- .../test/test_core.cpp} | 1225 +++++++- .../test/test_denoising.cpp} | 105 +- .../test/test_features2d.cpp | 0 .../{gpufilters => gpu}/test/test_filters.cpp | 18 +- .../{gpuimgproc => gpu}/test/test_hough.cpp | 0 modules/gpu/test/test_imgproc.cpp | 1200 ++++++++ modules/gpu/test/test_main.cpp | 45 - .../{gpulegacy => gpu}/test/test_nvidia.cpp | 5 +- .../{gpuoptflow => gpu}/test/test_optflow.cpp | 94 +- modules/gpu/test/test_precomp.hpp | 27 +- .../test/test_pyramids.cpp | 4 +- .../{gpuwarping => gpu}/test/test_remap.cpp | 2 +- .../{gpuwarping => gpu}/test/test_resize.cpp | 6 +- .../test/test_threshold.cpp} | 40 +- modules/{gpucodec => gpu}/test/test_video.cpp | 54 +- .../test/test_warp_affine.cpp | 6 +- .../test/test_warp_perspective.cpp | 6 +- modules/gpuarithm/CMakeLists.txt | 29 - modules/gpuarithm/doc/arithm.rst | 211 -- modules/gpuarithm/doc/core.rst | 128 - modules/gpuarithm/doc/gpuarithm.rst | 11 - .../gpuarithm/include/opencv2/gpuarithm.hpp | 346 --- modules/gpuarithm/perf/perf_arithm.cpp | 306 -- modules/gpuarithm/perf/perf_core.cpp | 317 -- modules/gpuarithm/perf/perf_main.cpp | 47 - modules/gpuarithm/perf/perf_precomp.cpp | 43 - modules/gpuarithm/perf/perf_precomp.hpp | 65 - modules/gpuarithm/perf/perf_reductions.cpp | 466 --- modules/gpuarithm/src/arithm.cpp | 735 ----- modules/gpuarithm/src/core.cpp | 488 --- modules/gpuarithm/src/cuda/absdiff_mat.cu | 147 - modules/gpuarithm/src/cuda/absdiff_scalar.cu | 98 - modules/gpuarithm/src/cuda/add_mat.cu | 185 -- modules/gpuarithm/src/cuda/add_scalar.cu | 148 - modules/gpuarithm/src/cuda/add_weighted.cu | 364 --- .../gpuarithm/src/cuda/arithm_func_traits.hpp | 145 - modules/gpuarithm/src/cuda/bitwise_mat.cu | 126 - modules/gpuarithm/src/cuda/bitwise_scalar.cu | 104 - modules/gpuarithm/src/cuda/cmp_mat.cu | 206 -- modules/gpuarithm/src/cuda/cmp_scalar.cu | 284 -- modules/gpuarithm/src/cuda/countnonzero.cu | 175 -- modules/gpuarithm/src/cuda/div_inv.cu | 144 - modules/gpuarithm/src/cuda/div_mat.cu | 230 -- modules/gpuarithm/src/cuda/div_scalar.cu | 144 - modules/gpuarithm/src/cuda/math.cu | 302 -- modules/gpuarithm/src/cuda/minmax.cu | 246 -- modules/gpuarithm/src/cuda/minmax_mat.cu | 228 -- modules/gpuarithm/src/cuda/minmaxloc.cu | 235 -- modules/gpuarithm/src/cuda/mul_mat.cu | 211 -- modules/gpuarithm/src/cuda/mul_scalar.cu | 144 - modules/gpuarithm/src/cuda/mul_spectrums.cu | 171 -- modules/gpuarithm/src/cuda/reduce.cu | 330 --- modules/gpuarithm/src/cuda/sub_mat.cu | 185 -- modules/gpuarithm/src/cuda/sub_scalar.cu | 148 - modules/gpuarithm/src/cuda/sum.cu | 380 --- modules/gpuarithm/src/cuda/threshold.cu | 114 - modules/gpuarithm/src/cuda/transpose.cu | 122 - modules/gpuarithm/src/cuda/unroll_detail.hpp | 135 - modules/gpuarithm/src/precomp.hpp | 70 - modules/gpuarithm/test/test_arithm.cpp | 439 --- modules/gpuarithm/test/test_core.cpp | 415 --- modules/gpuarithm/test/test_main.cpp | 45 - modules/gpuarithm/test/test_precomp.hpp | 61 - modules/gpuarithm/test/test_reductions.cpp | 819 ----- modules/gpubgsegm/CMakeLists.txt | 9 - .../gpubgsegm/doc/background_segmentation.rst | 408 --- modules/gpubgsegm/doc/gpubgsegm.rst | 8 - .../gpubgsegm/include/opencv2/gpubgsegm.hpp | 330 --- modules/gpubgsegm/perf/perf_bgsegm.cpp | 537 ---- modules/gpubgsegm/perf/perf_main.cpp | 47 - modules/gpubgsegm/perf/perf_precomp.cpp | 43 - modules/gpubgsegm/perf/perf_precomp.hpp | 64 - modules/gpubgsegm/src/precomp.cpp | 43 - modules/gpubgsegm/src/precomp.hpp | 55 - modules/gpubgsegm/test/test_main.cpp | 45 - modules/gpubgsegm/test/test_precomp.cpp | 43 - modules/gpubgsegm/test/test_precomp.hpp | 62 - modules/gpucodec/CMakeLists.txt | 29 - modules/gpucodec/doc/gpucodec.rst | 9 - modules/gpucodec/doc/videodec.rst | 234 -- modules/gpucodec/doc/videoenc.rst | 219 -- modules/gpucodec/include/opencv2/gpucodec.hpp | 265 -- modules/gpucodec/perf/perf_main.cpp | 47 - modules/gpucodec/perf/perf_precomp.cpp | 43 - modules/gpucodec/perf/perf_precomp.hpp | 64 - modules/gpucodec/perf/perf_video.cpp | 162 - modules/gpucodec/src/cuda/nv12_to_rgb.cu | 193 -- modules/gpucodec/src/cuda/rgb_to_yv12.cu | 170 -- modules/gpucodec/src/precomp.cpp | 43 - modules/gpucodec/src/precomp.hpp | 79 - modules/gpucodec/src/thread.cpp | 174 -- modules/gpucodec/test/test_main.cpp | 45 - modules/gpucodec/test/test_precomp.cpp | 43 - modules/gpucodec/test/test_precomp.hpp | 60 - modules/gpufeatures2d/CMakeLists.txt | 9 - modules/gpufeatures2d/doc/gpufeatures2d.rst | 8 - .../include/opencv2/gpufeatures2d.hpp | 361 --- modules/gpufeatures2d/perf/perf_main.cpp | 47 - modules/gpufeatures2d/perf/perf_precomp.cpp | 43 - modules/gpufeatures2d/perf/perf_precomp.hpp | 64 - modules/gpufeatures2d/src/precomp.cpp | 43 - modules/gpufeatures2d/src/precomp.hpp | 57 - modules/gpufeatures2d/test/test_main.cpp | 45 - modules/gpufeatures2d/test/test_precomp.cpp | 43 - modules/gpufeatures2d/test/test_precomp.hpp | 60 - modules/gpufilters/CMakeLists.txt | 9 - modules/gpufilters/doc/gpufilters.rst | 8 - .../gpufilters/include/opencv2/gpufilters.hpp | 269 -- modules/gpufilters/perf/perf_main.cpp | 47 - modules/gpufilters/perf/perf_precomp.cpp | 43 - modules/gpufilters/perf/perf_precomp.hpp | 64 - modules/gpufilters/src/cuda/filter2d.cu | 158 - modules/gpufilters/src/precomp.cpp | 43 - modules/gpufilters/src/precomp.hpp | 59 - modules/gpufilters/test/test_main.cpp | 45 - modules/gpufilters/test/test_precomp.cpp | 43 - modules/gpufilters/test/test_precomp.hpp | 60 - modules/gpuimgproc/CMakeLists.txt | 9 - modules/gpuimgproc/doc/color.rst | 74 - modules/gpuimgproc/doc/feature_detection.rst | 81 - modules/gpuimgproc/doc/gpuimgproc.rst | 12 - modules/gpuimgproc/doc/histogram.rst | 104 - modules/gpuimgproc/doc/hough.rst | 96 - modules/gpuimgproc/doc/imgproc.rst | 203 -- .../gpuimgproc/include/opencv2/gpuimgproc.hpp | 339 --- .../gpuimgproc/perf/perf_bilateral_filter.cpp | 93 - modules/gpuimgproc/perf/perf_blend.cpp | 86 - modules/gpuimgproc/perf/perf_color.cpp | 252 -- modules/gpuimgproc/perf/perf_corners.cpp | 137 - modules/gpuimgproc/perf/perf_gftt.cpp | 86 - modules/gpuimgproc/perf/perf_histogram.cpp | 221 -- modules/gpuimgproc/perf/perf_hough.cpp | 317 -- modules/gpuimgproc/perf/perf_main.cpp | 47 - .../gpuimgproc/perf/perf_match_template.cpp | 131 - modules/gpuimgproc/perf/perf_mean_shift.cpp | 152 - modules/gpuimgproc/perf/perf_precomp.cpp | 43 - modules/gpuimgproc/perf/perf_precomp.hpp | 64 - modules/gpuimgproc/src/bilateral_filter.cpp | 96 - modules/gpuimgproc/src/canny.cpp | 186 -- modules/gpuimgproc/src/corners.cpp | 149 - modules/gpuimgproc/src/cuda/corners.cu | 274 -- modules/gpuimgproc/src/cuda/mean_shift.cu | 182 -- modules/gpuimgproc/src/histogram.cpp | 557 ---- modules/gpuimgproc/src/mean_shift.cpp | 128 - modules/gpuimgproc/src/precomp.cpp | 43 - modules/gpuimgproc/src/precomp.hpp | 58 - modules/gpuimgproc/test/test_blend.cpp | 124 - modules/gpuimgproc/test/test_corners.cpp | 145 - modules/gpuimgproc/test/test_gftt.cpp | 131 - modules/gpuimgproc/test/test_histogram.cpp | 227 -- modules/gpuimgproc/test/test_main.cpp | 45 - .../gpuimgproc/test/test_match_template.cpp | 305 -- modules/gpuimgproc/test/test_mean_shift.cpp | 174 -- modules/gpuimgproc/test/test_precomp.cpp | 43 - modules/gpuimgproc/test/test_precomp.hpp | 61 - modules/gpulegacy/CMakeLists.txt | 9 - .../gpulegacy/include/opencv2/gpulegacy.hpp | 52 - modules/gpulegacy/src/cuda/NCV.cu | 180 -- modules/gpulegacy/src/precomp.cpp | 43 - modules/gpulegacy/src/precomp.hpp | 62 - modules/gpulegacy/test/test_precomp.cpp | 43 - modules/gpulegacy/test/test_precomp.hpp | 95 - modules/gpuoptflow/CMakeLists.txt | 9 - modules/gpuoptflow/doc/gpuoptflow.rst | 8 - modules/gpuoptflow/doc/optflow.rst | 219 -- .../gpuoptflow/include/opencv2/gpuoptflow.hpp | 311 -- modules/gpuoptflow/perf/perf_main.cpp | 47 - modules/gpuoptflow/perf/perf_optflow.cpp | 479 --- modules/gpuoptflow/perf/perf_precomp.cpp | 43 - modules/gpuoptflow/perf/perf_precomp.hpp | 64 - modules/gpuoptflow/src/bm_fast.cpp | 90 - modules/gpuoptflow/src/cuda/bm.cu | 169 -- modules/gpuoptflow/src/interpolate_frames.cpp | 113 - modules/gpuoptflow/src/precomp.cpp | 43 - modules/gpuoptflow/src/precomp.hpp | 62 - modules/gpuoptflow/test/test_main.cpp | 45 - modules/gpuoptflow/test/test_precomp.cpp | 43 - modules/gpuoptflow/test/test_precomp.hpp | 62 - modules/gpustereo/CMakeLists.txt | 9 - modules/gpustereo/doc/gpustereo.rst | 8 - .../gpustereo/include/opencv2/gpustereo.hpp | 247 -- modules/gpustereo/perf/perf_main.cpp | 47 - modules/gpustereo/perf/perf_precomp.cpp | 43 - modules/gpustereo/perf/perf_precomp.hpp | 64 - modules/gpustereo/perf/perf_stereo.cpp | 254 -- modules/gpustereo/src/cuda/util.cu | 235 -- modules/gpustereo/src/precomp.cpp | 43 - modules/gpustereo/src/precomp.hpp | 52 - modules/gpustereo/src/util.cpp | 117 - modules/gpustereo/test/test_main.cpp | 45 - modules/gpustereo/test/test_precomp.cpp | 43 - modules/gpustereo/test/test_precomp.hpp | 60 - modules/gpustereo/test/test_stereo.cpp | 207 -- modules/gpuwarping/CMakeLists.txt | 9 - modules/gpuwarping/doc/gpuwarping.rst | 8 - modules/gpuwarping/doc/warping.rst | 251 -- .../gpuwarping/include/opencv2/gpuwarping.hpp | 131 - modules/gpuwarping/perf/perf_main.cpp | 47 - modules/gpuwarping/perf/perf_precomp.cpp | 43 - modules/gpuwarping/perf/perf_precomp.hpp | 64 - modules/gpuwarping/perf/perf_warping.cpp | 592 ---- .../gpuwarping/src/cuda/build_warp_maps.cu | 221 -- modules/gpuwarping/src/precomp.cpp | 43 - modules/gpuwarping/src/precomp.hpp | 57 - modules/gpuwarping/test/test_main.cpp | 45 - modules/gpuwarping/test/test_precomp.cpp | 43 - modules/gpuwarping/test/test_precomp.hpp | 62 - modules/nonfree/CMakeLists.txt | 3 +- .../nonfree/include/opencv2/nonfree/gpu.hpp | 12 +- modules/nonfree/perf/perf_gpu.cpp | 10 +- modules/nonfree/perf/perf_precomp.hpp | 5 +- modules/nonfree/src/cuda/surf.cu | 5 +- modules/nonfree/src/cuda/vibe.cu | 6 + modules/nonfree/src/precomp.hpp | 8 +- modules/nonfree/src/surf_gpu.cpp | 10 +- modules/nonfree/src/vibe_gpu.cpp | 6 +- modules/nonfree/test/test_gpu.cpp | 8 +- modules/nonfree/test/test_precomp.hpp | 2 +- modules/photo/CMakeLists.txt | 7 +- modules/photo/doc/denoising.rst | 99 - modules/photo/include/opencv2/photo/gpu.hpp | 71 - modules/softcascade/src/detector_cuda.cpp | 2 +- modules/stitching/CMakeLists.txt | 3 +- .../opencv2/stitching/detail/matchers.hpp | 7 +- .../opencv2/stitching/detail/warpers.hpp | 6 +- .../include/opencv2/stitching/warpers.hpp | 2 +- modules/stitching/src/blenders.cpp | 10 +- modules/stitching/src/matchers.cpp | 19 +- modules/stitching/src/precomp.hpp | 20 +- modules/stitching/src/stitcher.cpp | 2 +- modules/stitching/src/warpers.cpp | 2 +- modules/superres/CMakeLists.txt | 4 +- modules/superres/src/btv_l1_gpu.cpp | 7 +- modules/superres/src/cuda/btv_l1_gpu.cu | 6 - modules/superres/src/frame_source.cpp | 6 +- modules/superres/src/input_array_utility.cpp | 2 +- modules/superres/src/optical_flow.cpp | 6 +- modules/superres/src/precomp.hpp | 27 +- modules/superres/test/test_superres.cpp | 2 +- modules/ts/include/opencv2/ts/gpu_test.hpp | 64 - modules/videostab/CMakeLists.txt | 3 +- .../opencv2/videostab/global_motion.hpp | 10 +- .../opencv2/videostab/optical_flow.hpp | 8 +- .../opencv2/videostab/wobble_suppression.hpp | 5 +- modules/videostab/src/global_motion.cpp | 10 +- modules/videostab/src/inpainting.cpp | 2 +- modules/videostab/src/optical_flow.cpp | 6 +- modules/videostab/src/wobble_suppression.cpp | 11 +- samples/cpp/CMakeLists.txt | 19 +- samples/cpp/stitching_detailed.cpp | 4 +- .../gpu-basics-similarity.cpp | 5 +- samples/cpp/videostab.cpp | 4 +- samples/gpu/CMakeLists.txt | 12 +- samples/gpu/bgfg_segm.cpp | 6 - samples/gpu/cascadeclassifier_nvidia_api.cpp | 3 +- samples/gpu/hog.cpp | 1 - samples/gpu/opticalflow_nvidia_api.cpp | 3 +- samples/gpu/video_reader.cpp | 23 +- samples/gpu/video_writer.cpp | 23 +- 472 files changed, 22945 insertions(+), 29803 deletions(-) create mode 100644 modules/gpu/app/nv_perf_test/CMakeLists.txt create mode 100644 modules/gpu/app/nv_perf_test/im1_1280x800.jpg create mode 100644 modules/gpu/app/nv_perf_test/im2_1280x800.jpg create mode 100644 modules/gpu/app/nv_perf_test/main.cpp delete mode 100644 modules/gpu/doc/calib3d.rst rename modules/{gpustereo/doc/stereo.rst => gpu/doc/camera_calibration_and_3d_reconstruction.rst} (92%) rename modules/{gpufeatures2d => gpu}/doc/feature_detection_and_description.rst (100%) rename modules/{gpufilters/doc/filtering.rst => gpu/doc/image_filtering.rst} (100%) create mode 100644 modules/gpu/doc/image_processing.rst rename modules/{gpuarithm/doc/reductions.rst => gpu/doc/matrix_reductions.rst} (80%) create mode 100644 modules/gpu/doc/operations_on_matrices.rst rename modules/{gpuarithm/doc/element_operations.rst => gpu/doc/per_element_operations.rst} (77%) create mode 100644 modules/gpu/doc/video.rst rename modules/{gpuarithm/test/test_precomp.cpp => gpu/include/opencv2/gpu/devmem2d.hpp} (98%) rename modules/{gpuarithm/src/precomp.cpp => gpu/include/opencv2/gpu/gpumat.hpp} (98%) rename modules/{gpuarithm/perf/perf_element_operations.cpp => gpu/perf/perf_core.cpp} (63%) rename modules/{photo/perf/perf_gpu.cpp => gpu/perf/perf_denoising.cpp} (79%) rename modules/{gpufeatures2d => gpu}/perf/perf_features2d.cpp (97%) rename modules/{gpufilters => gpu}/perf/perf_filters.cpp (89%) create mode 100644 modules/gpu/perf/perf_imgproc.cpp create mode 100644 modules/gpu/perf/perf_video.cpp create mode 100644 modules/gpu/src/arithm.cpp rename modules/{gpubgsegm/src/gmg.cpp => gpu/src/bgfg_gmg.cpp} (100%) rename modules/{gpubgsegm/src/mog.cpp => gpu/src/bgfg_mog.cpp} (100%) rename modules/{gpustereo/src/disparity_bilateral_filter.cpp => gpu/src/bilateral_filter.cpp} (100%) rename modules/{gpuimgproc => gpu}/src/blend.cpp (97%) rename modules/{gpufeatures2d => gpu}/src/brute_force_matcher.cpp (100%) rename modules/{gpuimgproc => gpu}/src/color.cpp (95%) create mode 100644 modules/gpu/src/cuda/NV12ToARGB.cu rename modules/{gpufeatures2d => gpu}/src/cuda/bf_knnmatch.cu (100%) rename modules/{gpufeatures2d => gpu}/src/cuda/bf_match.cu (100%) rename modules/{gpufeatures2d => gpu}/src/cuda/bf_radius_match.cu (100%) rename modules/{gpubgsegm/src/cuda/gmg.cu => gpu/src/cuda/bgfg_gmg.cu} (100%) rename modules/{gpubgsegm/src/cuda/mog.cu => gpu/src/cuda/bgfg_mog.cu} (100%) rename modules/{gpuimgproc => gpu}/src/cuda/bilateral_filter.cu (99%) rename modules/{gpuimgproc => gpu}/src/cuda/blend.cu (100%) rename modules/{gpuimgproc => gpu}/src/cuda/canny.cu (99%) rename modules/{gpuimgproc => gpu}/src/cuda/clahe.cu (100%) rename modules/{gpuimgproc => gpu}/src/cuda/color.cu (100%) rename modules/{gpufilters/src/cuda/column_filter.8uc1.cu => gpu/src/cuda/column_filter.0.cu} (98%) rename modules/{gpufilters/src/cuda/column_filter.8uc3.cu => gpu/src/cuda/column_filter.1.cu} (98%) rename modules/{gpufilters/src/cuda/column_filter.16uc1.cu => gpu/src/cuda/column_filter.10.cu} (98%) rename modules/{gpufilters/src/cuda/column_filter.16uc3.cu => gpu/src/cuda/column_filter.11.cu} (98%) rename modules/{gpufilters/src/cuda/column_filter.16uc4.cu => gpu/src/cuda/column_filter.12.cu} (98%) rename modules/{gpufilters/src/cuda/column_filter.32sc3.cu => gpu/src/cuda/column_filter.13.cu} (98%) rename modules/{gpufilters/src/cuda/column_filter.32sc4.cu => gpu/src/cuda/column_filter.14.cu} (98%) rename modules/{gpufilters/src/cuda/column_filter.8uc4.cu => gpu/src/cuda/column_filter.2.cu} (98%) rename modules/{gpufilters/src/cuda/column_filter.16sc3.cu => gpu/src/cuda/column_filter.3.cu} (98%) rename modules/{gpufilters/src/cuda/column_filter.32sc1.cu => gpu/src/cuda/column_filter.4.cu} (98%) rename modules/{gpufilters/src/cuda/column_filter.32fc1.cu => gpu/src/cuda/column_filter.5.cu} (98%) rename modules/{gpufilters/src/cuda/column_filter.32fc3.cu => gpu/src/cuda/column_filter.6.cu} (98%) rename modules/{gpufilters/src/cuda/column_filter.32fc4.cu => gpu/src/cuda/column_filter.7.cu} (98%) rename modules/{gpufilters/src/cuda/column_filter.16sc1.cu => gpu/src/cuda/column_filter.8.cu} (98%) rename modules/{gpufilters/src/cuda/column_filter.16sc4.cu => gpu/src/cuda/column_filter.9.cu} (98%) rename modules/{gpufilters/src/cuda/column_filter.hpp => gpu/src/cuda/column_filter.h} (100%) rename modules/{gpuarithm => gpu}/src/cuda/copy_make_border.cu (99%) rename modules/{gpuimgproc => gpu}/src/cuda/debayer.cu (100%) rename modules/{gpustereo/src/cuda/disparity_bilateral_filter.cu => gpu/src/cuda/disp_bilateral_filter.cu} (100%) create mode 100644 modules/gpu/src/cuda/element_operations.cu rename modules/{gpufeatures2d => gpu}/src/cuda/fast.cu (100%) rename modules/{gpubgsegm/src/cuda/fgd.cu => gpu/src/cuda/fgd_bgfg.cu} (99%) rename modules/{gpubgsegm/src/cuda/fgd.hpp => gpu/src/cuda/fgd_bgfg_common.hpp} (100%) rename modules/{gpuimgproc => gpu}/src/cuda/gftt.cu (100%) rename modules/{gpuimgproc => gpu}/src/cuda/hist.cu (100%) rename modules/{gpuimgproc => gpu}/src/cuda/hough.cu (100%) create mode 100644 modules/gpu/src/cuda/imgproc.cu rename modules/{gpuarithm/src/cuda/integral.cu => gpu/src/cuda/integral_image.cu} (100%) rename modules/{gpucodec/src/thread.h => gpu/src/cuda/internal_shared.hpp} (76%) rename modules/{gpuimgproc => gpu}/src/cuda/match_template.cu (100%) rename modules/{gpuarithm/src/cuda/polar_cart.cu => gpu/src/cuda/mathfunc.cu} (100%) create mode 100644 modules/gpu/src/cuda/matrix_reductions.cu rename modules/{photo => gpu}/src/cuda/nlm.cu (99%) rename modules/{gpuoptflow/src/cuda/bm_fast.cu => gpu/src/cuda/optflowbm.cu} (72%) rename modules/{gpuoptflow/src/cuda/needle_map.cu => gpu/src/cuda/optical_flow.cu} (100%) rename modules/{gpuoptflow/src/cuda/farneback.cu => gpu/src/cuda/optical_flow_farneback.cu} (96%) rename modules/{gpufeatures2d => gpu}/src/cuda/orb.cu (100%) rename modules/{gpuwarping => gpu}/src/cuda/pyr_down.cu (100%) rename modules/{gpuwarping => gpu}/src/cuda/pyr_up.cu (100%) rename modules/{gpuoptflow => gpu}/src/cuda/pyrlk.cu (100%) rename modules/{gpuwarping => gpu}/src/cuda/remap.cu (99%) rename modules/{gpuwarping => gpu}/src/cuda/resize.cu (100%) create mode 100644 modules/gpu/src/cuda/rgb_to_yv12.cu rename modules/{gpufilters/src/cuda/row_filter.8uc1.cu => gpu/src/cuda/row_filter.0.cu} (98%) rename modules/{gpufilters/src/cuda/row_filter.8uc3.cu => gpu/src/cuda/row_filter.1.cu} (98%) rename modules/{gpufilters/src/cuda/row_filter.16uc1.cu => gpu/src/cuda/row_filter.10.cu} (98%) rename modules/{gpufilters/src/cuda/row_filter.16uc3.cu => gpu/src/cuda/row_filter.11.cu} (98%) rename modules/{gpufilters/src/cuda/row_filter.16uc4.cu => gpu/src/cuda/row_filter.12.cu} (98%) rename modules/{gpufilters/src/cuda/row_filter.32sc3.cu => gpu/src/cuda/row_filter.13.cu} (98%) rename modules/{gpufilters/src/cuda/row_filter.32sc4.cu => gpu/src/cuda/row_filter.14.cu} (98%) rename modules/{gpufilters/src/cuda/row_filter.8uc4.cu => gpu/src/cuda/row_filter.2.cu} (98%) rename modules/{gpufilters/src/cuda/row_filter.16sc3.cu => gpu/src/cuda/row_filter.3.cu} (98%) rename modules/{gpufilters/src/cuda/row_filter.32sc1.cu => gpu/src/cuda/row_filter.4.cu} (98%) rename modules/{gpufilters/src/cuda/row_filter.32fc1.cu => gpu/src/cuda/row_filter.5.cu} (98%) rename modules/{gpufilters/src/cuda/row_filter.32fc3.cu => gpu/src/cuda/row_filter.6.cu} (98%) rename modules/{gpufilters/src/cuda/row_filter.32fc4.cu => gpu/src/cuda/row_filter.7.cu} (98%) rename modules/{gpufilters/src/cuda/row_filter.16sc1.cu => gpu/src/cuda/row_filter.8.cu} (98%) rename modules/{gpufilters/src/cuda/row_filter.16sc4.cu => gpu/src/cuda/row_filter.9.cu} (98%) rename modules/{gpufilters/src/cuda/row_filter.hpp => gpu/src/cuda/row_filter.h} (100%) rename modules/{gpucodec/src/frame_queue.h => gpu/src/cuda/safe_call.hpp} (57%) rename modules/{gpuarithm => gpu}/src/cuda/split_merge.cu (100%) rename modules/{gpustereo => gpu}/src/cuda/stereobm.cu (100%) rename modules/{gpustereo => gpu}/src/cuda/stereobp.cu (100%) rename modules/{gpustereo => gpu}/src/cuda/stereocsbp.cu (100%) rename modules/{gpuimgproc/perf/perf_canny.cpp => gpu/src/cuda/texture_binder.hpp} (60%) rename modules/{gpuoptflow => gpu}/src/cuda/tvl1flow.cu (100%) rename modules/{gpuwarping => gpu}/src/cuda/warp.cu (99%) rename modules/{gpucodec => gpu}/src/cuvid_video_source.cpp (96%) rename modules/{gpucodec => gpu}/src/cuvid_video_source.h (61%) rename modules/{gpuimgproc => gpu}/src/cvt_color_internal.h (100%) rename modules/{photo/src/denoising_gpu.cpp => gpu/src/denoising.cpp} (69%) rename modules/{gpuarithm => gpu}/src/element_operations.cpp (96%) create mode 100644 modules/gpu/src/error.cpp rename modules/{gpufeatures2d => gpu}/src/fast.cpp (100%) rename modules/{gpucodec => gpu}/src/ffmpeg_video_source.cpp (94%) rename modules/{gpucodec => gpu}/src/ffmpeg_video_source.h (69%) rename modules/{gpubgsegm/src/fgd.cpp => gpu/src/fgd_bgfg.cpp} (99%) rename modules/{gpufilters => gpu}/src/filtering.cpp (98%) rename modules/{gpucodec => gpu}/src/frame_queue.cpp (94%) rename modules/{gpuoptflow/src/needle_map.cpp => gpu/src/frame_queue.h} (54%) rename modules/{gpuimgproc => gpu}/src/gftt.cpp (97%) rename modules/{gpuimgproc => gpu}/src/hough.cpp (100%) create mode 100644 modules/gpu/src/imgproc.cpp rename modules/{gpuimgproc => gpu}/src/match_template.cpp (92%) rename modules/{gpuarithm/src/reductions.cpp => gpu/src/matrix_reductions.cpp} (88%) rename modules/{gpuimgproc => gpu}/src/mssegmentation.cpp (100%) rename modules/{gpulegacy/src/cuda => gpu/src/nvidia}/NCVBroxOpticalFlow.cu (99%) rename modules/{gpulegacy/include/opencv2/gpulegacy => gpu/src/nvidia}/NCVBroxOpticalFlow.hpp (98%) rename modules/{gpulegacy/src/cuda => gpu/src/nvidia}/NCVHaarObjectDetection.cu (92%) rename modules/{gpulegacy/include/opencv2/gpulegacy => gpu/src/nvidia}/NCVHaarObjectDetection.hpp (96%) rename modules/{gpulegacy/src/cuda => gpu/src/nvidia/NPP_staging}/NPP_staging.cu (99%) rename modules/{gpulegacy/include/opencv2/gpulegacy => gpu/src/nvidia/NPP_staging}/NPP_staging.hpp (98%) rename modules/{gpulegacy/src/NCV.cpp => gpu/src/nvidia/core/NCV.cu} (82%) rename modules/{gpulegacy/include/opencv2/gpulegacy => gpu/src/nvidia/core}/NCV.hpp (95%) rename modules/{gpulegacy/src/cuda => gpu/src/nvidia/core}/NCVAlg.hpp (99%) rename modules/{gpulegacy/src/cuda => gpu/src/nvidia/core}/NCVColorConversion.hpp (100%) rename modules/{gpulegacy/src/cuda => gpu/src/nvidia/core}/NCVPixelOperations.hpp (99%) rename modules/{gpulegacy/src/cuda => gpu/src/nvidia/core}/NCVPyramid.cu (88%) rename modules/{gpulegacy/include/opencv2/gpulegacy => gpu/src/nvidia/core}/NCVPyramid.hpp (88%) rename modules/{gpulegacy/src/cuda => gpu/src/nvidia/core}/NCVRuntimeTemplates.hpp (100%) rename modules/{gpuoptflow/src/bm.cpp => gpu/src/optflowbm.cpp} (73%) rename modules/{gpuoptflow/src/brox.cpp => gpu/src/optical_flow.cpp} (55%) rename modules/{gpuoptflow/src/farneback.cpp => gpu/src/optical_flow_farneback.cpp} (87%) rename modules/{gpufeatures2d => gpu}/src/orb.cpp (98%) rename modules/{gpuwarping => gpu}/src/pyramids.cpp (71%) rename modules/{gpuoptflow => gpu}/src/pyrlk.cpp (95%) rename modules/{gpuwarping => gpu}/src/remap.cpp (95%) rename modules/{gpuwarping => gpu}/src/resize.cpp (96%) create mode 100644 modules/gpu/src/speckle_filtering.cpp create mode 100644 modules/gpu/src/split_merge.cpp rename modules/{gpustereo => gpu}/src/stereobm.cpp (100%) rename modules/{gpustereo => gpu}/src/stereobp.cpp (100%) rename modules/{gpustereo => gpu}/src/stereocsbp.cpp (99%) create mode 100644 modules/gpu/src/thread_wrappers.cpp rename modules/{gpulegacy/include/opencv2/gpulegacy/private.hpp => gpu/src/thread_wrappers.h} (59%) rename modules/{gpuoptflow => gpu}/src/tvl1flow.cpp (100%) rename modules/{gpucodec => gpu}/src/video_decoder.cpp (97%) rename modules/{gpucodec => gpu}/src/video_decoder.h (50%) rename modules/{gpucodec => gpu}/src/video_parser.cpp (98%) rename modules/{gpucodec => gpu}/src/video_parser.h (54%) rename modules/{gpucodec => gpu}/src/video_reader.cpp (89%) rename modules/{gpucodec => gpu}/src/video_writer.cpp (97%) rename modules/{gpuwarping => gpu}/src/warp.cpp (68%) rename modules/{gpuwarping => gpu}/test/interpolation.hpp (100%) rename modules/{gpulegacy/test/test_main.cpp => gpu/test/main.cpp} (100%) rename modules/{gpulegacy => gpu}/test/main_test_nvidia.h (100%) rename modules/{gpulegacy/test => gpu/test/nvidia}/NCVAutoTestLister.hpp (97%) rename modules/{gpulegacy/test => gpu/test/nvidia}/NCVTest.hpp (99%) rename modules/{gpulegacy/test => gpu/test/nvidia}/NCVTestSourceProvider.hpp (99%) rename modules/{gpulegacy/test => gpu/test/nvidia}/TestCompact.cpp (98%) rename modules/{gpulegacy/test => gpu/test/nvidia}/TestCompact.h (100%) rename modules/{gpulegacy/test => gpu/test/nvidia}/TestDrawRects.cpp (98%) rename modules/{gpulegacy/test => gpu/test/nvidia}/TestDrawRects.h (100%) rename modules/{gpulegacy/test => gpu/test/nvidia}/TestHaarCascadeApplication.cpp (98%) rename modules/{gpulegacy/test => gpu/test/nvidia}/TestHaarCascadeApplication.h (100%) rename modules/{gpulegacy/test => gpu/test/nvidia}/TestHaarCascadeLoader.cpp (97%) rename modules/{gpulegacy/test => gpu/test/nvidia}/TestHaarCascadeLoader.h (100%) rename modules/{gpulegacy/test => gpu/test/nvidia}/TestHypothesesFilter.cpp (98%) rename modules/{gpulegacy/test => gpu/test/nvidia}/TestHypothesesFilter.h (100%) rename modules/{gpulegacy/test => gpu/test/nvidia}/TestHypothesesGrow.cpp (98%) rename modules/{gpulegacy/test => gpu/test/nvidia}/TestHypothesesGrow.h (100%) rename modules/{gpulegacy/test => gpu/test/nvidia}/TestIntegralImage.cpp (98%) rename modules/{gpulegacy/test => gpu/test/nvidia}/TestIntegralImage.h (100%) rename modules/{gpulegacy/test => gpu/test/nvidia}/TestIntegralImageSquared.cpp (98%) rename modules/{gpulegacy/test => gpu/test/nvidia}/TestIntegralImageSquared.h (100%) rename modules/{gpulegacy/test => gpu/test/nvidia}/TestRectStdDev.cpp (98%) rename modules/{gpulegacy/test => gpu/test/nvidia}/TestRectStdDev.h (100%) rename modules/{gpulegacy/test => gpu/test/nvidia}/TestResize.cpp (98%) rename modules/{gpulegacy/test => gpu/test/nvidia}/TestResize.h (100%) rename modules/{gpulegacy/test => gpu/test/nvidia}/TestTranspose.cpp (98%) rename modules/{gpulegacy/test => gpu/test/nvidia}/TestTranspose.h (100%) rename modules/{gpulegacy/test => gpu/test/nvidia}/main_nvidia.cpp (95%) rename modules/{gpubgsegm/test/test_bgsegm.cpp => gpu/test/test_bgfg.cpp} (97%) rename modules/{gpuimgproc => gpu}/test/test_color.cpp (100%) rename modules/{gpuimgproc/test/test_canny.cpp => gpu/test/test_copy_make_border.cpp} (63%) rename modules/{gpuarithm/test/test_element_operations.cpp => gpu/test/test_core.cpp} (67%) rename modules/{photo/test/test_denoising_gpu.cpp => gpu/test/test_denoising.cpp} (53%) rename modules/{gpufeatures2d => gpu}/test/test_features2d.cpp (100%) rename modules/{gpufilters => gpu}/test/test_filters.cpp (96%) rename modules/{gpuimgproc => gpu}/test/test_hough.cpp (100%) create mode 100644 modules/gpu/test/test_imgproc.cpp delete mode 100644 modules/gpu/test/test_main.cpp rename modules/{gpulegacy => gpu}/test/test_nvidia.cpp (96%) rename modules/{gpuoptflow => gpu}/test/test_optflow.cpp (87%) rename modules/{gpuwarping => gpu}/test/test_pyramids.cpp (97%) rename modules/{gpuwarping => gpu}/test/test_remap.cpp (99%) rename modules/{gpuwarping => gpu}/test/test_resize.cpp (97%) rename modules/{gpuimgproc/test/test_bilateral_filter.cpp => gpu/test/test_threshold.cpp} (74%) rename modules/{gpucodec => gpu}/test/test_video.cpp (75%) rename modules/{gpuwarping => gpu}/test/test_warp_affine.cpp (98%) rename modules/{gpuwarping => gpu}/test/test_warp_perspective.cpp (98%) delete mode 100644 modules/gpuarithm/CMakeLists.txt delete mode 100644 modules/gpuarithm/doc/arithm.rst delete mode 100644 modules/gpuarithm/doc/core.rst delete mode 100644 modules/gpuarithm/doc/gpuarithm.rst delete mode 100644 modules/gpuarithm/include/opencv2/gpuarithm.hpp delete mode 100644 modules/gpuarithm/perf/perf_arithm.cpp delete mode 100644 modules/gpuarithm/perf/perf_core.cpp delete mode 100644 modules/gpuarithm/perf/perf_main.cpp delete mode 100644 modules/gpuarithm/perf/perf_precomp.cpp delete mode 100644 modules/gpuarithm/perf/perf_precomp.hpp delete mode 100644 modules/gpuarithm/perf/perf_reductions.cpp delete mode 100644 modules/gpuarithm/src/arithm.cpp delete mode 100644 modules/gpuarithm/src/core.cpp delete mode 100644 modules/gpuarithm/src/cuda/absdiff_mat.cu delete mode 100644 modules/gpuarithm/src/cuda/absdiff_scalar.cu delete mode 100644 modules/gpuarithm/src/cuda/add_mat.cu delete mode 100644 modules/gpuarithm/src/cuda/add_scalar.cu delete mode 100644 modules/gpuarithm/src/cuda/add_weighted.cu delete mode 100644 modules/gpuarithm/src/cuda/arithm_func_traits.hpp delete mode 100644 modules/gpuarithm/src/cuda/bitwise_mat.cu delete mode 100644 modules/gpuarithm/src/cuda/bitwise_scalar.cu delete mode 100644 modules/gpuarithm/src/cuda/cmp_mat.cu delete mode 100644 modules/gpuarithm/src/cuda/cmp_scalar.cu delete mode 100644 modules/gpuarithm/src/cuda/countnonzero.cu delete mode 100644 modules/gpuarithm/src/cuda/div_inv.cu delete mode 100644 modules/gpuarithm/src/cuda/div_mat.cu delete mode 100644 modules/gpuarithm/src/cuda/div_scalar.cu delete mode 100644 modules/gpuarithm/src/cuda/math.cu delete mode 100644 modules/gpuarithm/src/cuda/minmax.cu delete mode 100644 modules/gpuarithm/src/cuda/minmax_mat.cu delete mode 100644 modules/gpuarithm/src/cuda/minmaxloc.cu delete mode 100644 modules/gpuarithm/src/cuda/mul_mat.cu delete mode 100644 modules/gpuarithm/src/cuda/mul_scalar.cu delete mode 100644 modules/gpuarithm/src/cuda/mul_spectrums.cu delete mode 100644 modules/gpuarithm/src/cuda/reduce.cu delete mode 100644 modules/gpuarithm/src/cuda/sub_mat.cu delete mode 100644 modules/gpuarithm/src/cuda/sub_scalar.cu delete mode 100644 modules/gpuarithm/src/cuda/sum.cu delete mode 100644 modules/gpuarithm/src/cuda/threshold.cu delete mode 100644 modules/gpuarithm/src/cuda/transpose.cu delete mode 100644 modules/gpuarithm/src/cuda/unroll_detail.hpp delete mode 100644 modules/gpuarithm/src/precomp.hpp delete mode 100644 modules/gpuarithm/test/test_arithm.cpp delete mode 100644 modules/gpuarithm/test/test_core.cpp delete mode 100644 modules/gpuarithm/test/test_main.cpp delete mode 100644 modules/gpuarithm/test/test_precomp.hpp delete mode 100644 modules/gpuarithm/test/test_reductions.cpp delete mode 100644 modules/gpubgsegm/CMakeLists.txt delete mode 100644 modules/gpubgsegm/doc/background_segmentation.rst delete mode 100644 modules/gpubgsegm/doc/gpubgsegm.rst delete mode 100644 modules/gpubgsegm/include/opencv2/gpubgsegm.hpp delete mode 100644 modules/gpubgsegm/perf/perf_bgsegm.cpp delete mode 100644 modules/gpubgsegm/perf/perf_main.cpp delete mode 100644 modules/gpubgsegm/perf/perf_precomp.cpp delete mode 100644 modules/gpubgsegm/perf/perf_precomp.hpp delete mode 100644 modules/gpubgsegm/src/precomp.cpp delete mode 100644 modules/gpubgsegm/src/precomp.hpp delete mode 100644 modules/gpubgsegm/test/test_main.cpp delete mode 100644 modules/gpubgsegm/test/test_precomp.cpp delete mode 100644 modules/gpubgsegm/test/test_precomp.hpp delete mode 100644 modules/gpucodec/CMakeLists.txt delete mode 100644 modules/gpucodec/doc/gpucodec.rst delete mode 100644 modules/gpucodec/doc/videodec.rst delete mode 100644 modules/gpucodec/doc/videoenc.rst delete mode 100644 modules/gpucodec/include/opencv2/gpucodec.hpp delete mode 100644 modules/gpucodec/perf/perf_main.cpp delete mode 100644 modules/gpucodec/perf/perf_precomp.cpp delete mode 100644 modules/gpucodec/perf/perf_precomp.hpp delete mode 100644 modules/gpucodec/perf/perf_video.cpp delete mode 100644 modules/gpucodec/src/cuda/nv12_to_rgb.cu delete mode 100644 modules/gpucodec/src/cuda/rgb_to_yv12.cu delete mode 100644 modules/gpucodec/src/precomp.cpp delete mode 100644 modules/gpucodec/src/precomp.hpp delete mode 100644 modules/gpucodec/src/thread.cpp delete mode 100644 modules/gpucodec/test/test_main.cpp delete mode 100644 modules/gpucodec/test/test_precomp.cpp delete mode 100644 modules/gpucodec/test/test_precomp.hpp delete mode 100644 modules/gpufeatures2d/CMakeLists.txt delete mode 100644 modules/gpufeatures2d/doc/gpufeatures2d.rst delete mode 100644 modules/gpufeatures2d/include/opencv2/gpufeatures2d.hpp delete mode 100644 modules/gpufeatures2d/perf/perf_main.cpp delete mode 100644 modules/gpufeatures2d/perf/perf_precomp.cpp delete mode 100644 modules/gpufeatures2d/perf/perf_precomp.hpp delete mode 100644 modules/gpufeatures2d/src/precomp.cpp delete mode 100644 modules/gpufeatures2d/src/precomp.hpp delete mode 100644 modules/gpufeatures2d/test/test_main.cpp delete mode 100644 modules/gpufeatures2d/test/test_precomp.cpp delete mode 100644 modules/gpufeatures2d/test/test_precomp.hpp delete mode 100644 modules/gpufilters/CMakeLists.txt delete mode 100644 modules/gpufilters/doc/gpufilters.rst delete mode 100644 modules/gpufilters/include/opencv2/gpufilters.hpp delete mode 100644 modules/gpufilters/perf/perf_main.cpp delete mode 100644 modules/gpufilters/perf/perf_precomp.cpp delete mode 100644 modules/gpufilters/perf/perf_precomp.hpp delete mode 100644 modules/gpufilters/src/cuda/filter2d.cu delete mode 100644 modules/gpufilters/src/precomp.cpp delete mode 100644 modules/gpufilters/src/precomp.hpp delete mode 100644 modules/gpufilters/test/test_main.cpp delete mode 100644 modules/gpufilters/test/test_precomp.cpp delete mode 100644 modules/gpufilters/test/test_precomp.hpp delete mode 100644 modules/gpuimgproc/CMakeLists.txt delete mode 100644 modules/gpuimgproc/doc/color.rst delete mode 100644 modules/gpuimgproc/doc/feature_detection.rst delete mode 100644 modules/gpuimgproc/doc/gpuimgproc.rst delete mode 100644 modules/gpuimgproc/doc/histogram.rst delete mode 100644 modules/gpuimgproc/doc/hough.rst delete mode 100644 modules/gpuimgproc/doc/imgproc.rst delete mode 100644 modules/gpuimgproc/include/opencv2/gpuimgproc.hpp delete mode 100644 modules/gpuimgproc/perf/perf_bilateral_filter.cpp delete mode 100644 modules/gpuimgproc/perf/perf_blend.cpp delete mode 100644 modules/gpuimgproc/perf/perf_color.cpp delete mode 100644 modules/gpuimgproc/perf/perf_corners.cpp delete mode 100644 modules/gpuimgproc/perf/perf_gftt.cpp delete mode 100644 modules/gpuimgproc/perf/perf_histogram.cpp delete mode 100644 modules/gpuimgproc/perf/perf_hough.cpp delete mode 100644 modules/gpuimgproc/perf/perf_main.cpp delete mode 100644 modules/gpuimgproc/perf/perf_match_template.cpp delete mode 100644 modules/gpuimgproc/perf/perf_mean_shift.cpp delete mode 100644 modules/gpuimgproc/perf/perf_precomp.cpp delete mode 100644 modules/gpuimgproc/perf/perf_precomp.hpp delete mode 100644 modules/gpuimgproc/src/bilateral_filter.cpp delete mode 100644 modules/gpuimgproc/src/canny.cpp delete mode 100644 modules/gpuimgproc/src/corners.cpp delete mode 100644 modules/gpuimgproc/src/cuda/corners.cu delete mode 100644 modules/gpuimgproc/src/cuda/mean_shift.cu delete mode 100644 modules/gpuimgproc/src/histogram.cpp delete mode 100644 modules/gpuimgproc/src/mean_shift.cpp delete mode 100644 modules/gpuimgproc/src/precomp.cpp delete mode 100644 modules/gpuimgproc/src/precomp.hpp delete mode 100644 modules/gpuimgproc/test/test_blend.cpp delete mode 100644 modules/gpuimgproc/test/test_corners.cpp delete mode 100644 modules/gpuimgproc/test/test_gftt.cpp delete mode 100644 modules/gpuimgproc/test/test_histogram.cpp delete mode 100644 modules/gpuimgproc/test/test_main.cpp delete mode 100644 modules/gpuimgproc/test/test_match_template.cpp delete mode 100644 modules/gpuimgproc/test/test_mean_shift.cpp delete mode 100644 modules/gpuimgproc/test/test_precomp.cpp delete mode 100644 modules/gpuimgproc/test/test_precomp.hpp delete mode 100644 modules/gpulegacy/CMakeLists.txt delete mode 100644 modules/gpulegacy/include/opencv2/gpulegacy.hpp delete mode 100644 modules/gpulegacy/src/cuda/NCV.cu delete mode 100644 modules/gpulegacy/src/precomp.cpp delete mode 100644 modules/gpulegacy/src/precomp.hpp delete mode 100644 modules/gpulegacy/test/test_precomp.cpp delete mode 100644 modules/gpulegacy/test/test_precomp.hpp delete mode 100644 modules/gpuoptflow/CMakeLists.txt delete mode 100644 modules/gpuoptflow/doc/gpuoptflow.rst delete mode 100644 modules/gpuoptflow/doc/optflow.rst delete mode 100644 modules/gpuoptflow/include/opencv2/gpuoptflow.hpp delete mode 100644 modules/gpuoptflow/perf/perf_main.cpp delete mode 100644 modules/gpuoptflow/perf/perf_optflow.cpp delete mode 100644 modules/gpuoptflow/perf/perf_precomp.cpp delete mode 100644 modules/gpuoptflow/perf/perf_precomp.hpp delete mode 100644 modules/gpuoptflow/src/bm_fast.cpp delete mode 100644 modules/gpuoptflow/src/cuda/bm.cu delete mode 100644 modules/gpuoptflow/src/interpolate_frames.cpp delete mode 100644 modules/gpuoptflow/src/precomp.cpp delete mode 100644 modules/gpuoptflow/src/precomp.hpp delete mode 100644 modules/gpuoptflow/test/test_main.cpp delete mode 100644 modules/gpuoptflow/test/test_precomp.cpp delete mode 100644 modules/gpuoptflow/test/test_precomp.hpp delete mode 100644 modules/gpustereo/CMakeLists.txt delete mode 100644 modules/gpustereo/doc/gpustereo.rst delete mode 100644 modules/gpustereo/include/opencv2/gpustereo.hpp delete mode 100644 modules/gpustereo/perf/perf_main.cpp delete mode 100644 modules/gpustereo/perf/perf_precomp.cpp delete mode 100644 modules/gpustereo/perf/perf_precomp.hpp delete mode 100644 modules/gpustereo/perf/perf_stereo.cpp delete mode 100644 modules/gpustereo/src/cuda/util.cu delete mode 100644 modules/gpustereo/src/precomp.cpp delete mode 100644 modules/gpustereo/src/precomp.hpp delete mode 100644 modules/gpustereo/src/util.cpp delete mode 100644 modules/gpustereo/test/test_main.cpp delete mode 100644 modules/gpustereo/test/test_precomp.cpp delete mode 100644 modules/gpustereo/test/test_precomp.hpp delete mode 100644 modules/gpustereo/test/test_stereo.cpp delete mode 100644 modules/gpuwarping/CMakeLists.txt delete mode 100644 modules/gpuwarping/doc/gpuwarping.rst delete mode 100644 modules/gpuwarping/doc/warping.rst delete mode 100644 modules/gpuwarping/include/opencv2/gpuwarping.hpp delete mode 100644 modules/gpuwarping/perf/perf_main.cpp delete mode 100644 modules/gpuwarping/perf/perf_precomp.cpp delete mode 100644 modules/gpuwarping/perf/perf_precomp.hpp delete mode 100644 modules/gpuwarping/perf/perf_warping.cpp delete mode 100644 modules/gpuwarping/src/cuda/build_warp_maps.cu delete mode 100644 modules/gpuwarping/src/precomp.cpp delete mode 100644 modules/gpuwarping/src/precomp.hpp delete mode 100644 modules/gpuwarping/test/test_main.cpp delete mode 100644 modules/gpuwarping/test/test_precomp.cpp delete mode 100644 modules/gpuwarping/test/test_precomp.hpp delete mode 100644 modules/photo/include/opencv2/photo/gpu.hpp diff --git a/cmake/OpenCVDetectCUDA.cmake b/cmake/OpenCVDetectCUDA.cmake index f1861fba74..f3d101ab21 100644 --- a/cmake/OpenCVDetectCUDA.cmake +++ b/cmake/OpenCVDetectCUDA.cmake @@ -28,9 +28,6 @@ if(CUDA_FOUND) if(WITH_NVCUVID) find_cuda_helper_libs(nvcuvid) - if(WIN32) - find_cuda_helper_libs(nvcuvenc) - endif() set(HAVE_NVCUVID 1) endif() diff --git a/modules/core/include/opencv2/core/cuda/border_interpolate.hpp b/modules/core/include/opencv2/core/cuda/border_interpolate.hpp index 6c53f09eee..1347a2f849 100644 --- a/modules/core/include/opencv2/core/cuda/border_interpolate.hpp +++ b/modules/core/include/opencv2/core/cuda/border_interpolate.hpp @@ -73,8 +73,8 @@ namespace cv { namespace gpu { namespace cudev return (x >= 0 && x < width) ? saturate_cast(data[x]) : val; } - int width; - D val; + const int width; + const D val; }; template struct BrdColConstant @@ -98,8 +98,8 @@ namespace cv { namespace gpu { namespace cudev return (y >= 0 && y < height) ? saturate_cast(*(const T*)((const char*)data + y * step)) : val; } - int height; - D val; + const int height; + const D val; }; template struct BrdConstant @@ -120,9 +120,9 @@ namespace cv { namespace gpu { namespace cudev return (x >= 0 && x < width && y >= 0 && y < height) ? saturate_cast(src(y, x)) : val; } - int height; - int width; - D val; + const int height; + const int width; + const D val; }; ////////////////////////////////////////////////////////////// @@ -165,7 +165,7 @@ namespace cv { namespace gpu { namespace cudev return saturate_cast(data[idx_col(x)]); } - int last_col; + const int last_col; }; template struct BrdColReplicate @@ -205,7 +205,7 @@ namespace cv { namespace gpu { namespace cudev return saturate_cast(*(const T*)((const char*)data + idx_row(y) * step)); } - int last_row; + const int last_row; }; template struct BrdReplicate @@ -255,8 +255,8 @@ namespace cv { namespace gpu { namespace cudev return saturate_cast(src(idx_row(y), idx_col(x))); } - int last_row; - int last_col; + const int last_row; + const int last_col; }; ////////////////////////////////////////////////////////////// @@ -299,7 +299,7 @@ namespace cv { namespace gpu { namespace cudev return saturate_cast(data[idx_col(x)]); } - int last_col; + const int last_col; }; template struct BrdColReflect101 @@ -339,7 +339,7 @@ namespace cv { namespace gpu { namespace cudev return saturate_cast(*(const D*)((const char*)data + idx_row(y) * step)); } - int last_row; + const int last_row; }; template struct BrdReflect101 @@ -389,8 +389,8 @@ namespace cv { namespace gpu { namespace cudev return saturate_cast(src(idx_row(y), idx_col(x))); } - int last_row; - int last_col; + const int last_row; + const int last_col; }; ////////////////////////////////////////////////////////////// @@ -433,7 +433,7 @@ namespace cv { namespace gpu { namespace cudev return saturate_cast(data[idx_col(x)]); } - int last_col; + const int last_col; }; template struct BrdColReflect @@ -473,7 +473,7 @@ namespace cv { namespace gpu { namespace cudev return saturate_cast(*(const D*)((const char*)data + idx_row(y) * step)); } - int last_row; + const int last_row; }; template struct BrdReflect @@ -523,8 +523,8 @@ namespace cv { namespace gpu { namespace cudev return saturate_cast(src(idx_row(y), idx_col(x))); } - int last_row; - int last_col; + const int last_row; + const int last_col; }; ////////////////////////////////////////////////////////////// @@ -567,7 +567,7 @@ namespace cv { namespace gpu { namespace cudev return saturate_cast(data[idx_col(x)]); } - int width; + const int width; }; template struct BrdColWrap @@ -607,7 +607,7 @@ namespace cv { namespace gpu { namespace cudev return saturate_cast(*(const D*)((const char*)data + idx_row(y) * step)); } - int height; + const int height; }; template struct BrdWrap @@ -664,8 +664,8 @@ namespace cv { namespace gpu { namespace cudev return saturate_cast(src(idx_row(y), idx_col(x))); } - int height; - int width; + const int height; + const int width; }; ////////////////////////////////////////////////////////////// @@ -683,8 +683,8 @@ namespace cv { namespace gpu { namespace cudev return b.at(y, x, ptr); } - Ptr2D ptr; - B b; + const Ptr2D ptr; + const B b; }; // under win32 there is some bug with templated types that passed as kernel parameters @@ -704,10 +704,10 @@ namespace cv { namespace gpu { namespace cudev return (x >= 0 && x < width && y >= 0 && y < height) ? saturate_cast(src(y, x)) : val; } - Ptr2D src; - int height; - int width; - D val; + const Ptr2D src; + const int height; + const int width; + const D val; }; }}} // namespace cv { namespace gpu { namespace cudev diff --git a/modules/core/include/opencv2/core/cuda/common.hpp b/modules/core/include/opencv2/core/cuda/common.hpp index 434a3eba1d..774500e649 100644 --- a/modules/core/include/opencv2/core/cuda/common.hpp +++ b/modules/core/include/opencv2/core/cuda/common.hpp @@ -87,6 +87,15 @@ namespace cv { namespace gpu namespace cv { namespace gpu { + enum + { + BORDER_REFLECT101_GPU = 0, + BORDER_REPLICATE_GPU, + BORDER_CONSTANT_GPU, + BORDER_REFLECT_GPU, + BORDER_WRAP_GPU + }; + namespace cudev { __host__ __device__ __forceinline__ int divUp(int total, int grain) diff --git a/modules/core/include/opencv2/core/cuda/emulation.hpp b/modules/core/include/opencv2/core/cuda/emulation.hpp index b484f2378e..3df26468b2 100644 --- a/modules/core/include/opencv2/core/cuda/emulation.hpp +++ b/modules/core/include/opencv2/core/cuda/emulation.hpp @@ -43,7 +43,6 @@ #ifndef OPENCV_GPU_EMULATION_HPP_ #define OPENCV_GPU_EMULATION_HPP_ -#include "common.hpp" #include "warp_reduce.hpp" namespace cv { namespace gpu { namespace cudev @@ -132,130 +131,8 @@ namespace cv { namespace gpu { namespace cudev return ::atomicMin(address, val); #endif } - }; // struct cmem - - struct glob - { - static __device__ __forceinline__ int atomicAdd(int* address, int val) - { - return ::atomicAdd(address, val); - } - static __device__ __forceinline__ unsigned int atomicAdd(unsigned int* address, unsigned int val) - { - return ::atomicAdd(address, val); - } - static __device__ __forceinline__ float atomicAdd(float* address, float val) - { - #if __CUDA_ARCH__ >= 200 - return ::atomicAdd(address, val); - #else - int* address_as_i = (int*) address; - int old = *address_as_i, assumed; - do { - assumed = old; - old = ::atomicCAS(address_as_i, assumed, - __float_as_int(val + __int_as_float(assumed))); - } while (assumed != old); - return __int_as_float(old); - #endif - } - static __device__ __forceinline__ double atomicAdd(double* address, double val) - { - #if __CUDA_ARCH__ >= 130 - unsigned long long int* address_as_ull = (unsigned long long int*) address; - unsigned long long int old = *address_as_ull, assumed; - do { - assumed = old; - old = ::atomicCAS(address_as_ull, assumed, - __double_as_longlong(val + __longlong_as_double(assumed))); - } while (assumed != old); - return __longlong_as_double(old); - #else - (void) address; - (void) val; - return 0.0; - #endif - } - - static __device__ __forceinline__ int atomicMin(int* address, int val) - { - return ::atomicMin(address, val); - } - static __device__ __forceinline__ float atomicMin(float* address, float val) - { - #if __CUDA_ARCH__ >= 120 - int* address_as_i = (int*) address; - int old = *address_as_i, assumed; - do { - assumed = old; - old = ::atomicCAS(address_as_i, assumed, - __float_as_int(::fminf(val, __int_as_float(assumed)))); - } while (assumed != old); - return __int_as_float(old); - #else - (void) address; - (void) val; - return 0.0f; - #endif - } - static __device__ __forceinline__ double atomicMin(double* address, double val) - { - #if __CUDA_ARCH__ >= 130 - unsigned long long int* address_as_ull = (unsigned long long int*) address; - unsigned long long int old = *address_as_ull, assumed; - do { - assumed = old; - old = ::atomicCAS(address_as_ull, assumed, - __double_as_longlong(::fmin(val, __longlong_as_double(assumed)))); - } while (assumed != old); - return __longlong_as_double(old); - #else - (void) address; - (void) val; - return 0.0; - #endif - } - - static __device__ __forceinline__ int atomicMax(int* address, int val) - { - return ::atomicMax(address, val); - } - static __device__ __forceinline__ float atomicMax(float* address, float val) - { - #if __CUDA_ARCH__ >= 120 - int* address_as_i = (int*) address; - int old = *address_as_i, assumed; - do { - assumed = old; - old = ::atomicCAS(address_as_i, assumed, - __float_as_int(::fmaxf(val, __int_as_float(assumed)))); - } while (assumed != old); - return __int_as_float(old); - #else - (void) address; - (void) val; - return 0.0f; - #endif - } - static __device__ __forceinline__ double atomicMax(double* address, double val) - { - #if __CUDA_ARCH__ >= 130 - unsigned long long int* address_as_ull = (unsigned long long int*) address; - unsigned long long int old = *address_as_ull, assumed; - do { - assumed = old; - old = ::atomicCAS(address_as_ull, assumed, - __double_as_longlong(::fmax(val, __longlong_as_double(assumed)))); - } while (assumed != old); - return __longlong_as_double(old); - #else - (void) address; - (void) val; - return 0.0; - #endif - } }; - }; //struct Emulation + }; }}} // namespace cv { namespace gpu { namespace cudev #endif /* OPENCV_GPU_EMULATION_HPP_ */ diff --git a/modules/core/include/opencv2/core/cuda/filters.hpp b/modules/core/include/opencv2/core/cuda/filters.hpp index f35f662e8b..19a8c5883f 100644 --- a/modules/core/include/opencv2/core/cuda/filters.hpp +++ b/modules/core/include/opencv2/core/cuda/filters.hpp @@ -67,7 +67,7 @@ namespace cv { namespace gpu { namespace cudev return src(__float2int_rz(y), __float2int_rz(x)); } - Ptr2D src; + const Ptr2D src; }; template struct LinearFilter @@ -107,7 +107,7 @@ namespace cv { namespace gpu { namespace cudev return saturate_cast(out); } - Ptr2D src; + const Ptr2D src; }; template struct CubicFilter @@ -166,7 +166,7 @@ namespace cv { namespace gpu { namespace cudev return saturate_cast(res); } - Ptr2D src; + const Ptr2D src; }; // for integer scaling template struct IntegerAreaFilter @@ -203,7 +203,7 @@ namespace cv { namespace gpu { namespace cudev return saturate_cast(out); } - Ptr2D src; + const Ptr2D src; float scale_x, scale_y ,scale; }; @@ -269,7 +269,7 @@ namespace cv { namespace gpu { namespace cudev return saturate_cast(out); } - Ptr2D src; + const Ptr2D src; float scale_x, scale_y; int width, haight; }; diff --git a/modules/core/include/opencv2/core/cuda/functional.hpp b/modules/core/include/opencv2/core/cuda/functional.hpp index 95706ca447..506ccd8768 100644 --- a/modules/core/include/opencv2/core/cuda/functional.hpp +++ b/modules/core/include/opencv2/core/cuda/functional.hpp @@ -552,8 +552,8 @@ namespace cv { namespace gpu { namespace cudev __device__ __forceinline__ thresh_binary_func():unary_function(){} - T thresh; - T maxVal; + const T thresh; + const T maxVal; }; template struct thresh_binary_inv_func : unary_function @@ -570,8 +570,8 @@ namespace cv { namespace gpu { namespace cudev __device__ __forceinline__ thresh_binary_inv_func():unary_function(){} - T thresh; - T maxVal; + const T thresh; + const T maxVal; }; template struct thresh_trunc_func : unary_function @@ -588,7 +588,7 @@ namespace cv { namespace gpu { namespace cudev __device__ __forceinline__ thresh_trunc_func():unary_function(){} - T thresh; + const T thresh; }; template struct thresh_to_zero_func : unary_function @@ -604,7 +604,7 @@ namespace cv { namespace gpu { namespace cudev __device__ __forceinline__ thresh_to_zero_func():unary_function(){} - T thresh; + const T thresh; }; template struct thresh_to_zero_inv_func : unary_function @@ -620,7 +620,7 @@ namespace cv { namespace gpu { namespace cudev __device__ __forceinline__ thresh_to_zero_inv_func():unary_function(){} - T thresh; + const T thresh; }; //bound!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ============> // Function Object Adaptors @@ -636,7 +636,7 @@ namespace cv { namespace gpu { namespace cudev __device__ __forceinline__ unary_negate(const unary_negate& other) : unary_function(){} __device__ __forceinline__ unary_negate() : unary_function(){} - Predicate pred; + const Predicate pred; }; template __host__ __device__ __forceinline__ unary_negate not1(const Predicate& pred) @@ -659,7 +659,7 @@ namespace cv { namespace gpu { namespace cudev __device__ __forceinline__ binary_negate() : binary_function(){} - Predicate pred; + const Predicate pred; }; template __host__ __device__ __forceinline__ binary_negate not2(const BinaryPredicate& pred) @@ -679,8 +679,8 @@ namespace cv { namespace gpu { namespace cudev __device__ __forceinline__ binder1st(const binder1st& other) : unary_function(){} - Op op; - typename Op::first_argument_type arg1; + const Op op; + const typename Op::first_argument_type arg1; }; template __host__ __device__ __forceinline__ binder1st bind1st(const Op& op, const T& x) @@ -700,8 +700,8 @@ namespace cv { namespace gpu { namespace cudev __device__ __forceinline__ binder2nd(const binder2nd& other) : unary_function(), op(other.op), arg2(other.arg2){} - Op op; - typename Op::second_argument_type arg2; + const Op op; + const typename Op::second_argument_type arg2; }; template __host__ __device__ __forceinline__ binder2nd bind2nd(const Op& op, const T& x) diff --git a/modules/core/include/opencv2/core/gpu_private.hpp b/modules/core/include/opencv2/core/gpu_private.hpp index 7692bc20e6..be194f54e3 100644 --- a/modules/core/include/opencv2/core/gpu_private.hpp +++ b/modules/core/include/opencv2/core/gpu_private.hpp @@ -74,6 +74,10 @@ namespace cv { namespace gpu { CV_EXPORTS cv::String getNppErrorMessage(int code); CV_EXPORTS cv::String getCudaDriverApiErrorMessage(int code); + + // Converts CPU border extrapolation mode into GPU internal analogue. + // Returns true if the GPU analogue exists, false otherwise. + CV_EXPORTS bool tryConvertToGpuBorderType(int cpuBorderType, int& gpuBorderType); }} #ifndef HAVE_CUDA diff --git a/modules/core/src/gpumat.cpp b/modules/core/src/gpumat.cpp index 11bb41948a..c5d4d7a4a6 100644 --- a/modules/core/src/gpumat.cpp +++ b/modules/core/src/gpumat.cpp @@ -1678,3 +1678,33 @@ String cv::gpu::getCudaDriverApiErrorMessage(int code) return getErrorString(code, cu_errors, cu_errors_num); #endif } + +bool cv::gpu::tryConvertToGpuBorderType(int cpuBorderType, int& gpuBorderType) +{ +#ifndef HAVE_CUDA + (void) cpuBorderType; + (void) gpuBorderType; + return false; +#else + switch (cpuBorderType) + { + case IPL_BORDER_REFLECT_101: + gpuBorderType = cv::gpu::BORDER_REFLECT101_GPU; + return true; + case IPL_BORDER_REPLICATE: + gpuBorderType = cv::gpu::BORDER_REPLICATE_GPU; + return true; + case IPL_BORDER_CONSTANT: + gpuBorderType = cv::gpu::BORDER_CONSTANT_GPU; + return true; + case IPL_BORDER_REFLECT: + gpuBorderType = cv::gpu::BORDER_REFLECT_GPU; + return true; + case IPL_BORDER_WRAP: + gpuBorderType = cv::gpu::BORDER_WRAP_GPU; + return true; + default: + return false; + }; +#endif +} diff --git a/modules/gpu/CMakeLists.txt b/modules/gpu/CMakeLists.txt index 3c39dc1520..f01a23b84f 100644 --- a/modules/gpu/CMakeLists.txt +++ b/modules/gpu/CMakeLists.txt @@ -3,10 +3,101 @@ if(ANDROID OR IOS) endif() set(the_description "GPU-accelerated Computer Vision") +ocv_add_module(gpu opencv_imgproc opencv_calib3d opencv_objdetect opencv_video opencv_photo opencv_legacy) -ocv_warnings_disable(CMAKE_CXX_FLAGS /wd4127 /wd4100 /wd4324 /wd4512 -Wundef -Wmissing-declarations -Wshadow -Wunused-parameter) +ocv_module_include_directories("${CMAKE_CURRENT_SOURCE_DIR}/src/cuda") -ocv_define_module(gpu opencv_calib3d opencv_objdetect opencv_gpuarithm opencv_gpuwarping OPTIONAL opencv_gpulegacy) +file(GLOB lib_hdrs "include/opencv2/*.hpp" "include/opencv2/${name}/*.hpp" "include/opencv2/${name}/*.h") +file(GLOB lib_int_hdrs "src/*.hpp" "src/*.h") +file(GLOB lib_cuda_hdrs "src/cuda/*.hpp" "src/cuda/*.h") +file(GLOB lib_srcs "src/*.cpp") +file(GLOB lib_cuda "src/cuda/*.cu*") + +source_group("Include" FILES ${lib_hdrs}) +source_group("Src\\Host" FILES ${lib_srcs} ${lib_int_hdrs}) +source_group("Src\\Cuda" FILES ${lib_cuda} ${lib_cuda_hdrs}) + +if(HAVE_CUDA) + file(GLOB_RECURSE ncv_srcs "src/nvidia/*.cpp" "src/nvidia/*.h*") + file(GLOB_RECURSE ncv_cuda "src/nvidia/*.cu") + set(ncv_files ${ncv_srcs} ${ncv_cuda}) + + source_group("Src\\NVidia" FILES ${ncv_files}) + ocv_include_directories("src/nvidia" "src/nvidia/core" "src/nvidia/NPP_staging" ${CUDA_INCLUDE_DIRS}) + ocv_warnings_disable(CMAKE_CXX_FLAGS -Wundef -Wmissing-declarations -Wshadow -Wunused-parameter /wd4211 /wd4201 /wd4100 /wd4505 /wd4408) + + if(MSVC) + if(NOT ENABLE_NOISY_WARNINGS) + foreach(var CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_RELEASE CMAKE_CXX_FLAGS_DEBUG) + string(REPLACE "/W4" "/W3" ${var} "${${var}}") + endforeach() + + set(CUDA_NVCC_FLAGS ${CUDA_NVCC_FLAGS} -Xcompiler /wd4251) + endif() + endif() + + ocv_cuda_compile(cuda_objs ${lib_cuda} ${ncv_cuda}) + + set(cuda_link_libs ${CUDA_LIBRARIES} ${CUDA_npp_LIBRARY}) + + if(WITH_NVCUVID) + set(cuda_link_libs ${cuda_link_libs} ${CUDA_nvcuvid_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}) + endif() +else() + set(lib_cuda "") + set(cuda_objs "") + set(cuda_link_libs "") + set(ncv_files "") +endif() + +ocv_set_module_sources( + HEADERS ${lib_hdrs} + SOURCES ${lib_int_hdrs} ${lib_cuda_hdrs} ${lib_srcs} ${lib_cuda} ${ncv_files} ${cuda_objs} + ) + +ocv_create_module(${cuda_link_libs}) + +if(HAVE_CUDA) + if(HAVE_CUFFT) + CUDA_ADD_CUFFT_TO_TARGET(${the_module}) + endif() + + if(HAVE_CUBLAS) + CUDA_ADD_CUBLAS_TO_TARGET(${the_module}) + endif() + + install(FILES src/nvidia/NPP_staging/NPP_staging.hpp src/nvidia/core/NCV.hpp + DESTINATION ${OPENCV_INCLUDE_INSTALL_PATH}/opencv2/${name} + COMPONENT main) +endif() + +ocv_add_precompiled_headers(${the_module}) + +################################################################################################################ +################################ GPU Module Tests ##################################################### +################################################################################################################ +file(GLOB test_srcs "test/*.cpp") +file(GLOB test_hdrs "test/*.hpp" "test/*.h") + +set(nvidia "") +if(HAVE_CUDA) + file(GLOB nvidia "test/nvidia/*.cpp" "test/nvidia/*.hpp" "test/nvidia/*.h") + set(nvidia FILES "Src\\\\\\\\NVidia" ${nvidia}) # 8 ugly backslashes :'( +endif() + +ocv_add_accuracy_tests(FILES "Include" ${test_hdrs} + FILES "Src" ${test_srcs} + ${nvidia}) +ocv_add_perf_tests() if(HAVE_CUDA) add_subdirectory(perf4au) diff --git a/modules/gpu/app/nv_perf_test/CMakeLists.txt b/modules/gpu/app/nv_perf_test/CMakeLists.txt new file mode 100644 index 0000000000..c13f5ef46b --- /dev/null +++ b/modules/gpu/app/nv_perf_test/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 2.8.3) + +project(nv_perf_test) + +find_package(OpenCV REQUIRED) +include_directories(${OpenCV_INCLUDE_DIR}) + +add_executable(${PROJECT_NAME} main.cpp) + +target_link_libraries(${PROJECT_NAME} ${OpenCV_LIBS}) diff --git a/modules/gpu/app/nv_perf_test/im1_1280x800.jpg b/modules/gpu/app/nv_perf_test/im1_1280x800.jpg new file mode 100644 index 0000000000000000000000000000000000000000..bdbbd4aee95128fe0d296a50800c9cb95f31f568 GIT binary patch literal 143316 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%0DTNi%!@pNRtF7GF(Blc<9&kYjvR^_ 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 GKmXZ^2xo2p literal 0 HcmV?d00001 diff --git a/modules/gpu/app/nv_perf_test/im2_1280x800.jpg b/modules/gpu/app/nv_perf_test/im2_1280x800.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ae49640a957d5a1bc26e0b0add1ee65a3c2a7bbb GIT binary patch literal 143366 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
qVjO~cImd$5&D96QV^r>Y_9J$ zg;hhb02I*_LoTd1s=ZrO7P25|0;x2wbe#N%da+mT@eH`V_AGik)^P$+v>k z%bvNQA-Ffz?(n|@sy^Z+k|P;hgT(=->CbC3g#}|iDdDlMdluRQ(c+OV9!r-b=YdbY zl1Z;tG#OttZ6q-&Zc&p#M<4yzEyifPmh5P~+_HJ(Xe_V>a41bSQ#R#cfme4H%GSpR zJXJI*u*_8P%^0N7T9`H6wj?AD38`X$2*nq1g9&mtpklaHAz1O6WZQ@nZP1bKj^i=t{7*5d^6MyU59$RlYLRMt;3ZYP?v zw6}8}(AWctGf`3DjT^cVV1%FWs17`eNY?a8woU-^Q=}%{3j>o;Lv=08g#vOs)oM-t z#@lX?L4rBRjpXL0jx%U5A1h3^wA%jT$@2c_4SFl5Py2ZYG>WA5*ky4(g)%E43@8cX zDBFiCM#`X^tsnuHYb|@H$>91c&pe8O#%Sw{c2Am*jSk8WRa;a#V8@E~1~XE!jAYbD zCje9k(sRW*0A6WAXMx>#DN~N>1b8{GJB1z7Ng{-~lqe;RD7FNL+)k>8Cl${B08#yK zZK7DnkvMyX7yurrpQwJci%Pb5A#l?$z`^RcHnnwksocEx5;GEW-D=Y4<(=E%73a>e zDqWPGq15j+d-*P|-I%f96V+aRpZdD%LA_L!*{zu1sXbLR_NQsbHCsA7Px2MkmI$cr zt`1tuE!(e?>MbWwv(;>t>efKOjFFzIa^w&@6~O-hPkmQ$qh72sIX2F4z@DqTX;(J7 zg`(WsNF-JnJoQ{nMxEl9=<8t4vM<>-K{X)AI0RHpfZvK&+y+NRvl=Ki+HIsO9MyzU zki%zHNdxR*xOY|%PlJJiC=j*WBqN*=Q(DV{Rj36x`2$2L44ahmMKBf)J~>|LqCLYT zb5Pq(e~XGJW3Y4hs6rVcS52;=hvuCEh)9i8D)_}pxF-gcfMd5G(V{|6bg##})4<~< zg^d_UH}&^L9)2UL9EX7yypF111}TaaR~6=P!~xY14lHn2#YF9jbGQQ@Xm(=V@F-AG z=3?D-Lf@fyj(Wuum?!Z|a-hYQAP>z36mq{&!T$hsay;=>4x3bGv0A@XMuu`xfj^4G zYqxhcy3bj}*A{{~mjsSM zq_bJ{+q7uc37X-`F`Qg{qtNNn$qlcwu0^Oa#y^SUsv7S2c6NhQmQy^@D~2N?hSbvO zn3N|1vqE!kZHWvC%9&}Y>a7)%5aT%LRtElgXSgr!6O-0xtERhFVC0n)Erq`OjnCF| zirWaRakh0aY2$`2M@s!Lyjyrm%^*+g*Frk>o}J7I{_B$Hu@IT(6=Y%C^J9#DYOnQ4 z<6BZZ{(T&BWO8iZ&FXz9HVmPE-m_k<)va#b1QG`r@M{^ZrdN9BzZA_nIRty05&hF- z*Gn!gOCg|y5}Ib8NjS4->1z?|Tkm>P8XgA~a<{TZ8UPGyAGI z;#2Kxlbac)=!o_oxeJL;G)|QauXH0{iv!}VX1uo3?831bQPoH38V>5Y#haz}JoFv}h)dwDVO@lY(BUAsg%E41vS8rnv0Q`ohxGhjM*60g7+FBes2$f{$C^o_StBaI5Jm{AaORdN zZith{rm3G9#ALH)j&;O}am^1Q_hn6YfPyUT}IDt z3bt$3_LDp#Xyj7N%M)x<#X8PwgCW}3T5EI?E0l2&1ZDhHUE&Gs-DeH%D9EeJraT@$ zx~#n$qanL)GeCwyW_k9h_^N%D}zfe;O(NN1E(N-in{Jtf;q<Z@!l=899l zBoka6s!HX-+O(fWqBzyCp>_6qW?ylGVMNZ0+b~gk61JQxl^A4mzxAE!`^} zTVe6t%`htJR8!YbwzP2@fEd)4_g2jqILm!dcbZ&xlZoVggmeWYcHhW;JeCh9?g%FZ zXtawFQRPkU_&?+UfX;T{( VltSC!4)Q#sl<|dSsAS5)ve{% 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 +#define HAVE_CUDA 1 +#include +#include +#include +#include +#include + +static void printOsInfo() +{ +#if defined _WIN32 +# if defined _WIN64 + printf("[----------]\n[ GPU INFO ] \tRun on OS Windows x64.\n[----------]\n"); fflush(stdout); +# else + printf("[----------]\n[ GPU INFO ] \tRun on OS Windows x32.\n[----------]\n"); fflush(stdout); +# endif +#elif defined linux +# if defined _LP64 + printf("[----------]\n[ GPU INFO ] \tRun on OS Linux x64.\n[----------]\n"); fflush(stdout); +# else + printf("[----------]\n[ GPU INFO ] \tRun on OS Linux x32.\n[----------]\n"); fflush(stdout); +# endif +#elif defined __APPLE__ +# if defined _LP64 + printf("[----------]\n[ GPU INFO ] \tRun on OS Apple x64.\n[----------]\n"); fflush(stdout); +# else + printf("[----------]\n[ GPU INFO ] \tRun on OS Apple x32.\n[----------]\n"); fflush(stdout); +# endif +#endif +} + +static void printCudaInfo() +{ + const int deviceCount = cv::gpu::getCudaEnabledDeviceCount(); + + printf("[----------]\n"); fflush(stdout); + printf("[ GPU INFO ] \tCUDA device count:: %d.\n", deviceCount); fflush(stdout); + printf("[----------]\n"); fflush(stdout); + + for (int i = 0; i < deviceCount; ++i) + { + cv::gpu::DeviceInfo info(i); + + printf("[----------]\n"); fflush(stdout); + printf("[ DEVICE ] \t# %d %s.\n", i, info.name().c_str()); fflush(stdout); + printf("[ ] \tCompute capability: %d.%d\n", info.majorVersion(), info.minorVersion()); fflush(stdout); + printf("[ ] \tMulti Processor Count: %d\n", info.multiProcessorCount()); fflush(stdout); + printf("[ ] \tTotal memory: %d Mb\n", static_cast(static_cast(info.totalMemory() / 1024.0) / 1024.0)); fflush(stdout); + printf("[ ] \tFree memory: %d Mb\n", static_cast(static_cast(info.freeMemory() / 1024.0) / 1024.0)); fflush(stdout); + if (!info.isCompatible()) + printf("[ GPU INFO ] \tThis device is NOT compatible with current GPU module build\n"); + printf("[----------]\n"); fflush(stdout); + } +} + +int main(int argc, char* argv[]) +{ + printOsInfo(); + printCudaInfo(); + + perf::Regression::Init("nv_perf_test"); + perf::TestBase::Init(argc, argv); + testing::InitGoogleTest(&argc, argv); + + return RUN_ALL_TESTS(); +} + +#define DEF_PARAM_TEST(name, ...) typedef ::perf::TestBaseWithParam< std::tr1::tuple< __VA_ARGS__ > > name +#define DEF_PARAM_TEST_1(name, param_type) typedef ::perf::TestBaseWithParam< param_type > name + +////////////////////////////////////////////////////////// +// HoughLinesP + +DEF_PARAM_TEST_1(Image, std::string); + +GPU_PERF_TEST_P(Image, HoughLinesP, testing::Values(std::string("im1_1280x800.jpg"))) +{ + declare.time(30.0); + + std::string fileName = GetParam(); + + const float rho = 1.f; + const float theta = 1.f; + const int threshold = 40; + const int minLineLenght = 20; + const int maxLineGap = 5; + + cv::Mat image = cv::imread(fileName, cv::IMREAD_GRAYSCALE); + + if (PERF_RUN_GPU()) + { + cv::gpu::GpuMat d_image(image); + cv::gpu::GpuMat d_lines; + cv::gpu::HoughLinesBuf d_buf; + + cv::gpu::HoughLinesP(d_image, d_lines, d_buf, rho, theta, minLineLenght, maxLineGap); + + TEST_CYCLE() + { + cv::gpu::HoughLinesP(d_image, d_lines, d_buf, rho, theta, minLineLenght, maxLineGap); + } + } + else + { + cv::Mat mask; + cv::Canny(image, mask, 50, 100); + + std::vector lines; + cv::HoughLinesP(mask, lines, rho, theta, threshold, minLineLenght, maxLineGap); + + TEST_CYCLE() + { + cv::HoughLinesP(mask, lines, rho, theta, threshold, minLineLenght, maxLineGap); + } + } + + SANITY_CHECK(0); +} + +////////////////////////////////////////////////////////// +// GoodFeaturesToTrack + +DEF_PARAM_TEST(Image_Depth, std::string, perf::MatDepth); + +GPU_PERF_TEST_P(Image_Depth, GoodFeaturesToTrack, + testing::Combine( + testing::Values(std::string("im1_1280x800.jpg")), + testing::Values(CV_8U, CV_16U) + )) +{ + declare.time(60); + + const std::string fileName = std::tr1::get<0>(GetParam()); + const int depth = std::tr1::get<1>(GetParam()); + + const int maxCorners = 5000; + const double qualityLevel = 0.05; + const int minDistance = 5; + const int blockSize = 3; + const bool useHarrisDetector = true; + const double k = 0.05; + + cv::Mat src = cv::imread(fileName, cv::IMREAD_GRAYSCALE); + if (src.empty()) + FAIL() << "Unable to load source image [" << fileName << "]"; + + if (depth != CV_8U) + src.convertTo(src, depth); + + cv::Mat mask(src.size(), CV_8UC1, cv::Scalar::all(1)); + mask(cv::Rect(0, 0, 100, 100)).setTo(cv::Scalar::all(0)); + + if (PERF_RUN_GPU()) + { + cv::gpu::GoodFeaturesToTrackDetector_GPU d_detector(maxCorners, qualityLevel, minDistance, blockSize, useHarrisDetector, k); + + cv::gpu::GpuMat d_src(src); + cv::gpu::GpuMat d_mask(mask); + cv::gpu::GpuMat d_pts; + + d_detector(d_src, d_pts, d_mask); + + TEST_CYCLE() + { + d_detector(d_src, d_pts, d_mask); + } + } + else + { + if (depth != CV_8U) + FAIL() << "Unsupported depth"; + + cv::Mat pts; + + cv::goodFeaturesToTrack(src, pts, maxCorners, qualityLevel, minDistance, mask, blockSize, useHarrisDetector, k); + + TEST_CYCLE() + { + cv::goodFeaturesToTrack(src, pts, maxCorners, qualityLevel, minDistance, mask, blockSize, useHarrisDetector, k); + } + } + + SANITY_CHECK(0); +} + +////////////////////////////////////////////////////////// +// OpticalFlowPyrLKSparse + +typedef std::pair string_pair; + +DEF_PARAM_TEST(ImagePair_Depth_GraySource, string_pair, perf::MatDepth, bool); + +GPU_PERF_TEST_P(ImagePair_Depth_GraySource, OpticalFlowPyrLKSparse, + testing::Combine( + testing::Values(string_pair("im1_1280x800.jpg", "im2_1280x800.jpg")), + testing::Values(CV_8U, CV_16U), + testing::Bool() + )) +{ + declare.time(60); + + const string_pair fileNames = std::tr1::get<0>(GetParam()); + const int depth = std::tr1::get<1>(GetParam()); + const bool graySource = std::tr1::get<2>(GetParam()); + + // PyrLK params + const cv::Size winSize(15, 15); + const int maxLevel = 5; + const cv::TermCriteria criteria(cv::TermCriteria::COUNT + cv::TermCriteria::EPS, 30, 0.01); + + // GoodFeaturesToTrack params + const int maxCorners = 5000; + const double qualityLevel = 0.05; + const int minDistance = 5; + const int blockSize = 3; + const bool useHarrisDetector = true; + const double k = 0.05; + + cv::Mat src1 = cv::imread(fileNames.first, graySource ? cv::IMREAD_GRAYSCALE : cv::IMREAD_COLOR); + if (src1.empty()) + FAIL() << "Unable to load source image [" << fileNames.first << "]"; + + cv::Mat src2 = cv::imread(fileNames.second, graySource ? cv::IMREAD_GRAYSCALE : cv::IMREAD_COLOR); + if (src2.empty()) + FAIL() << "Unable to load source image [" << fileNames.second << "]"; + + cv::Mat gray_src; + if (graySource) + gray_src = src1; + else + cv::cvtColor(src1, gray_src, cv::COLOR_BGR2GRAY); + + cv::Mat pts; + cv::goodFeaturesToTrack(gray_src, pts, maxCorners, qualityLevel, minDistance, cv::noArray(), blockSize, useHarrisDetector, k); + + if (depth != CV_8U) + { + src1.convertTo(src1, depth); + src2.convertTo(src2, depth); + } + + if (PERF_RUN_GPU()) + { + cv::gpu::GpuMat d_src1(src1); + cv::gpu::GpuMat d_src2(src2); + cv::gpu::GpuMat d_pts(pts.reshape(2, 1)); + cv::gpu::GpuMat d_nextPts; + cv::gpu::GpuMat d_status; + + cv::gpu::PyrLKOpticalFlow d_pyrLK; + d_pyrLK.winSize = winSize; + d_pyrLK.maxLevel = maxLevel; + d_pyrLK.iters = criteria.maxCount; + d_pyrLK.useInitialFlow = false; + + d_pyrLK.sparse(d_src1, d_src2, d_pts, d_nextPts, d_status); + + TEST_CYCLE() + { + d_pyrLK.sparse(d_src1, d_src2, d_pts, d_nextPts, d_status); + } + } + else + { + if (depth != CV_8U) + FAIL() << "Unsupported depth"; + + cv::Mat nextPts; + cv::Mat status; + + cv::calcOpticalFlowPyrLK(src1, src2, pts, nextPts, status, cv::noArray(), winSize, maxLevel, criteria); + + TEST_CYCLE() + { + cv::calcOpticalFlowPyrLK(src1, src2, pts, nextPts, status, cv::noArray(), winSize, maxLevel, criteria); + } + } + + SANITY_CHECK(0); +} + +////////////////////////////////////////////////////////// +// OpticalFlowFarneback + +DEF_PARAM_TEST(ImagePair_Depth, string_pair, perf::MatDepth); + +GPU_PERF_TEST_P(ImagePair_Depth, OpticalFlowFarneback, + testing::Combine( + testing::Values(string_pair("im1_1280x800.jpg", "im2_1280x800.jpg")), + testing::Values(CV_8U, CV_16U) + )) +{ + declare.time(500); + + const string_pair fileNames = std::tr1::get<0>(GetParam()); + const int depth = std::tr1::get<1>(GetParam()); + + const double pyrScale = 0.5; + const int numLevels = 6; + const int winSize = 7; + const int numIters = 15; + const int polyN = 7; + const double polySigma = 1.5; + const int flags = cv::OPTFLOW_USE_INITIAL_FLOW; + + cv::Mat src1 = cv::imread(fileNames.first, cv::IMREAD_GRAYSCALE); + if (src1.empty()) + FAIL() << "Unable to load source image [" << fileNames.first << "]"; + + cv::Mat src2 = cv::imread(fileNames.second, cv::IMREAD_GRAYSCALE); + if (src2.empty()) + FAIL() << "Unable to load source image [" << fileNames.second << "]"; + + if (depth != CV_8U) + { + src1.convertTo(src1, depth); + src2.convertTo(src2, depth); + } + + if (PERF_RUN_GPU()) + { + cv::gpu::GpuMat d_src1(src1); + cv::gpu::GpuMat d_src2(src2); + cv::gpu::GpuMat d_u(src1.size(), CV_32FC1, cv::Scalar::all(0)); + cv::gpu::GpuMat d_v(src1.size(), CV_32FC1, cv::Scalar::all(0)); + + cv::gpu::FarnebackOpticalFlow d_farneback; + d_farneback.pyrScale = pyrScale; + d_farneback.numLevels = numLevels; + d_farneback.winSize = winSize; + d_farneback.numIters = numIters; + d_farneback.polyN = polyN; + d_farneback.polySigma = polySigma; + d_farneback.flags = flags; + + d_farneback(d_src1, d_src2, d_u, d_v); + + TEST_CYCLE_N(10) + { + d_farneback(d_src1, d_src2, d_u, d_v); + } + } + else + { + if (depth != CV_8U) + FAIL() << "Unsupported depth"; + + cv::Mat flow(src1.size(), CV_32FC2, cv::Scalar::all(0)); + + cv::calcOpticalFlowFarneback(src1, src2, flow, pyrScale, numLevels, winSize, numIters, polyN, polySigma, flags); + + TEST_CYCLE_N(10) + { + cv::calcOpticalFlowFarneback(src1, src2, flow, pyrScale, numLevels, winSize, numIters, polyN, polySigma, flags); + } + } + + SANITY_CHECK(0); +} + +////////////////////////////////////////////////////////// +// OpticalFlowBM + +void calcOpticalFlowBM(const cv::Mat& prev, const cv::Mat& curr, + cv::Size bSize, cv::Size shiftSize, cv::Size maxRange, int usePrevious, + cv::Mat& velx, cv::Mat& vely) +{ + cv::Size sz((curr.cols - bSize.width + shiftSize.width)/shiftSize.width, (curr.rows - bSize.height + shiftSize.height)/shiftSize.height); + + velx.create(sz, CV_32FC1); + vely.create(sz, CV_32FC1); + + CvMat cvprev = prev; + CvMat cvcurr = curr; + + CvMat cvvelx = velx; + CvMat cvvely = vely; + + cvCalcOpticalFlowBM(&cvprev, &cvcurr, bSize, shiftSize, maxRange, usePrevious, &cvvelx, &cvvely); +} + +DEF_PARAM_TEST(ImagePair_BlockSize_ShiftSize_MaxRange, string_pair, cv::Size, cv::Size, cv::Size); + +GPU_PERF_TEST_P(ImagePair_BlockSize_ShiftSize_MaxRange, OpticalFlowBM, + testing::Combine( + testing::Values(string_pair("im1_1280x800.jpg", "im2_1280x800.jpg")), + testing::Values(cv::Size(16, 16)), + testing::Values(cv::Size(2, 2)), + testing::Values(cv::Size(16, 16)) + )) +{ + declare.time(3000); + + const string_pair fileNames = std::tr1::get<0>(GetParam()); + const cv::Size block_size = std::tr1::get<1>(GetParam()); + const cv::Size shift_size = std::tr1::get<2>(GetParam()); + const cv::Size max_range = std::tr1::get<3>(GetParam()); + + cv::Mat src1 = cv::imread(fileNames.first, cv::IMREAD_GRAYSCALE); + if (src1.empty()) + FAIL() << "Unable to load source image [" << fileNames.first << "]"; + + cv::Mat src2 = cv::imread(fileNames.second, cv::IMREAD_GRAYSCALE); + if (src2.empty()) + FAIL() << "Unable to load source image [" << fileNames.second << "]"; + + if (PERF_RUN_GPU()) + { + cv::gpu::GpuMat d_src1(src1); + cv::gpu::GpuMat d_src2(src2); + cv::gpu::GpuMat d_velx, d_vely, buf; + + cv::gpu::calcOpticalFlowBM(d_src1, d_src2, block_size, shift_size, max_range, false, d_velx, d_vely, buf); + + TEST_CYCLE_N(10) + { + cv::gpu::calcOpticalFlowBM(d_src1, d_src2, block_size, shift_size, max_range, false, d_velx, d_vely, buf); + } + } + else + { + cv::Mat velx, vely; + + calcOpticalFlowBM(src1, src2, block_size, shift_size, max_range, false, velx, vely); + + TEST_CYCLE_N(10) + { + calcOpticalFlowBM(src1, src2, block_size, shift_size, max_range, false, velx, vely); + } + } + + SANITY_CHECK(0); +} + +GPU_PERF_TEST_P(ImagePair_BlockSize_ShiftSize_MaxRange, FastOpticalFlowBM, + testing::Combine( + testing::Values(string_pair("im1_1280x800.jpg", "im2_1280x800.jpg")), + testing::Values(cv::Size(16, 16)), + testing::Values(cv::Size(1, 1)), + testing::Values(cv::Size(16, 16)) + )) +{ + declare.time(3000); + + const string_pair fileNames = std::tr1::get<0>(GetParam()); + const cv::Size block_size = std::tr1::get<1>(GetParam()); + const cv::Size shift_size = std::tr1::get<2>(GetParam()); + const cv::Size max_range = std::tr1::get<3>(GetParam()); + + cv::Mat src1 = cv::imread(fileNames.first, cv::IMREAD_GRAYSCALE); + if (src1.empty()) + FAIL() << "Unable to load source image [" << fileNames.first << "]"; + + cv::Mat src2 = cv::imread(fileNames.second, cv::IMREAD_GRAYSCALE); + if (src2.empty()) + FAIL() << "Unable to load source image [" << fileNames.second << "]"; + + if (PERF_RUN_GPU()) + { + cv::gpu::GpuMat d_src1(src1); + cv::gpu::GpuMat d_src2(src2); + cv::gpu::GpuMat d_velx, d_vely; + + cv::gpu::FastOpticalFlowBM fastBM; + + fastBM(d_src1, d_src2, d_velx, d_vely, max_range.width, block_size.width); + + TEST_CYCLE_N(10) + { + fastBM(d_src1, d_src2, d_velx, d_vely, max_range.width, block_size.width); + } + } + else + { + cv::Mat velx, vely; + + calcOpticalFlowBM(src1, src2, block_size, shift_size, max_range, false, velx, vely); + + TEST_CYCLE_N(10) + { + calcOpticalFlowBM(src1, src2, block_size, shift_size, max_range, false, velx, vely); + } + } + + SANITY_CHECK(0); +} diff --git a/modules/gpu/doc/calib3d.rst b/modules/gpu/doc/calib3d.rst deleted file mode 100644 index faa6c0fec2..0000000000 --- a/modules/gpu/doc/calib3d.rst +++ /dev/null @@ -1,36 +0,0 @@ -Camera Calibration and 3D Reconstruction -======================================== - -.. highlight:: cpp - - - -gpu::solvePnPRansac -------------------- -Finds the object pose from 3D-2D point correspondences. - -.. ocv:function:: void gpu::solvePnPRansac(const Mat& object, const Mat& image, const Mat& camera_mat, const Mat& dist_coef, Mat& rvec, Mat& tvec, bool use_extrinsic_guess=false, int num_iters=100, float max_dist=8.0, int min_inlier_count=100, vector* inliers=NULL) - - :param object: Single-row matrix of object points. - - :param image: Single-row matrix of image points. - - :param camera_mat: 3x3 matrix of intrinsic camera parameters. - - :param dist_coef: Distortion coefficients. See :ocv:func:`undistortPoints` for details. - - :param rvec: Output 3D rotation vector. - - :param tvec: Output 3D translation vector. - - :param use_extrinsic_guess: Flag to indicate that the function must use ``rvec`` and ``tvec`` as an initial transformation guess. It is not supported for now. - - :param num_iters: Maximum number of RANSAC iterations. - - :param max_dist: Euclidean distance threshold to detect whether point is inlier or not. - - :param min_inlier_count: Flag to indicate that the function must stop if greater or equal number of inliers is achieved. It is not supported for now. - - :param inliers: Output vector of inlier indices. - -.. seealso:: :ocv:func:`solvePnPRansac` diff --git a/modules/gpustereo/doc/stereo.rst b/modules/gpu/doc/camera_calibration_and_3d_reconstruction.rst similarity index 92% rename from modules/gpustereo/doc/stereo.rst rename to modules/gpu/doc/camera_calibration_and_3d_reconstruction.rst index cd2add0b94..587c253d2a 100644 --- a/modules/gpustereo/doc/stereo.rst +++ b/modules/gpu/doc/camera_calibration_and_3d_reconstruction.rst @@ -1,5 +1,5 @@ -Stereo Correspondence -===================== +Camera Calibration and 3D Reconstruction +======================================== .. highlight:: cpp @@ -462,6 +462,38 @@ Reprojects a disparity image to 3D space. +gpu::solvePnPRansac +------------------- +Finds the object pose from 3D-2D point correspondences. + +.. ocv:function:: void gpu::solvePnPRansac(const Mat& object, const Mat& image, const Mat& camera_mat, const Mat& dist_coef, Mat& rvec, Mat& tvec, bool use_extrinsic_guess=false, int num_iters=100, float max_dist=8.0, int min_inlier_count=100, vector* inliers=NULL) + + :param object: Single-row matrix of object points. + + :param image: Single-row matrix of image points. + + :param camera_mat: 3x3 matrix of intrinsic camera parameters. + + :param dist_coef: Distortion coefficients. See :ocv:func:`undistortPoints` for details. + + :param rvec: Output 3D rotation vector. + + :param tvec: Output 3D translation vector. + + :param use_extrinsic_guess: Flag to indicate that the function must use ``rvec`` and ``tvec`` as an initial transformation guess. It is not supported for now. + + :param num_iters: Maximum number of RANSAC iterations. + + :param max_dist: Euclidean distance threshold to detect whether point is inlier or not. + + :param min_inlier_count: Flag to indicate that the function must stop if greater or equal number of inliers is achieved. It is not supported for now. + + :param inliers: Output vector of inlier indices. + +.. seealso:: :ocv:func:`solvePnPRansac` + + + .. [Felzenszwalb2006] Pedro F. Felzenszwalb algorithm [Pedro F. Felzenszwalb and Daniel P. Huttenlocher. *Efficient belief propagation for early vision*. International Journal of Computer Vision, 70(1), October 2006 .. [Yang2010] Q. Yang, L. Wang, and N. Ahuja. *A constant-space belief propagation algorithm for stereo matching*. In CVPR, 2010. diff --git a/modules/gpufeatures2d/doc/feature_detection_and_description.rst b/modules/gpu/doc/feature_detection_and_description.rst similarity index 100% rename from modules/gpufeatures2d/doc/feature_detection_and_description.rst rename to modules/gpu/doc/feature_detection_and_description.rst diff --git a/modules/gpu/doc/gpu.rst b/modules/gpu/doc/gpu.rst index 2a0358e017..b21e2abac8 100644 --- a/modules/gpu/doc/gpu.rst +++ b/modules/gpu/doc/gpu.rst @@ -8,5 +8,12 @@ gpu. GPU-accelerated Computer Vision introduction initalization_and_information data_structures + operations_on_matrices + per_element_operations + image_processing + matrix_reductions object_detection - calib3d + feature_detection_and_description + image_filtering + camera_calibration_and_3d_reconstruction + video diff --git a/modules/gpufilters/doc/filtering.rst b/modules/gpu/doc/image_filtering.rst similarity index 100% rename from modules/gpufilters/doc/filtering.rst rename to modules/gpu/doc/image_filtering.rst diff --git a/modules/gpu/doc/image_processing.rst b/modules/gpu/doc/image_processing.rst new file mode 100644 index 0000000000..7b404c832a --- /dev/null +++ b/modules/gpu/doc/image_processing.rst @@ -0,0 +1,1087 @@ +Image Processing +================ + +.. highlight:: cpp + + + +gpu::meanShiftFiltering +--------------------------- +Performs mean-shift filtering for each point of the source image. + +.. ocv:function:: void gpu::meanShiftFiltering( const GpuMat& src, GpuMat& dst, int sp, int sr, TermCriteria criteria=TermCriteria(TermCriteria::MAX_ITER + TermCriteria::EPS, 5, 1), Stream& stream=Stream::Null() ) + + :param src: Source image. Only ``CV_8UC4`` images are supported for now. + + :param dst: Destination image containing the color of mapped points. It has the same size and type as ``src`` . + + :param sp: Spatial window radius. + + :param sr: Color window radius. + + :param criteria: Termination criteria. See :ocv:class:`TermCriteria`. + +It maps each point of the source image into another point. As a result, you have a new color and new position of each point. + + + +gpu::meanShiftProc +---------------------- +Performs a mean-shift procedure and stores information about processed points (their colors and positions) in two images. + +.. ocv:function:: void gpu::meanShiftProc( const GpuMat& src, GpuMat& dstr, GpuMat& dstsp, int sp, int sr, TermCriteria criteria=TermCriteria(TermCriteria::MAX_ITER + TermCriteria::EPS, 5, 1), Stream& stream=Stream::Null() ) + + :param src: Source image. Only ``CV_8UC4`` images are supported for now. + + :param dstr: Destination image containing the color of mapped points. The size and type is the same as ``src`` . + + :param dstsp: Destination image containing the position of mapped points. The size is the same as ``src`` size. The type is ``CV_16SC2`` . + + :param sp: Spatial window radius. + + :param sr: Color window radius. + + :param criteria: Termination criteria. See :ocv:class:`TermCriteria`. + +.. seealso:: :ocv:func:`gpu::meanShiftFiltering` + + + +gpu::meanShiftSegmentation +------------------------------ +Performs a mean-shift segmentation of the source image and eliminates small segments. + +.. ocv:function:: void gpu::meanShiftSegmentation(const GpuMat& src, Mat& dst, int sp, int sr, int minsize, TermCriteria criteria = TermCriteria(TermCriteria::MAX_ITER + TermCriteria::EPS, 5, 1)) + + :param src: Source image. Only ``CV_8UC4`` images are supported for now. + + :param dst: Segmented image with the same size and type as ``src`` . + + :param sp: Spatial window radius. + + :param sr: Color window radius. + + :param minsize: Minimum segment size. Smaller segments are merged. + + :param criteria: Termination criteria. See :ocv:class:`TermCriteria`. + + + +gpu::integral +----------------- +Computes an integral image. + +.. ocv:function:: void gpu::integral(const GpuMat& src, GpuMat& sum, Stream& stream = Stream::Null()) + + :param src: Source image. Only ``CV_8UC1`` images are supported for now. + + :param sum: Integral image containing 32-bit unsigned integer values packed into ``CV_32SC1`` . + + :param stream: Stream for the asynchronous version. + +.. seealso:: :ocv:func:`integral` + + + +gpu::sqrIntegral +-------------------- +Computes a squared integral image. + +.. ocv:function:: void gpu::sqrIntegral(const GpuMat& src, GpuMat& sqsum, Stream& stream = Stream::Null()) + + :param src: Source image. Only ``CV_8UC1`` images are supported for now. + + :param sqsum: Squared integral image containing 64-bit unsigned integer values packed into ``CV_64FC1`` . + + :param stream: Stream for the asynchronous version. + + + +gpu::columnSum +------------------ +Computes a vertical (column) sum. + +.. ocv:function:: void gpu::columnSum(const GpuMat& src, GpuMat& sum) + + :param src: Source image. Only ``CV_32FC1`` images are supported for now. + + :param sum: Destination image of the ``CV_32FC1`` type. + + + +gpu::cornerHarris +--------------------- +Computes the Harris cornerness criteria at each image pixel. + +.. ocv:function:: void gpu::cornerHarris(const GpuMat& src, GpuMat& dst, int blockSize, int ksize, double k, int borderType=BORDER_REFLECT101) + + :param src: Source image. Only ``CV_8UC1`` and ``CV_32FC1`` images are supported for now. + + :param dst: Destination image containing cornerness values. It has the same size as ``src`` and ``CV_32FC1`` type. + + :param blockSize: Neighborhood size. + + :param ksize: Aperture parameter for the Sobel operator. + + :param k: Harris detector free parameter. + + :param borderType: Pixel extrapolation method. Only ``BORDER_REFLECT101`` and ``BORDER_REPLICATE`` are supported for now. + +.. seealso:: :ocv:func:`cornerHarris` + + + +gpu::cornerMinEigenVal +-------------------------- +Computes the minimum eigen value of a 2x2 derivative covariation matrix at each pixel (the cornerness criteria). + +.. ocv:function:: void gpu::cornerMinEigenVal(const GpuMat& src, GpuMat& dst, int blockSize, int ksize, int borderType=BORDER_REFLECT101) + +.. ocv:function:: void gpu::cornerMinEigenVal(const GpuMat& src, GpuMat& dst, GpuMat& Dx, GpuMat& Dy, int blockSize, int ksize, int borderType=BORDER_REFLECT101) + +.. ocv:function:: void gpu::cornerMinEigenVal(const GpuMat& src, GpuMat& dst, GpuMat& Dx, GpuMat& Dy, GpuMat& buf, int blockSize, int ksize, int borderType=BORDER_REFLECT101, Stream& stream = Stream::Null()) + + :param src: Source image. Only ``CV_8UC1`` and ``CV_32FC1`` images are supported for now. + + :param dst: Destination image containing cornerness values. The size is the same. The type is ``CV_32FC1`` . + + :param blockSize: Neighborhood size. + + :param ksize: Aperture parameter for the Sobel operator. + + :param borderType: Pixel extrapolation method. Only ``BORDER_REFLECT101`` and ``BORDER_REPLICATE`` are supported for now. + +.. seealso:: :ocv:func:`cornerMinEigenVal` + + + +gpu::mulSpectrums +--------------------- +Performs a per-element multiplication of two Fourier spectrums. + +.. ocv:function:: void gpu::mulSpectrums( const GpuMat& a, const GpuMat& b, GpuMat& c, int flags, bool conjB=false, Stream& stream=Stream::Null() ) + + :param a: First spectrum. + + :param b: Second spectrum with the same size and type as ``a`` . + + :param c: Destination spectrum. + + :param flags: Mock parameter used for CPU/GPU interfaces similarity. + + :param conjB: Optional flag to specify if the second spectrum needs to be conjugated before the multiplication. + + Only full (not packed) ``CV_32FC2`` complex spectrums in the interleaved format are supported for now. + +.. seealso:: :ocv:func:`mulSpectrums` + + + +gpu::mulAndScaleSpectrums +----------------------------- +Performs a per-element multiplication of two Fourier spectrums and scales the result. + +.. ocv:function:: void gpu::mulAndScaleSpectrums( const GpuMat& a, const GpuMat& b, GpuMat& c, int flags, float scale, bool conjB=false, Stream& stream=Stream::Null() ) + + :param a: First spectrum. + + :param b: Second spectrum with the same size and type as ``a`` . + + :param c: Destination spectrum. + + :param flags: Mock parameter used for CPU/GPU interfaces similarity. + + :param scale: Scale constant. + + :param conjB: Optional flag to specify if the second spectrum needs to be conjugated before the multiplication. + + Only full (not packed) ``CV_32FC2`` complex spectrums in the interleaved format are supported for now. + +.. seealso:: :ocv:func:`mulSpectrums` + + + +gpu::dft +------------ +Performs a forward or inverse discrete Fourier transform (1D or 2D) of the floating point matrix. + +.. ocv:function:: void gpu::dft( const GpuMat& src, GpuMat& dst, Size dft_size, int flags=0, Stream& stream=Stream::Null() ) + + :param src: Source matrix (real or complex). + + :param dst: Destination matrix (real or complex). + + :param dft_size: Size of a discrete Fourier transform. + + :param flags: Optional flags: + + * **DFT_ROWS** transforms each individual row of the source matrix. + + * **DFT_SCALE** scales the result: divide it by the number of elements in the transform (obtained from ``dft_size`` ). + + * **DFT_INVERSE** inverts DFT. Use for complex-complex cases (real-complex and complex-real cases are always forward and inverse, respectively). + + * **DFT_REAL_OUTPUT** specifies the output as real. The source matrix is the result of real-complex transform, so the destination matrix must be real. + +Use to handle real matrices ( ``CV32FC1`` ) and complex matrices in the interleaved format ( ``CV32FC2`` ). + +The source matrix should be continuous, otherwise reallocation and data copying is performed. The function chooses an operation mode depending on the flags, size, and channel count of the source matrix: + + * If the source matrix is complex and the output is not specified as real, the destination matrix is complex and has the ``dft_size`` size and ``CV_32FC2`` type. The destination matrix contains a full result of the DFT (forward or inverse). + + * If the source matrix is complex and the output is specified as real, the function assumes that its input is the result of the forward transform (see the next item). The destination matrix has the ``dft_size`` size and ``CV_32FC1`` type. It contains the result of the inverse DFT. + + * If the source matrix is real (its type is ``CV_32FC1`` ), forward DFT is performed. The result of the DFT is packed into complex ( ``CV_32FC2`` ) matrix. So, the width of the destination matrix is ``dft_size.width / 2 + 1`` . But if the source is a single column, the height is reduced instead of the width. + +.. seealso:: :ocv:func:`dft` + + +gpu::ConvolveBuf +---------------- +.. ocv:struct:: gpu::ConvolveBuf + +Class providing a memory buffer for :ocv:func:`gpu::convolve` function, plus it allows to adjust some specific parameters. :: + + struct CV_EXPORTS ConvolveBuf + { + Size result_size; + Size block_size; + Size user_block_size; + Size dft_size; + int spect_len; + + GpuMat image_spect, templ_spect, result_spect; + GpuMat image_block, templ_block, result_data; + + void create(Size image_size, Size templ_size); + static Size estimateBlockSize(Size result_size, Size templ_size); + }; + +You can use field `user_block_size` to set specific block size for :ocv:func:`gpu::convolve` function. If you leave its default value `Size(0,0)` then automatic estimation of block size will be used (which is optimized for speed). By varying `user_block_size` you can reduce memory requirements at the cost of speed. + +gpu::ConvolveBuf::create +------------------------ +.. ocv:function:: gpu::ConvolveBuf::create(Size image_size, Size templ_size) + +Constructs a buffer for :ocv:func:`gpu::convolve` function with respective arguments. + + +gpu::convolve +----------------- +Computes a convolution (or cross-correlation) of two images. + +.. ocv:function:: void gpu::convolve(const GpuMat& image, const GpuMat& templ, GpuMat& result, bool ccorr=false) + +.. ocv:function:: void gpu::convolve( const GpuMat& image, const GpuMat& templ, GpuMat& result, bool ccorr, ConvolveBuf& buf, Stream& stream=Stream::Null() ) + + :param image: Source image. Only ``CV_32FC1`` images are supported for now. + + :param templ: Template image. The size is not greater than the ``image`` size. The type is the same as ``image`` . + + :param result: Result image. If ``image`` is *W x H* and ``templ`` is *w x h*, then ``result`` must be *W-w+1 x H-h+1*. + + :param ccorr: Flags to evaluate cross-correlation instead of convolution. + + :param buf: Optional buffer to avoid extra memory allocations and to adjust some specific parameters. See :ocv:struct:`gpu::ConvolveBuf`. + + :param stream: Stream for the asynchronous version. + +.. seealso:: :ocv:func:`gpu::filter2D` + +gpu::MatchTemplateBuf +--------------------- +.. ocv:struct:: gpu::MatchTemplateBuf + +Class providing memory buffers for :ocv:func:`gpu::matchTemplate` function, plus it allows to adjust some specific parameters. :: + + struct CV_EXPORTS MatchTemplateBuf + { + Size user_block_size; + GpuMat imagef, templf; + std::vector images; + std::vector image_sums; + std::vector image_sqsums; + }; + +You can use field `user_block_size` to set specific block size for :ocv:func:`gpu::matchTemplate` function. If you leave its default value `Size(0,0)` then automatic estimation of block size will be used (which is optimized for speed). By varying `user_block_size` you can reduce memory requirements at the cost of speed. + +gpu::matchTemplate +---------------------- +Computes a proximity map for a raster template and an image where the template is searched for. + +.. ocv:function:: void gpu::matchTemplate(const GpuMat& image, const GpuMat& templ, GpuMat& result, int method, Stream &stream = Stream::Null()) + +.. ocv:function:: void gpu::matchTemplate(const GpuMat& image, const GpuMat& templ, GpuMat& result, int method, MatchTemplateBuf &buf, Stream& stream = Stream::Null()) + + :param image: Source image. ``CV_32F`` and ``CV_8U`` depth images (1..4 channels) are supported for now. + + :param templ: Template image with the size and type the same as ``image`` . + + :param result: Map containing comparison results ( ``CV_32FC1`` ). If ``image`` is *W x H* and ``templ`` is *w x h*, then ``result`` must be *W-w+1 x H-h+1*. + + :param method: Specifies the way to compare the template with the image. + + :param buf: Optional buffer to avoid extra memory allocations and to adjust some specific parameters. See :ocv:struct:`gpu::MatchTemplateBuf`. + + :param stream: Stream for the asynchronous version. + + The following methods are supported for the ``CV_8U`` depth images for now: + + * ``CV_TM_SQDIFF`` + * ``CV_TM_SQDIFF_NORMED`` + * ``CV_TM_CCORR`` + * ``CV_TM_CCORR_NORMED`` + * ``CV_TM_CCOEFF`` + * ``CV_TM_CCOEFF_NORMED`` + + The following methods are supported for the ``CV_32F`` images for now: + + * ``CV_TM_SQDIFF`` + * ``CV_TM_CCORR`` + +.. seealso:: :ocv:func:`matchTemplate` + + +gpu::remap +-------------- +Applies a generic geometrical transformation to an image. + +.. ocv:function:: void gpu::remap( const GpuMat& src, GpuMat& dst, const GpuMat& xmap, const GpuMat& ymap, int interpolation, int borderMode=BORDER_CONSTANT, Scalar borderValue=Scalar(), Stream& stream=Stream::Null() ) + + :param src: Source image. + + :param dst: Destination image with the size the same as ``xmap`` and the type the same as ``src`` . + + :param xmap: X values. Only ``CV_32FC1`` type is supported. + + :param ymap: Y values. Only ``CV_32FC1`` type is supported. + + :param interpolation: Interpolation method (see :ocv:func:`resize` ). ``INTER_NEAREST`` , ``INTER_LINEAR`` and ``INTER_CUBIC`` are supported for now. + + :param borderMode: Pixel extrapolation method (see :ocv:func:`borderInterpolate` ). ``BORDER_REFLECT101`` , ``BORDER_REPLICATE`` , ``BORDER_CONSTANT`` , ``BORDER_REFLECT`` and ``BORDER_WRAP`` are supported for now. + + :param borderValue: Value used in case of a constant border. By default, it is 0. + + :param stream: Stream for the asynchronous version. + +The function transforms the source image using the specified map: + +.. math:: + + \texttt{dst} (x,y) = \texttt{src} (xmap(x,y), ymap(x,y)) + +Values of pixels with non-integer coordinates are computed using the bilinear interpolation. + +.. seealso:: :ocv:func:`remap` + + + +gpu::cvtColor +----------------- +Converts an image from one color space to another. + +.. ocv:function:: void gpu::cvtColor(const GpuMat& src, GpuMat& dst, int code, int dcn = 0, Stream& stream = Stream::Null()) + + :param src: Source image with ``CV_8U`` , ``CV_16U`` , or ``CV_32F`` depth and 1, 3, or 4 channels. + + :param dst: Destination image with the same size and depth as ``src`` . + + :param code: Color space conversion code. For details, see :ocv:func:`cvtColor` . Conversion to/from Luv and Bayer color spaces is not supported. + + :param dcn: Number of channels in the destination image. If the parameter is 0, the number of the channels is derived automatically from ``src`` and the ``code`` . + + :param stream: Stream for the asynchronous version. + +3-channel color spaces (like ``HSV``, ``XYZ``, and so on) can be stored in a 4-channel image for better performance. + +.. seealso:: :ocv:func:`cvtColor` + + + +gpu::swapChannels +----------------- +Exchanges the color channels of an image in-place. + +.. ocv:function:: void gpu::swapChannels(GpuMat& image, const int dstOrder[4], Stream& stream = Stream::Null()) + + :param image: Source image. Supports only ``CV_8UC4`` type. + + :param dstOrder: Integer array describing how channel values are permutated. The n-th entry of the array contains the number of the channel that is stored in the n-th channel of the output image. E.g. Given an RGBA image, aDstOrder = [3,2,1,0] converts this to ABGR channel order. + + :param stream: Stream for the asynchronous version. + +The methods support arbitrary permutations of the original channels, including replication. + + + +gpu::threshold +------------------ +Applies a fixed-level threshold to each array element. + +.. ocv:function:: double gpu::threshold(const GpuMat& src, GpuMat& dst, double thresh, double maxval, int type, Stream& stream = Stream::Null()) + + :param src: Source array (single-channel). + + :param dst: Destination array with the same size and type as ``src`` . + + :param thresh: Threshold value. + + :param maxval: Maximum value to use with ``THRESH_BINARY`` and ``THRESH_BINARY_INV`` threshold types. + + :param type: Threshold type. For details, see :ocv:func:`threshold` . The ``THRESH_OTSU`` threshold type is not supported. + + :param stream: Stream for the asynchronous version. + +.. seealso:: :ocv:func:`threshold` + + + +gpu::resize +--------------- +Resizes an image. + +.. ocv:function:: void gpu::resize(const GpuMat& src, GpuMat& dst, Size dsize, double fx=0, double fy=0, int interpolation = INTER_LINEAR, Stream& stream = Stream::Null()) + + :param src: Source image. + + :param dst: Destination image with the same type as ``src`` . The size is ``dsize`` (when it is non-zero) or the size is computed from ``src.size()`` , ``fx`` , and ``fy`` . + + :param dsize: Destination image size. If it is zero, it is computed as: + + .. math:: + \texttt{dsize = Size(round(fx*src.cols), round(fy*src.rows))} + + Either ``dsize`` or both ``fx`` and ``fy`` must be non-zero. + + :param fx: Scale factor along the horizontal axis. If it is zero, it is computed as: + + .. math:: + + \texttt{(double)dsize.width/src.cols} + + :param fy: Scale factor along the vertical axis. If it is zero, it is computed as: + + .. math:: + + \texttt{(double)dsize.height/src.rows} + + :param interpolation: Interpolation method. ``INTER_NEAREST`` , ``INTER_LINEAR`` and ``INTER_CUBIC`` are supported for now. + + :param stream: Stream for the asynchronous version. + +.. seealso:: :ocv:func:`resize` + + + +gpu::warpAffine +------------------- +Applies an affine transformation to an image. + +.. ocv:function:: void gpu::warpAffine( const GpuMat& src, GpuMat& dst, const Mat& M, Size dsize, int flags=INTER_LINEAR, int borderMode=BORDER_CONSTANT, Scalar borderValue=Scalar(), Stream& stream=Stream::Null() ) + + :param src: Source image. ``CV_8U`` , ``CV_16U`` , ``CV_32S`` , or ``CV_32F`` depth and 1, 3, or 4 channels are supported. + + :param dst: Destination image with the same type as ``src`` . The size is ``dsize`` . + + :param M: *2x3* transformation matrix. + + :param dsize: Size of the destination image. + + :param flags: Combination of interpolation methods (see :ocv:func:`resize`) and the optional flag ``WARP_INVERSE_MAP`` specifying that ``M`` is an inverse transformation ( ``dst=>src`` ). Only ``INTER_NEAREST`` , ``INTER_LINEAR`` , and ``INTER_CUBIC`` interpolation methods are supported. + + :param stream: Stream for the asynchronous version. + +.. seealso:: :ocv:func:`warpAffine` + + + +gpu::buildWarpAffineMaps +------------------------ +Builds transformation maps for affine transformation. + +.. ocv:function:: void gpu::buildWarpAffineMaps(const Mat& M, bool inverse, Size dsize, GpuMat& xmap, GpuMat& ymap, Stream& stream = Stream::Null()) + + :param M: *2x3* transformation matrix. + + :param inverse: Flag specifying that ``M`` is an inverse transformation ( ``dst=>src`` ). + + :param dsize: Size of the destination image. + + :param xmap: X values with ``CV_32FC1`` type. + + :param ymap: Y values with ``CV_32FC1`` type. + + :param stream: Stream for the asynchronous version. + +.. seealso:: :ocv:func:`gpu::warpAffine` , :ocv:func:`gpu::remap` + + + +gpu::warpPerspective +------------------------ +Applies a perspective transformation to an image. + +.. ocv:function:: void gpu::warpPerspective( const GpuMat& src, GpuMat& dst, const Mat& M, Size dsize, int flags=INTER_LINEAR, int borderMode=BORDER_CONSTANT, Scalar borderValue=Scalar(), Stream& stream=Stream::Null() ) + + :param src: Source image. ``CV_8U`` , ``CV_16U`` , ``CV_32S`` , or ``CV_32F`` depth and 1, 3, or 4 channels are supported. + + :param dst: Destination image with the same type as ``src`` . The size is ``dsize`` . + + :param M: *3x3* transformation matrix. + + :param dsize: Size of the destination image. + + :param flags: Combination of interpolation methods (see :ocv:func:`resize` ) and the optional flag ``WARP_INVERSE_MAP`` specifying that ``M`` is the inverse transformation ( ``dst => src`` ). Only ``INTER_NEAREST`` , ``INTER_LINEAR`` , and ``INTER_CUBIC`` interpolation methods are supported. + + :param stream: Stream for the asynchronous version. + +.. seealso:: :ocv:func:`warpPerspective` + + + +gpu::buildWarpPerspectiveMaps +----------------------------- +Builds transformation maps for perspective transformation. + +.. ocv:function:: void gpu::buildWarpAffineMaps(const Mat& M, bool inverse, Size dsize, GpuMat& xmap, GpuMat& ymap, Stream& stream = Stream::Null()) + + :param M: *3x3* transformation matrix. + + :param inverse: Flag specifying that ``M`` is an inverse transformation ( ``dst=>src`` ). + + :param dsize: Size of the destination image. + + :param xmap: X values with ``CV_32FC1`` type. + + :param ymap: Y values with ``CV_32FC1`` type. + + :param stream: Stream for the asynchronous version. + +.. seealso:: :ocv:func:`gpu::warpPerspective` , :ocv:func:`gpu::remap` + + + +gpu::rotate +--------------- +Rotates an image around the origin (0,0) and then shifts it. + +.. ocv:function:: void gpu::rotate(const GpuMat& src, GpuMat& dst, Size dsize, double angle, double xShift = 0, double yShift = 0, int interpolation = INTER_LINEAR, Stream& stream = Stream::Null()) + + :param src: Source image. Supports 1, 3 or 4 channels images with ``CV_8U`` , ``CV_16U`` or ``CV_32F`` depth. + + :param dst: Destination image with the same type as ``src`` . The size is ``dsize`` . + + :param dsize: Size of the destination image. + + :param angle: Angle of rotation in degrees. + + :param xShift: Shift along the horizontal axis. + + :param yShift: Shift along the vertical axis. + + :param interpolation: Interpolation method. Only ``INTER_NEAREST`` , ``INTER_LINEAR`` , and ``INTER_CUBIC`` are supported. + + :param stream: Stream for the asynchronous version. + +.. seealso:: :ocv:func:`gpu::warpAffine` + + + +gpu::copyMakeBorder +----------------------- +Forms a border around an image. + +.. ocv:function:: void gpu::copyMakeBorder(const GpuMat& src, GpuMat& dst, int top, int bottom, int left, int right, int borderType, const Scalar& value = Scalar(), Stream& stream = Stream::Null()) + + :param src: Source image. ``CV_8UC1`` , ``CV_8UC4`` , ``CV_32SC1`` , and ``CV_32FC1`` types are supported. + + :param dst: Destination image with the same type as ``src``. The size is ``Size(src.cols+left+right, src.rows+top+bottom)`` . + + :param top: + + :param bottom: + + :param left: + + :param right: Number of pixels in each direction from the source image rectangle to extrapolate. For example: ``top=1, bottom=1, left=1, right=1`` mean that 1 pixel-wide border needs to be built. + + :param borderType: Border type. See :ocv:func:`borderInterpolate` for details. ``BORDER_REFLECT101`` , ``BORDER_REPLICATE`` , ``BORDER_CONSTANT`` , ``BORDER_REFLECT`` and ``BORDER_WRAP`` are supported for now. + + :param value: Border value. + + :param stream: Stream for the asynchronous version. + +.. seealso:: :ocv:func:`copyMakeBorder` + + + +gpu::rectStdDev +------------------- +Computes a standard deviation of integral images. + +.. ocv:function:: void gpu::rectStdDev(const GpuMat& src, const GpuMat& sqr, GpuMat& dst, const Rect& rect, Stream& stream = Stream::Null()) + + :param src: Source image. Only the ``CV_32SC1`` type is supported. + + :param sqr: Squared source image. Only the ``CV_32FC1`` type is supported. + + :param dst: Destination image with the same type and size as ``src`` . + + :param rect: Rectangular window. + + :param stream: Stream for the asynchronous version. + + + +gpu::evenLevels +------------------- +Computes levels with even distribution. + +.. ocv:function:: void gpu::evenLevels(GpuMat& levels, int nLevels, int lowerLevel, int upperLevel) + + :param levels: Destination array. ``levels`` has 1 row, ``nLevels`` columns, and the ``CV_32SC1`` type. + + :param nLevels: Number of computed levels. ``nLevels`` must be at least 2. + + :param lowerLevel: Lower boundary value of the lowest level. + + :param upperLevel: Upper boundary value of the greatest level. + + + +gpu::histEven +----------------- +Calculates a histogram with evenly distributed bins. + +.. ocv:function:: void gpu::histEven(const GpuMat& src, GpuMat& hist, int histSize, int lowerLevel, int upperLevel, Stream& stream = Stream::Null()) + +.. ocv:function:: void gpu::histEven(const GpuMat& src, GpuMat& hist, GpuMat& buf, int histSize, int lowerLevel, int upperLevel, Stream& stream = Stream::Null()) + +.. ocv:function:: void gpu::histEven( const GpuMat& src, GpuMat hist[4], int histSize[4], int lowerLevel[4], int upperLevel[4], Stream& stream=Stream::Null() ) + +.. ocv:function:: void gpu::histEven( const GpuMat& src, GpuMat hist[4], GpuMat& buf, int histSize[4], int lowerLevel[4], int upperLevel[4], Stream& stream=Stream::Null() ) + + :param src: Source image. ``CV_8U``, ``CV_16U``, or ``CV_16S`` depth and 1 or 4 channels are supported. For a four-channel image, all channels are processed separately. + + :param hist: Destination histogram with one row, ``histSize`` columns, and the ``CV_32S`` type. + + :param histSize: Size of the histogram. + + :param lowerLevel: Lower boundary of lowest-level bin. + + :param upperLevel: Upper boundary of highest-level bin. + + :param buf: Optional buffer to avoid extra memory allocations (for many calls with the same sizes). + + :param stream: Stream for the asynchronous version. + + + +gpu::histRange +------------------ +Calculates a histogram with bins determined by the ``levels`` array. + +.. ocv:function:: void gpu::histRange(const GpuMat& src, GpuMat& hist, const GpuMat& levels, Stream& stream = Stream::Null()) + +.. ocv:function:: void gpu::histRange(const GpuMat& src, GpuMat& hist, const GpuMat& levels, GpuMat& buf, Stream& stream = Stream::Null()) + + :param src: Source image. ``CV_8U`` , ``CV_16U`` , or ``CV_16S`` depth and 1 or 4 channels are supported. For a four-channel image, all channels are processed separately. + + :param hist: Destination histogram with one row, ``(levels.cols-1)`` columns, and the ``CV_32SC1`` type. + + :param levels: Number of levels in the histogram. + + :param buf: Optional buffer to avoid extra memory allocations (for many calls with the same sizes). + + :param stream: Stream for the asynchronous version. + + + +gpu::calcHist +------------------ +Calculates histogram for one channel 8-bit image. + +.. ocv:function:: void gpu::calcHist(const GpuMat& src, GpuMat& hist, Stream& stream = Stream::Null()) + + :param src: Source image. + + :param hist: Destination histogram with one row, 256 columns, and the ``CV_32SC1`` type. + + :param stream: Stream for the asynchronous version. + + + +gpu::equalizeHist +------------------ +Equalizes the histogram of a grayscale image. + +.. ocv:function:: void gpu::equalizeHist(const GpuMat& src, GpuMat& dst, Stream& stream = Stream::Null()) + +.. ocv:function:: void gpu::equalizeHist(const GpuMat& src, GpuMat& dst, GpuMat& hist, GpuMat& buf, Stream& stream = Stream::Null()) + + :param src: Source image. + + :param dst: Destination image. + + :param hist: Destination histogram with one row, 256 columns, and the ``CV_32SC1`` type. + + :param buf: Optional buffer to avoid extra memory allocations (for many calls with the same sizes). + + :param stream: Stream for the asynchronous version. + +.. seealso:: :ocv:func:`equalizeHist` + + + +gpu::buildWarpPlaneMaps +----------------------- +Builds plane warping maps. + +.. ocv:function:: void gpu::buildWarpPlaneMaps( Size src_size, Rect dst_roi, const Mat & K, const Mat& R, const Mat & T, float scale, GpuMat& map_x, GpuMat& map_y, Stream& stream=Stream::Null() ) + + :param stream: Stream for the asynchronous version. + + + +gpu::buildWarpCylindricalMaps +----------------------------- +Builds cylindrical warping maps. + +.. ocv:function:: void gpu::buildWarpCylindricalMaps( Size src_size, Rect dst_roi, const Mat & K, const Mat& R, float scale, GpuMat& map_x, GpuMat& map_y, Stream& stream=Stream::Null() ) + + :param stream: Stream for the asynchronous version. + + + +gpu::buildWarpSphericalMaps +--------------------------- +Builds spherical warping maps. + +.. ocv:function:: void gpu::buildWarpSphericalMaps( Size src_size, Rect dst_roi, const Mat & K, const Mat& R, float scale, GpuMat& map_x, GpuMat& map_y, Stream& stream=Stream::Null() ) + + :param stream: Stream for the asynchronous version. + + + +gpu::pyrDown +------------------- +Smoothes an image and downsamples it. + +.. ocv:function:: void gpu::pyrDown(const GpuMat& src, GpuMat& dst, Stream& stream = Stream::Null()) + + :param src: Source image. + + :param dst: Destination image. Will have ``Size((src.cols+1)/2, (src.rows+1)/2)`` size and the same type as ``src`` . + + :param stream: Stream for the asynchronous version. + +.. seealso:: :ocv:func:`pyrDown` + + + +gpu::pyrUp +------------------- +Upsamples an image and then smoothes it. + +.. ocv:function:: void gpu::pyrUp(const GpuMat& src, GpuMat& dst, Stream& stream = Stream::Null()) + + :param src: Source image. + + :param dst: Destination image. Will have ``Size(src.cols*2, src.rows*2)`` size and the same type as ``src`` . + + :param stream: Stream for the asynchronous version. + +.. seealso:: :ocv:func:`pyrUp` + + + +gpu::blendLinear +------------------- +Performs linear blending of two images. + +.. ocv:function:: void gpu::blendLinear(const GpuMat& img1, const GpuMat& img2, const GpuMat& weights1, const GpuMat& weights2, GpuMat& result, Stream& stream = Stream::Null()) + + :param img1: First image. Supports only ``CV_8U`` and ``CV_32F`` depth. + + :param img2: Second image. Must have the same size and the same type as ``img1`` . + + :param weights1: Weights for first image. Must have tha same size as ``img1`` . Supports only ``CV_32F`` type. + + :param weights2: Weights for second image. Must have tha same size as ``img2`` . Supports only ``CV_32F`` type. + + :param result: Destination image. + + :param stream: Stream for the asynchronous version. + + +gpu::bilateralFilter +-------------------- +Performs bilateral filtering of passed image + +.. ocv:function:: void gpu::bilateralFilter( const GpuMat& src, GpuMat& dst, int kernel_size, float sigma_color, float sigma_spatial, int borderMode=BORDER_DEFAULT, Stream& stream=Stream::Null() ) + + :param src: Source image. Supports only (channles != 2 && depth() != CV_8S && depth() != CV_32S && depth() != CV_64F). + + :param dst: Destination imagwe. + + :param kernel_size: Kernel window size. + + :param sigma_color: Filter sigma in the color space. + + :param sigma_spatial: Filter sigma in the coordinate space. + + :param borderMode: Border type. See :ocv:func:`borderInterpolate` for details. ``BORDER_REFLECT101`` , ``BORDER_REPLICATE`` , ``BORDER_CONSTANT`` , ``BORDER_REFLECT`` and ``BORDER_WRAP`` are supported for now. + + :param stream: Stream for the asynchronous version. + +.. seealso:: + + :ocv:func:`bilateralFilter`, + + +gpu::nonLocalMeans +------------------- +Performs pure non local means denoising without any simplification, and thus it is not fast. + +.. ocv:function:: void gpu::nonLocalMeans(const GpuMat& src, GpuMat& dst, float h, int search_window = 21, int block_size = 7, int borderMode = BORDER_DEFAULT, Stream& s = Stream::Null()) + + :param src: Source image. Supports only CV_8UC1, CV_8UC2 and CV_8UC3. + + :param dst: Destination image. + + :param h: Filter sigma regulating filter strength for color. + + :param search_window: Size of search window. + + :param block_size: Size of block used for computing weights. + + :param borderMode: Border type. See :ocv:func:`borderInterpolate` for details. ``BORDER_REFLECT101`` , ``BORDER_REPLICATE`` , ``BORDER_CONSTANT`` , ``BORDER_REFLECT`` and ``BORDER_WRAP`` are supported for now. + + :param stream: Stream for the asynchronous version. + +.. seealso:: + + :ocv:func:`fastNlMeansDenoising` + +gpu::FastNonLocalMeansDenoising +------------------------------- +.. ocv:class:: gpu::FastNonLocalMeansDenoising + + :: + + class FastNonLocalMeansDenoising + { + public: + //! Simple method, recommended for grayscale images (though it supports multichannel images) + void simpleMethod(const GpuMat& src, GpuMat& dst, float h, int search_window = 21, int block_size = 7, Stream& s = Stream::Null()) + //! Processes luminance and color components separatelly + void labMethod(const GpuMat& src, GpuMat& dst, float h_luminance, float h_color, int search_window = 21, int block_size = 7, Stream& s = Stream::Null()) + }; + +The class implements fast approximate Non Local Means Denoising algorithm. + +gpu::FastNonLocalMeansDenoising::simpleMethod() +----------------------------------------------- +Perform image denoising using Non-local Means Denoising algorithm http://www.ipol.im/pub/algo/bcm_non_local_means_denoising with several computational optimizations. Noise expected to be a gaussian white noise + +.. ocv:function:: void gpu::FastNonLocalMeansDenoising::simpleMethod(const GpuMat& src, GpuMat& dst, float h, int search_window = 21, int block_size = 7, Stream& s = Stream::Null()) + + :param src: Input 8-bit 1-channel, 2-channel or 3-channel image. + + :param dst: Output image with the same size and type as ``src`` . + + :param h: Parameter regulating filter strength. Big h value perfectly removes noise but also removes image details, smaller h value preserves details but also preserves some noise + + :param search_window: Size in pixels of the window that is used to compute weighted average for given pixel. Should be odd. Affect performance linearly: greater search_window - greater denoising time. Recommended value 21 pixels + + :param block_size: Size in pixels of the template patch that is used to compute weights. Should be odd. Recommended value 7 pixels + + :param stream: Stream for the asynchronous invocations. + +This function expected to be applied to grayscale images. For colored images look at ``FastNonLocalMeansDenoising::labMethod``. + +.. seealso:: + + :ocv:func:`fastNlMeansDenoising` + +gpu::FastNonLocalMeansDenoising::labMethod() +-------------------------------------------- +Modification of ``FastNonLocalMeansDenoising::simpleMethod`` for color images + +.. ocv:function:: void gpu::FastNonLocalMeansDenoising::labMethod(const GpuMat& src, GpuMat& dst, float h_luminance, float h_color, int search_window = 21, int block_size = 7, Stream& s = Stream::Null()) + + :param src: Input 8-bit 3-channel image. + + :param dst: Output image with the same size and type as ``src`` . + + :param h_luminance: Parameter regulating filter strength. Big h value perfectly removes noise but also removes image details, smaller h value preserves details but also preserves some noise + + :param float: The same as h but for color components. For most images value equals 10 will be enought to remove colored noise and do not distort colors + + :param search_window: Size in pixels of the window that is used to compute weighted average for given pixel. Should be odd. Affect performance linearly: greater search_window - greater denoising time. Recommended value 21 pixels + + :param block_size: Size in pixels of the template patch that is used to compute weights. Should be odd. Recommended value 7 pixels + + :param stream: Stream for the asynchronous invocations. + +The function converts image to CIELAB colorspace and then separately denoise L and AB components with given h parameters using ``FastNonLocalMeansDenoising::simpleMethod`` function. + +.. seealso:: + + :ocv:func:`fastNlMeansDenoisingColored` + +gpu::alphaComp +------------------- +Composites two images using alpha opacity values contained in each image. + +.. ocv:function:: void gpu::alphaComp(const GpuMat& img1, const GpuMat& img2, GpuMat& dst, int alpha_op, Stream& stream = Stream::Null()) + + :param img1: First image. Supports ``CV_8UC4`` , ``CV_16UC4`` , ``CV_32SC4`` and ``CV_32FC4`` types. + + :param img2: Second image. Must have the same size and the same type as ``img1`` . + + :param dst: Destination image. + + :param alpha_op: Flag specifying the alpha-blending operation: + + * **ALPHA_OVER** + * **ALPHA_IN** + * **ALPHA_OUT** + * **ALPHA_ATOP** + * **ALPHA_XOR** + * **ALPHA_PLUS** + * **ALPHA_OVER_PREMUL** + * **ALPHA_IN_PREMUL** + * **ALPHA_OUT_PREMUL** + * **ALPHA_ATOP_PREMUL** + * **ALPHA_XOR_PREMUL** + * **ALPHA_PLUS_PREMUL** + * **ALPHA_PREMUL** + + :param stream: Stream for the asynchronous version. + + + +gpu::Canny +------------------- +Finds edges in an image using the [Canny86]_ algorithm. + +.. ocv:function:: void gpu::Canny(const GpuMat& image, GpuMat& edges, double low_thresh, double high_thresh, int apperture_size = 3, bool L2gradient = false) + +.. ocv:function:: void gpu::Canny(const GpuMat& image, CannyBuf& buf, GpuMat& edges, double low_thresh, double high_thresh, int apperture_size = 3, bool L2gradient = false) + +.. ocv:function:: void gpu::Canny(const GpuMat& dx, const GpuMat& dy, GpuMat& edges, double low_thresh, double high_thresh, bool L2gradient = false) + +.. ocv:function:: void gpu::Canny(const GpuMat& dx, const GpuMat& dy, CannyBuf& buf, GpuMat& edges, double low_thresh, double high_thresh, bool L2gradient = false) + + :param image: Single-channel 8-bit input image. + + :param dx: First derivative of image in the vertical direction. Support only ``CV_32S`` type. + + :param dy: First derivative of image in the horizontal direction. Support only ``CV_32S`` type. + + :param edges: Output edge map. It has the same size and type as ``image`` . + + :param low_thresh: First threshold for the hysteresis procedure. + + :param high_thresh: Second threshold for the hysteresis procedure. + + :param apperture_size: Aperture size for the :ocv:func:`Sobel` operator. + + :param L2gradient: Flag indicating whether a more accurate :math:`L_2` norm :math:`=\sqrt{(dI/dx)^2 + (dI/dy)^2}` should be used to compute the image gradient magnitude ( ``L2gradient=true`` ), or a faster default :math:`L_1` norm :math:`=|dI/dx|+|dI/dy|` is enough ( ``L2gradient=false`` ). + + :param buf: Optional buffer to avoid extra memory allocations (for many calls with the same sizes). + +.. seealso:: :ocv:func:`Canny` + + + +gpu::HoughLines +--------------- +Finds lines in a binary image using the classical Hough transform. + +.. ocv:function:: void gpu::HoughLines(const GpuMat& src, GpuMat& lines, float rho, float theta, int threshold, bool doSort = false, int maxLines = 4096) + +.. ocv:function:: void gpu::HoughLines(const GpuMat& src, GpuMat& lines, HoughLinesBuf& buf, float rho, float theta, int threshold, bool doSort = false, int maxLines = 4096) + + :param src: 8-bit, single-channel binary source image. + + :param lines: Output vector of lines. Each line is represented by a two-element vector :math:`(\rho, \theta)` . :math:`\rho` is the distance from the coordinate origin :math:`(0,0)` (top-left corner of the image). :math:`\theta` is the line rotation angle in radians ( :math:`0 \sim \textrm{vertical line}, \pi/2 \sim \textrm{horizontal line}` ). + + :param rho: Distance resolution of the accumulator in pixels. + + :param theta: Angle resolution of the accumulator in radians. + + :param threshold: Accumulator threshold parameter. Only those lines are returned that get enough votes ( :math:`>\texttt{threshold}` ). + + :param doSort: Performs lines sort by votes. + + :param maxLines: Maximum number of output lines. + + :param buf: Optional buffer to avoid extra memory allocations (for many calls with the same sizes). + +.. seealso:: :ocv:func:`HoughLines` + + + +gpu::HoughLinesDownload +----------------------- +Downloads results from :ocv:func:`gpu::HoughLines` to host memory. + +.. ocv:function:: void gpu::HoughLinesDownload(const GpuMat& d_lines, OutputArray h_lines, OutputArray h_votes = noArray()) + + :param d_lines: Result of :ocv:func:`gpu::HoughLines` . + + :param h_lines: Output host array. + + :param h_votes: Optional output array for line's votes. + +.. seealso:: :ocv:func:`gpu::HoughLines` + + + +gpu::HoughCircles +----------------- +Finds circles in a grayscale image using the Hough transform. + +.. ocv:function:: void gpu::HoughCircles(const GpuMat& src, GpuMat& circles, int method, float dp, float minDist, int cannyThreshold, int votesThreshold, int minRadius, int maxRadius, int maxCircles = 4096) + +.. ocv:function:: void gpu::HoughCircles(const GpuMat& src, GpuMat& circles, HoughCirclesBuf& buf, int method, float dp, float minDist, int cannyThreshold, int votesThreshold, int minRadius, int maxRadius, int maxCircles = 4096) + + :param src: 8-bit, single-channel grayscale input image. + + :param circles: Output vector of found circles. Each vector is encoded as a 3-element floating-point vector :math:`(x, y, radius)` . + + :param method: Detection method to use. Currently, the only implemented method is ``CV_HOUGH_GRADIENT`` , which is basically *21HT* , described in [Yuen90]_. + + :param dp: Inverse ratio of the accumulator resolution to the image resolution. For example, if ``dp=1`` , the accumulator has the same resolution as the input image. If ``dp=2`` , the accumulator has half as big width and height. + + :param minDist: Minimum distance between the centers of the detected circles. If the parameter is too small, multiple neighbor circles may be falsely detected in addition to a true one. If it is too large, some circles may be missed. + + :param cannyThreshold: The higher threshold of the two passed to the :ocv:func:`gpu::Canny` edge detector (the lower one is twice smaller). + + :param votesThreshold: The accumulator threshold for the circle centers at the detection stage. The smaller it is, the more false circles may be detected. + + :param minRadius: Minimum circle radius. + + :param maxRadius: Maximum circle radius. + + :param maxCircles: Maximum number of output circles. + + :param buf: Optional buffer to avoid extra memory allocations (for many calls with the same sizes). + +.. seealso:: :ocv:func:`HoughCircles` + + + +gpu::HoughCirclesDownload +------------------------- +Downloads results from :ocv:func:`gpu::HoughCircles` to host memory. + +.. ocv:function:: void gpu::HoughCirclesDownload(const GpuMat& d_circles, OutputArray h_circles) + + :param d_circles: Result of :ocv:func:`gpu::HoughCircles` . + + :param h_circles: Output host array. + +.. seealso:: :ocv:func:`gpu::HoughCircles` diff --git a/modules/gpuarithm/doc/reductions.rst b/modules/gpu/doc/matrix_reductions.rst similarity index 80% rename from modules/gpuarithm/doc/reductions.rst rename to modules/gpu/doc/matrix_reductions.rst index 938efc35bb..e9229f8a81 100644 --- a/modules/gpuarithm/doc/reductions.rst +++ b/modules/gpu/doc/matrix_reductions.rst @@ -5,6 +5,25 @@ Matrix Reductions +gpu::meanStdDev +------------------- +Computes a mean value and a standard deviation of matrix elements. + +.. ocv:function:: void gpu::meanStdDev(const GpuMat& mtx, Scalar& mean, Scalar& stddev) +.. ocv:function:: void gpu::meanStdDev(const GpuMat& mtx, Scalar& mean, Scalar& stddev, GpuMat& buf) + + :param mtx: Source matrix. ``CV_8UC1`` matrices are supported for now. + + :param mean: Mean value. + + :param stddev: Standard deviation value. + + :param buf: Optional buffer to avoid extra memory allocations. It is resized automatically. + +.. seealso:: :ocv:func:`meanStdDev` + + + gpu::norm ------------- Returns the norm of a matrix (or difference of two matrices). @@ -186,70 +205,3 @@ Reduces a matrix to a vector. The function ``reduce`` reduces the matrix to a vector by treating the matrix rows/columns as a set of 1D vectors and performing the specified operation on the vectors until a single row/column is obtained. For example, the function can be used to compute horizontal and vertical projections of a raster image. In case of ``CV_REDUCE_SUM`` and ``CV_REDUCE_AVG`` , the output may have a larger element bit-depth to preserve accuracy. And multi-channel arrays are also supported in these two reduction modes. .. seealso:: :ocv:func:`reduce` - - - -gpu::normalize --------------- -Normalizes the norm or value range of an array. - -.. ocv:function:: void gpu::normalize(const GpuMat& src, GpuMat& dst, double alpha = 1, double beta = 0, int norm_type = NORM_L2, int dtype = -1, const GpuMat& mask = GpuMat()) - -.. ocv:function:: void gpu::normalize(const GpuMat& src, GpuMat& dst, double a, double b, int norm_type, int dtype, const GpuMat& mask, GpuMat& norm_buf, GpuMat& cvt_buf) - - :param src: input array. - - :param dst: output array of the same size as ``src`` . - - :param alpha: norm value to normalize to or the lower range boundary in case of the range normalization. - - :param beta: upper range boundary in case of the range normalization; it is not used for the norm normalization. - - :param normType: normalization type (see the details below). - - :param dtype: when negative, the output array has the same type as ``src``; otherwise, it has the same number of channels as ``src`` and the depth ``=CV_MAT_DEPTH(dtype)``. - - :param mask: optional operation mask. - - :param norm_buf: Optional buffer to avoid extra memory allocations. It is resized automatically. - - :param cvt_buf: Optional buffer to avoid extra memory allocations. It is resized automatically. - -.. seealso:: :ocv:func:`normalize` - - - -gpu::meanStdDev -------------------- -Computes a mean value and a standard deviation of matrix elements. - -.. ocv:function:: void gpu::meanStdDev(const GpuMat& mtx, Scalar& mean, Scalar& stddev) -.. ocv:function:: void gpu::meanStdDev(const GpuMat& mtx, Scalar& mean, Scalar& stddev, GpuMat& buf) - - :param mtx: Source matrix. ``CV_8UC1`` matrices are supported for now. - - :param mean: Mean value. - - :param stddev: Standard deviation value. - - :param buf: Optional buffer to avoid extra memory allocations. It is resized automatically. - -.. seealso:: :ocv:func:`meanStdDev` - - - -gpu::rectStdDev -------------------- -Computes a standard deviation of integral images. - -.. ocv:function:: void gpu::rectStdDev(const GpuMat& src, const GpuMat& sqr, GpuMat& dst, const Rect& rect, Stream& stream = Stream::Null()) - - :param src: Source image. Only the ``CV_32SC1`` type is supported. - - :param sqr: Squared source image. Only the ``CV_32FC1`` type is supported. - - :param dst: Destination image with the same type and size as ``src`` . - - :param rect: Rectangular window. - - :param stream: Stream for the asynchronous version. diff --git a/modules/gpu/doc/operations_on_matrices.rst b/modules/gpu/doc/operations_on_matrices.rst new file mode 100644 index 0000000000..d1762f442a --- /dev/null +++ b/modules/gpu/doc/operations_on_matrices.rst @@ -0,0 +1,274 @@ +Operations on Matrices +====================== + +.. highlight:: cpp + + + +gpu::gemm +------------------ +Performs generalized matrix multiplication. + +.. ocv:function:: void gpu::gemm(const GpuMat& src1, const GpuMat& src2, double alpha, const GpuMat& src3, double beta, GpuMat& dst, int flags = 0, Stream& stream = Stream::Null()) + + :param src1: First multiplied input matrix that should have ``CV_32FC1`` , ``CV_64FC1`` , ``CV_32FC2`` , or ``CV_64FC2`` type. + + :param src2: Second multiplied input matrix of the same type as ``src1`` . + + :param alpha: Weight of the matrix product. + + :param src3: Third optional delta matrix added to the matrix product. It should have the same type as ``src1`` and ``src2`` . + + :param beta: Weight of ``src3`` . + + :param dst: Destination matrix. It has the proper size and the same type as input matrices. + + :param flags: Operation flags: + + * **GEMM_1_T** transpose ``src1`` + * **GEMM_2_T** transpose ``src2`` + * **GEMM_3_T** transpose ``src3`` + + :param stream: Stream for the asynchronous version. + +The function performs generalized matrix multiplication similar to the ``gemm`` functions in BLAS level 3. For example, ``gemm(src1, src2, alpha, src3, beta, dst, GEMM_1_T + GEMM_3_T)`` corresponds to + +.. math:: + + \texttt{dst} = \texttt{alpha} \cdot \texttt{src1} ^T \cdot \texttt{src2} + \texttt{beta} \cdot \texttt{src3} ^T + +.. note:: Transposition operation doesn't support ``CV_64FC2`` input type. + +.. seealso:: :ocv:func:`gemm` + + + +gpu::transpose +------------------ +Transposes a matrix. + +.. ocv:function:: void gpu::transpose( const GpuMat& src1, GpuMat& dst, Stream& stream=Stream::Null() ) + + :param src1: Source matrix. 1-, 4-, 8-byte element sizes are supported for now (CV_8UC1, CV_8UC4, CV_16UC2, CV_32FC1, etc). + + :param dst: Destination matrix. + + :param stream: Stream for the asynchronous version. + +.. seealso:: :ocv:func:`transpose` + + + +gpu::flip +------------- +Flips a 2D matrix around vertical, horizontal, or both axes. + +.. ocv:function:: void gpu::flip( const GpuMat& a, GpuMat& b, int flipCode, Stream& stream=Stream::Null() ) + + :param a: Source matrix. Supports 1, 3 and 4 channels images with ``CV_8U``, ``CV_16U``, ``CV_32S`` or ``CV_32F`` depth. + + :param b: Destination matrix. + + :param flipCode: Flip mode for the source: + + * ``0`` Flips around x-axis. + + * ``>0`` Flips around y-axis. + + * ``<0`` Flips around both axes. + + :param stream: Stream for the asynchronous version. + +.. seealso:: :ocv:func:`flip` + + + +gpu::LUT +------------ +Transforms the source matrix into the destination matrix using the given look-up table: ``dst(I) = lut(src(I))`` + +.. ocv:function:: void gpu::LUT(const GpuMat& src, const Mat& lut, GpuMat& dst, Stream& stream = Stream::Null()) + + :param src: Source matrix. ``CV_8UC1`` and ``CV_8UC3`` matrices are supported for now. + + :param lut: Look-up table of 256 elements. It is a continuous ``CV_8U`` matrix. + + :param dst: Destination matrix with the same depth as ``lut`` and the same number of channels as ``src`` . + + :param stream: Stream for the asynchronous version. + +.. seealso:: :ocv:func:`LUT` + + + +gpu::merge +-------------- +Makes a multi-channel matrix out of several single-channel matrices. + +.. ocv:function:: void gpu::merge(const GpuMat* src, size_t n, GpuMat& dst, Stream& stream = Stream::Null()) + +.. ocv:function:: void gpu::merge(const vector& src, GpuMat& dst, Stream& stream = Stream::Null()) + + :param src: Array/vector of source matrices. + + :param n: Number of source matrices. + + :param dst: Destination matrix. + + :param stream: Stream for the asynchronous version. + +.. seealso:: :ocv:func:`merge` + + + +gpu::split +-------------- +Copies each plane of a multi-channel matrix into an array. + +.. ocv:function:: void gpu::split(const GpuMat& src, GpuMat* dst, Stream& stream = Stream::Null()) + +.. ocv:function:: void gpu::split(const GpuMat& src, vector& dst, Stream& stream = Stream::Null()) + + :param src: Source matrix. + + :param dst: Destination array/vector of single-channel matrices. + + :param stream: Stream for the asynchronous version. + +.. seealso:: :ocv:func:`split` + + + +gpu::magnitude +------------------ +Computes magnitudes of complex matrix elements. + +.. ocv:function:: void gpu::magnitude( const GpuMat& xy, GpuMat& magnitude, Stream& stream=Stream::Null() ) + +.. ocv:function:: void gpu::magnitude(const GpuMat& x, const GpuMat& y, GpuMat& magnitude, Stream& stream = Stream::Null()) + + :param xy: Source complex matrix in the interleaved format ( ``CV_32FC2`` ). + + :param x: Source matrix containing real components ( ``CV_32FC1`` ). + + :param y: Source matrix containing imaginary components ( ``CV_32FC1`` ). + + :param magnitude: Destination matrix of float magnitudes ( ``CV_32FC1`` ). + + :param stream: Stream for the asynchronous version. + +.. seealso:: :ocv:func:`magnitude` + + + +gpu::magnitudeSqr +--------------------- +Computes squared magnitudes of complex matrix elements. + +.. ocv:function:: void gpu::magnitudeSqr( const GpuMat& xy, GpuMat& magnitude, Stream& stream=Stream::Null() ) + +.. ocv:function:: void gpu::magnitudeSqr(const GpuMat& x, const GpuMat& y, GpuMat& magnitude, Stream& stream = Stream::Null()) + + :param xy: Source complex matrix in the interleaved format ( ``CV_32FC2`` ). + + :param x: Source matrix containing real components ( ``CV_32FC1`` ). + + :param y: Source matrix containing imaginary components ( ``CV_32FC1`` ). + + :param magnitude: Destination matrix of float magnitude squares ( ``CV_32FC1`` ). + + :param stream: Stream for the asynchronous version. + + + +gpu::phase +-------------- +Computes polar angles of complex matrix elements. + +.. ocv:function:: void gpu::phase(const GpuMat& x, const GpuMat& y, GpuMat& angle, bool angleInDegrees=false, Stream& stream = Stream::Null()) + + :param x: Source matrix containing real components ( ``CV_32FC1`` ). + + :param y: Source matrix containing imaginary components ( ``CV_32FC1`` ). + + :param angle: Destination matrix of angles ( ``CV_32FC1`` ). + + :param angleInDegrees: Flag for angles that must be evaluated in degrees. + + :param stream: Stream for the asynchronous version. + +.. seealso:: :ocv:func:`phase` + + + +gpu::cartToPolar +-------------------- +Converts Cartesian coordinates into polar. + +.. ocv:function:: void gpu::cartToPolar(const GpuMat& x, const GpuMat& y, GpuMat& magnitude, GpuMat& angle, bool angleInDegrees=false, Stream& stream = Stream::Null()) + + :param x: Source matrix containing real components ( ``CV_32FC1`` ). + + :param y: Source matrix containing imaginary components ( ``CV_32FC1`` ). + + :param magnitude: Destination matrix of float magnitudes ( ``CV_32FC1`` ). + + :param angle: Destination matrix of angles ( ``CV_32FC1`` ). + + :param angleInDegrees: Flag for angles that must be evaluated in degrees. + + :param stream: Stream for the asynchronous version. + +.. seealso:: :ocv:func:`cartToPolar` + + + +gpu::polarToCart +-------------------- +Converts polar coordinates into Cartesian. + +.. ocv:function:: void gpu::polarToCart(const GpuMat& magnitude, const GpuMat& angle, GpuMat& x, GpuMat& y, bool angleInDegrees=false, Stream& stream = Stream::Null()) + + :param magnitude: Source matrix containing magnitudes ( ``CV_32FC1`` ). + + :param angle: Source matrix containing angles ( ``CV_32FC1`` ). + + :param x: Destination matrix of real components ( ``CV_32FC1`` ). + + :param y: Destination matrix of imaginary components ( ``CV_32FC1`` ). + + :param angleInDegrees: Flag that indicates angles in degrees. + + :param stream: Stream for the asynchronous version. + +.. seealso:: :ocv:func:`polarToCart` + + + +gpu::normalize +-------------- +Normalizes the norm or value range of an array. + +.. ocv:function:: void gpu::normalize(const GpuMat& src, GpuMat& dst, double alpha = 1, double beta = 0, int norm_type = NORM_L2, int dtype = -1, const GpuMat& mask = GpuMat()) + +.. ocv:function:: void gpu::normalize(const GpuMat& src, GpuMat& dst, double a, double b, int norm_type, int dtype, const GpuMat& mask, GpuMat& norm_buf, GpuMat& cvt_buf) + + :param src: input array. + + :param dst: output array of the same size as ``src`` . + + :param alpha: norm value to normalize to or the lower range boundary in case of the range normalization. + + :param beta: upper range boundary in case of the range normalization; it is not used for the norm normalization. + + :param normType: normalization type (see the details below). + + :param dtype: when negative, the output array has the same type as ``src``; otherwise, it has the same number of channels as ``src`` and the depth ``=CV_MAT_DEPTH(dtype)``. + + :param mask: optional operation mask. + + :param norm_buf: Optional buffer to avoid extra memory allocations. It is resized automatically. + + :param cvt_buf: Optional buffer to avoid extra memory allocations. It is resized automatically. + +.. seealso:: :ocv:func:`normalize` diff --git a/modules/gpuarithm/doc/element_operations.rst b/modules/gpu/doc/per_element_operations.rst similarity index 77% rename from modules/gpuarithm/doc/element_operations.rst rename to modules/gpu/doc/per_element_operations.rst index eae2ad7a2a..2670ba3233 100644 --- a/modules/gpuarithm/doc/element_operations.rst +++ b/modules/gpu/doc/per_element_operations.rst @@ -1,5 +1,5 @@ Per-element Operations -====================== +======================= .. highlight:: cpp @@ -112,7 +112,6 @@ This function, in contrast to :ocv:func:`divide`, uses a round-down rounding mod .. seealso:: :ocv:func:`divide` - gpu::addWeighted ---------------- Computes the weighted sum of two arrays. @@ -444,131 +443,3 @@ Computes the per-element maximum of two matrices (or a matrix and a scalar). :param stream: Stream for the asynchronous version. .. seealso:: :ocv:func:`max` - - - -gpu::threshold ------------------- -Applies a fixed-level threshold to each array element. - -.. ocv:function:: double gpu::threshold(const GpuMat& src, GpuMat& dst, double thresh, double maxval, int type, Stream& stream = Stream::Null()) - - :param src: Source array (single-channel). - - :param dst: Destination array with the same size and type as ``src`` . - - :param thresh: Threshold value. - - :param maxval: Maximum value to use with ``THRESH_BINARY`` and ``THRESH_BINARY_INV`` threshold types. - - :param type: Threshold type. For details, see :ocv:func:`threshold` . The ``THRESH_OTSU`` threshold type is not supported. - - :param stream: Stream for the asynchronous version. - -.. seealso:: :ocv:func:`threshold` - - - -gpu::magnitude ------------------- -Computes magnitudes of complex matrix elements. - -.. ocv:function:: void gpu::magnitude( const GpuMat& xy, GpuMat& magnitude, Stream& stream=Stream::Null() ) - -.. ocv:function:: void gpu::magnitude(const GpuMat& x, const GpuMat& y, GpuMat& magnitude, Stream& stream = Stream::Null()) - - :param xy: Source complex matrix in the interleaved format ( ``CV_32FC2`` ). - - :param x: Source matrix containing real components ( ``CV_32FC1`` ). - - :param y: Source matrix containing imaginary components ( ``CV_32FC1`` ). - - :param magnitude: Destination matrix of float magnitudes ( ``CV_32FC1`` ). - - :param stream: Stream for the asynchronous version. - -.. seealso:: :ocv:func:`magnitude` - - - -gpu::magnitudeSqr ---------------------- -Computes squared magnitudes of complex matrix elements. - -.. ocv:function:: void gpu::magnitudeSqr( const GpuMat& xy, GpuMat& magnitude, Stream& stream=Stream::Null() ) - -.. ocv:function:: void gpu::magnitudeSqr(const GpuMat& x, const GpuMat& y, GpuMat& magnitude, Stream& stream = Stream::Null()) - - :param xy: Source complex matrix in the interleaved format ( ``CV_32FC2`` ). - - :param x: Source matrix containing real components ( ``CV_32FC1`` ). - - :param y: Source matrix containing imaginary components ( ``CV_32FC1`` ). - - :param magnitude: Destination matrix of float magnitude squares ( ``CV_32FC1`` ). - - :param stream: Stream for the asynchronous version. - - - -gpu::phase --------------- -Computes polar angles of complex matrix elements. - -.. ocv:function:: void gpu::phase(const GpuMat& x, const GpuMat& y, GpuMat& angle, bool angleInDegrees=false, Stream& stream = Stream::Null()) - - :param x: Source matrix containing real components ( ``CV_32FC1`` ). - - :param y: Source matrix containing imaginary components ( ``CV_32FC1`` ). - - :param angle: Destination matrix of angles ( ``CV_32FC1`` ). - - :param angleInDegrees: Flag for angles that must be evaluated in degrees. - - :param stream: Stream for the asynchronous version. - -.. seealso:: :ocv:func:`phase` - - - -gpu::cartToPolar --------------------- -Converts Cartesian coordinates into polar. - -.. ocv:function:: void gpu::cartToPolar(const GpuMat& x, const GpuMat& y, GpuMat& magnitude, GpuMat& angle, bool angleInDegrees=false, Stream& stream = Stream::Null()) - - :param x: Source matrix containing real components ( ``CV_32FC1`` ). - - :param y: Source matrix containing imaginary components ( ``CV_32FC1`` ). - - :param magnitude: Destination matrix of float magnitudes ( ``CV_32FC1`` ). - - :param angle: Destination matrix of angles ( ``CV_32FC1`` ). - - :param angleInDegrees: Flag for angles that must be evaluated in degrees. - - :param stream: Stream for the asynchronous version. - -.. seealso:: :ocv:func:`cartToPolar` - - - -gpu::polarToCart --------------------- -Converts polar coordinates into Cartesian. - -.. ocv:function:: void gpu::polarToCart(const GpuMat& magnitude, const GpuMat& angle, GpuMat& x, GpuMat& y, bool angleInDegrees=false, Stream& stream = Stream::Null()) - - :param magnitude: Source matrix containing magnitudes ( ``CV_32FC1`` ). - - :param angle: Source matrix containing angles ( ``CV_32FC1`` ). - - :param x: Destination matrix of real components ( ``CV_32FC1`` ). - - :param y: Destination matrix of imaginary components ( ``CV_32FC1`` ). - - :param angleInDegrees: Flag that indicates angles in degrees. - - :param stream: Stream for the asynchronous version. - -.. seealso:: :ocv:func:`polarToCart` diff --git a/modules/gpu/doc/video.rst b/modules/gpu/doc/video.rst new file mode 100644 index 0000000000..f964100379 --- /dev/null +++ b/modules/gpu/doc/video.rst @@ -0,0 +1,1142 @@ +Video Analysis +============== + +.. highlight:: cpp + + + +gpu::BroxOpticalFlow +-------------------- +.. ocv:class:: gpu::BroxOpticalFlow + +Class computing the optical flow for two images using Brox et al Optical Flow algorithm ([Brox2004]_). :: + + class BroxOpticalFlow + { + public: + BroxOpticalFlow(float alpha_, float gamma_, float scale_factor_, int inner_iterations_, int outer_iterations_, int solver_iterations_); + + //! Compute optical flow + //! frame0 - source frame (supports only CV_32FC1 type) + //! frame1 - frame to track (with the same size and type as frame0) + //! u - flow horizontal component (along x axis) + //! v - flow vertical component (along y axis) + void operator ()(const GpuMat& frame0, const GpuMat& frame1, GpuMat& u, GpuMat& v, Stream& stream = Stream::Null()); + + //! flow smoothness + float alpha; + + //! gradient constancy importance + float gamma; + + //! pyramid scale factor + float scale_factor; + + //! number of lagged non-linearity iterations (inner loop) + int inner_iterations; + + //! number of warping iterations (number of pyramid levels) + int outer_iterations; + + //! number of linear system solver iterations + int solver_iterations; + + GpuMat buf; + }; + + + +gpu::GoodFeaturesToTrackDetector_GPU +------------------------------------ +.. ocv:class:: gpu::GoodFeaturesToTrackDetector_GPU + +Class used for strong corners detection on an image. :: + + class GoodFeaturesToTrackDetector_GPU + { + public: + explicit GoodFeaturesToTrackDetector_GPU(int maxCorners_ = 1000, double qualityLevel_ = 0.01, double minDistance_ = 0.0, + int blockSize_ = 3, bool useHarrisDetector_ = false, double harrisK_ = 0.04); + + void operator ()(const GpuMat& image, GpuMat& corners, const GpuMat& mask = GpuMat()); + + int maxCorners; + double qualityLevel; + double minDistance; + + int blockSize; + bool useHarrisDetector; + double harrisK; + + void releaseMemory(); + }; + +The class finds the most prominent corners in the image. + +.. seealso:: :ocv:func:`goodFeaturesToTrack` + + + +gpu::GoodFeaturesToTrackDetector_GPU::GoodFeaturesToTrackDetector_GPU +--------------------------------------------------------------------- +Constructor. + +.. ocv:function:: gpu::GoodFeaturesToTrackDetector_GPU::GoodFeaturesToTrackDetector_GPU(int maxCorners = 1000, double qualityLevel = 0.01, double minDistance = 0.0, int blockSize = 3, bool useHarrisDetector = false, double harrisK = 0.04) + + :param maxCorners: Maximum number of corners to return. If there are more corners than are found, the strongest of them is returned. + + :param qualityLevel: Parameter characterizing the minimal accepted quality of image corners. The parameter value is multiplied by the best corner quality measure, which is the minimal eigenvalue (see :ocv:func:`gpu::cornerMinEigenVal` ) or the Harris function response (see :ocv:func:`gpu::cornerHarris` ). The corners with the quality measure less than the product are rejected. For example, if the best corner has the quality measure = 1500, and the ``qualityLevel=0.01`` , then all the corners with the quality measure less than 15 are rejected. + + :param minDistance: Minimum possible Euclidean distance between the returned corners. + + :param blockSize: Size of an average block for computing a derivative covariation matrix over each pixel neighborhood. See :ocv:func:`cornerEigenValsAndVecs` . + + :param useHarrisDetector: Parameter indicating whether to use a Harris detector (see :ocv:func:`gpu::cornerHarris`) or :ocv:func:`gpu::cornerMinEigenVal`. + + :param harrisK: Free parameter of the Harris detector. + + + +gpu::GoodFeaturesToTrackDetector_GPU::operator () +------------------------------------------------- +Finds the most prominent corners in the image. + +.. ocv:function:: void gpu::GoodFeaturesToTrackDetector_GPU::operator ()(const GpuMat& image, GpuMat& corners, const GpuMat& mask = GpuMat()) + + :param image: Input 8-bit, single-channel image. + + :param corners: Output vector of detected corners (it will be one row matrix with CV_32FC2 type). + + :param mask: Optional region of interest. If the image is not empty (it needs to have the type ``CV_8UC1`` and the same size as ``image`` ), it specifies the region in which the corners are detected. + +.. seealso:: :ocv:func:`goodFeaturesToTrack` + + + +gpu::GoodFeaturesToTrackDetector_GPU::releaseMemory +--------------------------------------------------- +Releases inner buffers memory. + +.. ocv:function:: void gpu::GoodFeaturesToTrackDetector_GPU::releaseMemory() + + + +gpu::FarnebackOpticalFlow +------------------------- +.. ocv:class:: gpu::FarnebackOpticalFlow + +Class computing a dense optical flow using the Gunnar Farneback’s algorithm. :: + + class CV_EXPORTS FarnebackOpticalFlow + { + public: + FarnebackOpticalFlow() + { + numLevels = 5; + pyrScale = 0.5; + fastPyramids = false; + winSize = 13; + numIters = 10; + polyN = 5; + polySigma = 1.1; + flags = 0; + } + + int numLevels; + double pyrScale; + bool fastPyramids; + int winSize; + int numIters; + int polyN; + double polySigma; + int flags; + + void operator ()(const GpuMat &frame0, const GpuMat &frame1, GpuMat &flowx, GpuMat &flowy, Stream &s = Stream::Null()); + + void releaseMemory(); + + private: + /* hidden */ + }; + + + +gpu::FarnebackOpticalFlow::operator () +-------------------------------------- +Computes a dense optical flow using the Gunnar Farneback’s algorithm. + +.. ocv:function:: void gpu::FarnebackOpticalFlow::operator ()(const GpuMat &frame0, const GpuMat &frame1, GpuMat &flowx, GpuMat &flowy, Stream &s = Stream::Null()) + + :param frame0: First 8-bit gray-scale input image + :param frame1: Second 8-bit gray-scale input image + :param flowx: Flow horizontal component + :param flowy: Flow vertical component + :param s: Stream + +.. seealso:: :ocv:func:`calcOpticalFlowFarneback` + + + +gpu::FarnebackOpticalFlow::releaseMemory +---------------------------------------- +Releases unused auxiliary memory buffers. + +.. ocv:function:: void gpu::FarnebackOpticalFlow::releaseMemory() + + + +gpu::PyrLKOpticalFlow +--------------------- +.. ocv:class:: gpu::PyrLKOpticalFlow + +Class used for calculating an optical flow. :: + + class PyrLKOpticalFlow + { + public: + PyrLKOpticalFlow(); + + void sparse(const GpuMat& prevImg, const GpuMat& nextImg, const GpuMat& prevPts, GpuMat& nextPts, + GpuMat& status, GpuMat* err = 0); + + void dense(const GpuMat& prevImg, const GpuMat& nextImg, GpuMat& u, GpuMat& v, GpuMat* err = 0); + + Size winSize; + int maxLevel; + int iters; + bool useInitialFlow; + + void releaseMemory(); + }; + +The class can calculate an optical flow for a sparse feature set or dense optical flow using the iterative Lucas-Kanade method with pyramids. + +.. seealso:: :ocv:func:`calcOpticalFlowPyrLK` + + + +gpu::PyrLKOpticalFlow::sparse +----------------------------- +Calculate an optical flow for a sparse feature set. + +.. ocv:function:: void gpu::PyrLKOpticalFlow::sparse(const GpuMat& prevImg, const GpuMat& nextImg, const GpuMat& prevPts, GpuMat& nextPts, GpuMat& status, GpuMat* err = 0) + + :param prevImg: First 8-bit input image (supports both grayscale and color images). + + :param nextImg: Second input image of the same size and the same type as ``prevImg`` . + + :param prevPts: Vector of 2D points for which the flow needs to be found. It must be one row matrix with CV_32FC2 type. + + :param nextPts: Output vector of 2D points (with single-precision floating-point coordinates) containing the calculated new positions of input features in the second image. When ``useInitialFlow`` is true, the vector must have the same size as in the input. + + :param status: Output status vector (CV_8UC1 type). Each element of the vector is set to 1 if the flow for the corresponding features has been found. Otherwise, it is set to 0. + + :param err: Output vector (CV_32FC1 type) that contains the difference between patches around the original and moved points or min eigen value if ``getMinEigenVals`` is checked. It can be NULL, if not needed. + +.. seealso:: :ocv:func:`calcOpticalFlowPyrLK` + + + +gpu::PyrLKOpticalFlow::dense +----------------------------- +Calculate dense optical flow. + +.. ocv:function:: void gpu::PyrLKOpticalFlow::dense(const GpuMat& prevImg, const GpuMat& nextImg, GpuMat& u, GpuMat& v, GpuMat* err = 0) + + :param prevImg: First 8-bit grayscale input image. + + :param nextImg: Second input image of the same size and the same type as ``prevImg`` . + + :param u: Horizontal component of the optical flow of the same size as input images, 32-bit floating-point, single-channel + + :param v: Vertical component of the optical flow of the same size as input images, 32-bit floating-point, single-channel + + :param err: Output vector (CV_32FC1 type) that contains the difference between patches around the original and moved points or min eigen value if ``getMinEigenVals`` is checked. It can be NULL, if not needed. + + + +gpu::PyrLKOpticalFlow::releaseMemory +------------------------------------ +Releases inner buffers memory. + +.. ocv:function:: void gpu::PyrLKOpticalFlow::releaseMemory() + + + +gpu::interpolateFrames +---------------------- +Interpolates frames (images) using provided optical flow (displacement field). + +.. ocv:function:: void gpu::interpolateFrames(const GpuMat& frame0, const GpuMat& frame1, const GpuMat& fu, const GpuMat& fv, const GpuMat& bu, const GpuMat& bv, float pos, GpuMat& newFrame, GpuMat& buf, Stream& stream = Stream::Null()) + + :param frame0: First frame (32-bit floating point images, single channel). + + :param frame1: Second frame. Must have the same type and size as ``frame0`` . + + :param fu: Forward horizontal displacement. + + :param fv: Forward vertical displacement. + + :param bu: Backward horizontal displacement. + + :param bv: Backward vertical displacement. + + :param pos: New frame position. + + :param newFrame: Output image. + + :param buf: Temporary buffer, will have width x 6*height size, CV_32FC1 type and contain 6 GpuMat: occlusion masks for first frame, occlusion masks for second, interpolated forward horizontal flow, interpolated forward vertical flow, interpolated backward horizontal flow, interpolated backward vertical flow. + + :param stream: Stream for the asynchronous version. + + + +gpu::FGDStatModel +----------------- +.. ocv:class:: gpu::FGDStatModel + +Class used for background/foreground segmentation. :: + + class FGDStatModel + { + public: + struct Params + { + ... + }; + + explicit FGDStatModel(int out_cn = 3); + explicit FGDStatModel(const cv::gpu::GpuMat& firstFrame, const Params& params = Params(), int out_cn = 3); + + ~FGDStatModel(); + + void create(const cv::gpu::GpuMat& firstFrame, const Params& params = Params()); + void release(); + + int update(const cv::gpu::GpuMat& curFrame); + + //8UC3 or 8UC4 reference background image + cv::gpu::GpuMat background; + + //8UC1 foreground image + cv::gpu::GpuMat foreground; + + std::vector< std::vector > foreground_regions; + }; + + 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 [FGD2003]_. + + The results are available through the class fields: + + .. ocv:member:: cv::gpu::GpuMat background + + The output background image. + + .. ocv:member:: cv::gpu::GpuMat foreground + + The output foreground mask as an 8-bit binary image. + + .. ocv:member:: cv::gpu::GpuMat foreground_regions + + The output foreground regions calculated by :ocv:func:`findContours`. + + + +gpu::FGDStatModel::FGDStatModel +------------------------------- +Constructors. + +.. ocv:function:: gpu::FGDStatModel::FGDStatModel(int out_cn = 3) +.. ocv:function:: gpu::FGDStatModel::FGDStatModel(const cv::gpu::GpuMat& firstFrame, const Params& params = Params(), int out_cn = 3) + + :param firstFrame: First frame from video stream. Supports 3- and 4-channels input ( ``CV_8UC3`` and ``CV_8UC4`` ). + + :param params: Algorithm's parameters. See [FGD2003]_ for explanation. + + :param out_cn: Channels count in output result and inner buffers. Can be 3 or 4. 4-channels version requires more memory, but works a bit faster. + +.. seealso:: :ocv:func:`gpu::FGDStatModel::create` + + + +gpu::FGDStatModel::create +------------------------- +Initializes background model. + +.. ocv:function:: void gpu::FGDStatModel::create(const cv::gpu::GpuMat& firstFrame, const Params& params = Params()) + + :param firstFrame: First frame from video stream. Supports 3- and 4-channels input ( ``CV_8UC3`` and ``CV_8UC4`` ). + + :param params: Algorithm's parameters. See [FGD2003]_ for explanation. + + + +gpu::FGDStatModel::release +-------------------------- +Releases all inner buffer's memory. + +.. ocv:function:: void gpu::FGDStatModel::release() + + + +gpu::FGDStatModel::update +-------------------------- +Updates the background model and returns foreground regions count. + +.. ocv:function:: int gpu::FGDStatModel::update(const cv::gpu::GpuMat& curFrame) + + :param curFrame: Next video frame. + + + +gpu::MOG_GPU +------------ +.. ocv:class:: gpu::MOG_GPU + +Gaussian Mixture-based Backbround/Foreground Segmentation Algorithm. :: + + class MOG_GPU + { + public: + MOG_GPU(int nmixtures = -1); + + void initialize(Size frameSize, int frameType); + + void operator()(const GpuMat& frame, GpuMat& fgmask, float learningRate = 0.0f, Stream& stream = Stream::Null()); + + void getBackgroundImage(GpuMat& backgroundImage, Stream& stream = Stream::Null()) const; + + void release(); + + int history; + float varThreshold; + float backgroundRatio; + float noiseSigma; + }; + +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 [MOG2001]_. + +.. seealso:: :ocv:class:`BackgroundSubtractorMOG` + + + +gpu::MOG_GPU::MOG_GPU +--------------------- +The constructor. + +.. ocv:function:: gpu::MOG_GPU::MOG_GPU(int nmixtures = -1) + + :param nmixtures: Number of Gaussian mixtures. + +Default constructor sets all parameters to default values. + + + +gpu::MOG_GPU::operator() +------------------------ +Updates the background model and returns the foreground mask. + +.. ocv:function:: void gpu::MOG_GPU::operator()(const GpuMat& frame, GpuMat& fgmask, float learningRate = 0.0f, 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::MOG_GPU::getBackgroundImage +-------------------------------- +Computes a background image. + +.. ocv:function:: void gpu::MOG_GPU::getBackgroundImage(GpuMat& backgroundImage, Stream& stream = Stream::Null()) const + + :param backgroundImage: The output background image. + + :param stream: Stream for the asynchronous version. + + + +gpu::MOG_GPU::release +--------------------- +Releases all inner buffer's memory. + +.. ocv:function:: void gpu::MOG_GPU::release() + + + +gpu::MOG2_GPU +------------- +.. ocv:class:: gpu::MOG2_GPU + +Gaussian Mixture-based Background/Foreground Segmentation Algorithm. :: + + class MOG2_GPU + { + public: + MOG2_GPU(int nmixtures = -1); + + void initialize(Size frameSize, int frameType); + + void operator()(const GpuMat& frame, GpuMat& fgmask, float learningRate = 0.0f, Stream& stream = Stream::Null()); + + void getBackgroundImage(GpuMat& backgroundImage, Stream& stream = Stream::Null()) const; + + void release(); + + // parameters + ... + }; + + 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 [MOG2004]_. + + Here are important members of the class that control the algorithm, which you can set after constructing the class instance: + + .. ocv:member:: float backgroundRatio + + Threshold defining whether the component is significant enough to be included into the background model ( corresponds to ``TB=1-cf`` from the paper??which paper??). ``cf=0.1 => TB=0.9`` is default. For ``alpha=0.001``, it means that the mode should exist for approximately 105 frames before it is considered foreground. + + .. ocv:member:: float varThreshold + + Threshold for the squared Mahalanobis distance that helps decide when a sample is close to the existing components (corresponds to ``Tg``). If it is not close to any component, a new component is generated. ``3 sigma => Tg=3*3=9`` is default. A smaller ``Tg`` value generates more components. A higher ``Tg`` value may result in a small number of components but they can grow too large. + + .. ocv:member:: float fVarInit + + Initial variance for the newly generated components. It affects the speed of adaptation. The parameter value is based on your estimate of the typical standard deviation from the images. OpenCV uses 15 as a reasonable value. + + .. ocv:member:: float fVarMin + + Parameter used to further control the variance. + + .. ocv:member:: float fVarMax + + Parameter used to further control the variance. + + .. ocv:member:: float fCT + + Complexity reduction parameter. This parameter defines the number of samples needed to accept to prove the component exists. ``CT=0.05`` is a default value for all the samples. By setting ``CT=0`` you get an algorithm very similar to the standard Stauffer&Grimson algorithm. + + .. ocv:member:: uchar nShadowDetection + + The value for marking shadow pixels in the output foreground mask. Default value is 127. + + .. ocv:member:: float fTau + + Shadow threshold. The shadow is detected if the pixel is a darker version of the background. ``Tau`` is a threshold defining how much darker the shadow can be. ``Tau= 0.5`` means that if a pixel is more than twice darker then it is not shadow. See [ShadowDetect2003]_. + + .. ocv:member:: bool bShadowDetection + + Parameter defining whether shadow detection should be enabled. + +.. seealso:: :ocv:class:`BackgroundSubtractorMOG2` + + + +gpu::MOG2_GPU::MOG2_GPU +----------------------- +The constructor. + +.. ocv:function:: gpu::MOG2_GPU::MOG2_GPU(int nmixtures = -1) + + :param nmixtures: Number of Gaussian mixtures. + +Default constructor sets all parameters to default values. + + + +gpu::MOG2_GPU::operator() +------------------------- +Updates the background model and returns the foreground mask. + +.. ocv:function:: void gpu::MOG2_GPU::operator()( const GpuMat& frame, GpuMat& fgmask, float learningRate=-1.0f, 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::MOG2_GPU::getBackgroundImage +--------------------------------- +Computes a background image. + +.. ocv:function:: void gpu::MOG2_GPU::getBackgroundImage(GpuMat& backgroundImage, Stream& stream = Stream::Null()) const + + :param backgroundImage: The output background image. + + :param stream: Stream for the asynchronous version. + + + +gpu::MOG2_GPU::release +---------------------- +Releases all inner buffer's memory. + +.. ocv:function:: void gpu::MOG2_GPU::release() + + + +gpu::GMG_GPU +------------ +.. ocv:class:: gpu::GMG_GPU + + Class used for background/foreground segmentation. :: + + class GMG_GPU_GPU + { + public: + GMG_GPU(); + + void initialize(Size frameSize, float min = 0.0f, float max = 255.0f); + + void operator ()(const GpuMat& frame, GpuMat& fgmask, float learningRate = -1.0f, Stream& stream = Stream::Null()); + + void release(); + + int maxFeatures; + float learningRate; + int numInitializationFrames; + int quantizationLevels; + float backgroundPrior; + float decisionThreshold; + int smoothingRadius; + + ... + }; + + 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 [GMG2012]_. + + Here are important members of the class that control the algorithm, which you can set after constructing the class instance: + + .. ocv:member:: int maxFeatures + + Total number of distinct colors to maintain in histogram. + + .. ocv:member:: float learningRate + + Set between 0.0 and 1.0, determines how quickly features are "forgotten" from histograms. + + .. ocv:member:: int numInitializationFrames + + Number of frames of video to use to initialize histograms. + + .. ocv:member:: int quantizationLevels + + Number of discrete levels in each channel to be used in histograms. + + .. ocv:member:: float backgroundPrior + + Prior probability that any given pixel is a background pixel. A sensitivity parameter. + + .. ocv:member:: float decisionThreshold + + Value above which pixel is determined to be FG. + + .. ocv:member:: float smoothingRadius + + Smoothing radius, in pixels, for cleaning up FG image. + + + +gpu::GMG_GPU::GMG_GPU +--------------------- +The default constructor. + +.. ocv:function:: gpu::GMG_GPU::GMG_GPU() + +Default constructor sets all parameters to default values. + + + +gpu::GMG_GPU::initialize +------------------------ +Initialize background model and allocates all inner buffers. + +.. ocv:function:: void gpu::GMG_GPU::initialize(Size frameSize, float min = 0.0f, float max = 255.0f) + + :param frameSize: Input frame size. + + :param min: Minimum value taken on by pixels in image sequence. Usually 0. + + :param max: Maximum value taken on by pixels in image sequence, e.g. 1.0 or 255. + + + +gpu::GMG_GPU::operator() +------------------------ +Updates the background model and returns the foreground mask + +.. ocv:function:: void gpu::GMG_GPU::operator ()( const GpuMat& frame, GpuMat& fgmask, float learningRate=-1.0f, 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::GMG_GPU::release +--------------------- +Releases all inner buffer's memory. + +.. ocv:function:: void gpu::GMG_GPU::release() + + + +gpu::VideoWriter_GPU +--------------------- +Video writer class. + +.. ocv:class:: gpu::VideoWriter_GPU + +The class uses H264 video codec. + +.. note:: Currently only Windows platform is supported. + + + +gpu::VideoWriter_GPU::VideoWriter_GPU +------------------------------------- +Constructors. + +.. ocv:function:: gpu::VideoWriter_GPU::VideoWriter_GPU() +.. ocv:function:: gpu::VideoWriter_GPU::VideoWriter_GPU(const String& fileName, cv::Size frameSize, double fps, SurfaceFormat format = SF_BGR) +.. ocv:function:: gpu::VideoWriter_GPU::VideoWriter_GPU(const String& fileName, cv::Size frameSize, double fps, const EncoderParams& params, SurfaceFormat format = SF_BGR) +.. ocv:function:: gpu::VideoWriter_GPU::VideoWriter_GPU(const cv::Ptr& encoderCallback, cv::Size frameSize, double fps, SurfaceFormat format = SF_BGR) +.. ocv:function:: gpu::VideoWriter_GPU::VideoWriter_GPU(const cv::Ptr& encoderCallback, cv::Size frameSize, double fps, const EncoderParams& params, SurfaceFormat format = SF_BGR) + + :param fileName: Name of the output video file. Only AVI file format is supported. + + :param frameSize: Size of the input video frames. + + :param fps: Framerate of the created video stream. + + :param params: Encoder parameters. See :ocv:struct:`gpu::VideoWriter_GPU::EncoderParams` . + + :param format: Surface format of input frames ( ``SF_UYVY`` , ``SF_YUY2`` , ``SF_YV12`` , ``SF_NV12`` , ``SF_IYUV`` , ``SF_BGR`` or ``SF_GRAY``). BGR or gray frames will be converted to YV12 format before encoding, frames with other formats will be used as is. + + :param encoderCallback: Callbacks for video encoder. See :ocv:class:`gpu::VideoWriter_GPU::EncoderCallBack` . Use it if you want to work with raw video stream. + +The constructors initialize video writer. FFMPEG is used to write videos. User can implement own multiplexing with :ocv:class:`gpu::VideoWriter_GPU::EncoderCallBack` . + + + +gpu::VideoWriter_GPU::open +-------------------------- +Initializes or reinitializes video writer. + +.. ocv:function:: void gpu::VideoWriter_GPU::open(const String& fileName, cv::Size frameSize, double fps, SurfaceFormat format = SF_BGR) +.. ocv:function:: void gpu::VideoWriter_GPU::open(const String& fileName, cv::Size frameSize, double fps, const EncoderParams& params, SurfaceFormat format = SF_BGR) +.. ocv:function:: void gpu::VideoWriter_GPU::open(const cv::Ptr& encoderCallback, cv::Size frameSize, double fps, SurfaceFormat format = SF_BGR) +.. ocv:function:: void gpu::VideoWriter_GPU::open(const cv::Ptr& encoderCallback, cv::Size frameSize, double fps, const EncoderParams& params, SurfaceFormat format = SF_BGR) + +The method opens video writer. Parameters are the same as in the constructor :ocv:func:`gpu::VideoWriter_GPU::VideoWriter_GPU` . The method throws :ocv:class:`Exception` if error occurs. + + + +gpu::VideoWriter_GPU::isOpened +------------------------------ +Returns true if video writer has been successfully initialized. + +.. ocv:function:: bool gpu::VideoWriter_GPU::isOpened() const + + + +gpu::VideoWriter_GPU::close +--------------------------- +Releases the video writer. + +.. ocv:function:: void gpu::VideoWriter_GPU::close() + + + +gpu::VideoWriter_GPU::write +--------------------------- +Writes the next video frame. + +.. ocv:function:: void gpu::VideoWriter_GPU::write(const cv::gpu::GpuMat& image, bool lastFrame = false) + + :param image: The written frame. + + :param lastFrame: Indicates that it is end of stream. The parameter can be ignored. + +The method write the specified image to video file. The image must have the same size and the same surface format as has been specified when opening the video writer. + + + +gpu::VideoWriter_GPU::EncoderParams +----------------------------------- +.. ocv:struct:: gpu::VideoWriter_GPU::EncoderParams + +Different parameters for CUDA video encoder. :: + + struct EncoderParams + { + int P_Interval; // NVVE_P_INTERVAL, + int IDR_Period; // NVVE_IDR_PERIOD, + int DynamicGOP; // NVVE_DYNAMIC_GOP, + int RCType; // NVVE_RC_TYPE, + int AvgBitrate; // NVVE_AVG_BITRATE, + int PeakBitrate; // NVVE_PEAK_BITRATE, + int QP_Level_Intra; // NVVE_QP_LEVEL_INTRA, + int QP_Level_InterP; // NVVE_QP_LEVEL_INTER_P, + int QP_Level_InterB; // NVVE_QP_LEVEL_INTER_B, + int DeblockMode; // NVVE_DEBLOCK_MODE, + int ProfileLevel; // NVVE_PROFILE_LEVEL, + int ForceIntra; // NVVE_FORCE_INTRA, + int ForceIDR; // NVVE_FORCE_IDR, + int ClearStat; // NVVE_CLEAR_STAT, + int DIMode; // NVVE_SET_DEINTERLACE, + int Presets; // NVVE_PRESETS, + int DisableCabac; // NVVE_DISABLE_CABAC, + int NaluFramingType; // NVVE_CONFIGURE_NALU_FRAMING_TYPE + int DisableSPSPPS; // NVVE_DISABLE_SPS_PPS + + EncoderParams(); + explicit EncoderParams(const String& configFile); + + void load(const String& configFile); + void save(const String& configFile) const; + }; + + + +gpu::VideoWriter_GPU::EncoderParams::EncoderParams +-------------------------------------------------- +Constructors. + +.. ocv:function:: gpu::VideoWriter_GPU::EncoderParams::EncoderParams() +.. ocv:function:: gpu::VideoWriter_GPU::EncoderParams::EncoderParams(const String& configFile) + + :param configFile: Config file name. + +Creates default parameters or reads parameters from config file. + + + +gpu::VideoWriter_GPU::EncoderParams::load +----------------------------------------- +Reads parameters from config file. + +.. ocv:function:: void gpu::VideoWriter_GPU::EncoderParams::load(const String& configFile) + + :param configFile: Config file name. + + + +gpu::VideoWriter_GPU::EncoderParams::save +----------------------------------------- +Saves parameters to config file. + +.. ocv:function:: void gpu::VideoWriter_GPU::EncoderParams::save(const String& configFile) const + + :param configFile: Config file name. + + + +gpu::VideoWriter_GPU::EncoderCallBack +------------------------------------- +.. ocv:class:: gpu::VideoWriter_GPU::EncoderCallBack + +Callbacks for CUDA video encoder. :: + + class EncoderCallBack + { + public: + enum PicType + { + IFRAME = 1, + PFRAME = 2, + BFRAME = 3 + }; + + virtual ~EncoderCallBack() {} + + virtual unsigned char* acquireBitStream(int* bufferSize) = 0; + virtual void releaseBitStream(unsigned char* data, int size) = 0; + virtual void onBeginFrame(int frameNumber, PicType picType) = 0; + virtual void onEndFrame(int frameNumber, PicType picType) = 0; + }; + + + +gpu::VideoWriter_GPU::EncoderCallBack::acquireBitStream +------------------------------------------------------- +Callback function to signal the start of bitstream that is to be encoded. + +.. ocv:function:: virtual uchar* gpu::VideoWriter_GPU::EncoderCallBack::acquireBitStream(int* bufferSize) = 0 + +Callback must allocate buffer for CUDA encoder and return pointer to it and it's size. + + + +gpu::VideoWriter_GPU::EncoderCallBack::releaseBitStream +------------------------------------------------------- +Callback function to signal that the encoded bitstream is ready to be written to file. + +.. ocv:function:: virtual void gpu::VideoWriter_GPU::EncoderCallBack::releaseBitStream(unsigned char* data, int size) = 0 + + + +gpu::VideoWriter_GPU::EncoderCallBack::onBeginFrame +--------------------------------------------------- +Callback function to signal that the encoding operation on the frame has started. + +.. ocv:function:: virtual void gpu::VideoWriter_GPU::EncoderCallBack::onBeginFrame(int frameNumber, PicType picType) = 0 + + :param picType: Specify frame type (I-Frame, P-Frame or B-Frame). + + + +gpu::VideoWriter_GPU::EncoderCallBack::onEndFrame +------------------------------------------------- +Callback function signals that the encoding operation on the frame has finished. + +.. ocv:function:: virtual void gpu::VideoWriter_GPU::EncoderCallBack::onEndFrame(int frameNumber, PicType picType) = 0 + + :param picType: Specify frame type (I-Frame, P-Frame or B-Frame). + + + +gpu::VideoReader_GPU +-------------------- +Class for reading video from files. + +.. ocv:class:: gpu::VideoReader_GPU + +.. note:: Currently only Windows and Linux platforms are supported. + + + +gpu::VideoReader_GPU::Codec +--------------------------- + +Video codecs supported by :ocv:class:`gpu::VideoReader_GPU` . + +.. ocv:enum:: gpu::VideoReader_GPU::Codec + + .. ocv:emember:: MPEG1 = 0 + .. ocv:emember:: MPEG2 + .. ocv:emember:: MPEG4 + .. ocv:emember:: VC1 + .. ocv:emember:: H264 + .. ocv:emember:: JPEG + .. ocv:emember:: H264_SVC + .. ocv:emember:: H264_MVC + + .. ocv:emember:: Uncompressed_YUV420 = (('I'<<24)|('Y'<<16)|('U'<<8)|('V')) + + Y,U,V (4:2:0) + + .. ocv:emember:: Uncompressed_YV12 = (('Y'<<24)|('V'<<16)|('1'<<8)|('2')) + + Y,V,U (4:2:0) + + .. ocv:emember:: Uncompressed_NV12 = (('N'<<24)|('V'<<16)|('1'<<8)|('2')) + + Y,UV (4:2:0) + + .. ocv:emember:: Uncompressed_YUYV = (('Y'<<24)|('U'<<16)|('Y'<<8)|('V')) + + YUYV/YUY2 (4:2:2) + + .. ocv:emember:: Uncompressed_UYVY = (('U'<<24)|('Y'<<16)|('V'<<8)|('Y')) + + UYVY (4:2:2) + + +gpu::VideoReader_GPU::ChromaFormat +---------------------------------- + +Chroma formats supported by :ocv:class:`gpu::VideoReader_GPU` . + +.. ocv:enum:: gpu::VideoReader_GPU::ChromaFormat + + .. ocv:emember:: Monochrome = 0 + .. ocv:emember:: YUV420 + .. ocv:emember:: YUV422 + .. ocv:emember:: YUV444 + + +gpu::VideoReader_GPU::FormatInfo +-------------------------------- +.. ocv:struct:: gpu::VideoReader_GPU::FormatInfo + +Struct providing information about video file format. :: + + struct FormatInfo + { + Codec codec; + ChromaFormat chromaFormat; + int width; + int height; + }; + + +gpu::VideoReader_GPU::VideoReader_GPU +------------------------------------- +Constructors. + +.. ocv:function:: gpu::VideoReader_GPU::VideoReader_GPU() +.. ocv:function:: gpu::VideoReader_GPU::VideoReader_GPU(const String& filename) +.. ocv:function:: gpu::VideoReader_GPU::VideoReader_GPU(const cv::Ptr& source) + + :param filename: Name of the input video file. + + :param source: Video file parser implemented by user. + +The constructors initialize video reader. FFMPEG is used to read videos. User can implement own demultiplexing with :ocv:class:`gpu::VideoReader_GPU::VideoSource` . + + + +gpu::VideoReader_GPU::open +-------------------------- +Initializes or reinitializes video reader. + +.. ocv:function:: void gpu::VideoReader_GPU::open(const String& filename) +.. ocv:function:: void gpu::VideoReader_GPU::open(const cv::Ptr& source) + +The method opens video reader. Parameters are the same as in the constructor :ocv:func:`gpu::VideoReader_GPU::VideoReader_GPU` . The method throws :ocv:class:`Exception` if error occurs. + + + +gpu::VideoReader_GPU::isOpened +------------------------------ +Returns true if video reader has been successfully initialized. + +.. ocv:function:: bool gpu::VideoReader_GPU::isOpened() const + + + +gpu::VideoReader_GPU::close +--------------------------- +Releases the video reader. + +.. ocv:function:: void gpu::VideoReader_GPU::close() + + + +gpu::VideoReader_GPU::read +-------------------------- +Grabs, decodes and returns the next video frame. + +.. ocv:function:: bool gpu::VideoReader_GPU::read(GpuMat& image) + +If no frames has been grabbed (there are no more frames in video file), the methods return ``false`` . The method throws :ocv:class:`Exception` if error occurs. + + + +gpu::VideoReader_GPU::format +---------------------------- +Returns information about video file format. + +.. ocv:function:: FormatInfo gpu::VideoReader_GPU::format() const + +The method throws :ocv:class:`Exception` if video reader wasn't initialized. + + + +gpu::VideoReader_GPU::dumpFormat +-------------------------------- +Dump information about video file format to specified stream. + +.. ocv:function:: void gpu::VideoReader_GPU::dumpFormat(std::ostream& st) + + :param st: Output stream. + +The method throws :ocv:class:`Exception` if video reader wasn't initialized. + + + +gpu::VideoReader_GPU::VideoSource +----------------------------------- +.. ocv:class:: gpu::VideoReader_GPU::VideoSource + +Interface for video demultiplexing. :: + + class VideoSource + { + public: + VideoSource(); + virtual ~VideoSource() {} + + virtual FormatInfo format() const = 0; + virtual void start() = 0; + virtual void stop() = 0; + virtual bool isStarted() const = 0; + virtual bool hasError() const = 0; + + protected: + bool parseVideoData(const unsigned char* data, size_t size, bool endOfStream = false); + }; + +User can implement own demultiplexing by implementing this interface. + + + +gpu::VideoReader_GPU::VideoSource::format +----------------------------------------- +Returns information about video file format. + +.. ocv:function:: virtual FormatInfo gpu::VideoReader_GPU::VideoSource::format() const = 0 + + + +gpu::VideoReader_GPU::VideoSource::start +---------------------------------------- +Starts processing. + +.. ocv:function:: virtual void gpu::VideoReader_GPU::VideoSource::start() = 0 + +Implementation must create own thread with video processing and call periodic :ocv:func:`gpu::VideoReader_GPU::VideoSource::parseVideoData` . + + + +gpu::VideoReader_GPU::VideoSource::stop +--------------------------------------- +Stops processing. + +.. ocv:function:: virtual void gpu::VideoReader_GPU::VideoSource::stop() = 0 + + + +gpu::VideoReader_GPU::VideoSource::isStarted +-------------------------------------------- +Returns ``true`` if processing was successfully started. + +.. ocv:function:: virtual bool gpu::VideoReader_GPU::VideoSource::isStarted() const = 0 + + + +gpu::VideoReader_GPU::VideoSource::hasError +------------------------------------------- +Returns ``true`` if error occured during processing. + +.. ocv:function:: virtual bool gpu::VideoReader_GPU::VideoSource::hasError() const = 0 + + + +gpu::VideoReader_GPU::VideoSource::parseVideoData +------------------------------------------------- +Parse next video frame. Implementation must call this method after new frame was grabbed. + +.. ocv:function:: bool gpu::VideoReader_GPU::VideoSource::parseVideoData(const uchar* data, size_t size, bool endOfStream = false) + + :param data: Pointer to frame data. Can be ``NULL`` if ``endOfStream`` if ``true`` . + + :param size: Size in bytes of current frame. + + :param endOfStream: Indicates that it is end of stream. + + + +.. [Brox2004] T. Brox, A. Bruhn, N. Papenberg, J. Weickert. *High accuracy optical flow estimation based on a theory for warping*. ECCV 2004. +.. [FGD2003] Liyuan Li, Weimin Huang, Irene Y.H. Gu, and Qi Tian. *Foreground Object Detection from Videos Containing Complex Background*. ACM MM2003 9p, 2003. +.. [MOG2001] P. KadewTraKuPong and R. Bowden. *An improved adaptive background mixture model for real-time tracking with shadow detection*. Proc. 2nd European Workshop on Advanced Video-Based Surveillance Systems, 2001 +.. [MOG2004] Z. Zivkovic. *Improved adaptive Gausian mixture model for background subtraction*. International Conference Pattern Recognition, UK, August, 2004 +.. [ShadowDetect2003] Prati, Mikic, Trivedi and Cucchiarra. *Detecting Moving Shadows...*. IEEE PAMI, 2003 +.. [GMG2012] A. Godbehere, A. Matsukawa and K. Goldberg. *Visual Tracking of Human Visitors under Variable-Lighting Conditions for a Responsive Audio Art Installation*. American Control Conference, Montreal, June 2012 diff --git a/modules/gpu/include/opencv2/gpu.hpp b/modules/gpu/include/opencv2/gpu.hpp index 5dc9b9549e..84de397dc1 100644 --- a/modules/gpu/include/opencv2/gpu.hpp +++ b/modules/gpu/include/opencv2/gpu.hpp @@ -43,56 +43,1081 @@ #ifndef __OPENCV_GPU_HPP__ #define __OPENCV_GPU_HPP__ -#ifndef __cplusplus -# error gpu.hpp header must be compiled as C++ +#ifndef SKIP_INCLUDES +#include +#include +#include #endif #include "opencv2/core/gpumat.hpp" - -#if !defined(__OPENCV_BUILD) && !defined(OPENCV_GPU_SKIP_INCLUDE) - #include "opencv2/opencv_modules.hpp" - - #ifdef HAVE_OPENCV_GPUARITHM - #include "opencv2/gpuarithm.hpp" - #endif - - #ifdef HAVE_OPENCV_GPUWARPING - #include "opencv2/gpuwarping.hpp" - #endif - - #ifdef HAVE_OPENCV_GPUFILTERS - #include "opencv2/gpufilters.hpp" - #endif - - #ifdef HAVE_OPENCV_GPUIMGPROC - #include "opencv2/gpuimgproc.hpp" - #endif - - #ifdef HAVE_OPENCV_GPUFEATURES2D - #include "opencv2/gpufeatures2d.hpp" - #endif - - #ifdef HAVE_OPENCV_GPUOPTFLOW - #include "opencv2/gpuoptflow.hpp" - #endif - - #ifdef HAVE_OPENCV_GPUBGSEGM - #include "opencv2/gpubgsegm.hpp" - #endif - - #ifdef HAVE_OPENCV_GPUSTEREO - #include "opencv2/gpustereo.hpp" - #endif - - #ifdef HAVE_OPENCV_GPUCODEC - #include "opencv2/gpucodec.hpp" - #endif -#endif +#include "opencv2/imgproc.hpp" +#include "opencv2/objdetect.hpp" +#include "opencv2/features2d.hpp" namespace cv { namespace gpu { +//////////////////////////////// Filter Engine //////////////////////////////// + +/*! +The Base Class for 1D or Row-wise Filters + +This is the base class for linear or non-linear filters that process 1D data. +In particular, such filters are used for the "horizontal" filtering parts in separable filters. +*/ +class CV_EXPORTS BaseRowFilter_GPU +{ +public: + BaseRowFilter_GPU(int ksize_, int anchor_) : ksize(ksize_), anchor(anchor_) {} + virtual ~BaseRowFilter_GPU() {} + virtual void operator()(const GpuMat& src, GpuMat& dst, Stream& stream = Stream::Null()) = 0; + int ksize, anchor; +}; + +/*! +The Base Class for Column-wise Filters + +This is the base class for linear or non-linear filters that process columns of 2D arrays. +Such filters are used for the "vertical" filtering parts in separable filters. +*/ +class CV_EXPORTS BaseColumnFilter_GPU +{ +public: + BaseColumnFilter_GPU(int ksize_, int anchor_) : ksize(ksize_), anchor(anchor_) {} + virtual ~BaseColumnFilter_GPU() {} + virtual void operator()(const GpuMat& src, GpuMat& dst, Stream& stream = Stream::Null()) = 0; + int ksize, anchor; +}; + +/*! +The Base Class for Non-Separable 2D Filters. + +This is the base class for linear or non-linear 2D filters. +*/ +class CV_EXPORTS BaseFilter_GPU +{ +public: + BaseFilter_GPU(const Size& ksize_, const Point& anchor_) : ksize(ksize_), anchor(anchor_) {} + virtual ~BaseFilter_GPU() {} + virtual void operator()(const GpuMat& src, GpuMat& dst, Stream& stream = Stream::Null()) = 0; + Size ksize; + Point anchor; +}; + +/*! +The Base Class for Filter Engine. + +The class can be used to apply an arbitrary filtering operation to an image. +It contains all the necessary intermediate buffers. +*/ +class CV_EXPORTS FilterEngine_GPU +{ +public: + virtual ~FilterEngine_GPU() {} + + virtual void apply(const GpuMat& src, GpuMat& dst, Rect roi = Rect(0,0,-1,-1), Stream& stream = Stream::Null()) = 0; +}; + +//! returns the non-separable filter engine with the specified filter +CV_EXPORTS Ptr createFilter2D_GPU(const Ptr& filter2D, int srcType, int dstType); + +//! returns the separable filter engine with the specified filters +CV_EXPORTS Ptr createSeparableFilter_GPU(const Ptr& rowFilter, + const Ptr& columnFilter, int srcType, int bufType, int dstType); +CV_EXPORTS Ptr createSeparableFilter_GPU(const Ptr& rowFilter, + const Ptr& columnFilter, int srcType, int bufType, int dstType, GpuMat& buf); + +//! returns horizontal 1D box filter +//! supports only CV_8UC1 source type and CV_32FC1 sum type +CV_EXPORTS Ptr getRowSumFilter_GPU(int srcType, int sumType, int ksize, int anchor = -1); + +//! returns vertical 1D box filter +//! supports only CV_8UC1 sum type and CV_32FC1 dst type +CV_EXPORTS Ptr getColumnSumFilter_GPU(int sumType, int dstType, int ksize, int anchor = -1); + +//! returns 2D box filter +//! supports CV_8UC1 and CV_8UC4 source type, dst type must be the same as source type +CV_EXPORTS Ptr getBoxFilter_GPU(int srcType, int dstType, const Size& ksize, Point anchor = Point(-1, -1)); + +//! returns box filter engine +CV_EXPORTS Ptr createBoxFilter_GPU(int srcType, int dstType, const Size& ksize, + const Point& anchor = Point(-1,-1)); + +//! returns 2D morphological filter +//! only MORPH_ERODE and MORPH_DILATE are supported +//! supports CV_8UC1 and CV_8UC4 types +//! kernel must have CV_8UC1 type, one rows and cols == ksize.width * ksize.height +CV_EXPORTS Ptr getMorphologyFilter_GPU(int op, int type, const Mat& kernel, const Size& ksize, + Point anchor=Point(-1,-1)); + +//! returns morphological filter engine. Only MORPH_ERODE and MORPH_DILATE are supported. +CV_EXPORTS Ptr createMorphologyFilter_GPU(int op, int type, const Mat& kernel, + const Point& anchor = Point(-1,-1), int iterations = 1); +CV_EXPORTS Ptr createMorphologyFilter_GPU(int op, int type, const Mat& kernel, GpuMat& buf, + const Point& anchor = Point(-1,-1), int iterations = 1); + +//! returns 2D filter with the specified kernel +//! supports CV_8U, CV_16U and CV_32F one and four channel image +CV_EXPORTS Ptr getLinearFilter_GPU(int srcType, int dstType, const Mat& kernel, Point anchor = Point(-1, -1), int borderType = BORDER_DEFAULT); + +//! returns the non-separable linear filter engine +CV_EXPORTS Ptr createLinearFilter_GPU(int srcType, int dstType, const Mat& kernel, + Point anchor = Point(-1,-1), int borderType = BORDER_DEFAULT); + +//! returns the primitive row filter with the specified kernel. +//! supports only CV_8UC1, CV_8UC4, CV_16SC1, CV_16SC2, CV_32SC1, CV_32FC1 source type. +//! there are two version of algorithm: NPP and OpenCV. +//! NPP calls when srcType == CV_8UC1 or srcType == CV_8UC4 and bufType == srcType, +//! otherwise calls OpenCV version. +//! NPP supports only BORDER_CONSTANT border type. +//! OpenCV version supports only CV_32F as buffer depth and +//! BORDER_REFLECT101, BORDER_REPLICATE and BORDER_CONSTANT border types. +CV_EXPORTS Ptr getLinearRowFilter_GPU(int srcType, int bufType, const Mat& rowKernel, + int anchor = -1, int borderType = BORDER_DEFAULT); + +//! returns the primitive column filter with the specified kernel. +//! supports only CV_8UC1, CV_8UC4, CV_16SC1, CV_16SC2, CV_32SC1, CV_32FC1 dst type. +//! there are two version of algorithm: NPP and OpenCV. +//! NPP calls when dstType == CV_8UC1 or dstType == CV_8UC4 and bufType == dstType, +//! otherwise calls OpenCV version. +//! NPP supports only BORDER_CONSTANT border type. +//! OpenCV version supports only CV_32F as buffer depth and +//! BORDER_REFLECT101, BORDER_REPLICATE and BORDER_CONSTANT border types. +CV_EXPORTS Ptr getLinearColumnFilter_GPU(int bufType, int dstType, const Mat& columnKernel, + int anchor = -1, int borderType = BORDER_DEFAULT); + +//! returns the separable linear filter engine +CV_EXPORTS Ptr createSeparableLinearFilter_GPU(int srcType, int dstType, const Mat& rowKernel, + const Mat& columnKernel, const Point& anchor = Point(-1,-1), int rowBorderType = BORDER_DEFAULT, + int columnBorderType = -1); +CV_EXPORTS Ptr createSeparableLinearFilter_GPU(int srcType, int dstType, const Mat& rowKernel, + const Mat& columnKernel, GpuMat& buf, const Point& anchor = Point(-1,-1), int rowBorderType = BORDER_DEFAULT, + int columnBorderType = -1); + +//! returns filter engine for the generalized Sobel operator +CV_EXPORTS Ptr createDerivFilter_GPU(int srcType, int dstType, int dx, int dy, int ksize, + int rowBorderType = BORDER_DEFAULT, int columnBorderType = -1); +CV_EXPORTS Ptr createDerivFilter_GPU(int srcType, int dstType, int dx, int dy, int ksize, GpuMat& buf, + int rowBorderType = BORDER_DEFAULT, int columnBorderType = -1); + +//! returns the Gaussian filter engine +CV_EXPORTS Ptr createGaussianFilter_GPU(int type, Size ksize, double sigma1, double sigma2 = 0, + int rowBorderType = BORDER_DEFAULT, int columnBorderType = -1); +CV_EXPORTS Ptr createGaussianFilter_GPU(int type, Size ksize, GpuMat& buf, double sigma1, double sigma2 = 0, + int rowBorderType = BORDER_DEFAULT, int columnBorderType = -1); + +//! returns maximum filter +CV_EXPORTS Ptr getMaxFilter_GPU(int srcType, int dstType, const Size& ksize, Point anchor = Point(-1,-1)); + +//! returns minimum filter +CV_EXPORTS Ptr getMinFilter_GPU(int srcType, int dstType, const Size& ksize, Point anchor = Point(-1,-1)); + +//! smooths the image using the normalized box filter +//! supports CV_8UC1, CV_8UC4 types +CV_EXPORTS void boxFilter(const GpuMat& src, GpuMat& dst, int ddepth, Size ksize, Point anchor = Point(-1,-1), Stream& stream = Stream::Null()); + +//! a synonym for normalized box filter +static inline void blur(const GpuMat& src, GpuMat& dst, Size ksize, Point anchor = Point(-1,-1), Stream& stream = Stream::Null()) +{ + boxFilter(src, dst, -1, ksize, anchor, stream); +} + +//! erodes the image (applies the local minimum operator) +CV_EXPORTS void erode(const GpuMat& src, GpuMat& dst, const Mat& kernel, Point anchor = Point(-1, -1), int iterations = 1); +CV_EXPORTS void erode(const GpuMat& src, GpuMat& dst, const Mat& kernel, GpuMat& buf, + Point anchor = Point(-1, -1), int iterations = 1, + Stream& stream = Stream::Null()); + +//! dilates the image (applies the local maximum operator) +CV_EXPORTS void dilate(const GpuMat& src, GpuMat& dst, const Mat& kernel, Point anchor = Point(-1, -1), int iterations = 1); +CV_EXPORTS void dilate(const GpuMat& src, GpuMat& dst, const Mat& kernel, GpuMat& buf, + Point anchor = Point(-1, -1), int iterations = 1, + Stream& stream = Stream::Null()); + +//! applies an advanced morphological operation to the image +CV_EXPORTS void morphologyEx(const GpuMat& src, GpuMat& dst, int op, const Mat& kernel, Point anchor = Point(-1, -1), int iterations = 1); +CV_EXPORTS void morphologyEx(const GpuMat& src, GpuMat& dst, int op, const Mat& kernel, GpuMat& buf1, GpuMat& buf2, + Point anchor = Point(-1, -1), int iterations = 1, Stream& stream = Stream::Null()); + +//! applies non-separable 2D linear filter to the image +CV_EXPORTS void filter2D(const GpuMat& src, GpuMat& dst, int ddepth, const Mat& kernel, Point anchor=Point(-1,-1), int borderType = BORDER_DEFAULT, Stream& stream = Stream::Null()); + +//! applies separable 2D linear filter to the image +CV_EXPORTS void sepFilter2D(const GpuMat& src, GpuMat& dst, int ddepth, const Mat& kernelX, const Mat& kernelY, + Point anchor = Point(-1,-1), int rowBorderType = BORDER_DEFAULT, int columnBorderType = -1); +CV_EXPORTS void sepFilter2D(const GpuMat& src, GpuMat& dst, int ddepth, const Mat& kernelX, const Mat& kernelY, GpuMat& buf, + Point anchor = Point(-1,-1), int rowBorderType = BORDER_DEFAULT, int columnBorderType = -1, + Stream& stream = Stream::Null()); + +//! applies generalized Sobel operator to the image +CV_EXPORTS void Sobel(const GpuMat& src, GpuMat& dst, int ddepth, int dx, int dy, int ksize = 3, double scale = 1, + int rowBorderType = BORDER_DEFAULT, int columnBorderType = -1); +CV_EXPORTS void Sobel(const GpuMat& src, GpuMat& dst, int ddepth, int dx, int dy, GpuMat& buf, int ksize = 3, double scale = 1, + int rowBorderType = BORDER_DEFAULT, int columnBorderType = -1, Stream& stream = Stream::Null()); + +//! applies the vertical or horizontal Scharr operator to the image +CV_EXPORTS void Scharr(const GpuMat& src, GpuMat& dst, int ddepth, int dx, int dy, double scale = 1, + int rowBorderType = BORDER_DEFAULT, int columnBorderType = -1); +CV_EXPORTS void Scharr(const GpuMat& src, GpuMat& dst, int ddepth, int dx, int dy, GpuMat& buf, double scale = 1, + int rowBorderType = BORDER_DEFAULT, int columnBorderType = -1, Stream& stream = Stream::Null()); + +//! smooths the image using Gaussian filter. +CV_EXPORTS void GaussianBlur(const GpuMat& src, GpuMat& dst, Size ksize, double sigma1, double sigma2 = 0, + int rowBorderType = BORDER_DEFAULT, int columnBorderType = -1); +CV_EXPORTS void GaussianBlur(const GpuMat& src, GpuMat& dst, Size ksize, GpuMat& buf, double sigma1, double sigma2 = 0, + int rowBorderType = BORDER_DEFAULT, int columnBorderType = -1, Stream& stream = Stream::Null()); + +//! applies Laplacian operator to the image +//! supports only ksize = 1 and ksize = 3 +CV_EXPORTS void Laplacian(const GpuMat& src, GpuMat& dst, int ddepth, int ksize = 1, double scale = 1, int borderType = BORDER_DEFAULT, Stream& stream = Stream::Null()); + + +////////////////////////////// Arithmetics /////////////////////////////////// + +//! implements generalized matrix product algorithm GEMM from BLAS +CV_EXPORTS void gemm(const GpuMat& src1, const GpuMat& src2, double alpha, + const GpuMat& src3, double beta, GpuMat& dst, int flags = 0, Stream& stream = Stream::Null()); + +//! transposes the matrix +//! supports matrix with element size = 1, 4 and 8 bytes (CV_8UC1, CV_8UC4, CV_16UC2, CV_32FC1, etc) +CV_EXPORTS void transpose(const GpuMat& src1, GpuMat& dst, Stream& stream = Stream::Null()); + +//! reverses the order of the rows, columns or both in a matrix +//! supports 1, 3 and 4 channels images with CV_8U, CV_16U, CV_32S or CV_32F depth +CV_EXPORTS void flip(const GpuMat& a, GpuMat& b, int flipCode, Stream& stream = Stream::Null()); + +//! transforms 8-bit unsigned integers using lookup table: dst(i)=lut(src(i)) +//! destination array will have the depth type as lut and the same channels number as source +//! supports CV_8UC1, CV_8UC3 types +CV_EXPORTS void LUT(const GpuMat& src, const Mat& lut, GpuMat& dst, Stream& stream = Stream::Null()); + +//! makes multi-channel array out of several single-channel arrays +CV_EXPORTS void merge(const GpuMat* src, size_t n, GpuMat& dst, Stream& stream = Stream::Null()); + +//! makes multi-channel array out of several single-channel arrays +CV_EXPORTS void merge(const std::vector& src, GpuMat& dst, Stream& stream = Stream::Null()); + +//! copies each plane of a multi-channel array to a dedicated array +CV_EXPORTS void split(const GpuMat& src, GpuMat* dst, Stream& stream = Stream::Null()); + +//! copies each plane of a multi-channel array to a dedicated array +CV_EXPORTS void split(const GpuMat& src, std::vector& dst, Stream& stream = Stream::Null()); + +//! computes magnitude of complex (x(i).re, x(i).im) vector +//! supports only CV_32FC2 type +CV_EXPORTS void magnitude(const GpuMat& xy, GpuMat& magnitude, Stream& stream = Stream::Null()); + +//! computes squared magnitude of complex (x(i).re, x(i).im) vector +//! supports only CV_32FC2 type +CV_EXPORTS void magnitudeSqr(const GpuMat& xy, GpuMat& magnitude, Stream& stream = Stream::Null()); + +//! computes magnitude of each (x(i), y(i)) vector +//! supports only floating-point source +CV_EXPORTS void magnitude(const GpuMat& x, const GpuMat& y, GpuMat& magnitude, Stream& stream = Stream::Null()); + +//! computes squared magnitude of each (x(i), y(i)) vector +//! supports only floating-point source +CV_EXPORTS void magnitudeSqr(const GpuMat& x, const GpuMat& y, GpuMat& magnitude, Stream& stream = Stream::Null()); + +//! computes angle (angle(i)) of each (x(i), y(i)) vector +//! supports only floating-point source +CV_EXPORTS void phase(const GpuMat& x, const GpuMat& y, GpuMat& angle, bool angleInDegrees = false, Stream& stream = Stream::Null()); + +//! converts Cartesian coordinates to polar +//! supports only floating-point source +CV_EXPORTS void cartToPolar(const GpuMat& x, const GpuMat& y, GpuMat& magnitude, GpuMat& angle, bool angleInDegrees = false, Stream& stream = Stream::Null()); + +//! converts polar coordinates to Cartesian +//! supports only floating-point source +CV_EXPORTS void polarToCart(const GpuMat& magnitude, const GpuMat& angle, GpuMat& x, GpuMat& y, bool angleInDegrees = false, Stream& stream = Stream::Null()); + +//! scales and shifts array elements so that either the specified norm (alpha) or the minimum (alpha) and maximum (beta) array values get the specified values +CV_EXPORTS void normalize(const GpuMat& src, GpuMat& dst, double alpha = 1, double beta = 0, + int norm_type = NORM_L2, int dtype = -1, const GpuMat& mask = GpuMat()); +CV_EXPORTS void normalize(const GpuMat& src, GpuMat& dst, double a, double b, + int norm_type, int dtype, const GpuMat& mask, GpuMat& norm_buf, GpuMat& cvt_buf); + + +//////////////////////////// Per-element operations //////////////////////////////////// + +//! adds one matrix to another (c = a + b) +CV_EXPORTS void add(const GpuMat& a, const GpuMat& b, GpuMat& c, const GpuMat& mask = GpuMat(), int dtype = -1, Stream& stream = Stream::Null()); +//! adds scalar to a matrix (c = a + s) +CV_EXPORTS void add(const GpuMat& a, const Scalar& sc, GpuMat& c, const GpuMat& mask = GpuMat(), int dtype = -1, Stream& stream = Stream::Null()); + +//! subtracts one matrix from another (c = a - b) +CV_EXPORTS void subtract(const GpuMat& a, const GpuMat& b, GpuMat& c, const GpuMat& mask = GpuMat(), int dtype = -1, Stream& stream = Stream::Null()); +//! subtracts scalar from a matrix (c = a - s) +CV_EXPORTS void subtract(const GpuMat& a, const Scalar& sc, GpuMat& c, const GpuMat& mask = GpuMat(), int dtype = -1, Stream& stream = Stream::Null()); + +//! computes element-wise weighted product of the two arrays (c = scale * a * b) +CV_EXPORTS void multiply(const GpuMat& a, const GpuMat& b, GpuMat& c, double scale = 1, int dtype = -1, Stream& stream = Stream::Null()); +//! weighted multiplies matrix to a scalar (c = scale * a * s) +CV_EXPORTS void multiply(const GpuMat& a, const Scalar& sc, GpuMat& c, double scale = 1, int dtype = -1, Stream& stream = Stream::Null()); + +//! computes element-wise weighted quotient of the two arrays (c = a / b) +CV_EXPORTS void divide(const GpuMat& a, const GpuMat& b, GpuMat& c, double scale = 1, int dtype = -1, Stream& stream = Stream::Null()); +//! computes element-wise weighted quotient of matrix and scalar (c = a / s) +CV_EXPORTS void divide(const GpuMat& a, const Scalar& sc, GpuMat& c, double scale = 1, int dtype = -1, Stream& stream = Stream::Null()); +//! computes element-wise weighted reciprocal of an array (dst = scale/src2) +CV_EXPORTS void divide(double scale, const GpuMat& b, GpuMat& c, int dtype = -1, Stream& stream = Stream::Null()); + +//! computes the weighted sum of two arrays (dst = alpha*src1 + beta*src2 + gamma) +CV_EXPORTS void addWeighted(const GpuMat& src1, double alpha, const GpuMat& src2, double beta, double gamma, GpuMat& dst, + int dtype = -1, Stream& stream = Stream::Null()); + +//! adds scaled array to another one (dst = alpha*src1 + src2) +static inline void scaleAdd(const GpuMat& src1, double alpha, const GpuMat& src2, GpuMat& dst, Stream& stream = Stream::Null()) +{ + addWeighted(src1, alpha, src2, 1.0, 0.0, dst, -1, stream); +} + +//! computes element-wise absolute difference of two arrays (c = abs(a - b)) +CV_EXPORTS void absdiff(const GpuMat& a, const GpuMat& b, GpuMat& c, Stream& stream = Stream::Null()); +//! computes element-wise absolute difference of array and scalar (c = abs(a - s)) +CV_EXPORTS void absdiff(const GpuMat& a, const Scalar& s, GpuMat& c, Stream& stream = Stream::Null()); + +//! computes absolute value of each matrix element +//! supports CV_16S and CV_32F depth +CV_EXPORTS void abs(const GpuMat& src, GpuMat& dst, Stream& stream = Stream::Null()); + +//! computes square of each pixel in an image +//! supports CV_8U, CV_16U, CV_16S and CV_32F depth +CV_EXPORTS void sqr(const GpuMat& src, GpuMat& dst, Stream& stream = Stream::Null()); + +//! computes square root of each pixel in an image +//! supports CV_8U, CV_16U, CV_16S and CV_32F depth +CV_EXPORTS void sqrt(const GpuMat& src, GpuMat& dst, Stream& stream = Stream::Null()); + +//! computes exponent of each matrix element (b = e**a) +//! supports CV_8U, CV_16U, CV_16S and CV_32F depth +CV_EXPORTS void exp(const GpuMat& a, GpuMat& b, Stream& stream = Stream::Null()); + +//! computes natural logarithm of absolute value of each matrix element: b = log(abs(a)) +//! supports CV_8U, CV_16U, CV_16S and CV_32F depth +CV_EXPORTS void log(const GpuMat& a, GpuMat& b, Stream& stream = Stream::Null()); + +//! computes power of each matrix element: +// (dst(i,j) = pow( src(i,j) , power), if src.type() is integer +// (dst(i,j) = pow(fabs(src(i,j)), power), otherwise +//! supports all, except depth == CV_64F +CV_EXPORTS void pow(const GpuMat& src, double power, GpuMat& dst, Stream& stream = Stream::Null()); + +//! compares elements of two arrays (c = a b) +CV_EXPORTS void compare(const GpuMat& a, const GpuMat& b, GpuMat& c, int cmpop, Stream& stream = Stream::Null()); +CV_EXPORTS void compare(const GpuMat& a, Scalar sc, GpuMat& c, int cmpop, Stream& stream = Stream::Null()); + +//! performs per-elements bit-wise inversion +CV_EXPORTS void bitwise_not(const GpuMat& src, GpuMat& dst, const GpuMat& mask=GpuMat(), Stream& stream = Stream::Null()); + +//! calculates per-element bit-wise disjunction of two arrays +CV_EXPORTS void bitwise_or(const GpuMat& src1, const GpuMat& src2, GpuMat& dst, const GpuMat& mask=GpuMat(), Stream& stream = Stream::Null()); +//! calculates per-element bit-wise disjunction of array and scalar +//! supports 1, 3 and 4 channels images with CV_8U, CV_16U or CV_32S depth +CV_EXPORTS void bitwise_or(const GpuMat& src1, const Scalar& sc, GpuMat& dst, Stream& stream = Stream::Null()); + +//! calculates per-element bit-wise conjunction of two arrays +CV_EXPORTS void bitwise_and(const GpuMat& src1, const GpuMat& src2, GpuMat& dst, const GpuMat& mask=GpuMat(), Stream& stream = Stream::Null()); +//! calculates per-element bit-wise conjunction of array and scalar +//! supports 1, 3 and 4 channels images with CV_8U, CV_16U or CV_32S depth +CV_EXPORTS void bitwise_and(const GpuMat& src1, const Scalar& sc, GpuMat& dst, Stream& stream = Stream::Null()); + +//! calculates per-element bit-wise "exclusive or" operation +CV_EXPORTS void bitwise_xor(const GpuMat& src1, const GpuMat& src2, GpuMat& dst, const GpuMat& mask=GpuMat(), Stream& stream = Stream::Null()); +//! calculates per-element bit-wise "exclusive or" of array and scalar +//! supports 1, 3 and 4 channels images with CV_8U, CV_16U or CV_32S depth +CV_EXPORTS void bitwise_xor(const GpuMat& src1, const Scalar& sc, GpuMat& dst, Stream& stream = Stream::Null()); + +//! pixel by pixel right shift of an image by a constant value +//! supports 1, 3 and 4 channels images with integers elements +CV_EXPORTS void rshift(const GpuMat& src, Scalar_ sc, GpuMat& dst, Stream& stream = Stream::Null()); + +//! pixel by pixel left shift of an image by a constant value +//! supports 1, 3 and 4 channels images with CV_8U, CV_16U or CV_32S depth +CV_EXPORTS void lshift(const GpuMat& src, Scalar_ sc, GpuMat& dst, Stream& stream = Stream::Null()); + +//! computes per-element minimum of two arrays (dst = min(src1, src2)) +CV_EXPORTS void min(const GpuMat& src1, const GpuMat& src2, GpuMat& dst, Stream& stream = Stream::Null()); + +//! computes per-element minimum of array and scalar (dst = min(src1, src2)) +CV_EXPORTS void min(const GpuMat& src1, double src2, GpuMat& dst, Stream& stream = Stream::Null()); + +//! computes per-element maximum of two arrays (dst = max(src1, src2)) +CV_EXPORTS void max(const GpuMat& src1, const GpuMat& src2, GpuMat& dst, Stream& stream = Stream::Null()); + +//! computes per-element maximum of array and scalar (dst = max(src1, src2)) +CV_EXPORTS void max(const GpuMat& src1, double src2, GpuMat& dst, Stream& stream = Stream::Null()); + +enum { ALPHA_OVER, ALPHA_IN, ALPHA_OUT, ALPHA_ATOP, ALPHA_XOR, ALPHA_PLUS, ALPHA_OVER_PREMUL, ALPHA_IN_PREMUL, ALPHA_OUT_PREMUL, + ALPHA_ATOP_PREMUL, ALPHA_XOR_PREMUL, ALPHA_PLUS_PREMUL, ALPHA_PREMUL}; + +//! Composite two images using alpha opacity values contained in each image +//! Supports CV_8UC4, CV_16UC4, CV_32SC4 and CV_32FC4 types +CV_EXPORTS void alphaComp(const GpuMat& img1, const GpuMat& img2, GpuMat& dst, int alpha_op, Stream& stream = Stream::Null()); + + +////////////////////////////// Image processing ////////////////////////////// + +//! DST[x,y] = SRC[xmap[x,y],ymap[x,y]] +//! supports only CV_32FC1 map type +CV_EXPORTS void remap(const GpuMat& src, GpuMat& dst, const GpuMat& xmap, const GpuMat& ymap, + int interpolation, int borderMode = BORDER_CONSTANT, Scalar borderValue = Scalar(), + Stream& stream = Stream::Null()); + +//! Does mean shift filtering on GPU. +CV_EXPORTS void meanShiftFiltering(const GpuMat& src, GpuMat& dst, int sp, int sr, + TermCriteria criteria = TermCriteria(TermCriteria::MAX_ITER + TermCriteria::EPS, 5, 1), + Stream& stream = Stream::Null()); + +//! Does mean shift procedure on GPU. +CV_EXPORTS void meanShiftProc(const GpuMat& src, GpuMat& dstr, GpuMat& dstsp, int sp, int sr, + TermCriteria criteria = TermCriteria(TermCriteria::MAX_ITER + TermCriteria::EPS, 5, 1), + Stream& stream = Stream::Null()); + +//! Does mean shift segmentation with elimination of small regions. +CV_EXPORTS void meanShiftSegmentation(const GpuMat& src, Mat& dst, int sp, int sr, int minsize, + TermCriteria criteria = TermCriteria(TermCriteria::MAX_ITER + TermCriteria::EPS, 5, 1)); + +//! Does coloring of disparity image: [0..ndisp) -> [0..240, 1, 1] in HSV. +//! Supported types of input disparity: CV_8U, CV_16S. +//! Output disparity has CV_8UC4 type in BGRA format (alpha = 255). +CV_EXPORTS void drawColorDisp(const GpuMat& src_disp, GpuMat& dst_disp, int ndisp, Stream& stream = Stream::Null()); + +//! Reprojects disparity image to 3D space. +//! Supports CV_8U and CV_16S types of input disparity. +//! The output is a 3- or 4-channel floating-point matrix. +//! Each element of this matrix will contain the 3D coordinates of the point (x,y,z,1), computed from the disparity map. +//! Q is the 4x4 perspective transformation matrix that can be obtained with cvStereoRectify. +CV_EXPORTS void reprojectImageTo3D(const GpuMat& disp, GpuMat& xyzw, const Mat& Q, int dst_cn = 4, Stream& stream = Stream::Null()); + +//! converts image from one color space to another +CV_EXPORTS void cvtColor(const GpuMat& src, GpuMat& dst, int code, int dcn = 0, Stream& stream = Stream::Null()); + +enum +{ + // Bayer Demosaicing (Malvar, He, and Cutler) + COLOR_BayerBG2BGR_MHT = 256, + COLOR_BayerGB2BGR_MHT = 257, + COLOR_BayerRG2BGR_MHT = 258, + COLOR_BayerGR2BGR_MHT = 259, + + COLOR_BayerBG2RGB_MHT = COLOR_BayerRG2BGR_MHT, + COLOR_BayerGB2RGB_MHT = COLOR_BayerGR2BGR_MHT, + COLOR_BayerRG2RGB_MHT = COLOR_BayerBG2BGR_MHT, + COLOR_BayerGR2RGB_MHT = COLOR_BayerGB2BGR_MHT, + + COLOR_BayerBG2GRAY_MHT = 260, + COLOR_BayerGB2GRAY_MHT = 261, + COLOR_BayerRG2GRAY_MHT = 262, + COLOR_BayerGR2GRAY_MHT = 263 +}; +CV_EXPORTS void demosaicing(const GpuMat& src, GpuMat& dst, int code, int dcn = -1, Stream& stream = Stream::Null()); + +//! swap channels +//! dstOrder - Integer array describing how channel values are permutated. The n-th entry +//! of the array contains the number of the channel that is stored in the n-th channel of +//! the output image. E.g. Given an RGBA image, aDstOrder = [3,2,1,0] converts this to ABGR +//! channel order. +CV_EXPORTS void swapChannels(GpuMat& image, const int dstOrder[4], Stream& stream = Stream::Null()); + +//! Routines for correcting image color gamma +CV_EXPORTS void gammaCorrection(const GpuMat& src, GpuMat& dst, bool forward = true, Stream& stream = Stream::Null()); + +//! applies fixed threshold to the image +CV_EXPORTS double threshold(const GpuMat& src, GpuMat& dst, double thresh, double maxval, int type, Stream& stream = Stream::Null()); + +//! resizes the image +//! Supports INTER_NEAREST, INTER_LINEAR, INTER_CUBIC, INTER_AREA +CV_EXPORTS void resize(const GpuMat& src, GpuMat& dst, Size dsize, double fx=0, double fy=0, int interpolation = INTER_LINEAR, Stream& stream = Stream::Null()); + +//! warps the image using affine transformation +//! Supports INTER_NEAREST, INTER_LINEAR, INTER_CUBIC +CV_EXPORTS void warpAffine(const GpuMat& src, GpuMat& dst, const Mat& M, Size dsize, int flags = INTER_LINEAR, + int borderMode = BORDER_CONSTANT, Scalar borderValue = Scalar(), Stream& stream = Stream::Null()); + +CV_EXPORTS void buildWarpAffineMaps(const Mat& M, bool inverse, Size dsize, GpuMat& xmap, GpuMat& ymap, Stream& stream = Stream::Null()); + +//! warps the image using perspective transformation +//! Supports INTER_NEAREST, INTER_LINEAR, INTER_CUBIC +CV_EXPORTS void warpPerspective(const GpuMat& src, GpuMat& dst, const Mat& M, Size dsize, int flags = INTER_LINEAR, + int borderMode = BORDER_CONSTANT, Scalar borderValue = Scalar(), Stream& stream = Stream::Null()); + +CV_EXPORTS void buildWarpPerspectiveMaps(const Mat& M, bool inverse, Size dsize, GpuMat& xmap, GpuMat& ymap, Stream& stream = Stream::Null()); + +//! builds plane warping maps +CV_EXPORTS void buildWarpPlaneMaps(Size src_size, Rect dst_roi, const Mat &K, const Mat& R, const Mat &T, float scale, + GpuMat& map_x, GpuMat& map_y, Stream& stream = Stream::Null()); + +//! builds cylindrical warping maps +CV_EXPORTS void buildWarpCylindricalMaps(Size src_size, Rect dst_roi, const Mat &K, const Mat& R, float scale, + GpuMat& map_x, GpuMat& map_y, Stream& stream = Stream::Null()); + +//! builds spherical warping maps +CV_EXPORTS void buildWarpSphericalMaps(Size src_size, Rect dst_roi, const Mat &K, const Mat& R, float scale, + GpuMat& map_x, GpuMat& map_y, Stream& stream = Stream::Null()); + +//! rotates an image around the origin (0,0) and then shifts it +//! supports INTER_NEAREST, INTER_LINEAR, INTER_CUBIC +//! supports 1, 3 or 4 channels images with CV_8U, CV_16U or CV_32F depth +CV_EXPORTS void rotate(const GpuMat& src, GpuMat& dst, Size dsize, double angle, double xShift = 0, double yShift = 0, + int interpolation = INTER_LINEAR, Stream& stream = Stream::Null()); + +//! copies 2D array to a larger destination array and pads borders with user-specifiable constant +CV_EXPORTS void copyMakeBorder(const GpuMat& src, GpuMat& dst, int top, int bottom, int left, int right, int borderType, + const Scalar& value = Scalar(), Stream& stream = Stream::Null()); + +//! computes the integral image +//! sum will have CV_32S type, but will contain unsigned int values +//! supports only CV_8UC1 source type +CV_EXPORTS void integral(const GpuMat& src, GpuMat& sum, Stream& stream = Stream::Null()); +//! buffered version +CV_EXPORTS void integralBuffered(const GpuMat& src, GpuMat& sum, GpuMat& buffer, Stream& stream = Stream::Null()); + +//! computes squared integral image +//! result matrix will have 64F type, but will contain 64U values +//! supports source images of 8UC1 type only +CV_EXPORTS void sqrIntegral(const GpuMat& src, GpuMat& sqsum, Stream& stream = Stream::Null()); + +//! computes vertical sum, supports only CV_32FC1 images +CV_EXPORTS void columnSum(const GpuMat& src, GpuMat& sum); + +//! computes the standard deviation of integral images +//! supports only CV_32SC1 source type and CV_32FC1 sqr type +//! output will have CV_32FC1 type +CV_EXPORTS void rectStdDev(const GpuMat& src, const GpuMat& sqr, GpuMat& dst, const Rect& rect, Stream& stream = Stream::Null()); + +//! computes Harris cornerness criteria at each image pixel +CV_EXPORTS void cornerHarris(const GpuMat& src, GpuMat& dst, int blockSize, int ksize, double k, int borderType = BORDER_REFLECT101); +CV_EXPORTS void cornerHarris(const GpuMat& src, GpuMat& dst, GpuMat& Dx, GpuMat& Dy, int blockSize, int ksize, double k, int borderType = BORDER_REFLECT101); +CV_EXPORTS void cornerHarris(const GpuMat& src, GpuMat& dst, GpuMat& Dx, GpuMat& Dy, GpuMat& buf, int blockSize, int ksize, double k, + int borderType = BORDER_REFLECT101, Stream& stream = Stream::Null()); + +//! computes minimum eigen value of 2x2 derivative covariation matrix at each pixel - the cornerness criteria +CV_EXPORTS void cornerMinEigenVal(const GpuMat& src, GpuMat& dst, int blockSize, int ksize, int borderType=BORDER_REFLECT101); +CV_EXPORTS void cornerMinEigenVal(const GpuMat& src, GpuMat& dst, GpuMat& Dx, GpuMat& Dy, int blockSize, int ksize, int borderType=BORDER_REFLECT101); +CV_EXPORTS void cornerMinEigenVal(const GpuMat& src, GpuMat& dst, GpuMat& Dx, GpuMat& Dy, GpuMat& buf, int blockSize, int ksize, + int borderType=BORDER_REFLECT101, Stream& stream = Stream::Null()); + +//! performs per-element multiplication of two full (not packed) Fourier spectrums +//! supports 32FC2 matrixes only (interleaved format) +CV_EXPORTS void mulSpectrums(const GpuMat& a, const GpuMat& b, GpuMat& c, int flags, bool conjB=false, Stream& stream = Stream::Null()); + +//! performs per-element multiplication of two full (not packed) Fourier spectrums +//! supports 32FC2 matrixes only (interleaved format) +CV_EXPORTS void mulAndScaleSpectrums(const GpuMat& a, const GpuMat& b, GpuMat& c, int flags, float scale, bool conjB=false, Stream& stream = Stream::Null()); + +//! Performs a forward or inverse discrete Fourier transform (1D or 2D) of floating point matrix. +//! Param dft_size is the size of DFT transform. +//! +//! If the source matrix is not continous, then additional copy will be done, +//! so to avoid copying ensure the source matrix is continous one. If you want to use +//! preallocated output ensure it is continuous too, otherwise it will be reallocated. +//! +//! Being implemented via CUFFT real-to-complex transform result contains only non-redundant values +//! in CUFFT's format. Result as full complex matrix for such kind of transform cannot be retrieved. +//! +//! For complex-to-real transform it is assumed that the source matrix is packed in CUFFT's format. +CV_EXPORTS void dft(const GpuMat& src, GpuMat& dst, Size dft_size, int flags=0, Stream& stream = Stream::Null()); + +struct CV_EXPORTS ConvolveBuf +{ + Size result_size; + Size block_size; + Size user_block_size; + Size dft_size; + int spect_len; + + GpuMat image_spect, templ_spect, result_spect; + GpuMat image_block, templ_block, result_data; + + void create(Size image_size, Size templ_size); + static Size estimateBlockSize(Size result_size, Size templ_size); +}; + + +//! computes convolution (or cross-correlation) of two images using discrete Fourier transform +//! supports source images of 32FC1 type only +//! result matrix will have 32FC1 type +CV_EXPORTS void convolve(const GpuMat& image, const GpuMat& templ, GpuMat& result, bool ccorr = false); +CV_EXPORTS void convolve(const GpuMat& image, const GpuMat& templ, GpuMat& result, bool ccorr, ConvolveBuf& buf, Stream& stream = Stream::Null()); + +struct CV_EXPORTS MatchTemplateBuf +{ + Size user_block_size; + GpuMat imagef, templf; + std::vector images; + std::vector image_sums; + std::vector image_sqsums; +}; + +//! computes the proximity map for the raster template and the image where the template is searched for +CV_EXPORTS void matchTemplate(const GpuMat& image, const GpuMat& templ, GpuMat& result, int method, Stream &stream = Stream::Null()); + +//! computes the proximity map for the raster template and the image where the template is searched for +CV_EXPORTS void matchTemplate(const GpuMat& image, const GpuMat& templ, GpuMat& result, int method, MatchTemplateBuf &buf, Stream& stream = Stream::Null()); + +//! smoothes the source image and downsamples it +CV_EXPORTS void pyrDown(const GpuMat& src, GpuMat& dst, Stream& stream = Stream::Null()); + +//! upsamples the source image and then smoothes it +CV_EXPORTS void pyrUp(const GpuMat& src, GpuMat& dst, Stream& stream = Stream::Null()); + +//! performs linear blending of two images +//! to avoid accuracy errors sum of weigths shouldn't be very close to zero +CV_EXPORTS void blendLinear(const GpuMat& img1, const GpuMat& img2, const GpuMat& weights1, const GpuMat& weights2, + GpuMat& result, Stream& stream = Stream::Null()); + +//! Performa bilateral filtering of passsed image +CV_EXPORTS void bilateralFilter(const GpuMat& src, GpuMat& dst, int kernel_size, float sigma_color, float sigma_spatial, + int borderMode = BORDER_DEFAULT, Stream& stream = Stream::Null()); + +//! Brute force non-local means algorith (slow but universal) +CV_EXPORTS void nonLocalMeans(const GpuMat& src, GpuMat& dst, float h, int search_window = 21, int block_size = 7, int borderMode = BORDER_DEFAULT, Stream& s = Stream::Null()); + +//! Fast (but approximate)version of non-local means algorith similar to CPU function (running sums technique) +class CV_EXPORTS FastNonLocalMeansDenoising +{ +public: + //! Simple method, recommended for grayscale images (though it supports multichannel images) + void simpleMethod(const GpuMat& src, GpuMat& dst, float h, int search_window = 21, int block_size = 7, Stream& s = Stream::Null()); + + //! Processes luminance and color components separatelly + void labMethod(const GpuMat& src, GpuMat& dst, float h_luminance, float h_color, int search_window = 21, int block_size = 7, Stream& s = Stream::Null()); + +private: + + GpuMat buffer, extended_src_buffer; + GpuMat lab, l, ab; +}; + +struct CV_EXPORTS CannyBuf +{ + void create(const Size& image_size, int apperture_size = 3); + void release(); + + GpuMat dx, dy; + GpuMat mag; + GpuMat map; + GpuMat st1, st2; + Ptr filterDX, filterDY; +}; + +CV_EXPORTS void Canny(const GpuMat& image, GpuMat& edges, double low_thresh, double high_thresh, int apperture_size = 3, bool L2gradient = false); +CV_EXPORTS void Canny(const GpuMat& image, CannyBuf& buf, GpuMat& edges, double low_thresh, double high_thresh, int apperture_size = 3, bool L2gradient = false); +CV_EXPORTS void Canny(const GpuMat& dx, const GpuMat& dy, GpuMat& edges, double low_thresh, double high_thresh, bool L2gradient = false); +CV_EXPORTS void Canny(const GpuMat& dx, const GpuMat& dy, CannyBuf& buf, GpuMat& edges, double low_thresh, double high_thresh, bool L2gradient = false); + +class CV_EXPORTS ImagePyramid +{ +public: + inline ImagePyramid() : nLayers_(0) {} + inline ImagePyramid(const GpuMat& img, int nLayers, Stream& stream = Stream::Null()) + { + build(img, nLayers, stream); + } + + void build(const GpuMat& img, int nLayers, Stream& stream = Stream::Null()); + + void getLayer(GpuMat& outImg, Size outRoi, Stream& stream = Stream::Null()) const; + + inline void release() + { + layer0_.release(); + pyramid_.clear(); + nLayers_ = 0; + } + +private: + GpuMat layer0_; + std::vector pyramid_; + int nLayers_; +}; + +//! HoughLines + +struct HoughLinesBuf +{ + GpuMat accum; + GpuMat list; +}; + +CV_EXPORTS void HoughLines(const GpuMat& src, GpuMat& lines, float rho, float theta, int threshold, bool doSort = false, int maxLines = 4096); +CV_EXPORTS void HoughLines(const GpuMat& src, GpuMat& lines, HoughLinesBuf& buf, float rho, float theta, int threshold, bool doSort = false, int maxLines = 4096); +CV_EXPORTS void HoughLinesDownload(const GpuMat& d_lines, OutputArray h_lines, OutputArray h_votes = noArray()); + +//! HoughLinesP + +//! finds line segments in the black-n-white image using probabalistic Hough transform +CV_EXPORTS void HoughLinesP(const GpuMat& image, GpuMat& lines, HoughLinesBuf& buf, float rho, float theta, int minLineLength, int maxLineGap, int maxLines = 4096); + +//! HoughCircles + +struct HoughCirclesBuf +{ + GpuMat edges; + GpuMat accum; + GpuMat list; + CannyBuf cannyBuf; +}; + +CV_EXPORTS void HoughCircles(const GpuMat& src, GpuMat& circles, int method, float dp, float minDist, int cannyThreshold, int votesThreshold, int minRadius, int maxRadius, int maxCircles = 4096); +CV_EXPORTS void HoughCircles(const GpuMat& src, GpuMat& circles, HoughCirclesBuf& buf, int method, float dp, float minDist, int cannyThreshold, int votesThreshold, int minRadius, int maxRadius, int maxCircles = 4096); +CV_EXPORTS void HoughCirclesDownload(const GpuMat& d_circles, OutputArray h_circles); + +//! finds arbitrary template in the grayscale image using Generalized Hough Transform +//! Ballard, D.H. (1981). Generalizing the Hough transform to detect arbitrary shapes. Pattern Recognition 13 (2): 111-122. +//! Guil, N., González-Linares, J.M. and Zapata, E.L. (1999). Bidimensional shape detection using an invariant approach. Pattern Recognition 32 (6): 1025-1038. +class CV_EXPORTS GeneralizedHough_GPU : public cv::Algorithm +{ +public: + static Ptr create(int method); + + virtual ~GeneralizedHough_GPU(); + + //! set template to search + void setTemplate(const GpuMat& templ, int cannyThreshold = 100, Point templCenter = Point(-1, -1)); + void setTemplate(const GpuMat& edges, const GpuMat& dx, const GpuMat& dy, Point templCenter = Point(-1, -1)); + + //! find template on image + void detect(const GpuMat& image, GpuMat& positions, int cannyThreshold = 100); + void detect(const GpuMat& edges, const GpuMat& dx, const GpuMat& dy, GpuMat& positions); + + void download(const GpuMat& d_positions, OutputArray h_positions, OutputArray h_votes = noArray()); + + void release(); + +protected: + virtual void setTemplateImpl(const GpuMat& edges, const GpuMat& dx, const GpuMat& dy, Point templCenter) = 0; + virtual void detectImpl(const GpuMat& edges, const GpuMat& dx, const GpuMat& dy, GpuMat& positions) = 0; + virtual void releaseImpl() = 0; + +private: + GpuMat edges_; + CannyBuf cannyBuf_; +}; + +////////////////////////////// Matrix reductions ////////////////////////////// + +//! computes mean value and standard deviation of all or selected array elements +//! supports only CV_8UC1 type +CV_EXPORTS void meanStdDev(const GpuMat& mtx, Scalar& mean, Scalar& stddev); +//! buffered version +CV_EXPORTS void meanStdDev(const GpuMat& mtx, Scalar& mean, Scalar& stddev, GpuMat& buf); + +//! computes norm of array +//! supports NORM_INF, NORM_L1, NORM_L2 +//! supports all matrices except 64F +CV_EXPORTS double norm(const GpuMat& src1, int normType=NORM_L2); +CV_EXPORTS double norm(const GpuMat& src1, int normType, GpuMat& buf); +CV_EXPORTS double norm(const GpuMat& src1, int normType, const GpuMat& mask, GpuMat& buf); + +//! computes norm of the difference between two arrays +//! supports NORM_INF, NORM_L1, NORM_L2 +//! supports only CV_8UC1 type +CV_EXPORTS double norm(const GpuMat& src1, const GpuMat& src2, int normType=NORM_L2); + +//! computes sum of array elements +//! supports only single channel images +CV_EXPORTS Scalar sum(const GpuMat& src); +CV_EXPORTS Scalar sum(const GpuMat& src, GpuMat& buf); +CV_EXPORTS Scalar sum(const GpuMat& src, const GpuMat& mask, GpuMat& buf); + +//! computes sum of array elements absolute values +//! supports only single channel images +CV_EXPORTS Scalar absSum(const GpuMat& src); +CV_EXPORTS Scalar absSum(const GpuMat& src, GpuMat& buf); +CV_EXPORTS Scalar absSum(const GpuMat& src, const GpuMat& mask, GpuMat& buf); + +//! computes squared sum of array elements +//! supports only single channel images +CV_EXPORTS Scalar sqrSum(const GpuMat& src); +CV_EXPORTS Scalar sqrSum(const GpuMat& src, GpuMat& buf); +CV_EXPORTS Scalar sqrSum(const GpuMat& src, const GpuMat& mask, GpuMat& buf); + +//! finds global minimum and maximum array elements and returns their values +CV_EXPORTS void minMax(const GpuMat& src, double* minVal, double* maxVal=0, const GpuMat& mask=GpuMat()); +CV_EXPORTS void minMax(const GpuMat& src, double* minVal, double* maxVal, const GpuMat& mask, GpuMat& buf); + +//! finds global minimum and maximum array elements and returns their values with locations +CV_EXPORTS void minMaxLoc(const GpuMat& src, double* minVal, double* maxVal=0, Point* minLoc=0, Point* maxLoc=0, + const GpuMat& mask=GpuMat()); +CV_EXPORTS void minMaxLoc(const GpuMat& src, double* minVal, double* maxVal, Point* minLoc, Point* maxLoc, + const GpuMat& mask, GpuMat& valbuf, GpuMat& locbuf); + +//! counts non-zero array elements +CV_EXPORTS int countNonZero(const GpuMat& src); +CV_EXPORTS int countNonZero(const GpuMat& src, GpuMat& buf); + +//! reduces a matrix to a vector +CV_EXPORTS void reduce(const GpuMat& mtx, GpuMat& vec, int dim, int reduceOp, int dtype = -1, Stream& stream = Stream::Null()); + + +///////////////////////////// Calibration 3D ////////////////////////////////// + +CV_EXPORTS void transformPoints(const GpuMat& src, const Mat& rvec, const Mat& tvec, + GpuMat& dst, Stream& stream = Stream::Null()); + +CV_EXPORTS void projectPoints(const GpuMat& src, const Mat& rvec, const Mat& tvec, + const Mat& camera_mat, const Mat& dist_coef, GpuMat& dst, + Stream& stream = Stream::Null()); + +CV_EXPORTS void solvePnPRansac(const Mat& object, const Mat& image, const Mat& camera_mat, + const Mat& dist_coef, Mat& rvec, Mat& tvec, bool use_extrinsic_guess=false, + int num_iters=100, float max_dist=8.0, int min_inlier_count=100, + std::vector* inliers=NULL); + +//////////////////////////////// Image Labeling //////////////////////////////// + +//!performs labeling via graph cuts of a 2D regular 4-connected graph. +CV_EXPORTS void graphcut(GpuMat& terminals, GpuMat& leftTransp, GpuMat& rightTransp, GpuMat& top, GpuMat& bottom, GpuMat& labels, + GpuMat& buf, Stream& stream = Stream::Null()); + +//!performs labeling via graph cuts of a 2D regular 8-connected graph. +CV_EXPORTS void graphcut(GpuMat& terminals, GpuMat& leftTransp, GpuMat& rightTransp, GpuMat& top, GpuMat& topLeft, GpuMat& topRight, + GpuMat& bottom, GpuMat& bottomLeft, GpuMat& bottomRight, + GpuMat& labels, + GpuMat& buf, Stream& stream = Stream::Null()); + +//! compute mask for Generalized Flood fill componetns labeling. +CV_EXPORTS void connectivityMask(const GpuMat& image, GpuMat& mask, const cv::Scalar& lo, const cv::Scalar& hi, Stream& stream = Stream::Null()); + +//! performs connected componnents labeling. +CV_EXPORTS void labelComponents(const GpuMat& mask, GpuMat& components, int flags = 0, Stream& stream = Stream::Null()); + +////////////////////////////////// Histograms ////////////////////////////////// + +//! Compute levels with even distribution. levels will have 1 row and nLevels cols and CV_32SC1 type. +CV_EXPORTS void evenLevels(GpuMat& levels, int nLevels, int lowerLevel, int upperLevel); +//! Calculates histogram with evenly distributed bins for signle channel source. +//! Supports CV_8UC1, CV_16UC1 and CV_16SC1 source types. +//! Output hist will have one row and histSize cols and CV_32SC1 type. +CV_EXPORTS void histEven(const GpuMat& src, GpuMat& hist, int histSize, int lowerLevel, int upperLevel, Stream& stream = Stream::Null()); +CV_EXPORTS void histEven(const GpuMat& src, GpuMat& hist, GpuMat& buf, int histSize, int lowerLevel, int upperLevel, Stream& stream = Stream::Null()); +//! Calculates histogram with evenly distributed bins for four-channel source. +//! All channels of source are processed separately. +//! Supports CV_8UC4, CV_16UC4 and CV_16SC4 source types. +//! Output hist[i] will have one row and histSize[i] cols and CV_32SC1 type. +CV_EXPORTS void histEven(const GpuMat& src, GpuMat hist[4], int histSize[4], int lowerLevel[4], int upperLevel[4], Stream& stream = Stream::Null()); +CV_EXPORTS void histEven(const GpuMat& src, GpuMat hist[4], GpuMat& buf, int histSize[4], int lowerLevel[4], int upperLevel[4], Stream& stream = Stream::Null()); +//! Calculates histogram with bins determined by levels array. +//! levels must have one row and CV_32SC1 type if source has integer type or CV_32FC1 otherwise. +//! Supports CV_8UC1, CV_16UC1, CV_16SC1 and CV_32FC1 source types. +//! Output hist will have one row and (levels.cols-1) cols and CV_32SC1 type. +CV_EXPORTS void histRange(const GpuMat& src, GpuMat& hist, const GpuMat& levels, Stream& stream = Stream::Null()); +CV_EXPORTS void histRange(const GpuMat& src, GpuMat& hist, const GpuMat& levels, GpuMat& buf, Stream& stream = Stream::Null()); +//! Calculates histogram with bins determined by levels array. +//! All levels must have one row and CV_32SC1 type if source has integer type or CV_32FC1 otherwise. +//! All channels of source are processed separately. +//! Supports CV_8UC4, CV_16UC4, CV_16SC4 and CV_32FC4 source types. +//! Output hist[i] will have one row and (levels[i].cols-1) cols and CV_32SC1 type. +CV_EXPORTS void histRange(const GpuMat& src, GpuMat hist[4], const GpuMat levels[4], Stream& stream = Stream::Null()); +CV_EXPORTS void histRange(const GpuMat& src, GpuMat hist[4], const GpuMat levels[4], GpuMat& buf, Stream& stream = Stream::Null()); + +//! Calculates histogram for 8u one channel image +//! Output hist will have one row, 256 cols and CV32SC1 type. +CV_EXPORTS void calcHist(const GpuMat& src, GpuMat& hist, Stream& stream = Stream::Null()); +CV_EXPORTS void calcHist(const GpuMat& src, GpuMat& hist, GpuMat& buf, Stream& stream = Stream::Null()); + +//! normalizes the grayscale image brightness and contrast by normalizing its histogram +CV_EXPORTS void equalizeHist(const GpuMat& src, GpuMat& dst, Stream& stream = Stream::Null()); +CV_EXPORTS void equalizeHist(const GpuMat& src, GpuMat& dst, GpuMat& hist, Stream& stream = Stream::Null()); +CV_EXPORTS void equalizeHist(const GpuMat& src, GpuMat& dst, GpuMat& hist, GpuMat& buf, Stream& stream = Stream::Null()); + +class CV_EXPORTS CLAHE : public cv::CLAHE +{ +public: + using cv::CLAHE::apply; + virtual void apply(InputArray src, OutputArray dst, Stream& stream) = 0; +}; +CV_EXPORTS Ptr createCLAHE(double clipLimit = 40.0, Size tileGridSize = Size(8, 8)); + +//////////////////////////////// StereoBM_GPU //////////////////////////////// + +class CV_EXPORTS StereoBM_GPU +{ +public: + enum { BASIC_PRESET = 0, PREFILTER_XSOBEL = 1 }; + + enum { DEFAULT_NDISP = 64, DEFAULT_WINSZ = 19 }; + + //! the default constructor + StereoBM_GPU(); + //! the full constructor taking the camera-specific preset, number of disparities and the SAD window size. ndisparities must be multiple of 8. + StereoBM_GPU(int preset, int ndisparities = DEFAULT_NDISP, int winSize = DEFAULT_WINSZ); + + //! the stereo correspondence operator. Finds the disparity for the specified rectified stereo pair + //! Output disparity has CV_8U type. + void operator()(const GpuMat& left, const GpuMat& right, GpuMat& disparity, Stream& stream = Stream::Null()); + + //! Some heuristics that tries to estmate + // if current GPU will be faster than CPU in this algorithm. + // It queries current active device. + static bool checkIfGpuCallReasonable(); + + int preset; + int ndisp; + int winSize; + + // If avergeTexThreshold == 0 => post procesing is disabled + // If avergeTexThreshold != 0 then disparity is set 0 in each point (x,y) where for left image + // SumOfHorizontalGradiensInWindow(x, y, winSize) < (winSize * winSize) * avergeTexThreshold + // i.e. input left image is low textured. + float avergeTexThreshold; + +private: + GpuMat minSSD, leBuf, riBuf; +}; + +////////////////////////// StereoBeliefPropagation /////////////////////////// +// "Efficient Belief Propagation for Early Vision" +// P.Felzenszwalb + +class CV_EXPORTS StereoBeliefPropagation +{ +public: + enum { DEFAULT_NDISP = 64 }; + enum { DEFAULT_ITERS = 5 }; + enum { DEFAULT_LEVELS = 5 }; + + static void estimateRecommendedParams(int width, int height, int& ndisp, int& iters, int& levels); + + //! the default constructor + explicit StereoBeliefPropagation(int ndisp = DEFAULT_NDISP, + int iters = DEFAULT_ITERS, + int levels = DEFAULT_LEVELS, + int msg_type = CV_32F); + + //! the full constructor taking the number of disparities, number of BP iterations on each level, + //! number of levels, truncation of data cost, data weight, + //! truncation of discontinuity cost and discontinuity single jump + //! DataTerm = data_weight * min(fabs(I2-I1), max_data_term) + //! DiscTerm = min(disc_single_jump * fabs(f1-f2), max_disc_term) + //! please see paper for more details + StereoBeliefPropagation(int ndisp, int iters, int levels, + float max_data_term, float data_weight, + float max_disc_term, float disc_single_jump, + int msg_type = CV_32F); + + //! the stereo correspondence operator. Finds the disparity for the specified rectified stereo pair, + //! if disparity is empty output type will be CV_16S else output type will be disparity.type(). + void operator()(const GpuMat& left, const GpuMat& right, GpuMat& disparity, Stream& stream = Stream::Null()); + + + //! version for user specified data term + void operator()(const GpuMat& data, GpuMat& disparity, Stream& stream = Stream::Null()); + + int ndisp; + + int iters; + int levels; + + float max_data_term; + float data_weight; + float max_disc_term; + float disc_single_jump; + + int msg_type; +private: + GpuMat u, d, l, r, u2, d2, l2, r2; + std::vector datas; + GpuMat out; +}; + +/////////////////////////// StereoConstantSpaceBP /////////////////////////// +// "A Constant-Space Belief Propagation Algorithm for Stereo Matching" +// Qingxiong Yang, Liang Wang, Narendra Ahuja +// http://vision.ai.uiuc.edu/~qyang6/ + +class CV_EXPORTS StereoConstantSpaceBP +{ +public: + enum { DEFAULT_NDISP = 128 }; + enum { DEFAULT_ITERS = 8 }; + enum { DEFAULT_LEVELS = 4 }; + enum { DEFAULT_NR_PLANE = 4 }; + + static void estimateRecommendedParams(int width, int height, int& ndisp, int& iters, int& levels, int& nr_plane); + + //! the default constructor + explicit StereoConstantSpaceBP(int ndisp = DEFAULT_NDISP, + int iters = DEFAULT_ITERS, + int levels = DEFAULT_LEVELS, + int nr_plane = DEFAULT_NR_PLANE, + int msg_type = CV_32F); + + //! the full constructor taking the number of disparities, number of BP iterations on each level, + //! number of levels, number of active disparity on the first level, truncation of data cost, data weight, + //! truncation of discontinuity cost, discontinuity single jump and minimum disparity threshold + StereoConstantSpaceBP(int ndisp, int iters, int levels, int nr_plane, + float max_data_term, float data_weight, float max_disc_term, float disc_single_jump, + int min_disp_th = 0, + int msg_type = CV_32F); + + //! the stereo correspondence operator. Finds the disparity for the specified rectified stereo pair, + //! if disparity is empty output type will be CV_16S else output type will be disparity.type(). + void operator()(const GpuMat& left, const GpuMat& right, GpuMat& disparity, Stream& stream = Stream::Null()); + + int ndisp; + + int iters; + int levels; + + int nr_plane; + + float max_data_term; + float data_weight; + float max_disc_term; + float disc_single_jump; + + int min_disp_th; + + int msg_type; + + bool use_local_init_data_cost; +private: + GpuMat messages_buffers; + + GpuMat temp; + GpuMat out; +}; + +/////////////////////////// DisparityBilateralFilter /////////////////////////// +// Disparity map refinement using joint bilateral filtering given a single color image. +// Qingxiong Yang, Liang Wang, Narendra Ahuja +// http://vision.ai.uiuc.edu/~qyang6/ + +class CV_EXPORTS DisparityBilateralFilter +{ +public: + enum { DEFAULT_NDISP = 64 }; + enum { DEFAULT_RADIUS = 3 }; + enum { DEFAULT_ITERS = 1 }; + + //! the default constructor + explicit DisparityBilateralFilter(int ndisp = DEFAULT_NDISP, int radius = DEFAULT_RADIUS, int iters = DEFAULT_ITERS); + + //! the full constructor taking the number of disparities, filter radius, + //! number of iterations, truncation of data continuity, truncation of disparity continuity + //! and filter range sigma + DisparityBilateralFilter(int ndisp, int radius, int iters, float edge_threshold, float max_disc_threshold, float sigma_range); + + //! the disparity map refinement operator. Refine disparity map using joint bilateral filtering given a single color image. + //! disparity must have CV_8U or CV_16S type, image must have CV_8UC1 or CV_8UC3 type. + void operator()(const GpuMat& disparity, const GpuMat& image, GpuMat& dst, Stream& stream = Stream::Null()); + +private: + int ndisp; + int radius; + int iters; + + float edge_threshold; + float max_disc_threshold; + float sigma_range; + + GpuMat table_color; + GpuMat table_space; +}; + //////////////// HOG (Histogram-of-Oriented-Gradients) Descriptor and Object Detector ////////////// - struct CV_EXPORTS HOGConfidence { double scale; @@ -183,8 +1208,185 @@ protected: std::vector image_scales; }; -//////////////////////////// CascadeClassifier //////////////////////////// +////////////////////////////////// BruteForceMatcher ////////////////////////////////// + +class CV_EXPORTS BFMatcher_GPU +{ +public: + explicit BFMatcher_GPU(int norm = cv::NORM_L2); + + // 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 GpuMat& query, const GpuMat& train, + GpuMat& trainIdx, GpuMat& distance, + const GpuMat& mask = GpuMat(), Stream& stream = Stream::Null()); + + // Download trainIdx and distance and convert it to CPU vector with DMatch + static void matchDownload(const GpuMat& trainIdx, const GpuMat& 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 GpuMat& query, const GpuMat& train, std::vector& matches, const GpuMat& mask = GpuMat()); + + // Make gpu collection of trains and masks in suitable format for matchCollection function + void makeGpuCollection(GpuMat& trainCollection, GpuMat& maskCollection, const std::vector& masks = std::vector()); + + // Find one best match from train collection for each query descriptor + void matchCollection(const GpuMat& query, const GpuMat& trainCollection, + GpuMat& trainIdx, GpuMat& imgIdx, GpuMat& distance, + const GpuMat& masks = GpuMat(), Stream& stream = Stream::Null()); + + // Download trainIdx, imgIdx and distance and convert it to vector with DMatch + static void matchDownload(const GpuMat& trainIdx, const GpuMat& imgIdx, const GpuMat& 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 GpuMat& 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 GpuMat& query, const GpuMat& train, + GpuMat& trainIdx, GpuMat& distance, GpuMat& allDist, int k, + const GpuMat& mask = GpuMat(), Stream& stream = Stream::Null()); + + // 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 GpuMat& trainIdx, const GpuMat& 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 GpuMat& query, const GpuMat& train, + std::vector< std::vector >& matches, int k, const GpuMat& mask = GpuMat(), + bool compactResult = false); + + // Find k best matches from train collection for each query descriptor (in increasing order of distances) + void knnMatch2Collection(const GpuMat& query, const GpuMat& trainCollection, + GpuMat& trainIdx, GpuMat& imgIdx, GpuMat& distance, + const GpuMat& maskCollection = GpuMat(), Stream& stream = Stream::Null()); + + // 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 GpuMat& trainIdx, const GpuMat& imgIdx, const GpuMat& 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 GpuMat& 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 GpuMat& query, const GpuMat& train, + GpuMat& trainIdx, GpuMat& distance, GpuMat& nMatches, float maxDistance, + const GpuMat& mask = GpuMat(), Stream& stream = Stream::Null()); + + // 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 GpuMat& trainIdx, const GpuMat& distance, const GpuMat& 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 GpuMat& query, const GpuMat& train, + std::vector< std::vector >& matches, float maxDistance, + const GpuMat& mask = GpuMat(), 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 GpuMat& query, GpuMat& trainIdx, GpuMat& imgIdx, GpuMat& distance, GpuMat& nMatches, float maxDistance, + const std::vector& masks = std::vector(), Stream& stream = Stream::Null()); + + // 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 GpuMat& trainIdx, const GpuMat& imgIdx, const GpuMat& distance, const GpuMat& 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 GpuMat& query, std::vector< std::vector >& matches, float maxDistance, + const std::vector& masks = std::vector(), bool compactResult = false); + + int norm; + +private: + std::vector trainDescCollection; +}; + +template +class CV_EXPORTS BruteForceMatcher_GPU; + +template +class CV_EXPORTS BruteForceMatcher_GPU< L1 > : public BFMatcher_GPU +{ +public: + explicit BruteForceMatcher_GPU() : BFMatcher_GPU(NORM_L1) {} + explicit BruteForceMatcher_GPU(L1 /*d*/) : BFMatcher_GPU(NORM_L1) {} +}; +template +class CV_EXPORTS BruteForceMatcher_GPU< L2 > : public BFMatcher_GPU +{ +public: + explicit BruteForceMatcher_GPU() : BFMatcher_GPU(NORM_L2) {} + explicit BruteForceMatcher_GPU(L2 /*d*/) : BFMatcher_GPU(NORM_L2) {} +}; +template <> class CV_EXPORTS BruteForceMatcher_GPU< Hamming > : public BFMatcher_GPU +{ +public: + explicit BruteForceMatcher_GPU() : BFMatcher_GPU(NORM_HAMMING) {} + explicit BruteForceMatcher_GPU(Hamming /*d*/) : BFMatcher_GPU(NORM_HAMMING) {} +}; + +////////////////////////////////// CascadeClassifier_GPU ////////////////////////////////////////// // The cascade classifier class for object detection: supports old haar and new lbp xlm formats and nvbin for haar cascades olny. class CV_EXPORTS CascadeClassifier_GPU { @@ -214,39 +1416,950 @@ private: friend class CascadeClassifier_GPU_LBP; }; -//////////////////////////// Labeling //////////////////////////// +////////////////////////////////// FAST ////////////////////////////////////////// -//!performs labeling via graph cuts of a 2D regular 4-connected graph. -CV_EXPORTS void graphcut(GpuMat& terminals, GpuMat& leftTransp, GpuMat& rightTransp, GpuMat& top, GpuMat& bottom, GpuMat& labels, - GpuMat& buf, Stream& stream = Stream::Null()); +class CV_EXPORTS FAST_GPU +{ +public: + enum + { + LOCATION_ROW = 0, + RESPONSE_ROW, + ROWS_COUNT + }; -//!performs labeling via graph cuts of a 2D regular 8-connected graph. -CV_EXPORTS void graphcut(GpuMat& terminals, GpuMat& leftTransp, GpuMat& rightTransp, GpuMat& top, GpuMat& topLeft, GpuMat& topRight, - GpuMat& bottom, GpuMat& bottomLeft, GpuMat& bottomRight, - GpuMat& labels, - GpuMat& buf, Stream& stream = Stream::Null()); + // all features have same size + static const int FEATURE_SIZE = 7; -//! compute mask for Generalized Flood fill componetns labeling. -CV_EXPORTS void connectivityMask(const GpuMat& image, GpuMat& mask, const cv::Scalar& lo, const cv::Scalar& hi, Stream& stream = Stream::Null()); + explicit FAST_GPU(int threshold, bool nonmaxSupression = true, double keypointsRatio = 0.05); -//! performs connected componnents labeling. -CV_EXPORTS void labelComponents(const GpuMat& mask, GpuMat& components, int flags = 0, Stream& stream = Stream::Null()); + //! finds the keypoints using FAST detector + //! supports only CV_8UC1 images + void operator ()(const GpuMat& image, const GpuMat& mask, GpuMat& keypoints); + void operator ()(const GpuMat& image, const GpuMat& mask, std::vector& keypoints); -//////////////////////////// Calib3d //////////////////////////// + //! download keypoints from device to host memory + static void downloadKeypoints(const GpuMat& d_keypoints, std::vector& keypoints); -CV_EXPORTS void transformPoints(const GpuMat& src, const Mat& rvec, const Mat& tvec, - GpuMat& dst, Stream& stream = Stream::Null()); + //! convert keypoints to KeyPoint vector + static void convertKeypoints(const Mat& h_keypoints, std::vector& keypoints); -CV_EXPORTS void projectPoints(const GpuMat& src, const Mat& rvec, const Mat& tvec, - const Mat& camera_mat, const Mat& dist_coef, GpuMat& dst, - Stream& stream = Stream::Null()); + //! release temporary buffer's memory + void release(); -CV_EXPORTS void solvePnPRansac(const Mat& object, const Mat& image, const Mat& camera_mat, - const Mat& dist_coef, Mat& rvec, Mat& tvec, bool use_extrinsic_guess=false, - int num_iters=100, float max_dist=8.0, int min_inlier_count=100, - std::vector* inliers=NULL); + bool nonmaxSupression; -//////////////////////////// VStab //////////////////////////// + int threshold; + + //! max keypoints = keypointsRatio * img.size().area() + double keypointsRatio; + + //! find keypoints and compute it's response if nonmaxSupression is true + //! return count of detected keypoints + int calcKeyPointsLocation(const GpuMat& image, const GpuMat& mask); + + //! get final array of keypoints + //! performs nonmax supression if needed + //! return final count of keypoints + int getKeyPoints(GpuMat& keypoints); + +private: + GpuMat kpLoc_; + int count_; + + GpuMat score_; + + GpuMat d_keypoints_; +}; + +////////////////////////////////// ORB ////////////////////////////////////////// + +class CV_EXPORTS ORB_GPU +{ +public: + enum + { + X_ROW = 0, + Y_ROW, + RESPONSE_ROW, + ANGLE_ROW, + OCTAVE_ROW, + SIZE_ROW, + ROWS_COUNT + }; + + enum + { + DEFAULT_FAST_THRESHOLD = 20 + }; + + //! Constructor + explicit ORB_GPU(int nFeatures = 500, float scaleFactor = 1.2f, int nLevels = 8, int edgeThreshold = 31, + int firstLevel = 0, int WTA_K = 2, int scoreType = 0, int patchSize = 31); + + //! Compute the ORB features on an image + //! image - the image to compute the features (supports only CV_8UC1 images) + //! mask - the mask to apply + //! keypoints - the resulting keypoints + void operator()(const GpuMat& image, const GpuMat& mask, std::vector& keypoints); + void operator()(const GpuMat& image, const GpuMat& mask, GpuMat& keypoints); + + //! Compute the ORB features and descriptors on an image + //! image - the image to compute the features (supports only CV_8UC1 images) + //! mask - the mask to apply + //! keypoints - the resulting keypoints + //! descriptors - descriptors array + void operator()(const GpuMat& image, const GpuMat& mask, std::vector& keypoints, GpuMat& descriptors); + void operator()(const GpuMat& image, const GpuMat& mask, GpuMat& keypoints, GpuMat& descriptors); + + //! download keypoints from device to host memory + static void downloadKeyPoints(const GpuMat& d_keypoints, std::vector& keypoints); + //! convert keypoints to KeyPoint vector + static void convertKeyPoints(const Mat& d_keypoints, std::vector& keypoints); + + //! returns the descriptor size in bytes + inline int descriptorSize() const { return kBytes; } + + inline void setFastParams(int threshold, bool nonmaxSupression = true) + { + fastDetector_.threshold = threshold; + fastDetector_.nonmaxSupression = nonmaxSupression; + } + + //! release temporary buffer's memory + void release(); + + //! if true, image will be blurred before descriptors calculation + bool blurForDescriptor; + +private: + enum { kBytes = 32 }; + + void buildScalePyramids(const GpuMat& image, const GpuMat& mask); + + void computeKeyPointsPyramid(); + + void computeDescriptors(GpuMat& descriptors); + + void mergeKeyPoints(GpuMat& keypoints); + + int nFeatures_; + float scaleFactor_; + int nLevels_; + int edgeThreshold_; + int firstLevel_; + int WTA_K_; + int scoreType_; + int patchSize_; + + // The number of desired features per scale + std::vector n_features_per_level_; + + // Points to compute BRIEF descriptors from + GpuMat pattern_; + + std::vector imagePyr_; + std::vector maskPyr_; + + GpuMat buf_; + + std::vector keyPointsPyr_; + std::vector keyPointsCount_; + + FAST_GPU fastDetector_; + + Ptr blurFilter; + + GpuMat d_keypoints_; +}; + +////////////////////////////////// Optical Flow ////////////////////////////////////////// + +class CV_EXPORTS BroxOpticalFlow +{ +public: + BroxOpticalFlow(float alpha_, float gamma_, float scale_factor_, int inner_iterations_, int outer_iterations_, int solver_iterations_) : + alpha(alpha_), gamma(gamma_), scale_factor(scale_factor_), + inner_iterations(inner_iterations_), outer_iterations(outer_iterations_), solver_iterations(solver_iterations_) + { + } + + //! Compute optical flow + //! frame0 - source frame (supports only CV_32FC1 type) + //! frame1 - frame to track (with the same size and type as frame0) + //! u - flow horizontal component (along x axis) + //! v - flow vertical component (along y axis) + void operator ()(const GpuMat& frame0, const GpuMat& frame1, GpuMat& u, GpuMat& v, Stream& stream = Stream::Null()); + + //! flow smoothness + float alpha; + + //! gradient constancy importance + float gamma; + + //! pyramid scale factor + float scale_factor; + + //! number of lagged non-linearity iterations (inner loop) + int inner_iterations; + + //! number of warping iterations (number of pyramid levels) + int outer_iterations; + + //! number of linear system solver iterations + int solver_iterations; + + GpuMat buf; +}; + +class CV_EXPORTS GoodFeaturesToTrackDetector_GPU +{ +public: + explicit GoodFeaturesToTrackDetector_GPU(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 GpuMat& image, GpuMat& corners, const GpuMat& mask = GpuMat()); + + int maxCorners; + double qualityLevel; + double minDistance; + + int blockSize; + bool useHarrisDetector; + double harrisK; + + void releaseMemory() + { + Dx_.release(); + Dy_.release(); + buf_.release(); + eig_.release(); + minMaxbuf_.release(); + tmpCorners_.release(); + } + +private: + GpuMat Dx_; + GpuMat Dy_; + GpuMat buf_; + GpuMat eig_; + GpuMat minMaxbuf_; + GpuMat tmpCorners_; +}; + +inline GoodFeaturesToTrackDetector_GPU::GoodFeaturesToTrackDetector_GPU(int maxCorners_, double qualityLevel_, double minDistance_, + int blockSize_, bool useHarrisDetector_, double harrisK_) +{ + maxCorners = maxCorners_; + qualityLevel = qualityLevel_; + minDistance = minDistance_; + blockSize = blockSize_; + useHarrisDetector = useHarrisDetector_; + harrisK = harrisK_; +} + + +class CV_EXPORTS PyrLKOpticalFlow +{ +public: + PyrLKOpticalFlow(); + + void sparse(const GpuMat& prevImg, const GpuMat& nextImg, const GpuMat& prevPts, GpuMat& nextPts, + GpuMat& status, GpuMat* err = 0); + + void dense(const GpuMat& prevImg, const GpuMat& nextImg, GpuMat& u, GpuMat& v, GpuMat* err = 0); + + void releaseMemory(); + + Size winSize; + int maxLevel; + int iters; + bool useInitialFlow; + +private: + std::vector prevPyr_; + std::vector nextPyr_; + + GpuMat buf_; + + GpuMat uPyr_[2]; + GpuMat vPyr_[2]; +}; + + +class CV_EXPORTS FarnebackOpticalFlow +{ +public: + FarnebackOpticalFlow() + { + numLevels = 5; + pyrScale = 0.5; + fastPyramids = false; + winSize = 13; + numIters = 10; + polyN = 5; + polySigma = 1.1; + flags = 0; + } + + int numLevels; + double pyrScale; + bool fastPyramids; + int winSize; + int numIters; + int polyN; + double polySigma; + int flags; + + void operator ()(const GpuMat &frame0, const GpuMat &frame1, GpuMat &flowx, GpuMat &flowy, Stream &s = Stream::Null()); + + void releaseMemory() + { + frames_[0].release(); + frames_[1].release(); + pyrLevel_[0].release(); + pyrLevel_[1].release(); + M_.release(); + bufM_.release(); + R_[0].release(); + R_[1].release(); + blurredFrame_[0].release(); + blurredFrame_[1].release(); + pyramid0_.clear(); + pyramid1_.clear(); + } + +private: + void prepareGaussian( + int n, double sigma, float *g, float *xg, float *xxg, + double &ig11, double &ig03, double &ig33, double &ig55); + + void setPolynomialExpansionConsts(int n, double sigma); + + void updateFlow_boxFilter( + const GpuMat& R0, const GpuMat& R1, GpuMat& flowx, GpuMat &flowy, + GpuMat& M, GpuMat &bufM, int blockSize, bool updateMatrices, Stream streams[]); + + void updateFlow_gaussianBlur( + const GpuMat& R0, const GpuMat& R1, GpuMat& flowx, GpuMat& flowy, + GpuMat& M, GpuMat &bufM, int blockSize, bool updateMatrices, Stream streams[]); + + GpuMat frames_[2]; + GpuMat pyrLevel_[2], M_, bufM_, R_[2], blurredFrame_[2]; + std::vector pyramid0_, pyramid1_; +}; + + +// 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_GPU +{ +public: + OpticalFlowDual_TVL1_GPU(); + + void operator ()(const GpuMat& I0, const GpuMat& I1, GpuMat& flowx, GpuMat& 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; + + double scaleStep; + + bool useInitialFlow; + +private: + void procOneScale(const GpuMat& I0, const GpuMat& I1, GpuMat& u1, GpuMat& u2); + + std::vector I0s; + std::vector I1s; + std::vector u1s; + std::vector u2s; + + GpuMat I1x_buf; + GpuMat I1y_buf; + + GpuMat I1w_buf; + GpuMat I1wx_buf; + GpuMat I1wy_buf; + + GpuMat grad_buf; + GpuMat rho_c_buf; + + GpuMat p11_buf; + GpuMat p12_buf; + GpuMat p21_buf; + GpuMat p22_buf; + + GpuMat diff_buf; + GpuMat norm_buf; +}; + + +//! Calculates optical flow for 2 images using block matching algorithm */ +CV_EXPORTS void calcOpticalFlowBM(const GpuMat& prev, const GpuMat& curr, + Size block_size, Size shift_size, Size max_range, bool use_previous, + GpuMat& velx, GpuMat& vely, GpuMat& buf, + Stream& stream = Stream::Null()); + +class CV_EXPORTS FastOpticalFlowBM +{ +public: + void operator ()(const GpuMat& I0, const GpuMat& I1, GpuMat& flowx, GpuMat& flowy, int search_window = 21, int block_window = 7, Stream& s = Stream::Null()); + +private: + GpuMat buffer; + GpuMat extended_I0; + GpuMat extended_I1; +}; + + +//! Interpolate frames (images) using provided optical flow (displacement field). +//! frame0 - frame 0 (32-bit floating point images, single channel) +//! frame1 - frame 1 (the same type and size) +//! fu - forward horizontal displacement +//! fv - forward vertical displacement +//! bu - backward horizontal displacement +//! bv - backward vertical displacement +//! pos - new frame position +//! newFrame - new frame +//! buf - temporary buffer, will have width x 6*height size, CV_32FC1 type and contain 6 GpuMat; +//! occlusion masks 0, occlusion masks 1, +//! interpolated forward flow 0, interpolated forward flow 1, +//! interpolated backward flow 0, interpolated backward flow 1 +//! +CV_EXPORTS void interpolateFrames(const GpuMat& frame0, const GpuMat& frame1, + const GpuMat& fu, const GpuMat& fv, + const GpuMat& bu, const GpuMat& bv, + float pos, GpuMat& newFrame, GpuMat& buf, + Stream& stream = Stream::Null()); + +CV_EXPORTS void createOpticalFlowNeedleMap(const GpuMat& u, const GpuMat& v, GpuMat& vertex, GpuMat& colors); + + +//////////////////////// Background/foreground segmentation //////////////////////// + +// Foreground Object Detection from Videos Containing Complex Background. +// Liyuan Li, Weimin Huang, Irene Y.H. Gu, and Qi Tian. +// ACM MM2003 9p +class CV_EXPORTS FGDStatModel +{ +public: + struct CV_EXPORTS Params + { + int Lc; // Quantized levels per 'color' component. Power of two, typically 32, 64 or 128. + int N1c; // Number of color vectors used to model normal background color variation at a given pixel. + int N2c; // Number of color vectors retained at given pixel. Must be > N1c, typically ~ 5/3 of N1c. + // Used to allow the first N1c vectors to adapt over time to changing background. + + int Lcc; // Quantized levels per 'color co-occurrence' component. Power of two, typically 16, 32 or 64. + int N1cc; // Number of color co-occurrence vectors used to model normal background color variation at a given pixel. + int N2cc; // Number of color co-occurrence vectors retained at given pixel. Must be > N1cc, typically ~ 5/3 of N1cc. + // Used to allow the first N1cc vectors to adapt over time to changing background. + + bool is_obj_without_holes; // If TRUE we ignore holes within foreground blobs. Defaults to TRUE. + int perform_morphing; // Number of erode-dilate-erode foreground-blob cleanup iterations. + // These erase one-pixel junk blobs and merge almost-touching blobs. Default value is 1. + + float alpha1; // How quickly we forget old background pixel values seen. Typically set to 0.1. + float alpha2; // "Controls speed of feature learning". Depends on T. Typical value circa 0.005. + float alpha3; // Alternate to alpha2, used (e.g.) for quicker initial convergence. Typical value 0.1. + + float delta; // Affects color and color co-occurrence quantization, typically set to 2. + float T; // A percentage value which determines when new features can be recognized as new background. (Typically 0.9). + float minArea; // Discard foreground blobs whose bounding box is smaller than this threshold. + + // default Params + Params(); + }; + + // out_cn - channels count in output result (can be 3 or 4) + // 4-channels require more memory, but a bit faster + explicit FGDStatModel(int out_cn = 3); + explicit FGDStatModel(const cv::gpu::GpuMat& firstFrame, const Params& params = Params(), int out_cn = 3); + + ~FGDStatModel(); + + void create(const cv::gpu::GpuMat& firstFrame, const Params& params = Params()); + void release(); + + int update(const cv::gpu::GpuMat& curFrame); + + //8UC3 or 8UC4 reference background image + cv::gpu::GpuMat background; + + //8UC1 foreground image + cv::gpu::GpuMat foreground; + + std::vector< std::vector > foreground_regions; + +private: + FGDStatModel(const FGDStatModel&); + FGDStatModel& operator=(const FGDStatModel&); + + class Impl; + std::auto_ptr impl_; +}; + +/*! + Gaussian Mixture-based Backbround/Foreground Segmentation Algorithm + + The class implements the following algorithm: + "An improved adaptive background mixture model for real-time tracking with shadow detection" + P. KadewTraKuPong and R. Bowden, + Proc. 2nd European Workshp on Advanced Video-Based Surveillance Systems, 2001." + http://personal.ee.surrey.ac.uk/Personal/R.Bowden/publications/avbs01/avbs01.pdf +*/ +class CV_EXPORTS MOG_GPU +{ +public: + //! the default constructor + MOG_GPU(int nmixtures = -1); + + //! re-initiaization method + void initialize(Size frameSize, int frameType); + + //! the update operator + void operator()(const GpuMat& frame, GpuMat& fgmask, float learningRate = 0.0f, Stream& stream = Stream::Null()); + + //! computes a background image which are the mean of all background gaussians + void getBackgroundImage(GpuMat& backgroundImage, Stream& stream = Stream::Null()) const; + + //! releases all inner buffers + void release(); + + int history; + float varThreshold; + float backgroundRatio; + float noiseSigma; + +private: + int nmixtures_; + + Size frameSize_; + int frameType_; + int nframes_; + + GpuMat weight_; + GpuMat sortKey_; + GpuMat mean_; + GpuMat var_; +}; + +/*! + The class implements the following algorithm: + "Improved adaptive Gausian mixture model for background subtraction" + Z.Zivkovic + International Conference Pattern Recognition, UK, August, 2004. + http://www.zoranz.net/Publications/zivkovic2004ICPR.pdf +*/ +class CV_EXPORTS MOG2_GPU +{ +public: + //! the default constructor + MOG2_GPU(int nmixtures = -1); + + //! re-initiaization method + void initialize(Size frameSize, int frameType); + + //! the update operator + void operator()(const GpuMat& frame, GpuMat& fgmask, float learningRate = -1.0f, Stream& stream = Stream::Null()); + + //! computes a background image which are the mean of all background gaussians + void getBackgroundImage(GpuMat& backgroundImage, Stream& stream = Stream::Null()) const; + + //! releases all inner buffers + void release(); + + // parameters + // you should call initialize after parameters changes + + int history; + + //! here it is the maximum allowed number of mixture components. + //! Actual number is determined dynamically per pixel + float varThreshold; + // threshold on the squared Mahalanobis distance to decide if it is well described + // by the background model or not. Related to Cthr from the paper. + // This does not influence the update of the background. A typical value could be 4 sigma + // and that is varThreshold=4*4=16; Corresponds to Tb in the paper. + + ///////////////////////// + // less important parameters - things you might change but be carefull + //////////////////////// + + float backgroundRatio; + // corresponds to fTB=1-cf from the paper + // TB - threshold when the component becomes significant enough to be included into + // the background model. It is the TB=1-cf from the paper. So I use cf=0.1 => TB=0. + // For alpha=0.001 it means that the mode should exist for approximately 105 frames before + // it is considered foreground + // float noiseSigma; + float varThresholdGen; + + //correspondts to Tg - threshold on the squared Mahalan. dist. to decide + //when a sample is close to the existing components. If it is not close + //to any a new component will be generated. I use 3 sigma => Tg=3*3=9. + //Smaller Tg leads to more generated components and higher Tg might make + //lead to small number of components but they can grow too large + float fVarInit; + float fVarMin; + float fVarMax; + + //initial variance for the newly generated components. + //It will will influence the speed of adaptation. A good guess should be made. + //A simple way is to estimate the typical standard deviation from the images. + //I used here 10 as a reasonable value + // min and max can be used to further control the variance + float fCT; //CT - complexity reduction prior + //this is related to the number of samples needed to accept that a component + //actually exists. We use CT=0.05 of all the samples. By setting CT=0 you get + //the standard Stauffer&Grimson algorithm (maybe not exact but very similar) + + //shadow detection parameters + bool bShadowDetection; //default 1 - do shadow detection + unsigned char nShadowDetection; //do shadow detection - insert this value as the detection result - 127 default value + float fTau; + // Tau - shadow threshold. The shadow is detected if the pixel is darker + //version of the background. Tau is a threshold on how much darker the shadow can be. + //Tau= 0.5 means that if pixel is more than 2 times darker then it is not shadow + //See: Prati,Mikic,Trivedi,Cucchiarra,"Detecting Moving Shadows...",IEEE PAMI,2003. + +private: + int nmixtures_; + + Size frameSize_; + int frameType_; + int nframes_; + + GpuMat weight_; + GpuMat variance_; + GpuMat mean_; + + GpuMat bgmodelUsedModes_; //keep track of number of modes per pixel +}; + +/** + * Background Subtractor module. Takes a series of images and returns a sequence of mask (8UC1) + * images of the same size, where 255 indicates Foreground and 0 represents Background. + * This class implements an algorithm described in "Visual Tracking of Human Visitors under + * Variable-Lighting Conditions for a Responsive Audio Art Installation," A. Godbehere, + * A. Matsukawa, K. Goldberg, American Control Conference, Montreal, June 2012. + */ +class CV_EXPORTS GMG_GPU +{ +public: + GMG_GPU(); + + /** + * Validate parameters and set up data structures for appropriate frame size. + * @param frameSize Input frame size + * @param min Minimum value taken on by pixels in image sequence. Usually 0 + * @param max Maximum value taken on by pixels in image sequence. e.g. 1.0 or 255 + */ + void initialize(Size frameSize, float min = 0.0f, float max = 255.0f); + + /** + * Performs single-frame background subtraction and builds up a statistical background image + * model. + * @param frame Input frame + * @param fgmask Output mask image representing foreground and background pixels + * @param stream Stream for the asynchronous version + */ + void operator ()(const GpuMat& frame, GpuMat& fgmask, float learningRate = -1.0f, Stream& stream = Stream::Null()); + + //! Releases all inner buffers + void release(); + + //! Total number of distinct colors to maintain in histogram. + int maxFeatures; + + //! Set between 0.0 and 1.0, determines how quickly features are "forgotten" from histograms. + float learningRate; + + //! Number of frames of video to use to initialize histograms. + int numInitializationFrames; + + //! Number of discrete levels in each channel to be used in histograms. + int quantizationLevels; + + //! Prior probability that any given pixel is a background pixel. A sensitivity parameter. + float backgroundPrior; + + //! Value above which pixel is determined to be FG. + float decisionThreshold; + + //! Smoothing radius, in pixels, for cleaning up FG image. + int smoothingRadius; + + //! Perform background model update. + bool updateBackgroundModel; + +private: + float maxVal_, minVal_; + + Size frameSize_; + + int frameNum_; + + GpuMat nfeatures_; + GpuMat colors_; + GpuMat weights_; + + Ptr boxFilter_; + GpuMat buf_; +}; + +////////////////////////////////// Video Encoding ////////////////////////////////// + +// Works only under Windows +// Supports olny H264 video codec and AVI files +class CV_EXPORTS VideoWriter_GPU +{ +public: + struct EncoderParams; + + // Callbacks for video encoder, use it if you want to work with raw video stream + class EncoderCallBack; + + enum SurfaceFormat + { + SF_UYVY = 0, + SF_YUY2, + SF_YV12, + SF_NV12, + SF_IYUV, + SF_BGR, + SF_GRAY = SF_BGR + }; + + VideoWriter_GPU(); + VideoWriter_GPU(const String& fileName, cv::Size frameSize, double fps, SurfaceFormat format = SF_BGR); + VideoWriter_GPU(const String& fileName, cv::Size frameSize, double fps, const EncoderParams& params, SurfaceFormat format = SF_BGR); + VideoWriter_GPU(const cv::Ptr& encoderCallback, cv::Size frameSize, double fps, SurfaceFormat format = SF_BGR); + VideoWriter_GPU(const cv::Ptr& encoderCallback, cv::Size frameSize, double fps, const EncoderParams& params, SurfaceFormat format = SF_BGR); + ~VideoWriter_GPU(); + + // all methods throws cv::Exception if error occurs + void open(const String& fileName, cv::Size frameSize, double fps, SurfaceFormat format = SF_BGR); + void open(const String& fileName, cv::Size frameSize, double fps, const EncoderParams& params, SurfaceFormat format = SF_BGR); + void open(const cv::Ptr& encoderCallback, cv::Size frameSize, double fps, SurfaceFormat format = SF_BGR); + void open(const cv::Ptr& encoderCallback, cv::Size frameSize, double fps, const EncoderParams& params, SurfaceFormat format = SF_BGR); + + bool isOpened() const; + void close(); + + void write(const cv::gpu::GpuMat& image, bool lastFrame = false); + + struct CV_EXPORTS EncoderParams + { + int P_Interval; // NVVE_P_INTERVAL, + int IDR_Period; // NVVE_IDR_PERIOD, + int DynamicGOP; // NVVE_DYNAMIC_GOP, + int RCType; // NVVE_RC_TYPE, + int AvgBitrate; // NVVE_AVG_BITRATE, + int PeakBitrate; // NVVE_PEAK_BITRATE, + int QP_Level_Intra; // NVVE_QP_LEVEL_INTRA, + int QP_Level_InterP; // NVVE_QP_LEVEL_INTER_P, + int QP_Level_InterB; // NVVE_QP_LEVEL_INTER_B, + int DeblockMode; // NVVE_DEBLOCK_MODE, + int ProfileLevel; // NVVE_PROFILE_LEVEL, + int ForceIntra; // NVVE_FORCE_INTRA, + int ForceIDR; // NVVE_FORCE_IDR, + int ClearStat; // NVVE_CLEAR_STAT, + int DIMode; // NVVE_SET_DEINTERLACE, + int Presets; // NVVE_PRESETS, + int DisableCabac; // NVVE_DISABLE_CABAC, + int NaluFramingType; // NVVE_CONFIGURE_NALU_FRAMING_TYPE + int DisableSPSPPS; // NVVE_DISABLE_SPS_PPS + + EncoderParams(); + explicit EncoderParams(const String& configFile); + + void load(const String& configFile); + void save(const String& configFile) const; + }; + + EncoderParams getParams() const; + + class CV_EXPORTS EncoderCallBack + { + public: + enum PicType + { + IFRAME = 1, + PFRAME = 2, + BFRAME = 3 + }; + + virtual ~EncoderCallBack() {} + + // callback function to signal the start of bitstream that is to be encoded + // must return pointer to buffer + virtual uchar* acquireBitStream(int* bufferSize) = 0; + + // callback function to signal that the encoded bitstream is ready to be written to file + virtual void releaseBitStream(unsigned char* data, int size) = 0; + + // callback function to signal that the encoding operation on the frame has started + virtual void onBeginFrame(int frameNumber, PicType picType) = 0; + + // callback function signals that the encoding operation on the frame has finished + virtual void onEndFrame(int frameNumber, PicType picType) = 0; + }; + +private: + VideoWriter_GPU(const VideoWriter_GPU&); + VideoWriter_GPU& operator=(const VideoWriter_GPU&); + + class Impl; + std::auto_ptr impl_; +}; + + +////////////////////////////////// Video Decoding ////////////////////////////////////////// + +namespace detail +{ + class FrameQueue; + class VideoParser; +} + +class CV_EXPORTS VideoReader_GPU +{ +public: + enum Codec + { + MPEG1 = 0, + MPEG2, + MPEG4, + VC1, + H264, + JPEG, + H264_SVC, + H264_MVC, + + Uncompressed_YUV420 = (('I'<<24)|('Y'<<16)|('U'<<8)|('V')), // Y,U,V (4:2:0) + Uncompressed_YV12 = (('Y'<<24)|('V'<<16)|('1'<<8)|('2')), // Y,V,U (4:2:0) + Uncompressed_NV12 = (('N'<<24)|('V'<<16)|('1'<<8)|('2')), // Y,UV (4:2:0) + Uncompressed_YUYV = (('Y'<<24)|('U'<<16)|('Y'<<8)|('V')), // YUYV/YUY2 (4:2:2) + Uncompressed_UYVY = (('U'<<24)|('Y'<<16)|('V'<<8)|('Y')), // UYVY (4:2:2) + }; + + enum ChromaFormat + { + Monochrome=0, + YUV420, + YUV422, + YUV444, + }; + + struct FormatInfo + { + Codec codec; + ChromaFormat chromaFormat; + int width; + int height; + }; + + class VideoSource; + + VideoReader_GPU(); + explicit VideoReader_GPU(const String& filename); + explicit VideoReader_GPU(const cv::Ptr& source); + + ~VideoReader_GPU(); + + void open(const String& filename); + void open(const cv::Ptr& source); + bool isOpened() const; + + void close(); + + bool read(GpuMat& image); + + FormatInfo format() const; + void dumpFormat(std::ostream& st); + + class CV_EXPORTS VideoSource + { + public: + VideoSource() : frameQueue_(0), videoParser_(0) {} + virtual ~VideoSource() {} + + virtual FormatInfo format() const = 0; + virtual void start() = 0; + virtual void stop() = 0; + virtual bool isStarted() const = 0; + virtual bool hasError() const = 0; + + void setFrameQueue(detail::FrameQueue* frameQueue) { frameQueue_ = frameQueue; } + void setVideoParser(detail::VideoParser* videoParser) { videoParser_ = videoParser; } + + protected: + bool parseVideoData(const uchar* data, size_t size, bool endOfStream = false); + + private: + VideoSource(const VideoSource&); + VideoSource& operator =(const VideoSource&); + + detail::FrameQueue* frameQueue_; + detail::VideoParser* videoParser_; + }; + +private: + VideoReader_GPU(const VideoReader_GPU&); + VideoReader_GPU& operator =(const VideoReader_GPU&); + + class Impl; + std::auto_ptr impl_; +}; //! removes points (CV_32FC2, single row matrix) with zero mask value CV_EXPORTS void compactPoints(GpuMat &points0, GpuMat &points1, const GpuMat &mask); @@ -255,6 +2368,8 @@ CV_EXPORTS void calcWobbleSuppressionMaps( int left, int idx, int right, Size size, const Mat &ml, const Mat &mr, GpuMat &mapx, GpuMat &mapy); -}} // namespace cv { namespace gpu { +} // namespace gpu + +} // namespace cv #endif /* __OPENCV_GPU_HPP__ */ diff --git a/modules/gpuarithm/test/test_precomp.cpp b/modules/gpu/include/opencv2/gpu/devmem2d.hpp similarity index 98% rename from modules/gpuarithm/test/test_precomp.cpp rename to modules/gpu/include/opencv2/gpu/devmem2d.hpp index 0fb6521809..18dfcd8acb 100644 --- a/modules/gpuarithm/test/test_precomp.cpp +++ b/modules/gpu/include/opencv2/gpu/devmem2d.hpp @@ -40,4 +40,4 @@ // //M*/ -#include "test_precomp.hpp" +#include "opencv2/core/cuda_devptrs.hpp" diff --git a/modules/gpuarithm/src/precomp.cpp b/modules/gpu/include/opencv2/gpu/gpumat.hpp similarity index 98% rename from modules/gpuarithm/src/precomp.cpp rename to modules/gpu/include/opencv2/gpu/gpumat.hpp index 3c01a2596d..840398b573 100644 --- a/modules/gpuarithm/src/precomp.cpp +++ b/modules/gpu/include/opencv2/gpu/gpumat.hpp @@ -40,4 +40,4 @@ // //M*/ -#include "precomp.hpp" +#include "opencv2/core/gpumat.hpp" diff --git a/modules/gpu/perf/perf_calib3d.cpp b/modules/gpu/perf/perf_calib3d.cpp index 185d9cd684..725f49c531 100644 --- a/modules/gpu/perf/perf_calib3d.cpp +++ b/modules/gpu/perf/perf_calib3d.cpp @@ -46,8 +46,181 @@ using namespace std; using namespace testing; using namespace perf; +////////////////////////////////////////////////////////////////////// +// StereoBM + +typedef std::tr1::tuple pair_string; +DEF_PARAM_TEST_1(ImagePair, pair_string); + +PERF_TEST_P(ImagePair, Calib3D_StereoBM, + Values(pair_string("gpu/perf/aloe.png", "gpu/perf/aloeR.png"))) +{ + declare.time(300.0); + + const cv::Mat imgLeft = readImage(GET_PARAM(0), cv::IMREAD_GRAYSCALE); + ASSERT_FALSE(imgLeft.empty()); + + const cv::Mat imgRight = readImage(GET_PARAM(1), cv::IMREAD_GRAYSCALE); + ASSERT_FALSE(imgRight.empty()); + + const int preset = 0; + const int ndisp = 256; + + if (PERF_RUN_GPU()) + { + cv::gpu::StereoBM_GPU d_bm(preset, ndisp); + + const cv::gpu::GpuMat d_imgLeft(imgLeft); + const cv::gpu::GpuMat d_imgRight(imgRight); + cv::gpu::GpuMat dst; + + TEST_CYCLE() d_bm(d_imgLeft, d_imgRight, dst); + + GPU_SANITY_CHECK(dst); + } + else + { + cv::Ptr bm = cv::createStereoBM(ndisp); + + cv::Mat dst; + + TEST_CYCLE() bm->compute(imgLeft, imgRight, dst); + + CPU_SANITY_CHECK(dst); + } +} + +////////////////////////////////////////////////////////////////////// +// StereoBeliefPropagation + +PERF_TEST_P(ImagePair, Calib3D_StereoBeliefPropagation, + Values(pair_string("gpu/stereobp/aloe-L.png", "gpu/stereobp/aloe-R.png"))) +{ + declare.time(300.0); + + const cv::Mat imgLeft = readImage(GET_PARAM(0)); + ASSERT_FALSE(imgLeft.empty()); + + const cv::Mat imgRight = readImage(GET_PARAM(1)); + ASSERT_FALSE(imgRight.empty()); + + const int ndisp = 64; + + if (PERF_RUN_GPU()) + { + cv::gpu::StereoBeliefPropagation d_bp(ndisp); + + const cv::gpu::GpuMat d_imgLeft(imgLeft); + const cv::gpu::GpuMat d_imgRight(imgRight); + cv::gpu::GpuMat dst; + + TEST_CYCLE() d_bp(d_imgLeft, d_imgRight, dst); + + GPU_SANITY_CHECK(dst); + } + else + { + FAIL_NO_CPU(); + } +} + +////////////////////////////////////////////////////////////////////// +// StereoConstantSpaceBP + +PERF_TEST_P(ImagePair, Calib3D_StereoConstantSpaceBP, + Values(pair_string("gpu/stereobm/aloe-L.png", "gpu/stereobm/aloe-R.png"))) +{ + declare.time(300.0); + + const cv::Mat imgLeft = readImage(GET_PARAM(0), cv::IMREAD_GRAYSCALE); + ASSERT_FALSE(imgLeft.empty()); + + const cv::Mat imgRight = readImage(GET_PARAM(1), cv::IMREAD_GRAYSCALE); + ASSERT_FALSE(imgRight.empty()); + + const int ndisp = 128; + + if (PERF_RUN_GPU()) + { + cv::gpu::StereoConstantSpaceBP d_csbp(ndisp); + + const cv::gpu::GpuMat d_imgLeft(imgLeft); + const cv::gpu::GpuMat d_imgRight(imgRight); + cv::gpu::GpuMat dst; + + TEST_CYCLE() d_csbp(d_imgLeft, d_imgRight, dst); + + GPU_SANITY_CHECK(dst); + } + else + { + FAIL_NO_CPU(); + } +} + +////////////////////////////////////////////////////////////////////// +// DisparityBilateralFilter + +PERF_TEST_P(ImagePair, Calib3D_DisparityBilateralFilter, + Values(pair_string("gpu/stereobm/aloe-L.png", "gpu/stereobm/aloe-disp.png"))) +{ + const cv::Mat img = readImage(GET_PARAM(0), cv::IMREAD_GRAYSCALE); + ASSERT_FALSE(img.empty()); + + const cv::Mat disp = readImage(GET_PARAM(1), cv::IMREAD_GRAYSCALE); + ASSERT_FALSE(disp.empty()); + + const int ndisp = 128; + + if (PERF_RUN_GPU()) + { + cv::gpu::DisparityBilateralFilter d_filter(ndisp); + + const cv::gpu::GpuMat d_img(img); + const cv::gpu::GpuMat d_disp(disp); + cv::gpu::GpuMat dst; + + TEST_CYCLE() d_filter(d_disp, d_img, dst); + + GPU_SANITY_CHECK(dst); + } + else + { + FAIL_NO_CPU(); + } +} + +////////////////////////////////////////////////////////////////////// +// TransformPoints + DEF_PARAM_TEST_1(Count, int); +PERF_TEST_P(Count, Calib3D_TransformPoints, + Values(5000, 10000, 20000)) +{ + const int count = GetParam(); + + cv::Mat src(1, count, CV_32FC3); + declare.in(src, WARMUP_RNG); + + const cv::Mat rvec = cv::Mat::ones(1, 3, CV_32FC1); + const cv::Mat tvec = cv::Mat::ones(1, 3, CV_32FC1); + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_src(src); + cv::gpu::GpuMat dst; + + TEST_CYCLE() cv::gpu::transformPoints(d_src, rvec, tvec, dst); + + GPU_SANITY_CHECK(dst); + } + else + { + FAIL_NO_CPU(); + } +} + ////////////////////////////////////////////////////////////////////// // ProjectPoints @@ -133,3 +306,66 @@ PERF_TEST_P(Count, Calib3D_SolvePnPRansac, CPU_SANITY_CHECK(tvec, 1e-6); } } + +////////////////////////////////////////////////////////////////////// +// ReprojectImageTo3D + +PERF_TEST_P(Sz_Depth, Calib3D_ReprojectImageTo3D, + Combine(GPU_TYPICAL_MAT_SIZES, + Values(CV_8U, CV_16S))) +{ + const cv::Size size = GET_PARAM(0); + const int depth = GET_PARAM(1); + + cv::Mat src(size, depth); + declare.in(src, WARMUP_RNG); + + cv::Mat Q(4, 4, CV_32FC1); + cv::randu(Q, 0.1, 1.0); + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_src(src); + cv::gpu::GpuMat dst; + + TEST_CYCLE() cv::gpu::reprojectImageTo3D(d_src, dst, Q); + + GPU_SANITY_CHECK(dst); + } + else + { + cv::Mat dst; + + TEST_CYCLE() cv::reprojectImageTo3D(src, dst, Q); + + CPU_SANITY_CHECK(dst); + } +} + +////////////////////////////////////////////////////////////////////// +// DrawColorDisp + +PERF_TEST_P(Sz_Depth, Calib3D_DrawColorDisp, + Combine(GPU_TYPICAL_MAT_SIZES, + Values(CV_8U, CV_16S))) +{ + const cv::Size size = GET_PARAM(0); + const int type = GET_PARAM(1); + + cv::Mat src(size, type); + declare.in(src, WARMUP_RNG); + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_src(src); + cv::gpu::GpuMat dst; + + TEST_CYCLE() cv::gpu::drawColorDisp(d_src, dst, 255); + + GPU_SANITY_CHECK(dst); + } + else + { + FAIL_NO_CPU(); + } +} diff --git a/modules/gpuarithm/perf/perf_element_operations.cpp b/modules/gpu/perf/perf_core.cpp similarity index 63% rename from modules/gpuarithm/perf/perf_element_operations.cpp rename to modules/gpu/perf/perf_core.cpp index 1370da5804..829637f4e3 100644 --- a/modules/gpuarithm/perf/perf_element_operations.cpp +++ b/modules/gpu/perf/perf_core.cpp @@ -48,10 +48,93 @@ using namespace perf; #define ARITHM_MAT_DEPTH Values(CV_8U, CV_16U, CV_32F, CV_64F) +////////////////////////////////////////////////////////////////////// +// Merge + +PERF_TEST_P(Sz_Depth_Cn, Core_Merge, + Combine(GPU_TYPICAL_MAT_SIZES, + ARITHM_MAT_DEPTH, + Values(2, 3, 4))) +{ + const cv::Size size = GET_PARAM(0); + const int depth = GET_PARAM(1); + const int channels = GET_PARAM(2); + + std::vector src(channels); + for (int i = 0; i < channels; ++i) + { + src[i].create(size, depth); + declare.in(src[i], WARMUP_RNG); + } + + if (PERF_RUN_GPU()) + { + std::vector d_src(channels); + for (int i = 0; i < channels; ++i) + d_src[i].upload(src[i]); + + cv::gpu::GpuMat dst; + + TEST_CYCLE() cv::gpu::merge(d_src, dst); + + GPU_SANITY_CHECK(dst, 1e-10); + } + else + { + cv::Mat dst; + + TEST_CYCLE() cv::merge(src, dst); + + CPU_SANITY_CHECK(dst); + } +} + +////////////////////////////////////////////////////////////////////// +// Split + +PERF_TEST_P(Sz_Depth_Cn, Core_Split, + Combine(GPU_TYPICAL_MAT_SIZES, + ARITHM_MAT_DEPTH, + Values(2, 3, 4))) +{ + const cv::Size size = GET_PARAM(0); + const int depth = GET_PARAM(1); + const int channels = GET_PARAM(2); + + cv::Mat src(size, CV_MAKE_TYPE(depth, channels)); + declare.in(src, WARMUP_RNG); + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_src(src); + std::vector dst; + + TEST_CYCLE() cv::gpu::split(d_src, dst); + + const cv::gpu::GpuMat& dst0 = dst[0]; + const cv::gpu::GpuMat& dst1 = dst[1]; + + GPU_SANITY_CHECK(dst0, 1e-10); + GPU_SANITY_CHECK(dst1, 1e-10); + } + else + { + std::vector dst; + + TEST_CYCLE() cv::split(src, dst); + + const cv::Mat& dst0 = dst[0]; + const cv::Mat& dst1 = dst[1]; + + CPU_SANITY_CHECK(dst0); + CPU_SANITY_CHECK(dst1); + } +} + ////////////////////////////////////////////////////////////////////// // AddMat -PERF_TEST_P(Sz_Depth, AddMat, +PERF_TEST_P(Sz_Depth, Core_AddMat, Combine(GPU_TYPICAL_MAT_SIZES, ARITHM_MAT_DEPTH)) { @@ -87,7 +170,7 @@ PERF_TEST_P(Sz_Depth, AddMat, ////////////////////////////////////////////////////////////////////// // AddScalar -PERF_TEST_P(Sz_Depth, AddScalar, +PERF_TEST_P(Sz_Depth, Core_AddScalar, Combine(GPU_TYPICAL_MAT_SIZES, ARITHM_MAT_DEPTH)) { @@ -122,7 +205,7 @@ PERF_TEST_P(Sz_Depth, AddScalar, ////////////////////////////////////////////////////////////////////// // SubtractMat -PERF_TEST_P(Sz_Depth, SubtractMat, +PERF_TEST_P(Sz_Depth, Core_SubtractMat, Combine(GPU_TYPICAL_MAT_SIZES, ARITHM_MAT_DEPTH)) { @@ -158,7 +241,7 @@ PERF_TEST_P(Sz_Depth, SubtractMat, ////////////////////////////////////////////////////////////////////// // SubtractScalar -PERF_TEST_P(Sz_Depth, SubtractScalar, +PERF_TEST_P(Sz_Depth, Core_SubtractScalar, Combine(GPU_TYPICAL_MAT_SIZES, ARITHM_MAT_DEPTH)) { @@ -193,7 +276,7 @@ PERF_TEST_P(Sz_Depth, SubtractScalar, ////////////////////////////////////////////////////////////////////// // MultiplyMat -PERF_TEST_P(Sz_Depth, MultiplyMat, +PERF_TEST_P(Sz_Depth, Core_MultiplyMat, Combine(GPU_TYPICAL_MAT_SIZES, ARITHM_MAT_DEPTH)) { @@ -229,7 +312,7 @@ PERF_TEST_P(Sz_Depth, MultiplyMat, ////////////////////////////////////////////////////////////////////// // MultiplyScalar -PERF_TEST_P(Sz_Depth, MultiplyScalar, +PERF_TEST_P(Sz_Depth, Core_MultiplyScalar, Combine(GPU_TYPICAL_MAT_SIZES, ARITHM_MAT_DEPTH)) { @@ -264,7 +347,7 @@ PERF_TEST_P(Sz_Depth, MultiplyScalar, ////////////////////////////////////////////////////////////////////// // DivideMat -PERF_TEST_P(Sz_Depth, DivideMat, +PERF_TEST_P(Sz_Depth, Core_DivideMat, Combine(GPU_TYPICAL_MAT_SIZES, ARITHM_MAT_DEPTH)) { @@ -300,7 +383,7 @@ PERF_TEST_P(Sz_Depth, DivideMat, ////////////////////////////////////////////////////////////////////// // DivideScalar -PERF_TEST_P(Sz_Depth, DivideScalar, +PERF_TEST_P(Sz_Depth, Core_DivideScalar, Combine(GPU_TYPICAL_MAT_SIZES, ARITHM_MAT_DEPTH)) { @@ -335,7 +418,7 @@ PERF_TEST_P(Sz_Depth, DivideScalar, ////////////////////////////////////////////////////////////////////// // DivideScalarInv -PERF_TEST_P(Sz_Depth, DivideScalarInv, +PERF_TEST_P(Sz_Depth, Core_DivideScalarInv, Combine(GPU_TYPICAL_MAT_SIZES, ARITHM_MAT_DEPTH)) { @@ -370,7 +453,7 @@ PERF_TEST_P(Sz_Depth, DivideScalarInv, ////////////////////////////////////////////////////////////////////// // AbsDiffMat -PERF_TEST_P(Sz_Depth, AbsDiffMat, +PERF_TEST_P(Sz_Depth, Core_AbsDiffMat, Combine(GPU_TYPICAL_MAT_SIZES, ARITHM_MAT_DEPTH)) { @@ -406,7 +489,7 @@ PERF_TEST_P(Sz_Depth, AbsDiffMat, ////////////////////////////////////////////////////////////////////// // AbsDiffScalar -PERF_TEST_P(Sz_Depth, AbsDiffScalar, +PERF_TEST_P(Sz_Depth, Core_AbsDiffScalar, Combine(GPU_TYPICAL_MAT_SIZES, ARITHM_MAT_DEPTH)) { @@ -441,7 +524,7 @@ PERF_TEST_P(Sz_Depth, AbsDiffScalar, ////////////////////////////////////////////////////////////////////// // Abs -PERF_TEST_P(Sz_Depth, Abs, +PERF_TEST_P(Sz_Depth, Core_Abs, Combine(GPU_TYPICAL_MAT_SIZES, Values(CV_16S, CV_32F))) { @@ -469,7 +552,7 @@ PERF_TEST_P(Sz_Depth, Abs, ////////////////////////////////////////////////////////////////////// // Sqr -PERF_TEST_P(Sz_Depth, Sqr, +PERF_TEST_P(Sz_Depth, Core_Sqr, Combine(GPU_TYPICAL_MAT_SIZES, Values(CV_8U, CV_16S, CV_32F))) { @@ -497,7 +580,7 @@ PERF_TEST_P(Sz_Depth, Sqr, ////////////////////////////////////////////////////////////////////// // Sqrt -PERF_TEST_P(Sz_Depth, Sqrt, +PERF_TEST_P(Sz_Depth, Core_Sqrt, Combine(GPU_TYPICAL_MAT_SIZES, Values(CV_8U, CV_16S, CV_32F))) { @@ -529,7 +612,7 @@ PERF_TEST_P(Sz_Depth, Sqrt, ////////////////////////////////////////////////////////////////////// // Log -PERF_TEST_P(Sz_Depth, Log, +PERF_TEST_P(Sz_Depth, Core_Log, Combine(GPU_TYPICAL_MAT_SIZES, Values(CV_8U, CV_16S, CV_32F))) { @@ -561,7 +644,7 @@ PERF_TEST_P(Sz_Depth, Log, ////////////////////////////////////////////////////////////////////// // Exp -PERF_TEST_P(Sz_Depth, Exp, +PERF_TEST_P(Sz_Depth, Core_Exp, Combine(GPU_TYPICAL_MAT_SIZES, Values(CV_8U, CV_16S, CV_32F))) { @@ -595,7 +678,7 @@ PERF_TEST_P(Sz_Depth, Exp, DEF_PARAM_TEST(Sz_Depth_Power, cv::Size, MatDepth, double); -PERF_TEST_P(Sz_Depth_Power, Pow, +PERF_TEST_P(Sz_Depth_Power, Core_Pow, Combine(GPU_TYPICAL_MAT_SIZES, Values(CV_8U, CV_16S, CV_32F), Values(0.3, 2.0, 2.4))) @@ -629,11 +712,11 @@ PERF_TEST_P(Sz_Depth_Power, Pow, ////////////////////////////////////////////////////////////////////// // CompareMat -CV_ENUM(CmpCode, cv::CMP_EQ, cv::CMP_GT, cv::CMP_GE, cv::CMP_LT, cv::CMP_LE, cv::CMP_NE) +CV_ENUM(CmpCode, CMP_EQ, CMP_GT, CMP_GE, CMP_LT, CMP_LE, CMP_NE) DEF_PARAM_TEST(Sz_Depth_Code, cv::Size, MatDepth, CmpCode); -PERF_TEST_P(Sz_Depth_Code, CompareMat, +PERF_TEST_P(Sz_Depth_Code, Core_CompareMat, Combine(GPU_TYPICAL_MAT_SIZES, ARITHM_MAT_DEPTH, CmpCode::all())) @@ -671,7 +754,7 @@ PERF_TEST_P(Sz_Depth_Code, CompareMat, ////////////////////////////////////////////////////////////////////// // CompareScalar -PERF_TEST_P(Sz_Depth_Code, CompareScalar, +PERF_TEST_P(Sz_Depth_Code, Core_CompareScalar, Combine(GPU_TYPICAL_MAT_SIZES, ARITHM_MAT_DEPTH, CmpCode::all())) @@ -708,7 +791,7 @@ PERF_TEST_P(Sz_Depth_Code, CompareScalar, ////////////////////////////////////////////////////////////////////// // BitwiseNot -PERF_TEST_P(Sz_Depth, BitwiseNot, +PERF_TEST_P(Sz_Depth, Core_BitwiseNot, Combine(GPU_TYPICAL_MAT_SIZES, Values(CV_8U, CV_16U, CV_32S))) { @@ -740,7 +823,7 @@ PERF_TEST_P(Sz_Depth, BitwiseNot, ////////////////////////////////////////////////////////////////////// // BitwiseAndMat -PERF_TEST_P(Sz_Depth, BitwiseAndMat, +PERF_TEST_P(Sz_Depth, Core_BitwiseAndMat, Combine(GPU_TYPICAL_MAT_SIZES, Values(CV_8U, CV_16U, CV_32S))) { @@ -776,7 +859,7 @@ PERF_TEST_P(Sz_Depth, BitwiseAndMat, ////////////////////////////////////////////////////////////////////// // BitwiseAndScalar -PERF_TEST_P(Sz_Depth_Cn, BitwiseAndScalar, +PERF_TEST_P(Sz_Depth_Cn, Core_BitwiseAndScalar, Combine(GPU_TYPICAL_MAT_SIZES, Values(CV_8U, CV_16U, CV_32S), GPU_CHANNELS_1_3_4)) @@ -816,7 +899,7 @@ PERF_TEST_P(Sz_Depth_Cn, BitwiseAndScalar, ////////////////////////////////////////////////////////////////////// // BitwiseOrMat -PERF_TEST_P(Sz_Depth, BitwiseOrMat, +PERF_TEST_P(Sz_Depth, Core_BitwiseOrMat, Combine(GPU_TYPICAL_MAT_SIZES, Values(CV_8U, CV_16U, CV_32S))) { @@ -852,7 +935,7 @@ PERF_TEST_P(Sz_Depth, BitwiseOrMat, ////////////////////////////////////////////////////////////////////// // BitwiseOrScalar -PERF_TEST_P(Sz_Depth_Cn, BitwiseOrScalar, +PERF_TEST_P(Sz_Depth_Cn, Core_BitwiseOrScalar, Combine(GPU_TYPICAL_MAT_SIZES, Values(CV_8U, CV_16U, CV_32S), GPU_CHANNELS_1_3_4)) @@ -892,7 +975,7 @@ PERF_TEST_P(Sz_Depth_Cn, BitwiseOrScalar, ////////////////////////////////////////////////////////////////////// // BitwiseXorMat -PERF_TEST_P(Sz_Depth, BitwiseXorMat, +PERF_TEST_P(Sz_Depth, Core_BitwiseXorMat, Combine(GPU_TYPICAL_MAT_SIZES, Values(CV_8U, CV_16U, CV_32S))) { @@ -928,7 +1011,7 @@ PERF_TEST_P(Sz_Depth, BitwiseXorMat, ////////////////////////////////////////////////////////////////////// // BitwiseXorScalar -PERF_TEST_P(Sz_Depth_Cn, BitwiseXorScalar, +PERF_TEST_P(Sz_Depth_Cn, Core_BitwiseXorScalar, Combine(GPU_TYPICAL_MAT_SIZES, Values(CV_8U, CV_16U, CV_32S), GPU_CHANNELS_1_3_4)) @@ -968,7 +1051,7 @@ PERF_TEST_P(Sz_Depth_Cn, BitwiseXorScalar, ////////////////////////////////////////////////////////////////////// // RShift -PERF_TEST_P(Sz_Depth_Cn, RShift, +PERF_TEST_P(Sz_Depth_Cn, Core_RShift, Combine(GPU_TYPICAL_MAT_SIZES, Values(CV_8U, CV_16U, CV_32S), GPU_CHANNELS_1_3_4)) @@ -1002,7 +1085,7 @@ PERF_TEST_P(Sz_Depth_Cn, RShift, ////////////////////////////////////////////////////////////////////// // LShift -PERF_TEST_P(Sz_Depth_Cn, LShift, +PERF_TEST_P(Sz_Depth_Cn, Core_LShift, Combine(GPU_TYPICAL_MAT_SIZES, Values(CV_8U, CV_16U, CV_32S), GPU_CHANNELS_1_3_4)) @@ -1036,7 +1119,7 @@ PERF_TEST_P(Sz_Depth_Cn, LShift, ////////////////////////////////////////////////////////////////////// // MinMat -PERF_TEST_P(Sz_Depth, MinMat, +PERF_TEST_P(Sz_Depth, Core_MinMat, Combine(GPU_TYPICAL_MAT_SIZES, Values(CV_8U, CV_16U, CV_32F))) { @@ -1072,7 +1155,7 @@ PERF_TEST_P(Sz_Depth, MinMat, ////////////////////////////////////////////////////////////////////// // MinScalar -PERF_TEST_P(Sz_Depth, MinScalar, +PERF_TEST_P(Sz_Depth, Core_MinScalar, Combine(GPU_TYPICAL_MAT_SIZES, Values(CV_8U, CV_16U, CV_32F))) { @@ -1107,7 +1190,7 @@ PERF_TEST_P(Sz_Depth, MinScalar, ////////////////////////////////////////////////////////////////////// // MaxMat -PERF_TEST_P(Sz_Depth, MaxMat, +PERF_TEST_P(Sz_Depth, Core_MaxMat, Combine(GPU_TYPICAL_MAT_SIZES, Values(CV_8U, CV_16U, CV_32F))) { @@ -1143,7 +1226,7 @@ PERF_TEST_P(Sz_Depth, MaxMat, ////////////////////////////////////////////////////////////////////// // MaxScalar -PERF_TEST_P(Sz_Depth, MaxScalar, +PERF_TEST_P(Sz_Depth, Core_MaxScalar, Combine(GPU_TYPICAL_MAT_SIZES, Values(CV_8U, CV_16U, CV_32F))) { @@ -1180,7 +1263,7 @@ PERF_TEST_P(Sz_Depth, MaxScalar, DEF_PARAM_TEST(Sz_3Depth, cv::Size, MatDepth, MatDepth, MatDepth); -PERF_TEST_P(Sz_3Depth, AddWeighted, +PERF_TEST_P(Sz_3Depth, Core_AddWeighted, Combine(GPU_TYPICAL_MAT_SIZES, Values(CV_8U, CV_16U, CV_32F, CV_64F), Values(CV_8U, CV_16U, CV_32F, CV_64F), @@ -1217,10 +1300,208 @@ PERF_TEST_P(Sz_3Depth, AddWeighted, } } +////////////////////////////////////////////////////////////////////// +// GEMM + +CV_FLAGS(GemmFlags, 0, GEMM_1_T, GEMM_2_T, GEMM_3_T) +#define ALL_GEMM_FLAGS Values(0, (int)cv::GEMM_1_T, (int)cv::GEMM_2_T, (int)cv::GEMM_3_T, \ + (int)cv::GEMM_1_T | cv::GEMM_2_T, (int)cv::GEMM_1_T | cv::GEMM_3_T, \ + (int)cv::GEMM_1_T | cv::GEMM_2_T | cv::GEMM_3_T) + +DEF_PARAM_TEST(Sz_Type_Flags, cv::Size, MatType, GemmFlags); + +PERF_TEST_P(Sz_Type_Flags, Core_GEMM, + Combine(Values(cv::Size(512, 512), cv::Size(1024, 1024)), + Values(CV_32FC1, CV_32FC2, CV_64FC1), + ALL_GEMM_FLAGS)) +{ + const cv::Size size = GET_PARAM(0); + const int type = GET_PARAM(1); + const int flags = GET_PARAM(2); + + cv::Mat src1(size, type); + declare.in(src1, WARMUP_RNG); + + cv::Mat src2(size, type); + declare.in(src2, WARMUP_RNG); + + cv::Mat src3(size, type); + declare.in(src3, WARMUP_RNG); + + if (PERF_RUN_GPU()) + { + declare.time(5.0); + + const cv::gpu::GpuMat d_src1(src1); + const cv::gpu::GpuMat d_src2(src2); + const cv::gpu::GpuMat d_src3(src3); + cv::gpu::GpuMat dst; + + TEST_CYCLE() cv::gpu::gemm(d_src1, d_src2, 1.0, d_src3, 1.0, dst, flags); + + GPU_SANITY_CHECK(dst, 1e-6); + } + else + { + declare.time(50.0); + + cv::Mat dst; + + TEST_CYCLE() cv::gemm(src1, src2, 1.0, src3, 1.0, dst, flags); + + CPU_SANITY_CHECK(dst); + } +} + +////////////////////////////////////////////////////////////////////// +// Transpose + +PERF_TEST_P(Sz_Type, Core_Transpose, + Combine(GPU_TYPICAL_MAT_SIZES, + Values(CV_8UC1, CV_8UC4, CV_16UC2, CV_16SC2, CV_32SC1, CV_32SC2, CV_64FC1))) +{ + const cv::Size size = GET_PARAM(0); + const int type = GET_PARAM(1); + + cv::Mat src(size, type); + declare.in(src, WARMUP_RNG); + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_src(src); + cv::gpu::GpuMat dst; + + TEST_CYCLE() cv::gpu::transpose(d_src, dst); + + GPU_SANITY_CHECK(dst, 1e-10); + } + else + { + cv::Mat dst; + + TEST_CYCLE() cv::transpose(src, dst); + + CPU_SANITY_CHECK(dst); + } +} + +////////////////////////////////////////////////////////////////////// +// Flip + +enum {FLIP_BOTH = 0, FLIP_X = 1, FLIP_Y = -1}; +CV_ENUM(FlipCode, FLIP_BOTH, FLIP_X, FLIP_Y) + +DEF_PARAM_TEST(Sz_Depth_Cn_Code, cv::Size, MatDepth, MatCn, FlipCode); + +PERF_TEST_P(Sz_Depth_Cn_Code, Core_Flip, + Combine(GPU_TYPICAL_MAT_SIZES, + Values(CV_8U, CV_16U, CV_32F), + GPU_CHANNELS_1_3_4, + FlipCode::all())) +{ + const cv::Size size = GET_PARAM(0); + const int depth = GET_PARAM(1); + const int channels = GET_PARAM(2); + const int flipCode = GET_PARAM(3); + + const int type = CV_MAKE_TYPE(depth, channels); + + cv::Mat src(size, type); + declare.in(src, WARMUP_RNG); + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_src(src); + cv::gpu::GpuMat dst; + + TEST_CYCLE() cv::gpu::flip(d_src, dst, flipCode); + + GPU_SANITY_CHECK(dst); + } + else + { + cv::Mat dst; + + TEST_CYCLE() cv::flip(src, dst, flipCode); + + CPU_SANITY_CHECK(dst); + } +} + +////////////////////////////////////////////////////////////////////// +// LutOneChannel + +PERF_TEST_P(Sz_Type, Core_LutOneChannel, + Combine(GPU_TYPICAL_MAT_SIZES, + Values(CV_8UC1, CV_8UC3))) +{ + const cv::Size size = GET_PARAM(0); + const int type = GET_PARAM(1); + + cv::Mat src(size, type); + declare.in(src, WARMUP_RNG); + + cv::Mat lut(1, 256, CV_8UC1); + declare.in(lut, WARMUP_RNG); + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_src(src); + cv::gpu::GpuMat dst; + + TEST_CYCLE() cv::gpu::LUT(d_src, lut, dst); + + GPU_SANITY_CHECK(dst); + } + else + { + cv::Mat dst; + + TEST_CYCLE() cv::LUT(src, lut, dst); + + CPU_SANITY_CHECK(dst); + } +} + +////////////////////////////////////////////////////////////////////// +// LutMultiChannel + +PERF_TEST_P(Sz_Type, Core_LutMultiChannel, + Combine(GPU_TYPICAL_MAT_SIZES, + Values(CV_8UC3))) +{ + const cv::Size size = GET_PARAM(0); + const int type = GET_PARAM(1); + + cv::Mat src(size, type); + declare.in(src, WARMUP_RNG); + + cv::Mat lut(1, 256, CV_MAKE_TYPE(CV_8U, src.channels())); + declare.in(lut, WARMUP_RNG); + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_src(src); + cv::gpu::GpuMat dst; + + TEST_CYCLE() cv::gpu::LUT(d_src, lut, dst); + + GPU_SANITY_CHECK(dst); + } + else + { + cv::Mat dst; + + TEST_CYCLE() cv::LUT(src, lut, dst); + + CPU_SANITY_CHECK(dst); + } +} + ////////////////////////////////////////////////////////////////////// // MagnitudeComplex -PERF_TEST_P(Sz, MagnitudeComplex, +PERF_TEST_P(Sz, Core_MagnitudeComplex, GPU_TYPICAL_MAT_SIZES) { const cv::Size size = GetParam(); @@ -1253,7 +1534,7 @@ PERF_TEST_P(Sz, MagnitudeComplex, ////////////////////////////////////////////////////////////////////// // MagnitudeSqrComplex -PERF_TEST_P(Sz, MagnitudeSqrComplex, +PERF_TEST_P(Sz, Core_MagnitudeSqrComplex, GPU_TYPICAL_MAT_SIZES) { const cv::Size size = GetParam(); @@ -1279,7 +1560,7 @@ PERF_TEST_P(Sz, MagnitudeSqrComplex, ////////////////////////////////////////////////////////////////////// // Magnitude -PERF_TEST_P(Sz, Magnitude, +PERF_TEST_P(Sz, Core_Magnitude, GPU_TYPICAL_MAT_SIZES) { const cv::Size size = GetParam(); @@ -1313,7 +1594,7 @@ PERF_TEST_P(Sz, Magnitude, ////////////////////////////////////////////////////////////////////// // MagnitudeSqr -PERF_TEST_P(Sz, MagnitudeSqr, +PERF_TEST_P(Sz, Core_MagnitudeSqr, GPU_TYPICAL_MAT_SIZES) { const cv::Size size = GetParam(); @@ -1345,7 +1626,7 @@ PERF_TEST_P(Sz, MagnitudeSqr, DEF_PARAM_TEST(Sz_AngleInDegrees, cv::Size, bool); -PERF_TEST_P(Sz_AngleInDegrees, Phase, +PERF_TEST_P(Sz_AngleInDegrees, Core_Phase, Combine(GPU_TYPICAL_MAT_SIZES, Bool())) { @@ -1381,7 +1662,7 @@ PERF_TEST_P(Sz_AngleInDegrees, Phase, ////////////////////////////////////////////////////////////////////// // CartToPolar -PERF_TEST_P(Sz_AngleInDegrees, CartToPolar, +PERF_TEST_P(Sz_AngleInDegrees, Core_CartToPolar, Combine(GPU_TYPICAL_MAT_SIZES, Bool())) { @@ -1421,7 +1702,7 @@ PERF_TEST_P(Sz_AngleInDegrees, CartToPolar, ////////////////////////////////////////////////////////////////////// // PolarToCart -PERF_TEST_P(Sz_AngleInDegrees, PolarToCart, +PERF_TEST_P(Sz_AngleInDegrees, Core_PolarToCart, Combine(GPU_TYPICAL_MAT_SIZES, Bool())) { @@ -1459,38 +1740,417 @@ PERF_TEST_P(Sz_AngleInDegrees, PolarToCart, } ////////////////////////////////////////////////////////////////////// -// Threshold +// MeanStdDev -CV_ENUM(ThreshOp, cv::THRESH_BINARY, cv::THRESH_BINARY_INV, cv::THRESH_TRUNC, cv::THRESH_TOZERO, cv::THRESH_TOZERO_INV) +PERF_TEST_P(Sz, Core_MeanStdDev, + GPU_TYPICAL_MAT_SIZES) +{ + const cv::Size size = GetParam(); -DEF_PARAM_TEST(Sz_Depth_Op, cv::Size, MatDepth, ThreshOp); + cv::Mat src(size, CV_8UC1); + declare.in(src, WARMUP_RNG); -PERF_TEST_P(Sz_Depth_Op, Threshold, + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_src(src); + cv::gpu::GpuMat d_buf; + cv::Scalar gpu_mean; + cv::Scalar gpu_stddev; + + TEST_CYCLE() cv::gpu::meanStdDev(d_src, gpu_mean, gpu_stddev, d_buf); + + SANITY_CHECK(gpu_mean); + SANITY_CHECK(gpu_stddev); + } + else + { + cv::Scalar cpu_mean; + cv::Scalar cpu_stddev; + + TEST_CYCLE() cv::meanStdDev(src, cpu_mean, cpu_stddev); + + SANITY_CHECK(cpu_mean); + SANITY_CHECK(cpu_stddev); + } +} + +////////////////////////////////////////////////////////////////////// +// Norm + +DEF_PARAM_TEST(Sz_Depth_Norm, cv::Size, MatDepth, NormType); + +PERF_TEST_P(Sz_Depth_Norm, Core_Norm, Combine(GPU_TYPICAL_MAT_SIZES, - Values(CV_8U, CV_16U, CV_32F, CV_64F), - ThreshOp::all())) + Values(CV_8U, CV_16U, CV_32S, CV_32F), + Values(NormType(cv::NORM_INF), NormType(cv::NORM_L1), NormType(cv::NORM_L2)))) +{ + const cv::Size size = GET_PARAM(0); + const int depth = GET_PARAM(1); + const int normType = GET_PARAM(2); + + cv::Mat src(size, depth); + if (depth == CV_8U) + cv::randu(src, 0, 254); + else + declare.in(src, WARMUP_RNG); + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_src(src); + cv::gpu::GpuMat d_buf; + double gpu_dst; + + TEST_CYCLE() gpu_dst = cv::gpu::norm(d_src, normType, d_buf); + + SANITY_CHECK(gpu_dst, 1e-6, ERROR_RELATIVE); + } + else + { + double cpu_dst; + + TEST_CYCLE() cpu_dst = cv::norm(src, normType); + + SANITY_CHECK(cpu_dst, 1e-6, ERROR_RELATIVE); + } +} + +////////////////////////////////////////////////////////////////////// +// NormDiff + +DEF_PARAM_TEST(Sz_Norm, cv::Size, NormType); + +PERF_TEST_P(Sz_Norm, Core_NormDiff, + Combine(GPU_TYPICAL_MAT_SIZES, + Values(NormType(cv::NORM_INF), NormType(cv::NORM_L1), NormType(cv::NORM_L2)))) +{ + const cv::Size size = GET_PARAM(0); + const int normType = GET_PARAM(1); + + cv::Mat src1(size, CV_8UC1); + declare.in(src1, WARMUP_RNG); + + cv::Mat src2(size, CV_8UC1); + declare.in(src2, WARMUP_RNG); + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_src1(src1); + const cv::gpu::GpuMat d_src2(src2); + double gpu_dst; + + TEST_CYCLE() gpu_dst = cv::gpu::norm(d_src1, d_src2, normType); + + SANITY_CHECK(gpu_dst); + + } + else + { + double cpu_dst; + + TEST_CYCLE() cpu_dst = cv::norm(src1, src2, normType); + + SANITY_CHECK(cpu_dst); + } +} + +////////////////////////////////////////////////////////////////////// +// Sum + +PERF_TEST_P(Sz_Depth_Cn, Core_Sum, + Combine(GPU_TYPICAL_MAT_SIZES, + Values(CV_8U, CV_16U, CV_32F), + GPU_CHANNELS_1_3_4)) +{ + const cv::Size size = GET_PARAM(0); + const int depth = GET_PARAM(1); + const int channels = GET_PARAM(2); + + const int type = CV_MAKE_TYPE(depth, channels); + + cv::Mat src(size, type); + declare.in(src, WARMUP_RNG); + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_src(src); + cv::gpu::GpuMat d_buf; + cv::Scalar gpu_dst; + + TEST_CYCLE() gpu_dst = cv::gpu::sum(d_src, d_buf); + + SANITY_CHECK(gpu_dst, 1e-5, ERROR_RELATIVE); + } + else + { + cv::Scalar cpu_dst; + + TEST_CYCLE() cpu_dst = cv::sum(src); + + SANITY_CHECK(cpu_dst, 1e-6, ERROR_RELATIVE); + } +} + +////////////////////////////////////////////////////////////////////// +// SumAbs + +PERF_TEST_P(Sz_Depth_Cn, Core_SumAbs, + Combine(GPU_TYPICAL_MAT_SIZES, + Values(CV_8U, CV_16U, CV_32F), + GPU_CHANNELS_1_3_4)) +{ + const cv::Size size = GET_PARAM(0); + const int depth = GET_PARAM(1); + const int channels = GET_PARAM(2); + + const int type = CV_MAKE_TYPE(depth, channels); + + cv::Mat src(size, type); + declare.in(src, WARMUP_RNG); + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_src(src); + cv::gpu::GpuMat d_buf; + cv::Scalar gpu_dst; + + TEST_CYCLE() gpu_dst = cv::gpu::absSum(d_src, d_buf); + + SANITY_CHECK(gpu_dst, 1e-6, ERROR_RELATIVE); + } + else + { + FAIL_NO_CPU(); + } +} + +////////////////////////////////////////////////////////////////////// +// SumSqr + +PERF_TEST_P(Sz_Depth_Cn, Core_SumSqr, + Combine(GPU_TYPICAL_MAT_SIZES, + Values(CV_8U, CV_16U, CV_32F), + GPU_CHANNELS_1_3_4)) +{ + const cv::Size size = GET_PARAM(0); + const int depth = GET_PARAM(1); + const int channels = GET_PARAM(2); + + const int type = CV_MAKE_TYPE(depth, channels); + + cv::Mat src(size, type); + declare.in(src, WARMUP_RNG); + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_src(src); + cv::gpu::GpuMat d_buf; + cv::Scalar gpu_dst; + + TEST_CYCLE() gpu_dst = cv::gpu::sqrSum(d_src, d_buf); + + SANITY_CHECK(gpu_dst, 1e-6, ERROR_RELATIVE); + } + else + { + FAIL_NO_CPU(); + } +} + +////////////////////////////////////////////////////////////////////// +// MinMax + +PERF_TEST_P(Sz_Depth, Core_MinMax, + Combine(GPU_TYPICAL_MAT_SIZES, + Values(CV_8U, CV_16U, CV_32F, CV_64F))) +{ + const cv::Size size = GET_PARAM(0); + const int depth = GET_PARAM(1); + + cv::Mat src(size, depth); + if (depth == CV_8U) + cv::randu(src, 0, 254); + else + declare.in(src, WARMUP_RNG); + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_src(src); + cv::gpu::GpuMat d_buf; + double gpu_minVal, gpu_maxVal; + + TEST_CYCLE() cv::gpu::minMax(d_src, &gpu_minVal, &gpu_maxVal, cv::gpu::GpuMat(), d_buf); + + SANITY_CHECK(gpu_minVal, 1e-10); + SANITY_CHECK(gpu_maxVal, 1e-10); + } + else + { + double cpu_minVal, cpu_maxVal; + + TEST_CYCLE() cv::minMaxLoc(src, &cpu_minVal, &cpu_maxVal); + + SANITY_CHECK(cpu_minVal); + SANITY_CHECK(cpu_maxVal); + } +} + +////////////////////////////////////////////////////////////////////// +// MinMaxLoc + +PERF_TEST_P(Sz_Depth, Core_MinMaxLoc, + Combine(GPU_TYPICAL_MAT_SIZES, + Values(CV_8U, CV_16U, CV_32F, CV_64F))) +{ + const cv::Size size = GET_PARAM(0); + const int depth = GET_PARAM(1); + + cv::Mat src(size, depth); + if (depth == CV_8U) + cv::randu(src, 0, 254); + else + declare.in(src, WARMUP_RNG); + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_src(src); + cv::gpu::GpuMat d_valbuf, d_locbuf; + double gpu_minVal, gpu_maxVal; + cv::Point gpu_minLoc, gpu_maxLoc; + + TEST_CYCLE() cv::gpu::minMaxLoc(d_src, &gpu_minVal, &gpu_maxVal, &gpu_minLoc, &gpu_maxLoc, cv::gpu::GpuMat(), d_valbuf, d_locbuf); + + SANITY_CHECK(gpu_minVal, 1e-10); + SANITY_CHECK(gpu_maxVal, 1e-10); + } + else + { + double cpu_minVal, cpu_maxVal; + cv::Point cpu_minLoc, cpu_maxLoc; + + TEST_CYCLE() cv::minMaxLoc(src, &cpu_minVal, &cpu_maxVal, &cpu_minLoc, &cpu_maxLoc); + + SANITY_CHECK(cpu_minVal); + SANITY_CHECK(cpu_maxVal); + } +} + +////////////////////////////////////////////////////////////////////// +// CountNonZero + +PERF_TEST_P(Sz_Depth, Core_CountNonZero, + Combine(GPU_TYPICAL_MAT_SIZES, + Values(CV_8U, CV_16U, CV_32F, CV_64F))) { const cv::Size size = GET_PARAM(0); const int depth = GET_PARAM(1); - const int threshOp = GET_PARAM(2); cv::Mat src(size, depth); declare.in(src, WARMUP_RNG); + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_src(src); + cv::gpu::GpuMat d_buf; + int gpu_dst = 0; + + TEST_CYCLE() gpu_dst = cv::gpu::countNonZero(d_src, d_buf); + + SANITY_CHECK(gpu_dst); + } + else + { + int cpu_dst = 0; + + TEST_CYCLE() cpu_dst = cv::countNonZero(src); + + SANITY_CHECK(cpu_dst); + } +} + +////////////////////////////////////////////////////////////////////// +// Reduce + +enum {Rows = 0, Cols = 1}; +CV_ENUM(ReduceCode, REDUCE_SUM, REDUCE_AVG, REDUCE_MAX, REDUCE_MIN) +CV_ENUM(ReduceDim, Rows, Cols) + +DEF_PARAM_TEST(Sz_Depth_Cn_Code_Dim, cv::Size, MatDepth, MatCn, ReduceCode, ReduceDim); + +PERF_TEST_P(Sz_Depth_Cn_Code_Dim, Core_Reduce, + Combine(GPU_TYPICAL_MAT_SIZES, + Values(CV_8U, CV_16U, CV_16S, CV_32F), + Values(1, 2, 3, 4), + ReduceCode::all(), + ReduceDim::all())) +{ + const cv::Size size = GET_PARAM(0); + const int depth = GET_PARAM(1); + const int channels = GET_PARAM(2); + const int reduceOp = GET_PARAM(3); + const int dim = GET_PARAM(4); + + const int type = CV_MAKE_TYPE(depth, channels); + + cv::Mat src(size, type); + declare.in(src, WARMUP_RNG); + if (PERF_RUN_GPU()) { const cv::gpu::GpuMat d_src(src); cv::gpu::GpuMat dst; - TEST_CYCLE() cv::gpu::threshold(d_src, dst, 100.0, 255.0, threshOp); + TEST_CYCLE() cv::gpu::reduce(d_src, dst, dim, reduceOp); - GPU_SANITY_CHECK(dst, 1e-10); + GPU_SANITY_CHECK(dst); } else { cv::Mat dst; - TEST_CYCLE() cv::threshold(src, dst, 100.0, 255.0, threshOp); + TEST_CYCLE() cv::reduce(src, dst, dim, reduceOp); + + CPU_SANITY_CHECK(dst); + } +} +////////////////////////////////////////////////////////////////////// +// Normalize + +DEF_PARAM_TEST(Sz_Depth_NormType, cv::Size, MatDepth, NormType); + +PERF_TEST_P(Sz_Depth_NormType, Core_Normalize, + Combine(GPU_TYPICAL_MAT_SIZES, + Values(CV_8U, CV_16U, CV_32F, CV_64F), + Values(NormType(cv::NORM_INF), + NormType(cv::NORM_L1), + NormType(cv::NORM_L2), + NormType(cv::NORM_MINMAX)))) +{ + const cv::Size size = GET_PARAM(0); + const int type = GET_PARAM(1); + const int norm_type = GET_PARAM(2); + + const double alpha = 1; + const double beta = 0; + + cv::Mat src(size, type); + declare.in(src, WARMUP_RNG); + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_src(src); + cv::gpu::GpuMat dst; + cv::gpu::GpuMat d_norm_buf, d_cvt_buf; + + TEST_CYCLE() cv::gpu::normalize(d_src, dst, alpha, beta, norm_type, type, cv::gpu::GpuMat(), d_norm_buf, d_cvt_buf); + + GPU_SANITY_CHECK(dst, 1e-6); + } + else + { + cv::Mat dst; + + TEST_CYCLE() cv::normalize(src, dst, alpha, beta, norm_type, type); CPU_SANITY_CHECK(dst); } diff --git a/modules/photo/perf/perf_gpu.cpp b/modules/gpu/perf/perf_denoising.cpp similarity index 79% rename from modules/photo/perf/perf_gpu.cpp rename to modules/gpu/perf/perf_denoising.cpp index ec62f7a0cc..1e33601d60 100644 --- a/modules/photo/perf/perf_gpu.cpp +++ b/modules/gpu/perf/perf_denoising.cpp @@ -42,25 +42,64 @@ #include "perf_precomp.hpp" -#include "opencv2/photo/gpu.hpp" -#include "opencv2/ts/gpu_perf.hpp" - -#include "opencv2/opencv_modules.hpp" - -#if defined (HAVE_CUDA) && defined(HAVE_OPENCV_GPUARITHM) && defined(HAVE_OPENCV_GPUIMGPROC) - using namespace std; using namespace testing; using namespace perf; #define GPU_DENOISING_IMAGE_SIZES testing::Values(perf::szVGA, perf::sz720p) +////////////////////////////////////////////////////////////////////// +// BilateralFilter + +DEF_PARAM_TEST(Sz_Depth_Cn_KernelSz, cv::Size, MatDepth, MatCn, int); + +PERF_TEST_P(Sz_Depth_Cn_KernelSz, Denoising_BilateralFilter, + Combine(GPU_DENOISING_IMAGE_SIZES, + Values(CV_8U, CV_32F), + GPU_CHANNELS_1_3, + Values(3, 5, 9))) +{ + declare.time(60.0); + + const cv::Size size = GET_PARAM(0); + const int depth = GET_PARAM(1); + const int channels = GET_PARAM(2); + const int kernel_size = GET_PARAM(3); + + const float sigma_color = 7; + const float sigma_spatial = 5; + const int borderMode = cv::BORDER_REFLECT101; + + const int type = CV_MAKE_TYPE(depth, channels); + + cv::Mat src(size, type); + declare.in(src, WARMUP_RNG); + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_src(src); + cv::gpu::GpuMat dst; + + TEST_CYCLE() cv::gpu::bilateralFilter(d_src, dst, kernel_size, sigma_color, sigma_spatial, borderMode); + + GPU_SANITY_CHECK(dst); + } + else + { + cv::Mat dst; + + TEST_CYCLE() cv::bilateralFilter(src, dst, kernel_size, sigma_color, sigma_spatial, borderMode); + + CPU_SANITY_CHECK(dst); + } +} + ////////////////////////////////////////////////////////////////////// // nonLocalMeans DEF_PARAM_TEST(Sz_Depth_Cn_WinSz_BlockSz, cv::Size, MatDepth, MatCn, int, int); -PERF_TEST_P(Sz_Depth_Cn_WinSz_BlockSz, GPU_NonLocalMeans, +PERF_TEST_P(Sz_Depth_Cn_WinSz_BlockSz, Denoising_NonLocalMeans, Combine(GPU_DENOISING_IMAGE_SIZES, Values(CV_8U), GPU_CHANNELS_1_3, @@ -104,7 +143,7 @@ PERF_TEST_P(Sz_Depth_Cn_WinSz_BlockSz, GPU_NonLocalMeans, DEF_PARAM_TEST(Sz_Depth_Cn_WinSz_BlockSz, cv::Size, MatDepth, MatCn, int, int); -PERF_TEST_P(Sz_Depth_Cn_WinSz_BlockSz, GPU_FastNonLocalMeans, +PERF_TEST_P(Sz_Depth_Cn_WinSz_BlockSz, Denoising_FastNonLocalMeans, Combine(GPU_DENOISING_IMAGE_SIZES, Values(CV_8U), GPU_CHANNELS_1_3, @@ -150,7 +189,7 @@ PERF_TEST_P(Sz_Depth_Cn_WinSz_BlockSz, GPU_FastNonLocalMeans, DEF_PARAM_TEST(Sz_Depth_WinSz_BlockSz, cv::Size, MatDepth, int, int); -PERF_TEST_P(Sz_Depth_WinSz_BlockSz, GPU_FastNonLocalMeansColored, +PERF_TEST_P(Sz_Depth_WinSz_BlockSz, Denoising_FastNonLocalMeansColored, Combine(GPU_DENOISING_IMAGE_SIZES, Values(CV_8U), Values(21), @@ -189,5 +228,3 @@ PERF_TEST_P(Sz_Depth_WinSz_BlockSz, GPU_FastNonLocalMeansColored, CPU_SANITY_CHECK(dst); } } - -#endif diff --git a/modules/gpufeatures2d/perf/perf_features2d.cpp b/modules/gpu/perf/perf_features2d.cpp similarity index 97% rename from modules/gpufeatures2d/perf/perf_features2d.cpp rename to modules/gpu/perf/perf_features2d.cpp index 9396ba2908..feee3a939e 100644 --- a/modules/gpufeatures2d/perf/perf_features2d.cpp +++ b/modules/gpu/perf/perf_features2d.cpp @@ -51,7 +51,7 @@ using namespace perf; DEF_PARAM_TEST(Image_Threshold_NonMaxSupression, string, int, bool); -PERF_TEST_P(Image_Threshold_NonMaxSupression, FAST, +PERF_TEST_P(Image_Threshold_NonMaxSupression, Features2D_FAST, Combine(Values("gpu/perf/aloe.png"), Values(20), Bool())) @@ -93,7 +93,7 @@ PERF_TEST_P(Image_Threshold_NonMaxSupression, FAST, DEF_PARAM_TEST(Image_NFeatures, string, int); -PERF_TEST_P(Image_NFeatures, ORB, +PERF_TEST_P(Image_NFeatures, Features2D_ORB, Combine(Values("gpu/perf/aloe.png"), Values(4000))) { @@ -145,7 +145,7 @@ PERF_TEST_P(Image_NFeatures, ORB, DEF_PARAM_TEST(DescSize_Norm, int, NormType); -PERF_TEST_P(DescSize_Norm, BFMatch, +PERF_TEST_P(DescSize_Norm, Features2D_BFMatch, Combine(Values(64, 128, 256), Values(NormType(cv::NORM_L1), NormType(cv::NORM_L2), NormType(cv::NORM_HAMMING)))) { @@ -202,7 +202,7 @@ static void toOneRowMatches(const std::vector< std::vector >& src, s DEF_PARAM_TEST(DescSize_K_Norm, int, int, NormType); -PERF_TEST_P(DescSize_K_Norm, BFKnnMatch, +PERF_TEST_P(DescSize_K_Norm, Features2D_BFKnnMatch, Combine(Values(64, 128, 256), Values(2, 3), Values(NormType(cv::NORM_L1), NormType(cv::NORM_L2)))) @@ -257,7 +257,7 @@ PERF_TEST_P(DescSize_K_Norm, BFKnnMatch, ////////////////////////////////////////////////////////////////////// // BFRadiusMatch -PERF_TEST_P(DescSize_Norm, BFRadiusMatch, +PERF_TEST_P(DescSize_Norm, Features2D_BFRadiusMatch, Combine(Values(64, 128, 256), Values(NormType(cv::NORM_L1), NormType(cv::NORM_L2)))) { diff --git a/modules/gpufilters/perf/perf_filters.cpp b/modules/gpu/perf/perf_filters.cpp similarity index 89% rename from modules/gpufilters/perf/perf_filters.cpp rename to modules/gpu/perf/perf_filters.cpp index 0dc506bc93..40d88aad45 100644 --- a/modules/gpufilters/perf/perf_filters.cpp +++ b/modules/gpu/perf/perf_filters.cpp @@ -51,7 +51,7 @@ using namespace perf; DEF_PARAM_TEST(Sz_Type_KernelSz, cv::Size, MatType, int); -PERF_TEST_P(Sz_Type_KernelSz, Blur, +PERF_TEST_P(Sz_Type_KernelSz, Filters_Blur, Combine(GPU_TYPICAL_MAT_SIZES, Values(CV_8UC1, CV_8UC4), Values(3, 5, 7))) @@ -87,7 +87,7 @@ PERF_TEST_P(Sz_Type_KernelSz, Blur, ////////////////////////////////////////////////////////////////////// // Sobel -PERF_TEST_P(Sz_Type_KernelSz, Sobel, Combine(GPU_TYPICAL_MAT_SIZES, Values(CV_8UC1, CV_8UC4, CV_32FC1), Values(3, 5, 7, 9, 11, 13, 15))) +PERF_TEST_P(Sz_Type_KernelSz, Filters_Sobel, Combine(GPU_TYPICAL_MAT_SIZES, Values(CV_8UC1, CV_8UC4, CV_32FC1), Values(3, 5, 7, 9, 11, 13, 15))) { declare.time(20.0); @@ -121,7 +121,7 @@ PERF_TEST_P(Sz_Type_KernelSz, Sobel, Combine(GPU_TYPICAL_MAT_SIZES, Values(CV_8U ////////////////////////////////////////////////////////////////////// // Scharr -PERF_TEST_P(Sz_Type, Scharr, Combine(GPU_TYPICAL_MAT_SIZES, Values(CV_8UC1, CV_8UC4, CV_32FC1))) +PERF_TEST_P(Sz_Type, Filters_Scharr, Combine(GPU_TYPICAL_MAT_SIZES, Values(CV_8UC1, CV_8UC4, CV_32FC1))) { declare.time(20.0); @@ -154,7 +154,7 @@ PERF_TEST_P(Sz_Type, Scharr, Combine(GPU_TYPICAL_MAT_SIZES, Values(CV_8UC1, CV_8 ////////////////////////////////////////////////////////////////////// // GaussianBlur -PERF_TEST_P(Sz_Type_KernelSz, GaussianBlur, Combine(GPU_TYPICAL_MAT_SIZES, Values(CV_8UC1, CV_8UC4, CV_32FC1), Values(3, 5, 7, 9, 11, 13, 15))) +PERF_TEST_P(Sz_Type_KernelSz, Filters_GaussianBlur, Combine(GPU_TYPICAL_MAT_SIZES, Values(CV_8UC1, CV_8UC4, CV_32FC1), Values(3, 5, 7, 9, 11, 13, 15))) { declare.time(20.0); @@ -188,7 +188,7 @@ PERF_TEST_P(Sz_Type_KernelSz, GaussianBlur, Combine(GPU_TYPICAL_MAT_SIZES, Value ////////////////////////////////////////////////////////////////////// // Laplacian -PERF_TEST_P(Sz_Type_KernelSz, Laplacian, Combine(GPU_TYPICAL_MAT_SIZES, Values(CV_8UC1, CV_8UC4, CV_32FC1, CV_32FC4), Values(1, 3))) +PERF_TEST_P(Sz_Type_KernelSz, Filters_Laplacian, Combine(GPU_TYPICAL_MAT_SIZES, Values(CV_8UC1, CV_8UC4, CV_32FC1, CV_32FC4), Values(1, 3))) { declare.time(20.0); @@ -221,7 +221,7 @@ PERF_TEST_P(Sz_Type_KernelSz, Laplacian, Combine(GPU_TYPICAL_MAT_SIZES, Values(C ////////////////////////////////////////////////////////////////////// // Erode -PERF_TEST_P(Sz_Type, Erode, Combine(GPU_TYPICAL_MAT_SIZES, Values(CV_8UC1, CV_8UC4))) +PERF_TEST_P(Sz_Type, Filters_Erode, Combine(GPU_TYPICAL_MAT_SIZES, Values(CV_8UC1, CV_8UC4))) { declare.time(20.0); @@ -256,7 +256,7 @@ PERF_TEST_P(Sz_Type, Erode, Combine(GPU_TYPICAL_MAT_SIZES, Values(CV_8UC1, CV_8U ////////////////////////////////////////////////////////////////////// // Dilate -PERF_TEST_P(Sz_Type, Dilate, Combine(GPU_TYPICAL_MAT_SIZES, Values(CV_8UC1, CV_8UC4))) +PERF_TEST_P(Sz_Type, Filters_Dilate, Combine(GPU_TYPICAL_MAT_SIZES, Values(CV_8UC1, CV_8UC4))) { declare.time(20.0); @@ -295,7 +295,7 @@ CV_ENUM(MorphOp, MORPH_OPEN, MORPH_CLOSE, MORPH_GRADIENT, MORPH_TOPHAT, MORPH_BL DEF_PARAM_TEST(Sz_Type_Op, cv::Size, MatType, MorphOp); -PERF_TEST_P(Sz_Type_Op, MorphologyEx, Combine(GPU_TYPICAL_MAT_SIZES, Values(CV_8UC1, CV_8UC4), MorphOp::all())) +PERF_TEST_P(Sz_Type_Op, Filters_MorphologyEx, Combine(GPU_TYPICAL_MAT_SIZES, Values(CV_8UC1, CV_8UC4), MorphOp::all())) { declare.time(20.0); @@ -332,7 +332,7 @@ PERF_TEST_P(Sz_Type_Op, MorphologyEx, Combine(GPU_TYPICAL_MAT_SIZES, Values(CV_8 ////////////////////////////////////////////////////////////////////// // Filter2D -PERF_TEST_P(Sz_Type_KernelSz, Filter2D, Combine(GPU_TYPICAL_MAT_SIZES, Values(CV_8UC1, CV_8UC4, CV_32FC1, CV_32FC4), Values(3, 5, 7, 9, 11, 13, 15))) +PERF_TEST_P(Sz_Type_KernelSz, Filters_Filter2D, Combine(GPU_TYPICAL_MAT_SIZES, Values(CV_8UC1, CV_8UC4, CV_32FC1, CV_32FC4), Values(3, 5, 7, 9, 11, 13, 15))) { declare.time(20.0); diff --git a/modules/gpu/perf/perf_imgproc.cpp b/modules/gpu/perf/perf_imgproc.cpp new file mode 100644 index 0000000000..0a24d24d32 --- /dev/null +++ b/modules/gpu/perf/perf_imgproc.cpp @@ -0,0 +1,1904 @@ +/*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 "perf_precomp.hpp" + +using namespace std; +using namespace testing; +using namespace perf; + +////////////////////////////////////////////////////////////////////// +// Remap + +enum { HALF_SIZE=0, UPSIDE_DOWN, REFLECTION_X, REFLECTION_BOTH }; +CV_ENUM(RemapMode, HALF_SIZE, UPSIDE_DOWN, REFLECTION_X, REFLECTION_BOTH); + +void generateMap(cv::Mat& map_x, cv::Mat& map_y, int remapMode) +{ + for (int j = 0; j < map_x.rows; ++j) + { + for (int i = 0; i < map_x.cols; ++i) + { + switch (remapMode) + { + case HALF_SIZE: + if (i > map_x.cols*0.25 && i < map_x.cols*0.75 && j > map_x.rows*0.25 && j < map_x.rows*0.75) + { + map_x.at(j,i) = 2.f * (i - map_x.cols * 0.25f) + 0.5f; + map_y.at(j,i) = 2.f * (j - map_x.rows * 0.25f) + 0.5f; + } + else + { + map_x.at(j,i) = 0.f; + map_y.at(j,i) = 0.f; + } + break; + case UPSIDE_DOWN: + map_x.at(j,i) = static_cast(i); + map_y.at(j,i) = static_cast(map_x.rows - j); + break; + case REFLECTION_X: + map_x.at(j,i) = static_cast(map_x.cols - i); + map_y.at(j,i) = static_cast(j); + break; + case REFLECTION_BOTH: + map_x.at(j,i) = static_cast(map_x.cols - i); + map_y.at(j,i) = static_cast(map_x.rows - j); + break; + } // end of switch + } + } +} + +DEF_PARAM_TEST(Sz_Depth_Cn_Inter_Border_Mode, cv::Size, MatDepth, MatCn, Interpolation, BorderMode, RemapMode); + +PERF_TEST_P(Sz_Depth_Cn_Inter_Border_Mode, ImgProc_Remap, + Combine(GPU_TYPICAL_MAT_SIZES, + Values(CV_8U, CV_16U, CV_32F), + GPU_CHANNELS_1_3_4, + Values(Interpolation(cv::INTER_NEAREST), Interpolation(cv::INTER_LINEAR), Interpolation(cv::INTER_CUBIC)), + ALL_BORDER_MODES, + RemapMode::all())) +{ + declare.time(20.0); + + const cv::Size size = GET_PARAM(0); + const int depth = GET_PARAM(1); + const int channels = GET_PARAM(2); + const int interpolation = GET_PARAM(3); + const int borderMode = GET_PARAM(4); + const int remapMode = GET_PARAM(5); + + const int type = CV_MAKE_TYPE(depth, channels); + + cv::Mat src(size, type); + declare.in(src, WARMUP_RNG); + + cv::Mat xmap(size, CV_32FC1); + cv::Mat ymap(size, CV_32FC1); + generateMap(xmap, ymap, remapMode); + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_src(src); + const cv::gpu::GpuMat d_xmap(xmap); + const cv::gpu::GpuMat d_ymap(ymap); + cv::gpu::GpuMat dst; + + TEST_CYCLE() cv::gpu::remap(d_src, dst, d_xmap, d_ymap, interpolation, borderMode); + + GPU_SANITY_CHECK(dst); + } + else + { + cv::Mat dst; + + TEST_CYCLE() cv::remap(src, dst, xmap, ymap, interpolation, borderMode); + + CPU_SANITY_CHECK(dst); + } +} + +////////////////////////////////////////////////////////////////////// +// Resize + +DEF_PARAM_TEST(Sz_Depth_Cn_Inter_Scale, cv::Size, MatDepth, MatCn, Interpolation, double); + +PERF_TEST_P(Sz_Depth_Cn_Inter_Scale, ImgProc_Resize, + Combine(GPU_TYPICAL_MAT_SIZES, + Values(CV_8U, CV_16U, CV_32F), + GPU_CHANNELS_1_3_4, + Values(Interpolation(cv::INTER_NEAREST), Interpolation(cv::INTER_LINEAR), Interpolation(cv::INTER_CUBIC)), + Values(0.5, 0.3, 2.0))) +{ + declare.time(20.0); + + const cv::Size size = GET_PARAM(0); + const int depth = GET_PARAM(1); + const int channels = GET_PARAM(2); + const int interpolation = GET_PARAM(3); + const double f = GET_PARAM(4); + + const int type = CV_MAKE_TYPE(depth, channels); + + cv::Mat src(size, type); + declare.in(src, WARMUP_RNG); + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_src(src); + cv::gpu::GpuMat dst; + + TEST_CYCLE() cv::gpu::resize(d_src, dst, cv::Size(), f, f, interpolation); + + GPU_SANITY_CHECK(dst, 1e-3, ERROR_RELATIVE); + } + else + { + cv::Mat dst; + + TEST_CYCLE() cv::resize(src, dst, cv::Size(), f, f, interpolation); + + CPU_SANITY_CHECK(dst); + } +} + +////////////////////////////////////////////////////////////////////// +// ResizeArea + +DEF_PARAM_TEST(Sz_Depth_Cn_Scale, cv::Size, MatDepth, MatCn, double); + +PERF_TEST_P(Sz_Depth_Cn_Scale, ImgProc_ResizeArea, + Combine(GPU_TYPICAL_MAT_SIZES, + Values(CV_8U, CV_16U, CV_32F), + GPU_CHANNELS_1_3_4, + Values(0.2, 0.1, 0.05))) +{ + declare.time(1.0); + + const cv::Size size = GET_PARAM(0); + const int depth = GET_PARAM(1); + const int channels = GET_PARAM(2); + const int interpolation = cv::INTER_AREA; + const double f = GET_PARAM(3); + + const int type = CV_MAKE_TYPE(depth, channels); + + cv::Mat src(size, type); + declare.in(src, WARMUP_RNG); + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_src(src); + cv::gpu::GpuMat dst; + + TEST_CYCLE() cv::gpu::resize(d_src, dst, cv::Size(), f, f, interpolation); + + GPU_SANITY_CHECK(dst); + } + else + { + cv::Mat dst; + + TEST_CYCLE() cv::resize(src, dst, cv::Size(), f, f, interpolation); + + CPU_SANITY_CHECK(dst); + } +} + +////////////////////////////////////////////////////////////////////// +// WarpAffine + +DEF_PARAM_TEST(Sz_Depth_Cn_Inter_Border, cv::Size, MatDepth, MatCn, Interpolation, BorderMode); + +PERF_TEST_P(Sz_Depth_Cn_Inter_Border, ImgProc_WarpAffine, + Combine(GPU_TYPICAL_MAT_SIZES, + Values(CV_8U, CV_16U, CV_32F), + GPU_CHANNELS_1_3_4, + Values(Interpolation(cv::INTER_NEAREST), Interpolation(cv::INTER_LINEAR), Interpolation(cv::INTER_CUBIC)), + ALL_BORDER_MODES)) +{ + declare.time(20.0); + + const cv::Size size = GET_PARAM(0); + const int depth = GET_PARAM(1); + const int channels = GET_PARAM(2); + const int interpolation = GET_PARAM(3); + const int borderMode = GET_PARAM(4); + + const int type = CV_MAKE_TYPE(depth, channels); + + cv::Mat src(size, type); + declare.in(src, WARMUP_RNG); + + const double aplha = CV_PI / 4; + const double mat[2 * 3] = + { + std::cos(aplha), -std::sin(aplha), src.cols / 2, + std::sin(aplha), std::cos(aplha), 0 + }; + const cv::Mat M(2, 3, CV_64F, (void*) mat); + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_src(src); + cv::gpu::GpuMat dst; + + TEST_CYCLE() cv::gpu::warpAffine(d_src, dst, M, size, interpolation, borderMode); + + GPU_SANITY_CHECK(dst, 1); + } + else + { + cv::Mat dst; + + TEST_CYCLE() cv::warpAffine(src, dst, M, size, interpolation, borderMode); + + CPU_SANITY_CHECK(dst); + } +} + +////////////////////////////////////////////////////////////////////// +// WarpPerspective + +PERF_TEST_P(Sz_Depth_Cn_Inter_Border, ImgProc_WarpPerspective, + Combine(GPU_TYPICAL_MAT_SIZES, + Values(CV_8U, CV_16U, CV_32F), + GPU_CHANNELS_1_3_4, + Values(Interpolation(cv::INTER_NEAREST), Interpolation(cv::INTER_LINEAR), Interpolation(cv::INTER_CUBIC)), + ALL_BORDER_MODES)) +{ + declare.time(20.0); + + const cv::Size size = GET_PARAM(0); + const int depth = GET_PARAM(1); + const int channels = GET_PARAM(2); + const int interpolation = GET_PARAM(3); + const int borderMode = GET_PARAM(4); + + const int type = CV_MAKE_TYPE(depth, channels); + + cv::Mat src(size, type); + declare.in(src, WARMUP_RNG); + + const double aplha = CV_PI / 4; + double mat[3][3] = { {std::cos(aplha), -std::sin(aplha), src.cols / 2}, + {std::sin(aplha), std::cos(aplha), 0}, + {0.0, 0.0, 1.0}}; + const cv::Mat M(3, 3, CV_64F, (void*) mat); + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_src(src); + cv::gpu::GpuMat dst; + + TEST_CYCLE() cv::gpu::warpPerspective(d_src, dst, M, size, interpolation, borderMode); + + GPU_SANITY_CHECK(dst, 1); + } + else + { + cv::Mat dst; + + TEST_CYCLE() cv::warpPerspective(src, dst, M, size, interpolation, borderMode); + + CPU_SANITY_CHECK(dst); + } +} + +////////////////////////////////////////////////////////////////////// +// CopyMakeBorder + +DEF_PARAM_TEST(Sz_Depth_Cn_Border, cv::Size, MatDepth, MatCn, BorderMode); + +PERF_TEST_P(Sz_Depth_Cn_Border, ImgProc_CopyMakeBorder, + Combine(GPU_TYPICAL_MAT_SIZES, + Values(CV_8U, CV_16U, CV_32F), + GPU_CHANNELS_1_3_4, + ALL_BORDER_MODES)) +{ + const cv::Size size = GET_PARAM(0); + const int depth = GET_PARAM(1); + const int channels = GET_PARAM(2); + const int borderMode = GET_PARAM(3); + + const int type = CV_MAKE_TYPE(depth, channels); + + cv::Mat src(size, type); + declare.in(src, WARMUP_RNG); + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_src(src); + cv::gpu::GpuMat dst; + + TEST_CYCLE() cv::gpu::copyMakeBorder(d_src, dst, 5, 5, 5, 5, borderMode); + + GPU_SANITY_CHECK(dst); + } + else + { + cv::Mat dst; + + TEST_CYCLE() cv::copyMakeBorder(src, dst, 5, 5, 5, 5, borderMode); + + CPU_SANITY_CHECK(dst); + } +} + +////////////////////////////////////////////////////////////////////// +// Threshold + +CV_ENUM(ThreshOp, THRESH_BINARY, THRESH_BINARY_INV, THRESH_TRUNC, THRESH_TOZERO, THRESH_TOZERO_INV) + +DEF_PARAM_TEST(Sz_Depth_Op, cv::Size, MatDepth, ThreshOp); + +PERF_TEST_P(Sz_Depth_Op, ImgProc_Threshold, + Combine(GPU_TYPICAL_MAT_SIZES, + Values(CV_8U, CV_16U, CV_32F, CV_64F), + ThreshOp::all())) +{ + const cv::Size size = GET_PARAM(0); + const int depth = GET_PARAM(1); + const int threshOp = GET_PARAM(2); + + cv::Mat src(size, depth); + declare.in(src, WARMUP_RNG); + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_src(src); + cv::gpu::GpuMat dst; + + TEST_CYCLE() cv::gpu::threshold(d_src, dst, 100.0, 255.0, threshOp); + + GPU_SANITY_CHECK(dst, 1e-10); + } + else + { + cv::Mat dst; + + TEST_CYCLE() cv::threshold(src, dst, 100.0, 255.0, threshOp); + + CPU_SANITY_CHECK(dst); + } +} + +////////////////////////////////////////////////////////////////////// +// Integral + +PERF_TEST_P(Sz, ImgProc_Integral, + GPU_TYPICAL_MAT_SIZES) +{ + const cv::Size size = GetParam(); + + cv::Mat src(size, CV_8UC1); + declare.in(src, WARMUP_RNG); + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_src(src); + cv::gpu::GpuMat dst; + cv::gpu::GpuMat d_buf; + + TEST_CYCLE() cv::gpu::integralBuffered(d_src, dst, d_buf); + + GPU_SANITY_CHECK(dst); + } + else + { + cv::Mat dst; + + TEST_CYCLE() cv::integral(src, dst); + + CPU_SANITY_CHECK(dst); + } +} + +////////////////////////////////////////////////////////////////////// +// IntegralSqr + +PERF_TEST_P(Sz, ImgProc_IntegralSqr, + GPU_TYPICAL_MAT_SIZES) +{ + const cv::Size size = GetParam(); + + cv::Mat src(size, CV_8UC1); + declare.in(src, WARMUP_RNG); + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_src(src); + cv::gpu::GpuMat dst; + + TEST_CYCLE() cv::gpu::sqrIntegral(d_src, dst); + + GPU_SANITY_CHECK(dst); + } + else + { + FAIL_NO_CPU(); + } +} + +////////////////////////////////////////////////////////////////////// +// HistEvenC1 + +PERF_TEST_P(Sz_Depth, ImgProc_HistEvenC1, + Combine(GPU_TYPICAL_MAT_SIZES, + Values(CV_8U, CV_16U, CV_16S))) +{ + const cv::Size size = GET_PARAM(0); + const int depth = GET_PARAM(1); + + cv::Mat src(size, depth); + declare.in(src, WARMUP_RNG); + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_src(src); + cv::gpu::GpuMat dst; + cv::gpu::GpuMat d_buf; + + TEST_CYCLE() cv::gpu::histEven(d_src, dst, d_buf, 30, 0, 180); + + GPU_SANITY_CHECK(dst); + } + else + { + const int hbins = 30; + const float hranges[] = {0.0f, 180.0f}; + const int histSize[] = {hbins}; + const float* ranges[] = {hranges}; + const int channels[] = {0}; + + cv::Mat dst; + + TEST_CYCLE() cv::calcHist(&src, 1, channels, cv::Mat(), dst, 1, histSize, ranges); + + CPU_SANITY_CHECK(dst); + } +} + +////////////////////////////////////////////////////////////////////// +// HistEvenC4 + +PERF_TEST_P(Sz_Depth, ImgProc_HistEvenC4, + Combine(GPU_TYPICAL_MAT_SIZES, + Values(CV_8U, CV_16U, CV_16S))) +{ + const cv::Size size = GET_PARAM(0); + const int depth = GET_PARAM(1); + + cv::Mat src(size, CV_MAKE_TYPE(depth, 4)); + declare.in(src, WARMUP_RNG); + + int histSize[] = {30, 30, 30, 30}; + int lowerLevel[] = {0, 0, 0, 0}; + int upperLevel[] = {180, 180, 180, 180}; + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_src(src); + cv::gpu::GpuMat d_hist[4]; + cv::gpu::GpuMat d_buf; + + TEST_CYCLE() cv::gpu::histEven(d_src, d_hist, d_buf, histSize, lowerLevel, upperLevel); + + cv::Mat cpu_hist0, cpu_hist1, cpu_hist2, cpu_hist3; + d_hist[0].download(cpu_hist0); + d_hist[1].download(cpu_hist1); + d_hist[2].download(cpu_hist2); + d_hist[3].download(cpu_hist3); + SANITY_CHECK(cpu_hist0); + SANITY_CHECK(cpu_hist1); + SANITY_CHECK(cpu_hist2); + SANITY_CHECK(cpu_hist3); + } + else + { + FAIL_NO_CPU(); + } +} + +////////////////////////////////////////////////////////////////////// +// CalcHist + +PERF_TEST_P(Sz, ImgProc_CalcHist, + GPU_TYPICAL_MAT_SIZES) +{ + const cv::Size size = GetParam(); + + cv::Mat src(size, CV_8UC1); + declare.in(src, WARMUP_RNG); + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_src(src); + cv::gpu::GpuMat dst; + + TEST_CYCLE() cv::gpu::calcHist(d_src, dst); + + GPU_SANITY_CHECK(dst); + } + else + { + FAIL_NO_CPU(); + } +} + +////////////////////////////////////////////////////////////////////// +// EqualizeHist + +PERF_TEST_P(Sz, ImgProc_EqualizeHist, + GPU_TYPICAL_MAT_SIZES) +{ + const cv::Size size = GetParam(); + + cv::Mat src(size, CV_8UC1); + declare.in(src, WARMUP_RNG); + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_src(src); + cv::gpu::GpuMat dst; + cv::gpu::GpuMat d_hist; + cv::gpu::GpuMat d_buf; + + TEST_CYCLE() cv::gpu::equalizeHist(d_src, dst, d_hist, d_buf); + + GPU_SANITY_CHECK(dst); + } + else + { + cv::Mat dst; + + TEST_CYCLE() cv::equalizeHist(src, dst); + + CPU_SANITY_CHECK(dst); + } +} + +DEF_PARAM_TEST(Sz_ClipLimit, cv::Size, double); + +PERF_TEST_P(Sz_ClipLimit, ImgProc_CLAHE, + Combine(GPU_TYPICAL_MAT_SIZES, + Values(0.0, 40.0))) +{ + const cv::Size size = GET_PARAM(0); + const double clipLimit = GET_PARAM(1); + + cv::Mat src(size, CV_8UC1); + declare.in(src, WARMUP_RNG); + + if (PERF_RUN_GPU()) + { + cv::Ptr clahe = cv::gpu::createCLAHE(clipLimit); + cv::gpu::GpuMat d_src(src); + cv::gpu::GpuMat dst; + + TEST_CYCLE() clahe->apply(d_src, dst); + + GPU_SANITY_CHECK(dst); + } + else + { + cv::Ptr clahe = cv::createCLAHE(clipLimit); + cv::Mat dst; + + TEST_CYCLE() clahe->apply(src, dst); + + CPU_SANITY_CHECK(dst); + } +} + +////////////////////////////////////////////////////////////////////// +// ColumnSum + +PERF_TEST_P(Sz, ImgProc_ColumnSum, + GPU_TYPICAL_MAT_SIZES) +{ + const cv::Size size = GetParam(); + + cv::Mat src(size, CV_32FC1); + declare.in(src, WARMUP_RNG); + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_src(src); + cv::gpu::GpuMat dst; + + TEST_CYCLE() cv::gpu::columnSum(d_src, dst); + + GPU_SANITY_CHECK(dst); + } + else + { + FAIL_NO_CPU(); + } +} + +////////////////////////////////////////////////////////////////////// +// Canny + +DEF_PARAM_TEST(Image_AppertureSz_L2gradient, string, int, bool); + +PERF_TEST_P(Image_AppertureSz_L2gradient, ImgProc_Canny, + Combine(Values("perf/800x600.png", "perf/1280x1024.png", "perf/1680x1050.png"), + Values(3, 5), + Bool())) +{ + const string fileName = GET_PARAM(0); + const int apperture_size = GET_PARAM(1); + const bool useL2gradient = GET_PARAM(2); + + const cv::Mat image = readImage(fileName, cv::IMREAD_GRAYSCALE); + ASSERT_FALSE(image.empty()); + + const double low_thresh = 50.0; + const double high_thresh = 100.0; + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_image(image); + cv::gpu::GpuMat dst; + cv::gpu::CannyBuf d_buf; + + TEST_CYCLE() cv::gpu::Canny(d_image, d_buf, dst, low_thresh, high_thresh, apperture_size, useL2gradient); + + GPU_SANITY_CHECK(dst); + } + else + { + cv::Mat dst; + + TEST_CYCLE() cv::Canny(image, dst, low_thresh, high_thresh, apperture_size, useL2gradient); + + CPU_SANITY_CHECK(dst); + } +} + +////////////////////////////////////////////////////////////////////// +// MeanShiftFiltering + +DEF_PARAM_TEST_1(Image, string); + +PERF_TEST_P(Image, ImgProc_MeanShiftFiltering, + Values("gpu/meanshift/cones.png")) +{ + declare.time(300.0); + + const cv::Mat img = readImage(GetParam()); + ASSERT_FALSE(img.empty()); + + cv::Mat rgba; + cv::cvtColor(img, rgba, cv::COLOR_BGR2BGRA); + + const int sp = 50; + const int sr = 50; + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_src(rgba); + cv::gpu::GpuMat dst; + + TEST_CYCLE() cv::gpu::meanShiftFiltering(d_src, dst, sp, sr); + + GPU_SANITY_CHECK(dst); + } + else + { + cv::Mat dst; + + TEST_CYCLE() cv::pyrMeanShiftFiltering(img, dst, sp, sr); + + CPU_SANITY_CHECK(dst); + } +} + +////////////////////////////////////////////////////////////////////// +// MeanShiftProc + +PERF_TEST_P(Image, ImgProc_MeanShiftProc, + Values("gpu/meanshift/cones.png")) +{ + declare.time(300.0); + + const cv::Mat img = readImage(GetParam()); + ASSERT_FALSE(img.empty()); + + cv::Mat rgba; + cv::cvtColor(img, rgba, cv::COLOR_BGR2BGRA); + + const int sp = 50; + const int sr = 50; + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_src(rgba); + cv::gpu::GpuMat dstr; + cv::gpu::GpuMat dstsp; + + TEST_CYCLE() cv::gpu::meanShiftProc(d_src, dstr, dstsp, sp, sr); + + GPU_SANITY_CHECK(dstr); + GPU_SANITY_CHECK(dstsp); + } + else + { + FAIL_NO_CPU(); + } +} + +////////////////////////////////////////////////////////////////////// +// MeanShiftSegmentation + +PERF_TEST_P(Image, ImgProc_MeanShiftSegmentation, + Values("gpu/meanshift/cones.png")) +{ + declare.time(300.0); + + const cv::Mat img = readImage(GetParam()); + ASSERT_FALSE(img.empty()); + + cv::Mat rgba; + cv::cvtColor(img, rgba, cv::COLOR_BGR2BGRA); + + const int sp = 10; + const int sr = 10; + const int minsize = 20; + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_src(rgba); + cv::Mat dst; + + TEST_CYCLE() cv::gpu::meanShiftSegmentation(d_src, dst, sp, sr, minsize); + + GPU_SANITY_CHECK(dst); + } + else + { + FAIL_NO_CPU(); + } +} + +////////////////////////////////////////////////////////////////////// +// BlendLinear + +PERF_TEST_P(Sz_Depth_Cn, ImgProc_BlendLinear, + Combine(GPU_TYPICAL_MAT_SIZES, + Values(CV_8U, CV_32F), + GPU_CHANNELS_1_3_4)) +{ + const cv::Size size = GET_PARAM(0); + const int depth = GET_PARAM(1); + const int channels = GET_PARAM(2); + + const int type = CV_MAKE_TYPE(depth, channels); + + cv::Mat img1(size, type); + cv::Mat img2(size, type); + declare.in(img1, img2, WARMUP_RNG); + + const cv::Mat weights1(size, CV_32FC1, cv::Scalar::all(0.5)); + const cv::Mat weights2(size, CV_32FC1, cv::Scalar::all(0.5)); + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_img1(img1); + const cv::gpu::GpuMat d_img2(img2); + const cv::gpu::GpuMat d_weights1(weights1); + const cv::gpu::GpuMat d_weights2(weights2); + cv::gpu::GpuMat dst; + + TEST_CYCLE() cv::gpu::blendLinear(d_img1, d_img2, d_weights1, d_weights2, dst); + + GPU_SANITY_CHECK(dst); + } + else + { + FAIL_NO_CPU(); + } +} + +////////////////////////////////////////////////////////////////////// +// Convolve + +DEF_PARAM_TEST(Sz_KernelSz_Ccorr, cv::Size, int, bool); + +PERF_TEST_P(Sz_KernelSz_Ccorr, ImgProc_Convolve, + Combine(GPU_TYPICAL_MAT_SIZES, + Values(17, 27, 32, 64), + Bool())) +{ + declare.time(10.0); + + const cv::Size size = GET_PARAM(0); + const int templ_size = GET_PARAM(1); + const bool ccorr = GET_PARAM(2); + + const cv::Mat image(size, CV_32FC1); + const cv::Mat templ(templ_size, templ_size, CV_32FC1); + declare.in(image, templ, WARMUP_RNG); + + if (PERF_RUN_GPU()) + { + cv::gpu::GpuMat d_image = cv::gpu::createContinuous(size, CV_32FC1); + d_image.upload(image); + + cv::gpu::GpuMat d_templ = cv::gpu::createContinuous(templ_size, templ_size, CV_32FC1); + d_templ.upload(templ); + + cv::gpu::GpuMat dst; + cv::gpu::ConvolveBuf d_buf; + + TEST_CYCLE() cv::gpu::convolve(d_image, d_templ, dst, ccorr, d_buf); + + GPU_SANITY_CHECK(dst); + } + else + { + if (ccorr) + FAIL_NO_CPU(); + + cv::Mat dst; + + TEST_CYCLE() cv::filter2D(image, dst, image.depth(), templ); + + CPU_SANITY_CHECK(dst); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// MatchTemplate8U + +CV_ENUM(TemplateMethod, TM_SQDIFF, TM_SQDIFF_NORMED, TM_CCORR, TM_CCORR_NORMED, TM_CCOEFF, TM_CCOEFF_NORMED) + +DEF_PARAM_TEST(Sz_TemplateSz_Cn_Method, cv::Size, cv::Size, MatCn, TemplateMethod); + +PERF_TEST_P(Sz_TemplateSz_Cn_Method, ImgProc_MatchTemplate8U, + Combine(GPU_TYPICAL_MAT_SIZES, + Values(cv::Size(5, 5), cv::Size(16, 16), cv::Size(30, 30)), + GPU_CHANNELS_1_3_4, + TemplateMethod::all())) +{ + declare.time(300.0); + + const cv::Size size = GET_PARAM(0); + const cv::Size templ_size = GET_PARAM(1); + const int cn = GET_PARAM(2); + const int method = GET_PARAM(3); + + cv::Mat image(size, CV_MAKE_TYPE(CV_8U, cn)); + cv::Mat templ(templ_size, CV_MAKE_TYPE(CV_8U, cn)); + declare.in(image, templ, WARMUP_RNG); + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_image(image); + const cv::gpu::GpuMat d_templ(templ); + cv::gpu::GpuMat dst; + + TEST_CYCLE() cv::gpu::matchTemplate(d_image, d_templ, dst, method); + + GPU_SANITY_CHECK(dst, 1e-5, ERROR_RELATIVE); + } + else + { + cv::Mat dst; + + TEST_CYCLE() cv::matchTemplate(image, templ, dst, method); + + CPU_SANITY_CHECK(dst); + } +}; + +//////////////////////////////////////////////////////////////////////////////// +// MatchTemplate32F + +PERF_TEST_P(Sz_TemplateSz_Cn_Method, ImgProc_MatchTemplate32F, + Combine(GPU_TYPICAL_MAT_SIZES, + Values(cv::Size(5, 5), cv::Size(16, 16), cv::Size(30, 30)), + GPU_CHANNELS_1_3_4, + Values(TemplateMethod(cv::TM_SQDIFF), TemplateMethod(cv::TM_CCORR)))) +{ + declare.time(300.0); + + const cv::Size size = GET_PARAM(0); + const cv::Size templ_size = GET_PARAM(1); + const int cn = GET_PARAM(2); + int method = GET_PARAM(3); + + cv::Mat image(size, CV_MAKE_TYPE(CV_32F, cn)); + cv::Mat templ(templ_size, CV_MAKE_TYPE(CV_32F, cn)); + declare.in(image, templ, WARMUP_RNG); + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_image(image); + const cv::gpu::GpuMat d_templ(templ); + cv::gpu::GpuMat dst; + + TEST_CYCLE() cv::gpu::matchTemplate(d_image, d_templ, dst, method); + + GPU_SANITY_CHECK(dst, 1e-6, ERROR_RELATIVE); + } + else + { + cv::Mat dst; + + TEST_CYCLE() cv::matchTemplate(image, templ, dst, method); + + CPU_SANITY_CHECK(dst); + } +}; + +////////////////////////////////////////////////////////////////////// +// MulSpectrums + +CV_FLAGS(DftFlags, 0, DFT_INVERSE, DFT_SCALE, DFT_ROWS, DFT_COMPLEX_OUTPUT, DFT_REAL_OUTPUT) + +DEF_PARAM_TEST(Sz_Flags, cv::Size, DftFlags); + +PERF_TEST_P(Sz_Flags, ImgProc_MulSpectrums, + Combine(GPU_TYPICAL_MAT_SIZES, + Values(0, DftFlags(cv::DFT_ROWS)))) +{ + const cv::Size size = GET_PARAM(0); + const int flag = GET_PARAM(1); + + cv::Mat a(size, CV_32FC2); + cv::Mat b(size, CV_32FC2); + declare.in(a, b, WARMUP_RNG); + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_a(a); + const cv::gpu::GpuMat d_b(b); + cv::gpu::GpuMat dst; + + TEST_CYCLE() cv::gpu::mulSpectrums(d_a, d_b, dst, flag); + + GPU_SANITY_CHECK(dst); + } + else + { + cv::Mat dst; + + TEST_CYCLE() cv::mulSpectrums(a, b, dst, flag); + + CPU_SANITY_CHECK(dst); + } +} + +////////////////////////////////////////////////////////////////////// +// MulAndScaleSpectrums + +PERF_TEST_P(Sz, ImgProc_MulAndScaleSpectrums, + GPU_TYPICAL_MAT_SIZES) +{ + const cv::Size size = GetParam(); + + const float scale = 1.f / size.area(); + + cv::Mat src1(size, CV_32FC2); + cv::Mat src2(size, CV_32FC2); + declare.in(src1,src2, WARMUP_RNG); + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_src1(src1); + const cv::gpu::GpuMat d_src2(src2); + cv::gpu::GpuMat dst; + + TEST_CYCLE() cv::gpu::mulAndScaleSpectrums(d_src1, d_src2, dst, cv::DFT_ROWS, scale, false); + + GPU_SANITY_CHECK(dst); + } + else + { + FAIL_NO_CPU(); + } +} + +////////////////////////////////////////////////////////////////////// +// Dft + +PERF_TEST_P(Sz_Flags, ImgProc_Dft, + Combine(GPU_TYPICAL_MAT_SIZES, + Values(0, DftFlags(cv::DFT_ROWS), DftFlags(cv::DFT_INVERSE)))) +{ + declare.time(10.0); + + const cv::Size size = GET_PARAM(0); + const int flag = GET_PARAM(1); + + cv::Mat src(size, CV_32FC2); + declare.in(src, WARMUP_RNG); + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_src(src); + cv::gpu::GpuMat dst; + + TEST_CYCLE() cv::gpu::dft(d_src, dst, size, flag); + + GPU_SANITY_CHECK(dst, 1e-6, ERROR_RELATIVE); + } + else + { + cv::Mat dst; + + TEST_CYCLE() cv::dft(src, dst, flag); + + CPU_SANITY_CHECK(dst); + } +} + +////////////////////////////////////////////////////////////////////// +// CornerHarris + +DEF_PARAM_TEST(Image_Type_Border_BlockSz_ApertureSz, string, MatType, BorderMode, int, int); + +PERF_TEST_P(Image_Type_Border_BlockSz_ApertureSz, ImgProc_CornerHarris, + Combine(Values("gpu/stereobm/aloe-L.png"), + Values(CV_8UC1, CV_32FC1), + Values(BorderMode(cv::BORDER_REFLECT101), BorderMode(cv::BORDER_REPLICATE), BorderMode(cv::BORDER_REFLECT)), + Values(3, 5, 7), + Values(0, 3, 5, 7))) +{ + const string fileName = GET_PARAM(0); + const int type = GET_PARAM(1); + const int borderMode = GET_PARAM(2); + const int blockSize = GET_PARAM(3); + const int apertureSize = GET_PARAM(4); + + cv::Mat img = readImage(fileName, cv::IMREAD_GRAYSCALE); + ASSERT_FALSE(img.empty()); + + img.convertTo(img, type, type == CV_32F ? 1.0 / 255.0 : 1.0); + + const double k = 0.5; + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_img(img); + cv::gpu::GpuMat dst; + cv::gpu::GpuMat d_Dx; + cv::gpu::GpuMat d_Dy; + cv::gpu::GpuMat d_buf; + + TEST_CYCLE() cv::gpu::cornerHarris(d_img, dst, d_Dx, d_Dy, d_buf, blockSize, apertureSize, k, borderMode); + + GPU_SANITY_CHECK(dst, 1e-4); + } + else + { + cv::Mat dst; + + TEST_CYCLE() cv::cornerHarris(img, dst, blockSize, apertureSize, k, borderMode); + + CPU_SANITY_CHECK(dst); + } +} + +////////////////////////////////////////////////////////////////////// +// CornerMinEigenVal + +PERF_TEST_P(Image_Type_Border_BlockSz_ApertureSz, ImgProc_CornerMinEigenVal, + Combine(Values("gpu/stereobm/aloe-L.png"), + Values(CV_8UC1, CV_32FC1), + Values(BorderMode(cv::BORDER_REFLECT101), BorderMode(cv::BORDER_REPLICATE), BorderMode(cv::BORDER_REFLECT)), + Values(3, 5, 7), + Values(0, 3, 5, 7))) +{ + const string fileName = GET_PARAM(0); + const int type = GET_PARAM(1); + const int borderMode = GET_PARAM(2); + const int blockSize = GET_PARAM(3); + const int apertureSize = GET_PARAM(4); + + cv::Mat img = readImage(fileName, cv::IMREAD_GRAYSCALE); + ASSERT_FALSE(img.empty()); + + img.convertTo(img, type, type == CV_32F ? 1.0 / 255.0 : 1.0); + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_img(img); + cv::gpu::GpuMat dst; + cv::gpu::GpuMat d_Dx; + cv::gpu::GpuMat d_Dy; + cv::gpu::GpuMat d_buf; + + TEST_CYCLE() cv::gpu::cornerMinEigenVal(d_img, dst, d_Dx, d_Dy, d_buf, blockSize, apertureSize, borderMode); + + GPU_SANITY_CHECK(dst, 1e-4); + } + else + { + cv::Mat dst; + + TEST_CYCLE() cv::cornerMinEigenVal(img, dst, blockSize, apertureSize, borderMode); + + CPU_SANITY_CHECK(dst); + } +} + +////////////////////////////////////////////////////////////////////// +// BuildWarpPlaneMaps + +PERF_TEST_P(Sz, ImgProc_BuildWarpPlaneMaps, + GPU_TYPICAL_MAT_SIZES) +{ + const cv::Size size = GetParam(); + + const cv::Mat K = cv::Mat::eye(3, 3, CV_32FC1); + const cv::Mat R = cv::Mat::ones(3, 3, CV_32FC1); + const cv::Mat T = cv::Mat::zeros(1, 3, CV_32F); + + if (PERF_RUN_GPU()) + { + cv::gpu::GpuMat map_x; + cv::gpu::GpuMat map_y; + + TEST_CYCLE() cv::gpu::buildWarpPlaneMaps(size, cv::Rect(0, 0, size.width, size.height), K, R, T, 1.0, map_x, map_y); + + GPU_SANITY_CHECK(map_x); + GPU_SANITY_CHECK(map_y); + } + else + { + FAIL_NO_CPU(); + } +} + +////////////////////////////////////////////////////////////////////// +// BuildWarpCylindricalMaps + +PERF_TEST_P(Sz, ImgProc_BuildWarpCylindricalMaps, + GPU_TYPICAL_MAT_SIZES) +{ + const cv::Size size = GetParam(); + + const cv::Mat K = cv::Mat::eye(3, 3, CV_32FC1); + const cv::Mat R = cv::Mat::ones(3, 3, CV_32FC1); + + if (PERF_RUN_GPU()) + { + cv::gpu::GpuMat map_x; + cv::gpu::GpuMat map_y; + + TEST_CYCLE() cv::gpu::buildWarpCylindricalMaps(size, cv::Rect(0, 0, size.width, size.height), K, R, 1.0, map_x, map_y); + + GPU_SANITY_CHECK(map_x); + GPU_SANITY_CHECK(map_y); + } + else + { + FAIL_NO_CPU(); + } +} + +////////////////////////////////////////////////////////////////////// +// BuildWarpSphericalMaps + +PERF_TEST_P(Sz, ImgProc_BuildWarpSphericalMaps, + GPU_TYPICAL_MAT_SIZES) +{ + const cv::Size size = GetParam(); + + const cv::Mat K = cv::Mat::eye(3, 3, CV_32FC1); + const cv::Mat R = cv::Mat::ones(3, 3, CV_32FC1); + + if (PERF_RUN_GPU()) + { + cv::gpu::GpuMat map_x; + cv::gpu::GpuMat map_y; + + TEST_CYCLE() cv::gpu::buildWarpSphericalMaps(size, cv::Rect(0, 0, size.width, size.height), K, R, 1.0, map_x, map_y); + + GPU_SANITY_CHECK(map_x); + GPU_SANITY_CHECK(map_y); + } + else + { + FAIL_NO_CPU(); + } +} + +////////////////////////////////////////////////////////////////////// +// Rotate + +DEF_PARAM_TEST(Sz_Depth_Cn_Inter, cv::Size, MatDepth, MatCn, Interpolation); + +PERF_TEST_P(Sz_Depth_Cn_Inter, ImgProc_Rotate, + Combine(GPU_TYPICAL_MAT_SIZES, + Values(CV_8U, CV_16U, CV_32F), + GPU_CHANNELS_1_3_4, + Values(Interpolation(cv::INTER_NEAREST), Interpolation(cv::INTER_LINEAR), Interpolation(cv::INTER_CUBIC)))) +{ + const cv::Size size = GET_PARAM(0); + const int depth = GET_PARAM(1); + const int channels = GET_PARAM(2); + const int interpolation = GET_PARAM(3); + + const int type = CV_MAKE_TYPE(depth, channels); + + cv::Mat src(size, type); + declare.in(src, WARMUP_RNG); + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_src(src); + cv::gpu::GpuMat dst; + + TEST_CYCLE() cv::gpu::rotate(d_src, dst, size, 30.0, 0, 0, interpolation); + + GPU_SANITY_CHECK(dst, 1e-3, ERROR_RELATIVE); + } + else + { + FAIL_NO_CPU(); + } +} + +////////////////////////////////////////////////////////////////////// +// PyrDown + +PERF_TEST_P(Sz_Depth_Cn, ImgProc_PyrDown, + Combine(GPU_TYPICAL_MAT_SIZES, + Values(CV_8U, CV_16U, CV_32F), + GPU_CHANNELS_1_3_4)) +{ + const cv::Size size = GET_PARAM(0); + const int depth = GET_PARAM(1); + const int channels = GET_PARAM(2); + + const int type = CV_MAKE_TYPE(depth, channels); + + cv::Mat src(size, type); + declare.in(src, WARMUP_RNG); + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_src(src); + cv::gpu::GpuMat dst; + + TEST_CYCLE() cv::gpu::pyrDown(d_src, dst); + + GPU_SANITY_CHECK(dst); + } + else + { + cv::Mat dst; + + TEST_CYCLE() cv::pyrDown(src, dst); + + CPU_SANITY_CHECK(dst); + } +} + +////////////////////////////////////////////////////////////////////// +// PyrUp + +PERF_TEST_P(Sz_Depth_Cn, ImgProc_PyrUp, + Combine(GPU_TYPICAL_MAT_SIZES, + Values(CV_8U, CV_16U, CV_32F), + GPU_CHANNELS_1_3_4)) +{ + const cv::Size size = GET_PARAM(0); + const int depth = GET_PARAM(1); + const int channels = GET_PARAM(2); + + const int type = CV_MAKE_TYPE(depth, channels); + + cv::Mat src(size, type); + declare.in(src, WARMUP_RNG); + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_src(src); + cv::gpu::GpuMat dst; + + TEST_CYCLE() cv::gpu::pyrUp(d_src, dst); + + GPU_SANITY_CHECK(dst); + } + else + { + cv::Mat dst; + + TEST_CYCLE() cv::pyrUp(src, dst); + + CPU_SANITY_CHECK(dst); + } +} + +////////////////////////////////////////////////////////////////////// +// CvtColor + +DEF_PARAM_TEST(Sz_Depth_Code, cv::Size, MatDepth, CvtColorInfo); + +PERF_TEST_P(Sz_Depth_Code, ImgProc_CvtColor, + Combine(GPU_TYPICAL_MAT_SIZES, + Values(CV_8U, CV_32F), + Values(CvtColorInfo(4, 4, cv::COLOR_RGBA2BGRA), + CvtColorInfo(4, 1, cv::COLOR_BGRA2GRAY), + CvtColorInfo(1, 4, cv::COLOR_GRAY2BGRA), + CvtColorInfo(3, 3, cv::COLOR_BGR2XYZ), + CvtColorInfo(3, 3, cv::COLOR_XYZ2BGR), + CvtColorInfo(3, 3, cv::COLOR_BGR2YCrCb), + CvtColorInfo(3, 3, cv::COLOR_YCrCb2BGR), + CvtColorInfo(3, 3, cv::COLOR_BGR2YUV), + CvtColorInfo(3, 3, cv::COLOR_YUV2BGR), + CvtColorInfo(3, 3, cv::COLOR_BGR2HSV), + CvtColorInfo(3, 3, cv::COLOR_HSV2BGR), + CvtColorInfo(3, 3, cv::COLOR_BGR2HLS), + CvtColorInfo(3, 3, cv::COLOR_HLS2BGR), + CvtColorInfo(3, 3, cv::COLOR_BGR2Lab), + CvtColorInfo(3, 3, cv::COLOR_LBGR2Lab), + CvtColorInfo(3, 3, cv::COLOR_BGR2Luv), + CvtColorInfo(3, 3, cv::COLOR_LBGR2Luv), + CvtColorInfo(3, 3, cv::COLOR_Lab2BGR), + CvtColorInfo(3, 3, cv::COLOR_Lab2LBGR), + CvtColorInfo(3, 3, cv::COLOR_Luv2RGB), + CvtColorInfo(3, 3, cv::COLOR_Luv2LRGB)))) +{ + const cv::Size size = GET_PARAM(0); + const int depth = GET_PARAM(1); + const CvtColorInfo info = GET_PARAM(2); + + cv::Mat src(size, CV_MAKETYPE(depth, info.scn)); + cv::randu(src, 0, depth == CV_8U ? 255.0 : 1.0); + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_src(src); + cv::gpu::GpuMat dst; + + TEST_CYCLE() cv::gpu::cvtColor(d_src, dst, info.code, info.dcn); + + GPU_SANITY_CHECK(dst, 1e-4); + } + else + { + cv::Mat dst; + + TEST_CYCLE() cv::cvtColor(src, dst, info.code, info.dcn); + + CPU_SANITY_CHECK(dst); + } +} + +PERF_TEST_P(Sz_Depth_Code, ImgProc_CvtColorBayer, + Combine(GPU_TYPICAL_MAT_SIZES, + Values(CV_8U, CV_16U), + Values(CvtColorInfo(1, 3, cv::COLOR_BayerBG2BGR), + CvtColorInfo(1, 3, cv::COLOR_BayerGB2BGR), + CvtColorInfo(1, 3, cv::COLOR_BayerRG2BGR), + CvtColorInfo(1, 3, cv::COLOR_BayerGR2BGR), + + CvtColorInfo(1, 1, cv::COLOR_BayerBG2GRAY), + CvtColorInfo(1, 1, cv::COLOR_BayerGB2GRAY), + CvtColorInfo(1, 1, cv::COLOR_BayerRG2GRAY), + CvtColorInfo(1, 1, cv::COLOR_BayerGR2GRAY)))) +{ + const cv::Size size = GET_PARAM(0); + const int depth = GET_PARAM(1); + const CvtColorInfo info = GET_PARAM(2); + + cv::Mat src(size, CV_MAKETYPE(depth, info.scn)); + declare.in(src, WARMUP_RNG); + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_src(src); + cv::gpu::GpuMat dst; + + TEST_CYCLE() cv::gpu::cvtColor(d_src, dst, info.code, info.dcn); + + GPU_SANITY_CHECK(dst); + } + else + { + cv::Mat dst; + + TEST_CYCLE() cv::cvtColor(src, dst, info.code, info.dcn); + + CPU_SANITY_CHECK(dst); + } +} + +CV_ENUM(DemosaicingCode, + COLOR_BayerBG2BGR, COLOR_BayerGB2BGR, COLOR_BayerRG2BGR, COLOR_BayerGR2BGR, + COLOR_BayerBG2GRAY, COLOR_BayerGB2GRAY, COLOR_BayerRG2GRAY, COLOR_BayerGR2GRAY, + COLOR_BayerBG2BGR_MHT, COLOR_BayerGB2BGR_MHT, COLOR_BayerRG2BGR_MHT, COLOR_BayerGR2BGR_MHT, + COLOR_BayerBG2GRAY_MHT, COLOR_BayerGB2GRAY_MHT, COLOR_BayerRG2GRAY_MHT, COLOR_BayerGR2GRAY_MHT) + +DEF_PARAM_TEST(Sz_Code, cv::Size, DemosaicingCode); + +PERF_TEST_P(Sz_Code, ImgProc_Demosaicing, + Combine(GPU_TYPICAL_MAT_SIZES, + DemosaicingCode::all())) +{ + const cv::Size size = GET_PARAM(0); + const int code = GET_PARAM(1); + + cv::Mat src(size, CV_8UC1); + declare.in(src, WARMUP_RNG); + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_src(src); + cv::gpu::GpuMat dst; + + TEST_CYCLE() cv::gpu::demosaicing(d_src, dst, code); + + GPU_SANITY_CHECK(dst); + } + else + { + if (code >= cv::COLOR_COLORCVT_MAX) + { + FAIL_NO_CPU(); + } + else + { + cv::Mat dst; + + TEST_CYCLE() cv::cvtColor(src, dst, code); + + CPU_SANITY_CHECK(dst); + } + } +} + +////////////////////////////////////////////////////////////////////// +// SwapChannels + +PERF_TEST_P(Sz, ImgProc_SwapChannels, + GPU_TYPICAL_MAT_SIZES) +{ + const cv::Size size = GetParam(); + + cv::Mat src(size, CV_8UC4); + declare.in(src, WARMUP_RNG); + + const int dstOrder[] = {2, 1, 0, 3}; + + if (PERF_RUN_GPU()) + { + cv::gpu::GpuMat dst(src); + + TEST_CYCLE() cv::gpu::swapChannels(dst, dstOrder); + + GPU_SANITY_CHECK(dst); + } + else + { + FAIL_NO_CPU(); + } +} + +////////////////////////////////////////////////////////////////////// +// AlphaComp + +CV_ENUM(AlphaOp, ALPHA_OVER, ALPHA_IN, ALPHA_OUT, ALPHA_ATOP, ALPHA_XOR, ALPHA_PLUS, ALPHA_OVER_PREMUL, ALPHA_IN_PREMUL, ALPHA_OUT_PREMUL, ALPHA_ATOP_PREMUL, ALPHA_XOR_PREMUL, ALPHA_PLUS_PREMUL, ALPHA_PREMUL) + +DEF_PARAM_TEST(Sz_Type_Op, cv::Size, MatType, AlphaOp); + +PERF_TEST_P(Sz_Type_Op, ImgProc_AlphaComp, + Combine(GPU_TYPICAL_MAT_SIZES, + Values(CV_8UC4, CV_16UC4, CV_32SC4, CV_32FC4), + AlphaOp::all())) +{ + const cv::Size size = GET_PARAM(0); + const int type = GET_PARAM(1); + const int alpha_op = GET_PARAM(2); + + cv::Mat img1(size, type); + cv::Mat img2(size, type); + declare.in(img1, img2, WARMUP_RNG); + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_img1(img1); + const cv::gpu::GpuMat d_img2(img2); + cv::gpu::GpuMat dst; + + TEST_CYCLE() cv::gpu::alphaComp(d_img1, d_img2, dst, alpha_op); + + GPU_SANITY_CHECK(dst, 1e-3, ERROR_RELATIVE); + } + else + { + FAIL_NO_CPU(); + } +} + +////////////////////////////////////////////////////////////////////// +// ImagePyramidBuild + +PERF_TEST_P(Sz_Depth_Cn, ImgProc_ImagePyramidBuild, + Combine(GPU_TYPICAL_MAT_SIZES, + Values(CV_8U, CV_16U, CV_32F), + GPU_CHANNELS_1_3_4)) +{ + const cv::Size size = GET_PARAM(0); + const int depth = GET_PARAM(1); + const int channels = GET_PARAM(2); + + const int type = CV_MAKE_TYPE(depth, channels); + + cv::Mat src(size, type); + declare.in(src, WARMUP_RNG); + + const int nLayers = 5; + const cv::Size dstSize(size.width / 2 + 10, size.height / 2 + 10); + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_src(src); + + cv::gpu::ImagePyramid d_pyr; + + TEST_CYCLE() d_pyr.build(d_src, nLayers); + + cv::gpu::GpuMat dst; + d_pyr.getLayer(dst, dstSize); + + GPU_SANITY_CHECK(dst); + } + else + { + FAIL_NO_CPU(); + } +} + +////////////////////////////////////////////////////////////////////// +// ImagePyramidGetLayer + +PERF_TEST_P(Sz_Depth_Cn, ImgProc_ImagePyramidGetLayer, + Combine(GPU_TYPICAL_MAT_SIZES, + Values(CV_8U, CV_16U, CV_32F), + GPU_CHANNELS_1_3_4)) +{ + const cv::Size size = GET_PARAM(0); + const int depth = GET_PARAM(1); + const int channels = GET_PARAM(2); + + const int type = CV_MAKE_TYPE(depth, channels); + + cv::Mat src(size, type); + declare.in(src, WARMUP_RNG); + + const int nLayers = 3; + const cv::Size dstSize(size.width / 2 + 10, size.height / 2 + 10); + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_src(src); + cv::gpu::GpuMat dst; + + cv::gpu::ImagePyramid d_pyr(d_src, nLayers); + + TEST_CYCLE() d_pyr.getLayer(dst, dstSize); + + GPU_SANITY_CHECK(dst); + } + else + { + FAIL_NO_CPU(); + } +} + +////////////////////////////////////////////////////////////////////// +// HoughLines + +namespace +{ + struct Vec4iComparator + { + bool operator()(const cv::Vec4i& a, const cv::Vec4i b) const + { + if (a[0] != b[0]) return a[0] < b[0]; + else if(a[1] != b[1]) return a[1] < b[1]; + else if(a[2] != b[2]) return a[2] < b[2]; + else return a[3] < b[3]; + } + }; + struct Vec3fComparator + { + bool operator()(const cv::Vec3f& a, const cv::Vec3f b) const + { + if(a[0] != b[0]) return a[0] < b[0]; + else if(a[1] != b[1]) return a[1] < b[1]; + else return a[2] < b[2]; + } + }; + struct Vec2fComparator + { + bool operator()(const cv::Vec2f& a, const cv::Vec2f b) const + { + if(a[0] != b[0]) return a[0] < b[0]; + else return a[1] < b[1]; + } + }; +} + +PERF_TEST_P(Sz, ImgProc_HoughLines, + GPU_TYPICAL_MAT_SIZES) +{ + declare.time(30.0); + + const cv::Size size = GetParam(); + + const float rho = 1.0f; + const float theta = static_cast(CV_PI / 180.0); + const int threshold = 300; + + cv::Mat src(size, CV_8UC1, cv::Scalar::all(0)); + cv::line(src, cv::Point(0, 100), cv::Point(src.cols, 100), cv::Scalar::all(255), 1); + cv::line(src, cv::Point(0, 200), cv::Point(src.cols, 200), cv::Scalar::all(255), 1); + cv::line(src, cv::Point(0, 400), cv::Point(src.cols, 400), cv::Scalar::all(255), 1); + cv::line(src, cv::Point(100, 0), cv::Point(100, src.rows), cv::Scalar::all(255), 1); + cv::line(src, cv::Point(200, 0), cv::Point(200, src.rows), cv::Scalar::all(255), 1); + cv::line(src, cv::Point(400, 0), cv::Point(400, src.rows), cv::Scalar::all(255), 1); + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_src(src); + cv::gpu::GpuMat d_lines; + cv::gpu::HoughLinesBuf d_buf; + + TEST_CYCLE() cv::gpu::HoughLines(d_src, d_lines, d_buf, rho, theta, threshold); + + cv::Mat gpu_lines(d_lines.row(0)); + cv::Vec2f* begin = gpu_lines.ptr(0); + cv::Vec2f* end = begin + gpu_lines.cols; + std::sort(begin, end, Vec2fComparator()); + SANITY_CHECK(gpu_lines); + } + else + { + std::vector cpu_lines; + + TEST_CYCLE() cv::HoughLines(src, cpu_lines, rho, theta, threshold); + + SANITY_CHECK(cpu_lines); + } +} + +////////////////////////////////////////////////////////////////////// +// HoughLinesP + +DEF_PARAM_TEST_1(Image, std::string); + +PERF_TEST_P(Image, ImgProc_HoughLinesP, + testing::Values("cv/shared/pic5.png", "stitching/a1.png")) +{ + declare.time(30.0); + + const std::string fileName = getDataPath(GetParam()); + + const float rho = 1.0f; + const float theta = static_cast(CV_PI / 180.0); + const int threshold = 100; + const int minLineLenght = 50; + const int maxLineGap = 5; + + const cv::Mat image = cv::imread(fileName, cv::IMREAD_GRAYSCALE); + ASSERT_FALSE(image.empty()); + + cv::Mat mask; + cv::Canny(image, mask, 50, 100); + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_mask(mask); + cv::gpu::GpuMat d_lines; + cv::gpu::HoughLinesBuf d_buf; + + TEST_CYCLE() cv::gpu::HoughLinesP(d_mask, d_lines, d_buf, rho, theta, minLineLenght, maxLineGap); + + cv::Mat gpu_lines(d_lines); + cv::Vec4i* begin = gpu_lines.ptr(); + cv::Vec4i* end = begin + gpu_lines.cols; + std::sort(begin, end, Vec4iComparator()); + SANITY_CHECK(gpu_lines); + } + else + { + std::vector cpu_lines; + + TEST_CYCLE() cv::HoughLinesP(mask, cpu_lines, rho, theta, threshold, minLineLenght, maxLineGap); + + SANITY_CHECK(cpu_lines); + } +} + +////////////////////////////////////////////////////////////////////// +// HoughCircles + +DEF_PARAM_TEST(Sz_Dp_MinDist, cv::Size, float, float); + +PERF_TEST_P(Sz_Dp_MinDist, ImgProc_HoughCircles, + Combine(GPU_TYPICAL_MAT_SIZES, + Values(1.0f, 2.0f, 4.0f), + Values(1.0f))) +{ + declare.time(30.0); + + const cv::Size size = GET_PARAM(0); + const float dp = GET_PARAM(1); + const float minDist = GET_PARAM(2); + + const int minRadius = 10; + const int maxRadius = 30; + const int cannyThreshold = 100; + const int votesThreshold = 15; + + cv::Mat src(size, CV_8UC1, cv::Scalar::all(0)); + cv::circle(src, cv::Point(100, 100), 20, cv::Scalar::all(255), -1); + cv::circle(src, cv::Point(200, 200), 25, cv::Scalar::all(255), -1); + cv::circle(src, cv::Point(200, 100), 25, cv::Scalar::all(255), -1); + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_src(src); + cv::gpu::GpuMat d_circles; + cv::gpu::HoughCirclesBuf d_buf; + + TEST_CYCLE() cv::gpu::HoughCircles(d_src, d_circles, d_buf, cv::HOUGH_GRADIENT, dp, minDist, cannyThreshold, votesThreshold, minRadius, maxRadius); + + cv::Mat gpu_circles(d_circles); + cv::Vec3f* begin = gpu_circles.ptr(0); + cv::Vec3f* end = begin + gpu_circles.cols; + std::sort(begin, end, Vec3fComparator()); + SANITY_CHECK(gpu_circles); + } + else + { + std::vector cpu_circles; + + TEST_CYCLE() cv::HoughCircles(src, cpu_circles, cv::HOUGH_GRADIENT, dp, minDist, cannyThreshold, votesThreshold, minRadius, maxRadius); + + SANITY_CHECK(cpu_circles); + } +} + +////////////////////////////////////////////////////////////////////// +// GeneralizedHough + +enum { GHT_POSITION = cv::GeneralizedHough::GHT_POSITION, + GHT_SCALE = cv::GeneralizedHough::GHT_SCALE, + GHT_ROTATION = cv::GeneralizedHough::GHT_ROTATION + }; + +CV_FLAGS(GHMethod, GHT_POSITION, GHT_SCALE, GHT_ROTATION); + +DEF_PARAM_TEST(Method_Sz, GHMethod, cv::Size); + +PERF_TEST_P(Method_Sz, ImgProc_GeneralizedHough, + Combine(Values(GHMethod(GHT_POSITION), GHMethod(GHT_POSITION | GHT_SCALE), GHMethod(GHT_POSITION | GHT_ROTATION), GHMethod(GHT_POSITION | GHT_SCALE | GHT_ROTATION)), + GPU_TYPICAL_MAT_SIZES)) +{ + declare.time(10); + + const int method = GET_PARAM(0); + const cv::Size imageSize = GET_PARAM(1); + + const cv::Mat templ = readImage("cv/shared/templ.png", cv::IMREAD_GRAYSCALE); + ASSERT_FALSE(templ.empty()); + + cv::Mat image(imageSize, CV_8UC1, cv::Scalar::all(0)); + templ.copyTo(image(cv::Rect(50, 50, templ.cols, templ.rows))); + + cv::RNG rng(123456789); + const int objCount = rng.uniform(5, 15); + for (int i = 0; i < objCount; ++i) + { + double scale = rng.uniform(0.7, 1.3); + bool rotate = 1 == rng.uniform(0, 2); + + cv::Mat obj; + cv::resize(templ, obj, cv::Size(), scale, scale); + if (rotate) + obj = obj.t(); + + cv::Point pos; + + pos.x = rng.uniform(0, image.cols - obj.cols); + pos.y = rng.uniform(0, image.rows - obj.rows); + + cv::Mat roi = image(cv::Rect(pos, obj.size())); + cv::add(roi, obj, roi); + } + + cv::Mat edges; + cv::Canny(image, edges, 50, 100); + + cv::Mat dx, dy; + cv::Sobel(image, dx, CV_32F, 1, 0); + cv::Sobel(image, dy, CV_32F, 0, 1); + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_edges(edges); + const cv::gpu::GpuMat d_dx(dx); + const cv::gpu::GpuMat d_dy(dy); + cv::gpu::GpuMat posAndVotes; + + cv::Ptr d_hough = cv::gpu::GeneralizedHough_GPU::create(method); + if (method & GHT_ROTATION) + { + d_hough->set("maxAngle", 90.0); + d_hough->set("angleStep", 2.0); + } + + d_hough->setTemplate(cv::gpu::GpuMat(templ)); + + TEST_CYCLE() d_hough->detect(d_edges, d_dx, d_dy, posAndVotes); + + const cv::gpu::GpuMat positions(1, posAndVotes.cols, CV_32FC4, posAndVotes.data); + GPU_SANITY_CHECK(positions); + } + else + { + cv::Mat positions; + + cv::Ptr hough = cv::GeneralizedHough::create(method); + if (method & GHT_ROTATION) + { + hough->set("maxAngle", 90.0); + hough->set("angleStep", 2.0); + } + + hough->setTemplate(templ); + + TEST_CYCLE() hough->detect(edges, dx, dy, positions); + + CPU_SANITY_CHECK(positions); + } +} diff --git a/modules/gpu/perf/perf_precomp.hpp b/modules/gpu/perf/perf_precomp.hpp index 5bfb14b52e..f365a5aea8 100644 --- a/modules/gpu/perf/perf_precomp.hpp +++ b/modules/gpu/perf/perf_precomp.hpp @@ -51,12 +51,21 @@ #ifndef __OPENCV_PERF_PRECOMP_HPP__ #define __OPENCV_PERF_PRECOMP_HPP__ +#include +#include + #include "opencv2/ts.hpp" #include "opencv2/ts/gpu_perf.hpp" +#include "opencv2/core.hpp" +#include "opencv2/highgui.hpp" #include "opencv2/gpu.hpp" #include "opencv2/calib3d.hpp" -#include "opencv2/objdetect.hpp" +#include "opencv2/imgproc.hpp" +#include "opencv2/video.hpp" +#include "opencv2/photo.hpp" + +#include "opencv2/core/gpu_private.hpp" #ifdef GTEST_CREATE_SHARED_LIBRARY #error no modules except ts should have GTEST_CREATE_SHARED_LIBRARY defined diff --git a/modules/gpu/perf/perf_video.cpp b/modules/gpu/perf/perf_video.cpp new file mode 100644 index 0000000000..c69b9606c8 --- /dev/null +++ b/modules/gpu/perf/perf_video.cpp @@ -0,0 +1,1107 @@ +/*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 "perf_precomp.hpp" +#include "opencv2/legacy.hpp" + +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 + +namespace cv +{ + template<> void Ptr::delete_obj() + { + cvReleaseBGStatModel(&obj); + } +} + +////////////////////////////////////////////////////// +// InterpolateFrames + +typedef pair pair_string; + +DEF_PARAM_TEST_1(ImagePair, pair_string); + +PERF_TEST_P(ImagePair, Video_InterpolateFrames, + Values(make_pair("gpu/opticalflow/frame0.png", "gpu/opticalflow/frame1.png"))) +{ + cv::Mat frame0 = readImage(GetParam().first, cv::IMREAD_GRAYSCALE); + ASSERT_FALSE(frame0.empty()); + + cv::Mat frame1 = readImage(GetParam().second, cv::IMREAD_GRAYSCALE); + ASSERT_FALSE(frame1.empty()); + + frame0.convertTo(frame0, CV_32FC1, 1.0 / 255.0); + frame1.convertTo(frame1, CV_32FC1, 1.0 / 255.0); + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_frame0(frame0); + const cv::gpu::GpuMat d_frame1(frame1); + cv::gpu::GpuMat d_fu, d_fv; + cv::gpu::GpuMat d_bu, d_bv; + + cv::gpu::BroxOpticalFlow d_flow(0.197f /*alpha*/, 50.0f /*gamma*/, 0.8f /*scale_factor*/, + 10 /*inner_iterations*/, 77 /*outer_iterations*/, 10 /*solver_iterations*/); + + d_flow(d_frame0, d_frame1, d_fu, d_fv); + d_flow(d_frame1, d_frame0, d_bu, d_bv); + + cv::gpu::GpuMat newFrame; + cv::gpu::GpuMat d_buf; + + 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); + } + else + { + FAIL_NO_CPU(); + } +} + +////////////////////////////////////////////////////// +// CreateOpticalFlowNeedleMap + +PERF_TEST_P(ImagePair, Video_CreateOpticalFlowNeedleMap, + Values(make_pair("gpu/opticalflow/frame0.png", "gpu/opticalflow/frame1.png"))) +{ + cv::Mat frame0 = readImage(GetParam().first, cv::IMREAD_GRAYSCALE); + ASSERT_FALSE(frame0.empty()); + + cv::Mat frame1 = readImage(GetParam().second, cv::IMREAD_GRAYSCALE); + ASSERT_FALSE(frame1.empty()); + + frame0.convertTo(frame0, CV_32FC1, 1.0 / 255.0); + frame1.convertTo(frame1, CV_32FC1, 1.0 / 255.0); + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_frame0(frame0); + const cv::gpu::GpuMat d_frame1(frame1); + cv::gpu::GpuMat u; + cv::gpu::GpuMat v; + + cv::gpu::BroxOpticalFlow d_flow(0.197f /*alpha*/, 50.0f /*gamma*/, 0.8f /*scale_factor*/, + 10 /*inner_iterations*/, 77 /*outer_iterations*/, 10 /*solver_iterations*/); + + d_flow(d_frame0, d_frame1, u, v); + + cv::gpu::GpuMat vertex, colors; + + TEST_CYCLE() cv::gpu::createOpticalFlowNeedleMap(u, v, vertex, colors); + + GPU_SANITY_CHECK(vertex); + GPU_SANITY_CHECK(colors); + } + else + { + FAIL_NO_CPU(); + } +} + +////////////////////////////////////////////////////// +// GoodFeaturesToTrack + +DEF_PARAM_TEST(Image_MinDistance, string, double); + +PERF_TEST_P(Image_MinDistance, Video_GoodFeaturesToTrack, + Combine(Values("gpu/perf/aloe.png"), + Values(0.0, 3.0))) +{ + const string fileName = GET_PARAM(0); + const double minDistance = GET_PARAM(1); + + const cv::Mat image = readImage(fileName, cv::IMREAD_GRAYSCALE); + ASSERT_FALSE(image.empty()); + + const int maxCorners = 8000; + const double qualityLevel = 0.01; + + if (PERF_RUN_GPU()) + { + cv::gpu::GoodFeaturesToTrackDetector_GPU d_detector(maxCorners, qualityLevel, minDistance); + + const cv::gpu::GpuMat d_image(image); + cv::gpu::GpuMat pts; + + TEST_CYCLE() d_detector(d_image, pts); + + GPU_SANITY_CHECK(pts); + } + else + { + cv::Mat pts; + + TEST_CYCLE() cv::goodFeaturesToTrack(image, pts, maxCorners, qualityLevel, minDistance); + + CPU_SANITY_CHECK(pts); + } +} + +////////////////////////////////////////////////////// +// BroxOpticalFlow + +PERF_TEST_P(ImagePair, Video_BroxOpticalFlow, + Values(make_pair("gpu/opticalflow/frame0.png", "gpu/opticalflow/frame1.png"))) +{ + declare.time(300); + + cv::Mat frame0 = readImage(GetParam().first, cv::IMREAD_GRAYSCALE); + ASSERT_FALSE(frame0.empty()); + + cv::Mat frame1 = readImage(GetParam().second, cv::IMREAD_GRAYSCALE); + ASSERT_FALSE(frame1.empty()); + + frame0.convertTo(frame0, CV_32FC1, 1.0 / 255.0); + frame1.convertTo(frame1, CV_32FC1, 1.0 / 255.0); + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_frame0(frame0); + const cv::gpu::GpuMat d_frame1(frame1); + cv::gpu::GpuMat u; + cv::gpu::GpuMat v; + + cv::gpu::BroxOpticalFlow d_flow(0.197f /*alpha*/, 50.0f /*gamma*/, 0.8f /*scale_factor*/, + 10 /*inner_iterations*/, 77 /*outer_iterations*/, 10 /*solver_iterations*/); + + TEST_CYCLE() d_flow(d_frame0, d_frame1, u, v); + + GPU_SANITY_CHECK(u); + GPU_SANITY_CHECK(v); + } + else + { + FAIL_NO_CPU(); + } +} + +////////////////////////////////////////////////////// +// PyrLKOpticalFlowSparse + +DEF_PARAM_TEST(ImagePair_Gray_NPts_WinSz_Levels_Iters, pair_string, bool, int, int, int, int); + +PERF_TEST_P(ImagePair_Gray_NPts_WinSz_Levels_Iters, Video_PyrLKOpticalFlowSparse, + Combine(Values(make_pair("gpu/opticalflow/frame0.png", "gpu/opticalflow/frame1.png")), + Bool(), + Values(8000), + Values(21), + Values(1, 3), + Values(1, 30))) +{ + declare.time(20.0); + + const pair_string imagePair = GET_PARAM(0); + const bool useGray = GET_PARAM(1); + const int points = GET_PARAM(2); + const int winSize = GET_PARAM(3); + const int levels = GET_PARAM(4); + const int iters = GET_PARAM(5); + + const cv::Mat frame0 = readImage(imagePair.first, useGray ? cv::IMREAD_GRAYSCALE : cv::IMREAD_COLOR); + ASSERT_FALSE(frame0.empty()); + + const cv::Mat frame1 = readImage(imagePair.second, useGray ? cv::IMREAD_GRAYSCALE : cv::IMREAD_COLOR); + ASSERT_FALSE(frame1.empty()); + + cv::Mat gray_frame; + if (useGray) + gray_frame = frame0; + else + cv::cvtColor(frame0, gray_frame, cv::COLOR_BGR2GRAY); + + cv::Mat pts; + cv::goodFeaturesToTrack(gray_frame, pts, points, 0.01, 0.0); + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_pts(pts.reshape(2, 1)); + + cv::gpu::PyrLKOpticalFlow d_pyrLK; + d_pyrLK.winSize = cv::Size(winSize, winSize); + d_pyrLK.maxLevel = levels - 1; + d_pyrLK.iters = iters; + + const cv::gpu::GpuMat d_frame0(frame0); + const cv::gpu::GpuMat d_frame1(frame1); + cv::gpu::GpuMat nextPts; + cv::gpu::GpuMat status; + + TEST_CYCLE() d_pyrLK.sparse(d_frame0, d_frame1, d_pts, nextPts, status); + + GPU_SANITY_CHECK(nextPts); + GPU_SANITY_CHECK(status); + } + else + { + cv::Mat nextPts; + cv::Mat status; + + TEST_CYCLE() + { + cv::calcOpticalFlowPyrLK(frame0, frame1, pts, nextPts, status, cv::noArray(), + cv::Size(winSize, winSize), levels - 1, + cv::TermCriteria(cv::TermCriteria::COUNT + cv::TermCriteria::EPS, iters, 0.01)); + } + + CPU_SANITY_CHECK(nextPts); + CPU_SANITY_CHECK(status); + } +} + +////////////////////////////////////////////////////// +// PyrLKOpticalFlowDense + +DEF_PARAM_TEST(ImagePair_WinSz_Levels_Iters, pair_string, int, int, int); + +PERF_TEST_P(ImagePair_WinSz_Levels_Iters, Video_PyrLKOpticalFlowDense, + Combine(Values(make_pair("gpu/opticalflow/frame0.png", "gpu/opticalflow/frame1.png")), + Values(3, 5, 7, 9, 13, 17, 21), + Values(1, 3), + Values(1, 10))) +{ + declare.time(30); + + const pair_string imagePair = GET_PARAM(0); + const int winSize = GET_PARAM(1); + const int levels = GET_PARAM(2); + const int iters = GET_PARAM(3); + + const cv::Mat frame0 = readImage(imagePair.first, cv::IMREAD_GRAYSCALE); + ASSERT_FALSE(frame0.empty()); + + const cv::Mat frame1 = readImage(imagePair.second, cv::IMREAD_GRAYSCALE); + ASSERT_FALSE(frame1.empty()); + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_frame0(frame0); + const cv::gpu::GpuMat d_frame1(frame1); + cv::gpu::GpuMat u; + cv::gpu::GpuMat v; + + cv::gpu::PyrLKOpticalFlow d_pyrLK; + d_pyrLK.winSize = cv::Size(winSize, winSize); + d_pyrLK.maxLevel = levels - 1; + d_pyrLK.iters = iters; + + TEST_CYCLE() d_pyrLK.dense(d_frame0, d_frame1, u, v); + + GPU_SANITY_CHECK(u); + GPU_SANITY_CHECK(v); + } + else + { + FAIL_NO_CPU(); + } +} + +////////////////////////////////////////////////////// +// FarnebackOpticalFlow + +PERF_TEST_P(ImagePair, Video_FarnebackOpticalFlow, + Values(make_pair("gpu/opticalflow/frame0.png", "gpu/opticalflow/frame1.png"))) +{ + declare.time(10); + + const cv::Mat frame0 = readImage(GetParam().first, cv::IMREAD_GRAYSCALE); + ASSERT_FALSE(frame0.empty()); + + const cv::Mat frame1 = readImage(GetParam().second, cv::IMREAD_GRAYSCALE); + ASSERT_FALSE(frame1.empty()); + + const int numLevels = 5; + const double pyrScale = 0.5; + const int winSize = 13; + const int numIters = 10; + const int polyN = 5; + const double polySigma = 1.1; + const int flags = 0; + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_frame0(frame0); + const cv::gpu::GpuMat d_frame1(frame1); + cv::gpu::GpuMat u; + cv::gpu::GpuMat v; + + cv::gpu::FarnebackOpticalFlow d_farneback; + d_farneback.numLevels = numLevels; + d_farneback.pyrScale = pyrScale; + d_farneback.winSize = winSize; + d_farneback.numIters = numIters; + d_farneback.polyN = polyN; + d_farneback.polySigma = polySigma; + d_farneback.flags = flags; + + TEST_CYCLE() d_farneback(d_frame0, d_frame1, u, v); + + GPU_SANITY_CHECK(u, 1e-4); + GPU_SANITY_CHECK(v, 1e-4); + } + else + { + cv::Mat flow; + + TEST_CYCLE() cv::calcOpticalFlowFarneback(frame0, frame1, flow, pyrScale, numLevels, winSize, numIters, polyN, polySigma, flags); + + CPU_SANITY_CHECK(flow); + } +} + +////////////////////////////////////////////////////// +// OpticalFlowDual_TVL1 + +PERF_TEST_P(ImagePair, Video_OpticalFlowDual_TVL1, + Values(make_pair("gpu/opticalflow/frame0.png", "gpu/opticalflow/frame1.png"))) +{ + declare.time(20); + + const cv::Mat frame0 = readImage(GetParam().first, cv::IMREAD_GRAYSCALE); + ASSERT_FALSE(frame0.empty()); + + const cv::Mat frame1 = readImage(GetParam().second, cv::IMREAD_GRAYSCALE); + ASSERT_FALSE(frame1.empty()); + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_frame0(frame0); + const cv::gpu::GpuMat d_frame1(frame1); + cv::gpu::GpuMat u; + cv::gpu::GpuMat v; + + cv::gpu::OpticalFlowDual_TVL1_GPU d_alg; + + TEST_CYCLE() d_alg(d_frame0, d_frame1, u, v); + + GPU_SANITY_CHECK(u, 1e-2); + GPU_SANITY_CHECK(v, 1e-2); + } + else + { + cv::Mat flow; + + cv::Ptr alg = cv::createOptFlow_DualTVL1(); + alg->set("medianFiltering", 1); + alg->set("innerIterations", 1); + alg->set("outerIterations", 300); + + TEST_CYCLE() alg->calc(frame0, frame1, flow); + + CPU_SANITY_CHECK(flow); + } +} + +////////////////////////////////////////////////////// +// OpticalFlowBM + +void calcOpticalFlowBM(const cv::Mat& prev, const cv::Mat& curr, + cv::Size bSize, cv::Size shiftSize, cv::Size maxRange, int usePrevious, + cv::Mat& velx, cv::Mat& vely) +{ + cv::Size sz((curr.cols - bSize.width + shiftSize.width)/shiftSize.width, (curr.rows - bSize.height + shiftSize.height)/shiftSize.height); + + velx.create(sz, CV_32FC1); + vely.create(sz, CV_32FC1); + + CvMat cvprev = prev; + CvMat cvcurr = curr; + + CvMat cvvelx = velx; + CvMat cvvely = vely; + + cvCalcOpticalFlowBM(&cvprev, &cvcurr, bSize, shiftSize, maxRange, usePrevious, &cvvelx, &cvvely); +} + +PERF_TEST_P(ImagePair, Video_OpticalFlowBM, + Values(make_pair("gpu/opticalflow/frame0.png", "gpu/opticalflow/frame1.png"))) +{ + declare.time(400); + + const cv::Mat frame0 = readImage(GetParam().first, cv::IMREAD_GRAYSCALE); + ASSERT_FALSE(frame0.empty()); + + const cv::Mat frame1 = readImage(GetParam().second, cv::IMREAD_GRAYSCALE); + ASSERT_FALSE(frame1.empty()); + + const cv::Size block_size(16, 16); + const cv::Size shift_size(1, 1); + const cv::Size max_range(16, 16); + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_frame0(frame0); + const cv::gpu::GpuMat d_frame1(frame1); + cv::gpu::GpuMat u, v, buf; + + TEST_CYCLE() cv::gpu::calcOpticalFlowBM(d_frame0, d_frame1, block_size, shift_size, max_range, false, u, v, buf); + + GPU_SANITY_CHECK(u); + GPU_SANITY_CHECK(v); + } + else + { + cv::Mat u, v; + + TEST_CYCLE() calcOpticalFlowBM(frame0, frame1, block_size, shift_size, max_range, false, u, v); + + CPU_SANITY_CHECK(u); + CPU_SANITY_CHECK(v); + } +} + +PERF_TEST_P(ImagePair, Video_FastOpticalFlowBM, + Values(make_pair("gpu/opticalflow/frame0.png", "gpu/opticalflow/frame1.png"))) +{ + declare.time(400); + + const cv::Mat frame0 = readImage(GetParam().first, cv::IMREAD_GRAYSCALE); + ASSERT_FALSE(frame0.empty()); + + const cv::Mat frame1 = readImage(GetParam().second, cv::IMREAD_GRAYSCALE); + ASSERT_FALSE(frame1.empty()); + + const cv::Size block_size(16, 16); + const cv::Size shift_size(1, 1); + const cv::Size max_range(16, 16); + + if (PERF_RUN_GPU()) + { + const cv::gpu::GpuMat d_frame0(frame0); + const cv::gpu::GpuMat d_frame1(frame1); + cv::gpu::GpuMat u, v; + + cv::gpu::FastOpticalFlowBM fastBM; + + TEST_CYCLE() fastBM(d_frame0, d_frame1, u, v, max_range.width, block_size.width); + + GPU_SANITY_CHECK(u, 2); + GPU_SANITY_CHECK(v, 2); + } + else + { + FAIL_NO_CPU(); + } +} + +////////////////////////////////////////////////////// +// FGDStatModel + +#if BUILD_WITH_VIDEO_INPUT_SUPPORT + +DEF_PARAM_TEST_1(Video, string); + +PERF_TEST_P(Video, Video_FGDStatModel, + Values(string("gpu/video/768x576.avi"))) +{ + declare.time(60); + + const string inputFile = perf::TestBase::getDataPath(GetParam()); + + cv::VideoCapture cap(inputFile); + ASSERT_TRUE(cap.isOpened()); + + cv::Mat frame; + cap >> frame; + ASSERT_FALSE(frame.empty()); + + if (PERF_RUN_GPU()) + { + cv::gpu::GpuMat d_frame(frame); + + cv::gpu::FGDStatModel d_model(4); + d_model.create(d_frame); + + for (int i = 0; i < 10; ++i) + { + cap >> frame; + ASSERT_FALSE(frame.empty()); + + d_frame.upload(frame); + + startTimer(); next(); + d_model.update(d_frame); + stopTimer(); + } + + const cv::gpu::GpuMat background = d_model.background; + const cv::gpu::GpuMat foreground = d_model.foreground; + + GPU_SANITY_CHECK(background, 1e-2, ERROR_RELATIVE); + GPU_SANITY_CHECK(foreground, 1e-2, ERROR_RELATIVE); + } + else + { + IplImage ipl_frame = frame; + cv::Ptr model(cvCreateFGDStatModel(&ipl_frame)); + + for (int i = 0; i < 10; ++i) + { + cap >> frame; + ASSERT_FALSE(frame.empty()); + + ipl_frame = frame; + + startTimer(); next(); + cvUpdateBGStatModel(&ipl_frame, model); + stopTimer(); + } + + const cv::Mat background = cv::cvarrToMat(model->background); + const cv::Mat foreground = cv::cvarrToMat(model->foreground); + + CPU_SANITY_CHECK(background); + CPU_SANITY_CHECK(foreground); + } +} + +#endif + +////////////////////////////////////////////////////// +// MOG + +#if BUILD_WITH_VIDEO_INPUT_SUPPORT + +DEF_PARAM_TEST(Video_Cn_LearningRate, string, MatCn, double); + +PERF_TEST_P(Video_Cn_LearningRate, Video_MOG, + Combine(Values("gpu/video/768x576.avi", "gpu/video/1920x1080.avi"), + GPU_CHANNELS_1_3_4, + Values(0.0, 0.01))) +{ + const string inputFile = perf::TestBase::getDataPath(GET_PARAM(0)); + const int cn = GET_PARAM(1); + const float learningRate = static_cast(GET_PARAM(2)); + + 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::MOG_GPU d_mog; + cv::gpu::GpuMat foreground; + + d_mog(d_frame, foreground, learningRate); + + 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(); + d_mog(d_frame, foreground, learningRate); + stopTimer(); + } + + GPU_SANITY_CHECK(foreground); + } + else + { + cv::Ptr mog = cv::createBackgroundSubtractorMOG(); + cv::Mat foreground; + + mog->apply(frame, foreground, learningRate); + + 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); + } + + startTimer(); next(); + mog->apply(frame, foreground, learningRate); + stopTimer(); + } + + CPU_SANITY_CHECK(foreground); + } +} + +#endif + +////////////////////////////////////////////////////// +// MOG2 + +#if BUILD_WITH_VIDEO_INPUT_SUPPORT + +DEF_PARAM_TEST(Video_Cn, string, int); + +PERF_TEST_P(Video_Cn, Video_MOG2, + 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::MOG2_GPU d_mog2; + d_mog2.bShadowDetection = false; + + cv::gpu::GpuMat d_frame(frame); + cv::gpu::GpuMat foreground; + + d_mog2(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(); + d_mog2(d_frame, foreground); + stopTimer(); + } + + GPU_SANITY_CHECK(foreground); + } + else + { + cv::Ptr mog2 = cv::createBackgroundSubtractorMOG2(); + mog2->set("detectShadows", false); + + cv::Mat foreground; + + mog2->apply(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); + } + + startTimer(); next(); + mog2->apply(frame, foreground); + stopTimer(); + } + + CPU_SANITY_CHECK(foreground); + } +} + +#endif + +////////////////////////////////////////////////////// +// MOG2GetBackgroundImage + +#if BUILD_WITH_VIDEO_INPUT_SUPPORT + +PERF_TEST_P(Video_Cn, Video_MOG2GetBackgroundImage, + 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; + + if (PERF_RUN_GPU()) + { + cv::gpu::GpuMat d_frame; + cv::gpu::MOG2_GPU d_mog2; + cv::gpu::GpuMat d_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); + + d_mog2(d_frame, d_foreground); + } + + cv::gpu::GpuMat background; + + TEST_CYCLE() d_mog2.getBackgroundImage(background); + + GPU_SANITY_CHECK(background, 1); + } + else + { + cv::Ptr mog2 = cv::createBackgroundSubtractorMOG2(); + cv::Mat 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); + } + + mog2->apply(frame, foreground); + } + + cv::Mat background; + + TEST_CYCLE() mog2->getBackgroundImage(background); + + CPU_SANITY_CHECK(background); + } +} + +#endif + +////////////////////////////////////////////////////// +// GMG + +#if BUILD_WITH_VIDEO_INPUT_SUPPORT + +DEF_PARAM_TEST(Video_Cn_MaxFeatures, string, MatCn, int); + +PERF_TEST_P(Video_Cn_MaxFeatures, Video_GMG, + Combine(Values(string("gpu/video/768x576.avi")), + GPU_CHANNELS_1_3_4, + Values(20, 40, 60))) +{ + const std::string inputFile = perf::TestBase::getDataPath(GET_PARAM(0)); + const int cn = GET_PARAM(1); + const int maxFeatures = GET_PARAM(2); + + 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::GpuMat foreground; + + cv::gpu::GMG_GPU d_gmg; + d_gmg.maxFeatures = maxFeatures; + + d_gmg(d_frame, foreground); + + for (int i = 0; i < 150; ++i) + { + cap >> frame; + if (frame.empty()) + { + cap.release(); + cap.open(inputFile); + cap >> frame; + } + + 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(); + d_gmg(d_frame, foreground); + stopTimer(); + } + + GPU_SANITY_CHECK(foreground); + } + else + { + cv::Mat foreground; + cv::Mat zeros(frame.size(), CV_8UC1, cv::Scalar::all(0)); + + cv::Ptr gmg = cv::createBackgroundSubtractorGMG(); + gmg->set("maxFeatures", maxFeatures); + //gmg.initialize(frame.size(), 0.0, 255.0); + + gmg->apply(frame, foreground); + + for (int i = 0; i < 150; ++i) + { + cap >> frame; + if (frame.empty()) + { + cap.release(); + cap.open(inputFile); + cap >> frame; + } + + 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); + } + + startTimer(); next(); + gmg->apply(frame, foreground); + stopTimer(); + } + + CPU_SANITY_CHECK(foreground); + } +} + +#endif + +////////////////////////////////////////////////////// +// VideoReader + +#if defined(HAVE_NVCUVID) && BUILD_WITH_VIDEO_INPUT_SUPPORT + +PERF_TEST_P(Video, DISABLED_Video_VideoReader, Values("gpu/video/768x576.avi", "gpu/video/1920x1080.avi")) +{ + declare.time(20); + + const string inputFile = perf::TestBase::getDataPath(GetParam()); + + if (PERF_RUN_GPU()) + { + cv::gpu::VideoReader_GPU d_reader(inputFile); + ASSERT_TRUE( d_reader.isOpened() ); + + cv::gpu::GpuMat frame; + + TEST_CYCLE_N(10) d_reader.read(frame); + + GPU_SANITY_CHECK(frame); + } + else + { + cv::VideoCapture reader(inputFile); + ASSERT_TRUE( reader.isOpened() ); + + cv::Mat frame; + + TEST_CYCLE_N(10) reader >> frame; + + CPU_SANITY_CHECK(frame); + } +} + +#endif + +////////////////////////////////////////////////////// +// VideoWriter + +#if defined(HAVE_NVCUVID) && defined(WIN32) + +PERF_TEST_P(Video, DISABLED_Video_VideoWriter, Values("gpu/video/768x576.avi", "gpu/video/1920x1080.avi")) +{ + declare.time(30); + + const string inputFile = perf::TestBase::getDataPath(GetParam()); + const string outputFile = cv::tempfile(".avi"); + + const double FPS = 25.0; + + cv::VideoCapture reader(inputFile); + ASSERT_TRUE( reader.isOpened() ); + + cv::Mat frame; + + if (PERF_RUN_GPU()) + { + cv::gpu::VideoWriter_GPU d_writer; + + cv::gpu::GpuMat d_frame; + + for (int i = 0; i < 10; ++i) + { + reader >> frame; + ASSERT_FALSE(frame.empty()); + + d_frame.upload(frame); + + if (!d_writer.isOpened()) + d_writer.open(outputFile, frame.size(), FPS); + + startTimer(); next(); + d_writer.write(d_frame); + stopTimer(); + } + } + else + { + cv::VideoWriter writer; + + for (int i = 0; i < 10; ++i) + { + reader >> frame; + ASSERT_FALSE(frame.empty()); + + if (!writer.isOpened()) + writer.open(outputFile, CV_FOURCC('X', 'V', 'I', 'D'), FPS, frame.size()); + + startTimer(); next(); + writer.write(frame); + stopTimer(); + } + } + + SANITY_CHECK(frame); +} + +#endif diff --git a/modules/gpu/perf4au/CMakeLists.txt b/modules/gpu/perf4au/CMakeLists.txt index cb395069d0..7452203826 100644 --- a/modules/gpu/perf4au/CMakeLists.txt +++ b/modules/gpu/perf4au/CMakeLists.txt @@ -1,4 +1,4 @@ -set(PERF4AU_REQUIRED_DEPS opencv_core opencv_imgproc opencv_highgui opencv_video opencv_legacy opencv_ml opencv_ts opencv_gpufilters opencv_gpuimgproc opencv_gpuoptflow) +set(PERF4AU_REQUIRED_DEPS opencv_core opencv_imgproc opencv_highgui opencv_video opencv_legacy opencv_gpu opencv_ts) ocv_check_dependencies(${PERF4AU_REQUIRED_DEPS}) @@ -25,3 +25,4 @@ if(WIN32) set_target_properties(${the_target} PROPERTIES LINK_FLAGS "/NODEFAULTLIB:atlthunk.lib /NODEFAULTLIB:atlsd.lib /DEBUG") endif() endif() + diff --git a/modules/gpu/perf4au/main.cpp b/modules/gpu/perf4au/main.cpp index ce40d61cb0..47a6c4e257 100644 --- a/modules/gpu/perf4au/main.cpp +++ b/modules/gpu/perf4au/main.cpp @@ -40,15 +40,18 @@ // //M*/ -#include "opencv2/ts.hpp" -#include "opencv2/ts/gpu_perf.hpp" - -#include "opencv2/gpuimgproc.hpp" -#include "opencv2/gpuoptflow.hpp" +#include +#ifdef HAVE_CVCONFIG_H +#include "cvconfig.h" +#endif +#include "opencv2/core.hpp" +#include "opencv2/gpu.hpp" #include "opencv2/highgui.hpp" #include "opencv2/video.hpp" #include "opencv2/legacy.hpp" +#include "opencv2/ts.hpp" +#include "opencv2/ts/gpu_perf.hpp" int main(int argc, char* argv[]) { diff --git a/modules/gpu/src/arithm.cpp b/modules/gpu/src/arithm.cpp new file mode 100644 index 0000000000..c40e7131e2 --- /dev/null +++ b/modules/gpu/src/arithm.cpp @@ -0,0 +1,565 @@ +/*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" + +using namespace cv; +using namespace cv::gpu; + +#if !defined (HAVE_CUDA) || defined (CUDA_DISABLER) + +void cv::gpu::gemm(const GpuMat&, const GpuMat&, double, const GpuMat&, double, GpuMat&, int, Stream&) { throw_no_cuda(); } +void cv::gpu::transpose(const GpuMat&, GpuMat&, Stream&) { throw_no_cuda(); } +void cv::gpu::flip(const GpuMat&, GpuMat&, int, Stream&) { throw_no_cuda(); } +void cv::gpu::LUT(const GpuMat&, const Mat&, GpuMat&, Stream&) { throw_no_cuda(); } +void cv::gpu::magnitude(const GpuMat&, GpuMat&, Stream&) { throw_no_cuda(); } +void cv::gpu::magnitudeSqr(const GpuMat&, GpuMat&, Stream&) { throw_no_cuda(); } +void cv::gpu::magnitude(const GpuMat&, const GpuMat&, GpuMat&, Stream&) { throw_no_cuda(); } +void cv::gpu::magnitudeSqr(const GpuMat&, const GpuMat&, GpuMat&, Stream&) { throw_no_cuda(); } +void cv::gpu::phase(const GpuMat&, const GpuMat&, GpuMat&, bool, Stream&) { throw_no_cuda(); } +void cv::gpu::cartToPolar(const GpuMat&, const GpuMat&, GpuMat&, GpuMat&, bool, Stream&) { throw_no_cuda(); } +void cv::gpu::polarToCart(const GpuMat&, const GpuMat&, GpuMat&, GpuMat&, bool, Stream&) { throw_no_cuda(); } +void cv::gpu::normalize(const GpuMat&, GpuMat&, double, double, int, int, const GpuMat&) { throw_no_cuda(); } +void cv::gpu::normalize(const GpuMat&, GpuMat&, double, double, int, int, const GpuMat&, GpuMat&, GpuMat&) { throw_no_cuda(); } + +#else /* !defined (HAVE_CUDA) */ + +//////////////////////////////////////////////////////////////////////// +// gemm + +void cv::gpu::gemm(const GpuMat& src1, const GpuMat& src2, double alpha, const GpuMat& src3, double beta, GpuMat& dst, int flags, Stream& stream) +{ +#ifndef HAVE_CUBLAS + (void)src1; + (void)src2; + (void)alpha; + (void)src3; + (void)beta; + (void)dst; + (void)flags; + (void)stream; + CV_Error(cv::Error::StsNotImplemented, "The library was build without CUBLAS"); +#else + // CUBLAS works with column-major matrices + + CV_Assert(src1.type() == CV_32FC1 || src1.type() == CV_32FC2 || src1.type() == CV_64FC1 || src1.type() == CV_64FC2); + CV_Assert(src2.type() == src1.type() && (src3.empty() || src3.type() == src1.type())); + + if (src1.depth() == CV_64F) + { + if (!deviceSupports(NATIVE_DOUBLE)) + CV_Error(cv::Error::StsUnsupportedFormat, "The device doesn't support double"); + } + + bool tr1 = (flags & GEMM_1_T) != 0; + bool tr2 = (flags & GEMM_2_T) != 0; + bool tr3 = (flags & GEMM_3_T) != 0; + + if (src1.type() == CV_64FC2) + { + if (tr1 || tr2 || tr3) + CV_Error(cv::Error::StsNotImplemented, "transpose operation doesn't implemented for CV_64FC2 type"); + } + + Size src1Size = tr1 ? Size(src1.rows, src1.cols) : src1.size(); + Size src2Size = tr2 ? Size(src2.rows, src2.cols) : src2.size(); + Size src3Size = tr3 ? Size(src3.rows, src3.cols) : src3.size(); + Size dstSize(src2Size.width, src1Size.height); + + CV_Assert(src1Size.width == src2Size.height); + CV_Assert(src3.empty() || src3Size == dstSize); + + dst.create(dstSize, src1.type()); + + if (beta != 0) + { + if (src3.empty()) + { + if (stream) + stream.enqueueMemSet(dst, Scalar::all(0)); + else + dst.setTo(Scalar::all(0)); + } + else + { + if (tr3) + { + transpose(src3, dst, stream); + } + else + { + if (stream) + stream.enqueueCopy(src3, dst); + else + src3.copyTo(dst); + } + } + } + + cublasHandle_t handle; + cublasSafeCall( cublasCreate_v2(&handle) ); + + cublasSafeCall( cublasSetStream_v2(handle, StreamAccessor::getStream(stream)) ); + + cublasSafeCall( cublasSetPointerMode_v2(handle, CUBLAS_POINTER_MODE_HOST) ); + + const float alphaf = static_cast(alpha); + const float betaf = static_cast(beta); + + const cuComplex alphacf = make_cuComplex(alphaf, 0); + const cuComplex betacf = make_cuComplex(betaf, 0); + + const cuDoubleComplex alphac = make_cuDoubleComplex(alpha, 0); + const cuDoubleComplex betac = make_cuDoubleComplex(beta, 0); + + cublasOperation_t transa = tr2 ? CUBLAS_OP_T : CUBLAS_OP_N; + cublasOperation_t transb = tr1 ? CUBLAS_OP_T : CUBLAS_OP_N; + + switch (src1.type()) + { + case CV_32FC1: + cublasSafeCall( cublasSgemm_v2(handle, transa, transb, tr2 ? src2.rows : src2.cols, tr1 ? src1.cols : src1.rows, tr2 ? src2.cols : src2.rows, + &alphaf, + src2.ptr(), static_cast(src2.step / sizeof(float)), + src1.ptr(), static_cast(src1.step / sizeof(float)), + &betaf, + dst.ptr(), static_cast(dst.step / sizeof(float))) ); + break; + + case CV_64FC1: + cublasSafeCall( cublasDgemm_v2(handle, transa, transb, tr2 ? src2.rows : src2.cols, tr1 ? src1.cols : src1.rows, tr2 ? src2.cols : src2.rows, + &alpha, + src2.ptr(), static_cast(src2.step / sizeof(double)), + src1.ptr(), static_cast(src1.step / sizeof(double)), + &beta, + dst.ptr(), static_cast(dst.step / sizeof(double))) ); + break; + + case CV_32FC2: + cublasSafeCall( cublasCgemm_v2(handle, transa, transb, tr2 ? src2.rows : src2.cols, tr1 ? src1.cols : src1.rows, tr2 ? src2.cols : src2.rows, + &alphacf, + src2.ptr(), static_cast(src2.step / sizeof(cuComplex)), + src1.ptr(), static_cast(src1.step / sizeof(cuComplex)), + &betacf, + dst.ptr(), static_cast(dst.step / sizeof(cuComplex))) ); + break; + + case CV_64FC2: + cublasSafeCall( cublasZgemm_v2(handle, transa, transb, tr2 ? src2.rows : src2.cols, tr1 ? src1.cols : src1.rows, tr2 ? src2.cols : src2.rows, + &alphac, + src2.ptr(), static_cast(src2.step / sizeof(cuDoubleComplex)), + src1.ptr(), static_cast(src1.step / sizeof(cuDoubleComplex)), + &betac, + dst.ptr(), static_cast(dst.step / sizeof(cuDoubleComplex))) ); + break; + } + + cublasSafeCall( cublasDestroy_v2(handle) ); +#endif +} + +//////////////////////////////////////////////////////////////////////// +// transpose + +void cv::gpu::transpose(const GpuMat& src, GpuMat& dst, Stream& s) +{ + CV_Assert(src.elemSize() == 1 || src.elemSize() == 4 || src.elemSize() == 8); + + dst.create( src.cols, src.rows, src.type() ); + + cudaStream_t stream = StreamAccessor::getStream(s); + + if (src.elemSize() == 1) + { + NppStreamHandler h(stream); + + NppiSize sz; + sz.width = src.cols; + sz.height = src.rows; + + nppSafeCall( nppiTranspose_8u_C1R(src.ptr(), static_cast(src.step), + dst.ptr(), static_cast(dst.step), sz) ); + } + else if (src.elemSize() == 4) + { + NppStStreamHandler h(stream); + + NcvSize32u sz; + sz.width = src.cols; + sz.height = src.rows; + + ncvSafeCall( nppiStTranspose_32u_C1R(const_cast(src.ptr()), static_cast(src.step), + dst.ptr(), static_cast(dst.step), sz) ); + } + else // if (src.elemSize() == 8) + { + if (!deviceSupports(NATIVE_DOUBLE)) + CV_Error(cv::Error::StsUnsupportedFormat, "The device doesn't support double"); + + NppStStreamHandler h(stream); + + NcvSize32u sz; + sz.width = src.cols; + sz.height = src.rows; + + ncvSafeCall( nppiStTranspose_64u_C1R(const_cast(src.ptr()), static_cast(src.step), + dst.ptr(), static_cast(dst.step), sz) ); + } + + if (stream == 0) + cudaSafeCall( cudaDeviceSynchronize() ); +} + +//////////////////////////////////////////////////////////////////////// +// flip + +namespace +{ + template struct NppTypeTraits; + template<> struct NppTypeTraits { typedef Npp8u npp_t; }; + template<> struct NppTypeTraits { typedef Npp8s npp_t; }; + template<> struct NppTypeTraits { typedef Npp16u npp_t; }; + template<> struct NppTypeTraits { typedef Npp16s npp_t; }; + template<> struct NppTypeTraits { typedef Npp32s npp_t; }; + template<> struct NppTypeTraits { typedef Npp32f npp_t; }; + template<> struct NppTypeTraits { typedef Npp64f npp_t; }; + + template struct NppMirrorFunc + { + typedef typename NppTypeTraits::npp_t npp_t; + + typedef NppStatus (*func_t)(const npp_t* pSrc, int nSrcStep, npp_t* pDst, int nDstStep, NppiSize oROI, NppiAxis flip); + }; + + template ::func_t func> struct NppMirror + { + typedef typename NppMirrorFunc::npp_t npp_t; + + static void call(const GpuMat& src, GpuMat& dst, int flipCode, cudaStream_t stream) + { + NppStreamHandler h(stream); + + NppiSize sz; + sz.width = src.cols; + sz.height = src.rows; + + nppSafeCall( func(src.ptr(), static_cast(src.step), + dst.ptr(), static_cast(dst.step), sz, + (flipCode == 0 ? NPP_HORIZONTAL_AXIS : (flipCode > 0 ? NPP_VERTICAL_AXIS : NPP_BOTH_AXIS))) ); + + if (stream == 0) + cudaSafeCall( cudaDeviceSynchronize() ); + } + }; +} + +void cv::gpu::flip(const GpuMat& src, GpuMat& dst, int flipCode, Stream& stream) +{ + typedef void (*func_t)(const GpuMat& src, GpuMat& dst, int flipCode, cudaStream_t stream); + static const func_t funcs[6][4] = + { + {NppMirror::call, 0, NppMirror::call, NppMirror::call}, + {0,0,0,0}, + {NppMirror::call, 0, NppMirror::call, NppMirror::call}, + {0,0,0,0}, + {NppMirror::call, 0, NppMirror::call, NppMirror::call}, + {NppMirror::call, 0, NppMirror::call, NppMirror::call} + }; + + CV_Assert(src.depth() == CV_8U || src.depth() == CV_16U || src.depth() == CV_32S || src.depth() == CV_32F); + CV_Assert(src.channels() == 1 || src.channels() == 3 || src.channels() == 4); + + dst.create(src.size(), src.type()); + + funcs[src.depth()][src.channels() - 1](src, dst, flipCode, StreamAccessor::getStream(stream)); +} + +//////////////////////////////////////////////////////////////////////// +// LUT + +void cv::gpu::LUT(const GpuMat& src, const Mat& lut, GpuMat& dst, Stream& s) +{ + const int cn = src.channels(); + + CV_Assert( src.type() == CV_8UC1 || src.type() == CV_8UC3 ); + CV_Assert( lut.depth() == CV_8U ); + CV_Assert( lut.channels() == 1 || lut.channels() == cn ); + CV_Assert( lut.rows * lut.cols == 256 && lut.isContinuous() ); + + dst.create(src.size(), CV_MAKE_TYPE(lut.depth(), cn)); + + NppiSize sz; + sz.height = src.rows; + sz.width = src.cols; + + Mat nppLut; + lut.convertTo(nppLut, CV_32S); + + int nValues3[] = {256, 256, 256}; + + Npp32s pLevels[256]; + for (int i = 0; i < 256; ++i) + pLevels[i] = i; + + const Npp32s* pLevels3[3]; + +#if (CUDA_VERSION <= 4020) + pLevels3[0] = pLevels3[1] = pLevels3[2] = pLevels; +#else + GpuMat d_pLevels; + d_pLevels.upload(Mat(1, 256, CV_32S, pLevels)); + pLevels3[0] = pLevels3[1] = pLevels3[2] = d_pLevels.ptr(); +#endif + + cudaStream_t stream = StreamAccessor::getStream(s); + NppStreamHandler h(stream); + + if (src.type() == CV_8UC1) + { +#if (CUDA_VERSION <= 4020) + nppSafeCall( nppiLUT_Linear_8u_C1R(src.ptr(), static_cast(src.step), + dst.ptr(), static_cast(dst.step), sz, nppLut.ptr(), pLevels, 256) ); +#else + GpuMat d_nppLut(Mat(1, 256, CV_32S, nppLut.data)); + nppSafeCall( nppiLUT_Linear_8u_C1R(src.ptr(), static_cast(src.step), + dst.ptr(), static_cast(dst.step), sz, d_nppLut.ptr(), d_pLevels.ptr(), 256) ); +#endif + } + else + { + const Npp32s* pValues3[3]; + + Mat nppLut3[3]; + if (nppLut.channels() == 1) + { +#if (CUDA_VERSION <= 4020) + pValues3[0] = pValues3[1] = pValues3[2] = nppLut.ptr(); +#else + GpuMat d_nppLut(Mat(1, 256, CV_32S, nppLut.data)); + pValues3[0] = pValues3[1] = pValues3[2] = d_nppLut.ptr(); +#endif + } + else + { + cv::split(nppLut, nppLut3); + +#if (CUDA_VERSION <= 4020) + pValues3[0] = nppLut3[0].ptr(); + pValues3[1] = nppLut3[1].ptr(); + pValues3[2] = nppLut3[2].ptr(); +#else + GpuMat d_nppLut0(Mat(1, 256, CV_32S, nppLut3[0].data)); + GpuMat d_nppLut1(Mat(1, 256, CV_32S, nppLut3[1].data)); + GpuMat d_nppLut2(Mat(1, 256, CV_32S, nppLut3[2].data)); + + pValues3[0] = d_nppLut0.ptr(); + pValues3[1] = d_nppLut1.ptr(); + pValues3[2] = d_nppLut2.ptr(); +#endif + } + + nppSafeCall( nppiLUT_Linear_8u_C3R(src.ptr(), static_cast(src.step), + dst.ptr(), static_cast(dst.step), sz, pValues3, pLevels3, nValues3) ); + } + + if (stream == 0) + cudaSafeCall( cudaDeviceSynchronize() ); +} + +//////////////////////////////////////////////////////////////////////// +// NPP magnitide + +namespace +{ + typedef NppStatus (*nppMagnitude_t)(const Npp32fc* pSrc, int nSrcStep, Npp32f* pDst, int nDstStep, NppiSize oSizeROI); + + inline void npp_magnitude(const GpuMat& src, GpuMat& dst, nppMagnitude_t func, cudaStream_t stream) + { + CV_Assert(src.type() == CV_32FC2); + + dst.create(src.size(), CV_32FC1); + + NppiSize sz; + sz.width = src.cols; + sz.height = src.rows; + + NppStreamHandler h(stream); + + nppSafeCall( func(src.ptr(), static_cast(src.step), dst.ptr(), static_cast(dst.step), sz) ); + + if (stream == 0) + cudaSafeCall( cudaDeviceSynchronize() ); + } +} + +void cv::gpu::magnitude(const GpuMat& src, GpuMat& dst, Stream& stream) +{ + npp_magnitude(src, dst, nppiMagnitude_32fc32f_C1R, StreamAccessor::getStream(stream)); +} + +void cv::gpu::magnitudeSqr(const GpuMat& src, GpuMat& dst, Stream& stream) +{ + npp_magnitude(src, dst, nppiMagnitudeSqr_32fc32f_C1R, StreamAccessor::getStream(stream)); +} + +//////////////////////////////////////////////////////////////////////// +// Polar <-> Cart + +namespace cv { namespace gpu { namespace cudev +{ + namespace mathfunc + { + void cartToPolar_gpu(PtrStepSzf x, PtrStepSzf y, PtrStepSzf mag, bool magSqr, PtrStepSzf angle, bool angleInDegrees, cudaStream_t stream); + void polarToCart_gpu(PtrStepSzf mag, PtrStepSzf angle, PtrStepSzf x, PtrStepSzf y, bool angleInDegrees, cudaStream_t stream); + } +}}} + +namespace +{ + inline void cartToPolar_caller(const GpuMat& x, const GpuMat& y, GpuMat* mag, bool magSqr, GpuMat* angle, bool angleInDegrees, cudaStream_t stream) + { + using namespace ::cv::gpu::cudev::mathfunc; + + CV_Assert(x.size() == y.size() && x.type() == y.type()); + CV_Assert(x.depth() == CV_32F); + + if (mag) + mag->create(x.size(), x.type()); + if (angle) + angle->create(x.size(), x.type()); + + GpuMat x1cn = x.reshape(1); + GpuMat y1cn = y.reshape(1); + GpuMat mag1cn = mag ? mag->reshape(1) : GpuMat(); + GpuMat angle1cn = angle ? angle->reshape(1) : GpuMat(); + + cartToPolar_gpu(x1cn, y1cn, mag1cn, magSqr, angle1cn, angleInDegrees, stream); + } + + inline void polarToCart_caller(const GpuMat& mag, const GpuMat& angle, GpuMat& x, GpuMat& y, bool angleInDegrees, cudaStream_t stream) + { + using namespace ::cv::gpu::cudev::mathfunc; + + CV_Assert((mag.empty() || mag.size() == angle.size()) && mag.type() == angle.type()); + CV_Assert(mag.depth() == CV_32F); + + x.create(mag.size(), mag.type()); + y.create(mag.size(), mag.type()); + + GpuMat mag1cn = mag.reshape(1); + GpuMat angle1cn = angle.reshape(1); + GpuMat x1cn = x.reshape(1); + GpuMat y1cn = y.reshape(1); + + polarToCart_gpu(mag1cn, angle1cn, x1cn, y1cn, angleInDegrees, stream); + } +} + +void cv::gpu::magnitude(const GpuMat& x, const GpuMat& y, GpuMat& dst, Stream& stream) +{ + cartToPolar_caller(x, y, &dst, false, 0, false, StreamAccessor::getStream(stream)); +} + +void cv::gpu::magnitudeSqr(const GpuMat& x, const GpuMat& y, GpuMat& dst, Stream& stream) +{ + cartToPolar_caller(x, y, &dst, true, 0, false, StreamAccessor::getStream(stream)); +} + +void cv::gpu::phase(const GpuMat& x, const GpuMat& y, GpuMat& angle, bool angleInDegrees, Stream& stream) +{ + cartToPolar_caller(x, y, 0, false, &angle, angleInDegrees, StreamAccessor::getStream(stream)); +} + +void cv::gpu::cartToPolar(const GpuMat& x, const GpuMat& y, GpuMat& mag, GpuMat& angle, bool angleInDegrees, Stream& stream) +{ + cartToPolar_caller(x, y, &mag, false, &angle, angleInDegrees, StreamAccessor::getStream(stream)); +} + +void cv::gpu::polarToCart(const GpuMat& magnitude, const GpuMat& angle, GpuMat& x, GpuMat& y, bool angleInDegrees, Stream& stream) +{ + polarToCart_caller(magnitude, angle, x, y, angleInDegrees, StreamAccessor::getStream(stream)); +} + +//////////////////////////////////////////////////////////////////////// +// normalize + +void cv::gpu::normalize(const GpuMat& src, GpuMat& dst, double a, double b, int norm_type, int dtype, const GpuMat& mask) +{ + GpuMat norm_buf; + GpuMat cvt_buf; + normalize(src, dst, a, b, norm_type, dtype, mask, norm_buf, cvt_buf); +} + +void cv::gpu::normalize(const GpuMat& src, GpuMat& dst, double a, double b, int norm_type, int dtype, const GpuMat& mask, GpuMat& norm_buf, GpuMat& cvt_buf) +{ + double scale = 1, shift = 0; + if (norm_type == NORM_MINMAX) + { + double smin = 0, smax = 0; + double dmin = std::min(a, b), dmax = std::max(a, b); + minMax(src, &smin, &smax, mask, norm_buf); + scale = (dmax - dmin) * (smax - smin > std::numeric_limits::epsilon() ? 1.0 / (smax - smin) : 0.0); + shift = dmin - smin * scale; + } + else if (norm_type == NORM_L2 || norm_type == NORM_L1 || norm_type == NORM_INF) + { + scale = norm(src, norm_type, mask, norm_buf); + scale = scale > std::numeric_limits::epsilon() ? a / scale : 0.0; + shift = 0; + } + else + { + CV_Error(cv::Error::StsBadArg, "Unknown/unsupported norm type"); + } + + if (mask.empty()) + { + src.convertTo(dst, dtype, scale, shift); + } + else + { + src.convertTo(cvt_buf, dtype, scale, shift); + cvt_buf.copyTo(dst, mask); + } +} + +#endif /* !defined (HAVE_CUDA) */ diff --git a/modules/gpubgsegm/src/gmg.cpp b/modules/gpu/src/bgfg_gmg.cpp similarity index 100% rename from modules/gpubgsegm/src/gmg.cpp rename to modules/gpu/src/bgfg_gmg.cpp diff --git a/modules/gpubgsegm/src/mog.cpp b/modules/gpu/src/bgfg_mog.cpp similarity index 100% rename from modules/gpubgsegm/src/mog.cpp rename to modules/gpu/src/bgfg_mog.cpp diff --git a/modules/gpustereo/src/disparity_bilateral_filter.cpp b/modules/gpu/src/bilateral_filter.cpp similarity index 100% rename from modules/gpustereo/src/disparity_bilateral_filter.cpp rename to modules/gpu/src/bilateral_filter.cpp diff --git a/modules/gpuimgproc/src/blend.cpp b/modules/gpu/src/blend.cpp similarity index 97% rename from modules/gpuimgproc/src/blend.cpp rename to modules/gpu/src/blend.cpp index e92e379455..3fd6507810 100644 --- a/modules/gpuimgproc/src/blend.cpp +++ b/modules/gpu/src/blend.cpp @@ -51,9 +51,6 @@ void cv::gpu::blendLinear(const GpuMat&, const GpuMat&, const GpuMat&, const Gpu #else -//////////////////////////////////////////////////////////////////////// -// blendLinear - namespace cv { namespace gpu { namespace cudev { namespace blend diff --git a/modules/gpufeatures2d/src/brute_force_matcher.cpp b/modules/gpu/src/brute_force_matcher.cpp similarity index 100% rename from modules/gpufeatures2d/src/brute_force_matcher.cpp rename to modules/gpu/src/brute_force_matcher.cpp diff --git a/modules/gpu/src/calib3d.cpp b/modules/gpu/src/calib3d.cpp index cb3b1464b6..abcc3423d8 100644 --- a/modules/gpu/src/calib3d.cpp +++ b/modules/gpu/src/calib3d.cpp @@ -48,7 +48,9 @@ using namespace cv::gpu; #if !defined HAVE_CUDA || defined(CUDA_DISABLER) void cv::gpu::transformPoints(const GpuMat&, const Mat&, const Mat&, GpuMat&, Stream&) { throw_no_cuda(); } + void cv::gpu::projectPoints(const GpuMat&, const Mat&, const Mat&, const Mat&, const Mat&, GpuMat&, Stream&) { throw_no_cuda(); } + void cv::gpu::solvePnPRansac(const Mat&, const Mat&, const Mat&, const Mat&, Mat&, Mat&, bool, int, float, int, std::vector*) { throw_no_cuda(); } #else @@ -148,7 +150,7 @@ namespace } // Computes rotation, translation pair for small subsets if the input data - class TransformHypothesesGenerator : public cv::ParallelLoopBody + class TransformHypothesesGenerator { public: TransformHypothesesGenerator(const Mat& object_, const Mat& image_, const Mat& dist_coef_, @@ -158,7 +160,7 @@ namespace num_points(num_points_), subset_size(subset_size_), rot_matrices(rot_matrices_), transl_vectors(transl_vectors_) {} - void operator()(const Range& range) const + void operator()(const BlockedRange& range) const { // Input data for generation of the current hypothesis std::vector subset_indices(subset_size); @@ -170,7 +172,7 @@ namespace Mat rot_mat(3, 3, CV_64F); Mat transl_vec(1, 3, CV_64F); - for (int iter = range.start; iter < range.end; ++iter) + for (int iter = range.begin(); iter < range.end(); ++iter) { selectRandom(subset_size, num_points, subset_indices); for (int i = 0; i < subset_size; ++i) @@ -236,7 +238,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_(Range(0, num_iters), body); + parallel_for(BlockedRange(0, num_iters), body); // Compute scores (i.e. number of inliers) for each hypothesis GpuMat d_object(object); @@ -250,7 +252,7 @@ void cv::gpu::solvePnPRansac(const Mat& object, const Mat& image, const Mat& cam // Find the best hypothesis index Point best_idx; double best_score; - gpu::minMaxLoc(d_hypothesis_scores, NULL, &best_score, NULL, &best_idx); + minMaxLoc(d_hypothesis_scores, NULL, &best_score, NULL, &best_idx); int num_inliers = static_cast(best_score); // Extract the best hypothesis data @@ -288,3 +290,5 @@ void cv::gpu::solvePnPRansac(const Mat& object, const Mat& image, const Mat& cam } #endif + + diff --git a/modules/gpu/src/cascadeclassifier.cpp b/modules/gpu/src/cascadeclassifier.cpp index 454c10591c..c7514bb217 100644 --- a/modules/gpu/src/cascadeclassifier.cpp +++ b/modules/gpu/src/cascadeclassifier.cpp @@ -41,6 +41,8 @@ //M*/ #include "precomp.hpp" +#include +#include #include "opencv2/objdetect/objdetect_c.h" using namespace cv; @@ -73,37 +75,6 @@ public: virtual bool read(const String& classifierAsXml) = 0; }; -#ifndef HAVE_OPENCV_GPULEGACY - -struct cv::gpu::CascadeClassifier_GPU::HaarCascade : cv::gpu::CascadeClassifier_GPU::CascadeClassifierImpl -{ -public: - HaarCascade() - { - throw_no_cuda(); - } - - unsigned int process(const GpuMat&, GpuMat&, float, int, bool, bool, cv::Size, cv::Size) - { - throw_no_cuda(); - return 0; - } - - cv::Size getClassifierCvSize() const - { - throw_no_cuda(); - return cv::Size(); - } - - bool read(const String&) - { - throw_no_cuda(); - return false; - } -}; - -#else - struct cv::gpu::CascadeClassifier_GPU::HaarCascade : cv::gpu::CascadeClassifier_GPU::CascadeClassifierImpl { public: @@ -313,8 +284,6 @@ private: virtual ~HaarCascade(){} }; -#endif - cv::Size operator -(const cv::Size& a, const cv::Size& b) { return cv::Size(a.width - b.width, a.height - b.height); @@ -508,8 +477,6 @@ private: resuzeBuffer.create(frame, CV_8UC1); integral.create(frame.height + 1, integralFactor * (frame.width + 1), CV_32SC1); - -#ifdef HAVE_OPENCV_GPULEGACY NcvSize32u roiSize; roiSize.width = frame.width; roiSize.height = frame.height; @@ -520,7 +487,6 @@ private: Ncv32u bufSize; ncvSafeCall( nppiStIntegralGetSize_8u32u(roiSize, &bufSize, prop) ); integralBuffer.create(1, bufSize, CV_8UC1); -#endif candidates.create(1 , frame.width >> 1, CV_32SC4); } @@ -756,3 +722,240 @@ bool cv::gpu::CascadeClassifier_GPU::load(const String& filename) } #endif + +////////////////////////////////////////////////////////////////////////////////////////////////////// + +#if defined (HAVE_CUDA) + +struct RectConvert +{ + Rect operator()(const NcvRect32u& nr) const { return Rect(nr.x, nr.y, nr.width, nr.height); } + NcvRect32u operator()(const Rect& nr) const + { + NcvRect32u rect; + rect.x = nr.x; + rect.y = nr.y; + rect.width = nr.width; + rect.height = nr.height; + return rect; + } +}; + +void groupRectangles(std::vector &hypotheses, int groupThreshold, double eps, std::vector *weights) +{ + std::vector rects(hypotheses.size()); + std::transform(hypotheses.begin(), hypotheses.end(), rects.begin(), RectConvert()); + + if (weights) + { + std::vector weights_int; + weights_int.assign(weights->begin(), weights->end()); + cv::groupRectangles(rects, weights_int, groupThreshold, eps); + } + else + { + cv::groupRectangles(rects, groupThreshold, eps); + } + std::transform(rects.begin(), rects.end(), hypotheses.begin(), RectConvert()); + hypotheses.resize(rects.size()); +} + +NCVStatus loadFromXML(const String &filename, + HaarClassifierCascadeDescriptor &haar, + std::vector &haarStages, + std::vector &haarClassifierNodes, + std::vector &haarFeatures) +{ + NCVStatus ncvStat; + + haar.NumStages = 0; + haar.NumClassifierRootNodes = 0; + haar.NumClassifierTotalNodes = 0; + haar.NumFeatures = 0; + haar.ClassifierSize.width = 0; + haar.ClassifierSize.height = 0; + haar.bHasStumpsOnly = true; + haar.bNeedsTiltedII = false; + Ncv32u curMaxTreeDepth; + + std::vector xmlFileCont; + + std::vector h_TmpClassifierNotRootNodes; + haarStages.resize(0); + haarClassifierNodes.resize(0); + haarFeatures.resize(0); + + Ptr oldCascade = (CvHaarClassifierCascade*)cvLoad(filename.c_str(), 0, 0, 0); + if (oldCascade.empty()) + { + return NCV_HAAR_XML_LOADING_EXCEPTION; + } + + haar.ClassifierSize.width = oldCascade->orig_window_size.width; + haar.ClassifierSize.height = oldCascade->orig_window_size.height; + + int stagesCound = oldCascade->count; + for(int s = 0; s < stagesCound; ++s) // by stages + { + HaarStage64 curStage; + curStage.setStartClassifierRootNodeOffset(static_cast(haarClassifierNodes.size())); + + curStage.setStageThreshold(oldCascade->stage_classifier[s].threshold); + + int treesCount = oldCascade->stage_classifier[s].count; + for(int t = 0; t < treesCount; ++t) // by trees + { + Ncv32u nodeId = 0; + CvHaarClassifier* tree = &oldCascade->stage_classifier[s].classifier[t]; + + int nodesCount = tree->count; + for(int n = 0; n < nodesCount; ++n) //by features + { + CvHaarFeature* feature = &tree->haar_feature[n]; + + HaarClassifierNode128 curNode; + curNode.setThreshold(tree->threshold[n]); + + NcvBool bIsLeftNodeLeaf = false; + NcvBool bIsRightNodeLeaf = false; + + HaarClassifierNodeDescriptor32 nodeLeft; + if ( tree->left[n] <= 0 ) + { + Ncv32f leftVal = tree->alpha[-tree->left[n]]; + ncvStat = nodeLeft.create(leftVal); + ncvAssertReturn(ncvStat == NCV_SUCCESS, ncvStat); + bIsLeftNodeLeaf = true; + } + else + { + Ncv32u leftNodeOffset = tree->left[n]; + nodeLeft.create((Ncv32u)(h_TmpClassifierNotRootNodes.size() + leftNodeOffset - 1)); + haar.bHasStumpsOnly = false; + } + curNode.setLeftNodeDesc(nodeLeft); + + HaarClassifierNodeDescriptor32 nodeRight; + if ( tree->right[n] <= 0 ) + { + Ncv32f rightVal = tree->alpha[-tree->right[n]]; + ncvStat = nodeRight.create(rightVal); + ncvAssertReturn(ncvStat == NCV_SUCCESS, ncvStat); + bIsRightNodeLeaf = true; + } + else + { + Ncv32u rightNodeOffset = tree->right[n]; + nodeRight.create((Ncv32u)(h_TmpClassifierNotRootNodes.size() + rightNodeOffset - 1)); + haar.bHasStumpsOnly = false; + } + curNode.setRightNodeDesc(nodeRight); + + Ncv32u tiltedVal = feature->tilted; + haar.bNeedsTiltedII = (tiltedVal != 0); + + Ncv32u featureId = 0; + for(int l = 0; l < CV_HAAR_FEATURE_MAX; ++l) //by rects + { + Ncv32u rectX = feature->rect[l].r.x; + Ncv32u rectY = feature->rect[l].r.y; + Ncv32u rectWidth = feature->rect[l].r.width; + Ncv32u rectHeight = feature->rect[l].r.height; + + Ncv32f rectWeight = feature->rect[l].weight; + + if (rectWeight == 0/* && rectX == 0 &&rectY == 0 && rectWidth == 0 && rectHeight == 0*/) + break; + + HaarFeature64 curFeature; + ncvStat = curFeature.setRect(rectX, rectY, rectWidth, rectHeight, haar.ClassifierSize.width, haar.ClassifierSize.height); + curFeature.setWeight(rectWeight); + ncvAssertReturn(NCV_SUCCESS == ncvStat, ncvStat); + haarFeatures.push_back(curFeature); + + featureId++; + } + + HaarFeatureDescriptor32 tmpFeatureDesc; + ncvStat = tmpFeatureDesc.create(haar.bNeedsTiltedII, bIsLeftNodeLeaf, bIsRightNodeLeaf, + featureId, static_cast(haarFeatures.size()) - featureId); + ncvAssertReturn(NCV_SUCCESS == ncvStat, ncvStat); + curNode.setFeatureDesc(tmpFeatureDesc); + + if (!nodeId) + { + //root node + haarClassifierNodes.push_back(curNode); + curMaxTreeDepth = 1; + } + else + { + //other node + h_TmpClassifierNotRootNodes.push_back(curNode); + curMaxTreeDepth++; + } + + nodeId++; + } + } + + curStage.setNumClassifierRootNodes(treesCount); + haarStages.push_back(curStage); + } + + //fill in cascade stats + haar.NumStages = static_cast(haarStages.size()); + haar.NumClassifierRootNodes = static_cast(haarClassifierNodes.size()); + haar.NumClassifierTotalNodes = static_cast(haar.NumClassifierRootNodes + h_TmpClassifierNotRootNodes.size()); + haar.NumFeatures = static_cast(haarFeatures.size()); + + //merge root and leaf nodes in one classifiers array + Ncv32u offsetRoot = static_cast(haarClassifierNodes.size()); + for (Ncv32u i=0; i struct NppAlphaCompFunc - { - typedef typename NPPTypeTraits::npp_type npp_t; - - typedef NppStatus (*func_t)(const npp_t* pSrc1, int nSrc1Step, const npp_t* pSrc2, int nSrc2Step, npp_t* pDst, int nDstStep, NppiSize oSizeROI, NppiAlphaOp eAlphaOp); - }; - - template ::func_t func> struct NppAlphaComp - { - typedef typename NPPTypeTraits::npp_type npp_t; - - static void call(const GpuMat& img1, const GpuMat& img2, GpuMat& dst, NppiAlphaOp eAlphaOp, cudaStream_t stream) - { - NppStreamHandler h(stream); - - NppiSize oSizeROI; - oSizeROI.width = img1.cols; - oSizeROI.height = img2.rows; - - nppSafeCall( func(img1.ptr(), static_cast(img1.step), img2.ptr(), static_cast(img2.step), - dst.ptr(), static_cast(dst.step), oSizeROI, eAlphaOp) ); - - if (stream == 0) - cudaSafeCall( cudaDeviceSynchronize() ); - } - }; -} - -void cv::gpu::alphaComp(const GpuMat& img1, const GpuMat& img2, GpuMat& dst, int alpha_op, Stream& stream) -{ - static const NppiAlphaOp npp_alpha_ops[] = { - NPPI_OP_ALPHA_OVER, - NPPI_OP_ALPHA_IN, - NPPI_OP_ALPHA_OUT, - NPPI_OP_ALPHA_ATOP, - NPPI_OP_ALPHA_XOR, - NPPI_OP_ALPHA_PLUS, - NPPI_OP_ALPHA_OVER_PREMUL, - NPPI_OP_ALPHA_IN_PREMUL, - NPPI_OP_ALPHA_OUT_PREMUL, - NPPI_OP_ALPHA_ATOP_PREMUL, - NPPI_OP_ALPHA_XOR_PREMUL, - NPPI_OP_ALPHA_PLUS_PREMUL, - NPPI_OP_ALPHA_PREMUL - }; - - typedef void (*func_t)(const GpuMat& img1, const GpuMat& img2, GpuMat& dst, NppiAlphaOp eAlphaOp, cudaStream_t stream); - - static const func_t funcs[] = - { - NppAlphaComp::call, - 0, - NppAlphaComp::call, - 0, - NppAlphaComp::call, - NppAlphaComp::call - }; - - CV_Assert( img1.type() == CV_8UC4 || img1.type() == CV_16UC4 || img1.type() == CV_32SC4 || img1.type() == CV_32FC4 ); - CV_Assert( img1.size() == img2.size() && img1.type() == img2.type() ); - - dst.create(img1.size(), img1.type()); - - const func_t func = funcs[img1.depth()]; - - func(img1, img2, dst, npp_alpha_ops[alpha_op], StreamAccessor::getStream(stream)); -} - #endif /* !defined (HAVE_CUDA) */ diff --git a/modules/gpu/src/cuda/NV12ToARGB.cu b/modules/gpu/src/cuda/NV12ToARGB.cu new file mode 100644 index 0000000000..09906613ff --- /dev/null +++ b/modules/gpu/src/cuda/NV12ToARGB.cu @@ -0,0 +1,201 @@ +/*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*/ + +/* + * NV12ToARGB color space conversion CUDA kernel + * + * This sample uses CUDA to perform a simple NV12 (YUV 4:2:0 planar) + * source and converts to output in ARGB format + */ + +#if !defined CUDA_DISABLER + +#include "opencv2/core/cuda/common.hpp" + +namespace cv { namespace gpu { namespace cudev { + namespace video_decoding + { + __constant__ uint constAlpha = ((uint)0xff << 24); + + __constant__ float constHueColorSpaceMat[9]; + + void loadHueCSC(float hueCSC[9]) + { + cudaSafeCall( cudaMemcpyToSymbol(constHueColorSpaceMat, hueCSC, 9 * sizeof(float)) ); + } + + __device__ void YUV2RGB(const uint* yuvi, float* red, float* green, float* blue) + { + float luma, chromaCb, chromaCr; + + // Prepare for hue adjustment + luma = (float)yuvi[0]; + chromaCb = (float)((int)yuvi[1] - 512.0f); + chromaCr = (float)((int)yuvi[2] - 512.0f); + + // Convert YUV To RGB with hue adjustment + *red = (luma * constHueColorSpaceMat[0]) + + (chromaCb * constHueColorSpaceMat[1]) + + (chromaCr * constHueColorSpaceMat[2]); + + *green = (luma * constHueColorSpaceMat[3]) + + (chromaCb * constHueColorSpaceMat[4]) + + (chromaCr * constHueColorSpaceMat[5]); + + *blue = (luma * constHueColorSpaceMat[6]) + + (chromaCb * constHueColorSpaceMat[7]) + + (chromaCr * constHueColorSpaceMat[8]); + } + + __device__ uint RGBAPACK_10bit(float red, float green, float blue, uint alpha) + { + uint ARGBpixel = 0; + + // Clamp final 10 bit results + red = ::fmin(::fmax(red, 0.0f), 1023.f); + green = ::fmin(::fmax(green, 0.0f), 1023.f); + blue = ::fmin(::fmax(blue, 0.0f), 1023.f); + + // Convert to 8 bit unsigned integers per color component + ARGBpixel = (((uint)blue >> 2) | + (((uint)green >> 2) << 8) | + (((uint)red >> 2) << 16) | + (uint)alpha); + + return ARGBpixel; + } + + // CUDA kernel for outputing the final ARGB output from NV12 + + #define COLOR_COMPONENT_BIT_SIZE 10 + #define COLOR_COMPONENT_MASK 0x3FF + + __global__ void NV12ToARGB(uchar* srcImage, size_t nSourcePitch, + uint* dstImage, size_t nDestPitch, + uint width, uint height) + { + // Pad borders with duplicate pixels, and we multiply by 2 because we process 2 pixels per thread + const int x = blockIdx.x * (blockDim.x << 1) + (threadIdx.x << 1); + const int y = blockIdx.y * blockDim.y + threadIdx.y; + + if (x >= width || y >= height) + return; + + // Read 2 Luma components at a time, so we don't waste processing since CbCr are decimated this way. + // if we move to texture we could read 4 luminance values + + uint yuv101010Pel[2]; + + yuv101010Pel[0] = (srcImage[y * nSourcePitch + x ]) << 2; + yuv101010Pel[1] = (srcImage[y * nSourcePitch + x + 1]) << 2; + + const size_t chromaOffset = nSourcePitch * height; + + const int y_chroma = y >> 1; + + if (y & 1) // odd scanline ? + { + uint chromaCb = srcImage[chromaOffset + y_chroma * nSourcePitch + x ]; + uint chromaCr = srcImage[chromaOffset + y_chroma * nSourcePitch + x + 1]; + + if (y_chroma < ((height >> 1) - 1)) // interpolate chroma vertically + { + chromaCb = (chromaCb + srcImage[chromaOffset + (y_chroma + 1) * nSourcePitch + x ] + 1) >> 1; + chromaCr = (chromaCr + srcImage[chromaOffset + (y_chroma + 1) * nSourcePitch + x + 1] + 1) >> 1; + } + + yuv101010Pel[0] |= (chromaCb << ( COLOR_COMPONENT_BIT_SIZE + 2)); + yuv101010Pel[0] |= (chromaCr << ((COLOR_COMPONENT_BIT_SIZE << 1) + 2)); + + yuv101010Pel[1] |= (chromaCb << ( COLOR_COMPONENT_BIT_SIZE + 2)); + yuv101010Pel[1] |= (chromaCr << ((COLOR_COMPONENT_BIT_SIZE << 1) + 2)); + } + else + { + yuv101010Pel[0] |= ((uint)srcImage[chromaOffset + y_chroma * nSourcePitch + x ] << ( COLOR_COMPONENT_BIT_SIZE + 2)); + yuv101010Pel[0] |= ((uint)srcImage[chromaOffset + y_chroma * nSourcePitch + x + 1] << ((COLOR_COMPONENT_BIT_SIZE << 1) + 2)); + + yuv101010Pel[1] |= ((uint)srcImage[chromaOffset + y_chroma * nSourcePitch + x ] << ( COLOR_COMPONENT_BIT_SIZE + 2)); + yuv101010Pel[1] |= ((uint)srcImage[chromaOffset + y_chroma * nSourcePitch + x + 1] << ((COLOR_COMPONENT_BIT_SIZE << 1) + 2)); + } + + // this steps performs the color conversion + uint yuvi[6]; + float red[2], green[2], blue[2]; + + yuvi[0] = (yuv101010Pel[0] & COLOR_COMPONENT_MASK ); + yuvi[1] = ((yuv101010Pel[0] >> COLOR_COMPONENT_BIT_SIZE) & COLOR_COMPONENT_MASK); + yuvi[2] = ((yuv101010Pel[0] >> (COLOR_COMPONENT_BIT_SIZE << 1)) & COLOR_COMPONENT_MASK); + + yuvi[3] = (yuv101010Pel[1] & COLOR_COMPONENT_MASK ); + yuvi[4] = ((yuv101010Pel[1] >> COLOR_COMPONENT_BIT_SIZE) & COLOR_COMPONENT_MASK); + yuvi[5] = ((yuv101010Pel[1] >> (COLOR_COMPONENT_BIT_SIZE << 1)) & COLOR_COMPONENT_MASK); + + // YUV to RGB Transformation conversion + YUV2RGB(&yuvi[0], &red[0], &green[0], &blue[0]); + YUV2RGB(&yuvi[3], &red[1], &green[1], &blue[1]); + + // Clamp the results to RGBA + + const size_t dstImagePitch = nDestPitch >> 2; + + dstImage[y * dstImagePitch + x ] = RGBAPACK_10bit(red[0], green[0], blue[0], constAlpha); + dstImage[y * dstImagePitch + x + 1 ] = RGBAPACK_10bit(red[1], green[1], blue[1], constAlpha); + } + + void NV12ToARGB_gpu(const PtrStepb decodedFrame, PtrStepSz interopFrame, cudaStream_t stream) + { + dim3 block(32, 8); + dim3 grid(divUp(interopFrame.cols, 2 * block.x), divUp(interopFrame.rows, block.y)); + + NV12ToARGB<<>>(decodedFrame.data, decodedFrame.step, interopFrame.data, interopFrame.step, + interopFrame.cols, interopFrame.rows); + + cudaSafeCall( cudaGetLastError() ); + + if (stream == 0) + cudaSafeCall( cudaDeviceSynchronize() ); + } + } +}}} + +#endif /* CUDA_DISABLER */ diff --git a/modules/gpufeatures2d/src/cuda/bf_knnmatch.cu b/modules/gpu/src/cuda/bf_knnmatch.cu similarity index 100% rename from modules/gpufeatures2d/src/cuda/bf_knnmatch.cu rename to modules/gpu/src/cuda/bf_knnmatch.cu diff --git a/modules/gpufeatures2d/src/cuda/bf_match.cu b/modules/gpu/src/cuda/bf_match.cu similarity index 100% rename from modules/gpufeatures2d/src/cuda/bf_match.cu rename to modules/gpu/src/cuda/bf_match.cu diff --git a/modules/gpufeatures2d/src/cuda/bf_radius_match.cu b/modules/gpu/src/cuda/bf_radius_match.cu similarity index 100% rename from modules/gpufeatures2d/src/cuda/bf_radius_match.cu rename to modules/gpu/src/cuda/bf_radius_match.cu diff --git a/modules/gpubgsegm/src/cuda/gmg.cu b/modules/gpu/src/cuda/bgfg_gmg.cu similarity index 100% rename from modules/gpubgsegm/src/cuda/gmg.cu rename to modules/gpu/src/cuda/bgfg_gmg.cu diff --git a/modules/gpubgsegm/src/cuda/mog.cu b/modules/gpu/src/cuda/bgfg_mog.cu similarity index 100% rename from modules/gpubgsegm/src/cuda/mog.cu rename to modules/gpu/src/cuda/bgfg_mog.cu diff --git a/modules/gpuimgproc/src/cuda/bilateral_filter.cu b/modules/gpu/src/cuda/bilateral_filter.cu similarity index 99% rename from modules/gpuimgproc/src/cuda/bilateral_filter.cu rename to modules/gpu/src/cuda/bilateral_filter.cu index 6aa5df27a6..4449274548 100644 --- a/modules/gpuimgproc/src/cuda/bilateral_filter.cu +++ b/modules/gpu/src/cuda/bilateral_filter.cu @@ -150,11 +150,11 @@ namespace cv { namespace gpu { namespace cudev static caller_t funcs[] = { - bilateral_caller, + bilateral_caller, bilateral_caller, + bilateral_caller, bilateral_caller, bilateral_caller, - bilateral_caller }; funcs[borderMode](src, dst, kernel_size, gauss_spatial_coeff, gauss_color_coeff, stream); } diff --git a/modules/gpuimgproc/src/cuda/blend.cu b/modules/gpu/src/cuda/blend.cu similarity index 100% rename from modules/gpuimgproc/src/cuda/blend.cu rename to modules/gpu/src/cuda/blend.cu diff --git a/modules/gpuimgproc/src/cuda/canny.cu b/modules/gpu/src/cuda/canny.cu similarity index 99% rename from modules/gpuimgproc/src/cuda/canny.cu rename to modules/gpu/src/cuda/canny.cu index 77b185a4cb..042e9afcc6 100644 --- a/modules/gpuimgproc/src/cuda/canny.cu +++ b/modules/gpu/src/cuda/canny.cu @@ -94,8 +94,8 @@ namespace canny texture tex_src(false, cudaFilterModePoint, cudaAddressModeClamp); struct SrcTex { - int xoff; - int yoff; + const int xoff; + const int yoff; __host__ SrcTex(int _xoff, int _yoff) : xoff(_xoff), yoff(_yoff) {} __device__ __forceinline__ int operator ()(int y, int x) const diff --git a/modules/gpuimgproc/src/cuda/clahe.cu b/modules/gpu/src/cuda/clahe.cu similarity index 100% rename from modules/gpuimgproc/src/cuda/clahe.cu rename to modules/gpu/src/cuda/clahe.cu diff --git a/modules/gpuimgproc/src/cuda/color.cu b/modules/gpu/src/cuda/color.cu similarity index 100% rename from modules/gpuimgproc/src/cuda/color.cu rename to modules/gpu/src/cuda/color.cu diff --git a/modules/gpufilters/src/cuda/column_filter.8uc1.cu b/modules/gpu/src/cuda/column_filter.0.cu similarity index 98% rename from modules/gpufilters/src/cuda/column_filter.8uc1.cu rename to modules/gpu/src/cuda/column_filter.0.cu index 470f3ee8e6..339fb80707 100644 --- a/modules/gpufilters/src/cuda/column_filter.8uc1.cu +++ b/modules/gpu/src/cuda/column_filter.0.cu @@ -42,7 +42,7 @@ #if !defined CUDA_DISABLER -#include "column_filter.hpp" +#include "column_filter.h" namespace filter { diff --git a/modules/gpufilters/src/cuda/column_filter.8uc3.cu b/modules/gpu/src/cuda/column_filter.1.cu similarity index 98% rename from modules/gpufilters/src/cuda/column_filter.8uc3.cu rename to modules/gpu/src/cuda/column_filter.1.cu index 5d5be58310..53914a2159 100644 --- a/modules/gpufilters/src/cuda/column_filter.8uc3.cu +++ b/modules/gpu/src/cuda/column_filter.1.cu @@ -42,7 +42,7 @@ #if !defined CUDA_DISABLER -#include "column_filter.hpp" +#include "column_filter.h" namespace filter { diff --git a/modules/gpufilters/src/cuda/column_filter.16uc1.cu b/modules/gpu/src/cuda/column_filter.10.cu similarity index 98% rename from modules/gpufilters/src/cuda/column_filter.16uc1.cu rename to modules/gpu/src/cuda/column_filter.10.cu index dc68b710f5..b71e25207e 100644 --- a/modules/gpufilters/src/cuda/column_filter.16uc1.cu +++ b/modules/gpu/src/cuda/column_filter.10.cu @@ -42,7 +42,7 @@ #if !defined CUDA_DISABLER -#include "column_filter.hpp" +#include "column_filter.h" namespace filter { diff --git a/modules/gpufilters/src/cuda/column_filter.16uc3.cu b/modules/gpu/src/cuda/column_filter.11.cu similarity index 98% rename from modules/gpufilters/src/cuda/column_filter.16uc3.cu rename to modules/gpu/src/cuda/column_filter.11.cu index f0a07d6ddd..ccfbf8e773 100644 --- a/modules/gpufilters/src/cuda/column_filter.16uc3.cu +++ b/modules/gpu/src/cuda/column_filter.11.cu @@ -42,7 +42,7 @@ #if !defined CUDA_DISABLER -#include "column_filter.hpp" +#include "column_filter.h" namespace filter { diff --git a/modules/gpufilters/src/cuda/column_filter.16uc4.cu b/modules/gpu/src/cuda/column_filter.12.cu similarity index 98% rename from modules/gpufilters/src/cuda/column_filter.16uc4.cu rename to modules/gpu/src/cuda/column_filter.12.cu index 638ef794ad..a38f93b531 100644 --- a/modules/gpufilters/src/cuda/column_filter.16uc4.cu +++ b/modules/gpu/src/cuda/column_filter.12.cu @@ -42,7 +42,7 @@ #if !defined CUDA_DISABLER -#include "column_filter.hpp" +#include "column_filter.h" namespace filter { diff --git a/modules/gpufilters/src/cuda/column_filter.32sc3.cu b/modules/gpu/src/cuda/column_filter.13.cu similarity index 98% rename from modules/gpufilters/src/cuda/column_filter.32sc3.cu rename to modules/gpu/src/cuda/column_filter.13.cu index b921d96103..40eec7a83f 100644 --- a/modules/gpufilters/src/cuda/column_filter.32sc3.cu +++ b/modules/gpu/src/cuda/column_filter.13.cu @@ -42,7 +42,7 @@ #if !defined CUDA_DISABLER -#include "column_filter.hpp" +#include "column_filter.h" namespace filter { diff --git a/modules/gpufilters/src/cuda/column_filter.32sc4.cu b/modules/gpu/src/cuda/column_filter.14.cu similarity index 98% rename from modules/gpufilters/src/cuda/column_filter.32sc4.cu rename to modules/gpu/src/cuda/column_filter.14.cu index dd21524c5d..08151ac6d0 100644 --- a/modules/gpufilters/src/cuda/column_filter.32sc4.cu +++ b/modules/gpu/src/cuda/column_filter.14.cu @@ -42,7 +42,7 @@ #if !defined CUDA_DISABLER -#include "column_filter.hpp" +#include "column_filter.h" namespace filter { diff --git a/modules/gpufilters/src/cuda/column_filter.8uc4.cu b/modules/gpu/src/cuda/column_filter.2.cu similarity index 98% rename from modules/gpufilters/src/cuda/column_filter.8uc4.cu rename to modules/gpu/src/cuda/column_filter.2.cu index 8a322f2995..a615944cb9 100644 --- a/modules/gpufilters/src/cuda/column_filter.8uc4.cu +++ b/modules/gpu/src/cuda/column_filter.2.cu @@ -42,7 +42,7 @@ #if !defined CUDA_DISABLER -#include "column_filter.hpp" +#include "column_filter.h" namespace filter { diff --git a/modules/gpufilters/src/cuda/column_filter.16sc3.cu b/modules/gpu/src/cuda/column_filter.3.cu similarity index 98% rename from modules/gpufilters/src/cuda/column_filter.16sc3.cu rename to modules/gpu/src/cuda/column_filter.3.cu index 419fdea652..7304565b96 100644 --- a/modules/gpufilters/src/cuda/column_filter.16sc3.cu +++ b/modules/gpu/src/cuda/column_filter.3.cu @@ -42,7 +42,7 @@ #if !defined CUDA_DISABLER -#include "column_filter.hpp" +#include "column_filter.h" namespace filter { diff --git a/modules/gpufilters/src/cuda/column_filter.32sc1.cu b/modules/gpu/src/cuda/column_filter.4.cu similarity index 98% rename from modules/gpufilters/src/cuda/column_filter.32sc1.cu rename to modules/gpu/src/cuda/column_filter.4.cu index ee052050d6..8c9db6985b 100644 --- a/modules/gpufilters/src/cuda/column_filter.32sc1.cu +++ b/modules/gpu/src/cuda/column_filter.4.cu @@ -42,7 +42,7 @@ #if !defined CUDA_DISABLER -#include "column_filter.hpp" +#include "column_filter.h" namespace filter { diff --git a/modules/gpufilters/src/cuda/column_filter.32fc1.cu b/modules/gpu/src/cuda/column_filter.5.cu similarity index 98% rename from modules/gpufilters/src/cuda/column_filter.32fc1.cu rename to modules/gpu/src/cuda/column_filter.5.cu index aa30933e69..a192660306 100644 --- a/modules/gpufilters/src/cuda/column_filter.32fc1.cu +++ b/modules/gpu/src/cuda/column_filter.5.cu @@ -42,7 +42,7 @@ #if !defined CUDA_DISABLER -#include "column_filter.hpp" +#include "column_filter.h" namespace filter { diff --git a/modules/gpufilters/src/cuda/column_filter.32fc3.cu b/modules/gpu/src/cuda/column_filter.6.cu similarity index 98% rename from modules/gpufilters/src/cuda/column_filter.32fc3.cu rename to modules/gpu/src/cuda/column_filter.6.cu index c0ed3ac3cd..f4f7c4ffb9 100644 --- a/modules/gpufilters/src/cuda/column_filter.32fc3.cu +++ b/modules/gpu/src/cuda/column_filter.6.cu @@ -42,7 +42,7 @@ #if !defined CUDA_DISABLER -#include "column_filter.hpp" +#include "column_filter.h" namespace filter { diff --git a/modules/gpufilters/src/cuda/column_filter.32fc4.cu b/modules/gpu/src/cuda/column_filter.7.cu similarity index 98% rename from modules/gpufilters/src/cuda/column_filter.32fc4.cu rename to modules/gpu/src/cuda/column_filter.7.cu index f37f71792b..9f94bed9d9 100644 --- a/modules/gpufilters/src/cuda/column_filter.32fc4.cu +++ b/modules/gpu/src/cuda/column_filter.7.cu @@ -42,7 +42,7 @@ #if !defined CUDA_DISABLER -#include "column_filter.hpp" +#include "column_filter.h" namespace filter { diff --git a/modules/gpufilters/src/cuda/column_filter.16sc1.cu b/modules/gpu/src/cuda/column_filter.8.cu similarity index 98% rename from modules/gpufilters/src/cuda/column_filter.16sc1.cu rename to modules/gpu/src/cuda/column_filter.8.cu index d4c6d19ab8..0a63a1dd43 100644 --- a/modules/gpufilters/src/cuda/column_filter.16sc1.cu +++ b/modules/gpu/src/cuda/column_filter.8.cu @@ -42,7 +42,7 @@ #if !defined CUDA_DISABLER -#include "column_filter.hpp" +#include "column_filter.h" namespace filter { diff --git a/modules/gpufilters/src/cuda/column_filter.16sc4.cu b/modules/gpu/src/cuda/column_filter.9.cu similarity index 98% rename from modules/gpufilters/src/cuda/column_filter.16sc4.cu rename to modules/gpu/src/cuda/column_filter.9.cu index 1caeb87758..758d9289d9 100644 --- a/modules/gpufilters/src/cuda/column_filter.16sc4.cu +++ b/modules/gpu/src/cuda/column_filter.9.cu @@ -42,7 +42,7 @@ #if !defined CUDA_DISABLER -#include "column_filter.hpp" +#include "column_filter.h" namespace filter { diff --git a/modules/gpufilters/src/cuda/column_filter.hpp b/modules/gpu/src/cuda/column_filter.h similarity index 100% rename from modules/gpufilters/src/cuda/column_filter.hpp rename to modules/gpu/src/cuda/column_filter.h index 6f10c36f5f..39b6d47622 100644 --- a/modules/gpufilters/src/cuda/column_filter.hpp +++ b/modules/gpu/src/cuda/column_filter.h @@ -187,38 +187,38 @@ namespace filter { { 0, - column_filter::caller< 1, T, D, BrdColConstant>, - column_filter::caller< 2, T, D, BrdColConstant>, - column_filter::caller< 3, T, D, BrdColConstant>, - column_filter::caller< 4, T, D, BrdColConstant>, - column_filter::caller< 5, T, D, BrdColConstant>, - column_filter::caller< 6, T, D, BrdColConstant>, - column_filter::caller< 7, T, D, BrdColConstant>, - column_filter::caller< 8, T, D, BrdColConstant>, - column_filter::caller< 9, T, D, BrdColConstant>, - column_filter::caller<10, T, D, BrdColConstant>, - column_filter::caller<11, T, D, BrdColConstant>, - column_filter::caller<12, T, D, BrdColConstant>, - column_filter::caller<13, T, D, BrdColConstant>, - column_filter::caller<14, T, D, BrdColConstant>, - column_filter::caller<15, T, D, BrdColConstant>, - column_filter::caller<16, T, D, BrdColConstant>, - column_filter::caller<17, T, D, BrdColConstant>, - column_filter::caller<18, T, D, BrdColConstant>, - column_filter::caller<19, T, D, BrdColConstant>, - column_filter::caller<20, T, D, BrdColConstant>, - column_filter::caller<21, T, D, BrdColConstant>, - column_filter::caller<22, T, D, BrdColConstant>, - column_filter::caller<23, T, D, BrdColConstant>, - column_filter::caller<24, T, D, BrdColConstant>, - column_filter::caller<25, T, D, BrdColConstant>, - column_filter::caller<26, T, D, BrdColConstant>, - column_filter::caller<27, T, D, BrdColConstant>, - column_filter::caller<28, T, D, BrdColConstant>, - column_filter::caller<29, T, D, BrdColConstant>, - column_filter::caller<30, T, D, BrdColConstant>, - column_filter::caller<31, T, D, BrdColConstant>, - column_filter::caller<32, T, D, BrdColConstant> + column_filter::caller< 1, T, D, BrdColReflect101>, + column_filter::caller< 2, T, D, BrdColReflect101>, + column_filter::caller< 3, T, D, BrdColReflect101>, + column_filter::caller< 4, T, D, BrdColReflect101>, + column_filter::caller< 5, T, D, BrdColReflect101>, + column_filter::caller< 6, T, D, BrdColReflect101>, + column_filter::caller< 7, T, D, BrdColReflect101>, + column_filter::caller< 8, T, D, BrdColReflect101>, + column_filter::caller< 9, T, D, BrdColReflect101>, + column_filter::caller<10, T, D, BrdColReflect101>, + column_filter::caller<11, T, D, BrdColReflect101>, + column_filter::caller<12, T, D, BrdColReflect101>, + column_filter::caller<13, T, D, BrdColReflect101>, + column_filter::caller<14, T, D, BrdColReflect101>, + column_filter::caller<15, T, D, BrdColReflect101>, + column_filter::caller<16, T, D, BrdColReflect101>, + column_filter::caller<17, T, D, BrdColReflect101>, + column_filter::caller<18, T, D, BrdColReflect101>, + column_filter::caller<19, T, D, BrdColReflect101>, + column_filter::caller<20, T, D, BrdColReflect101>, + column_filter::caller<21, T, D, BrdColReflect101>, + column_filter::caller<22, T, D, BrdColReflect101>, + column_filter::caller<23, T, D, BrdColReflect101>, + column_filter::caller<24, T, D, BrdColReflect101>, + column_filter::caller<25, T, D, BrdColReflect101>, + column_filter::caller<26, T, D, BrdColReflect101>, + column_filter::caller<27, T, D, BrdColReflect101>, + column_filter::caller<28, T, D, BrdColReflect101>, + column_filter::caller<29, T, D, BrdColReflect101>, + column_filter::caller<30, T, D, BrdColReflect101>, + column_filter::caller<31, T, D, BrdColReflect101>, + column_filter::caller<32, T, D, BrdColReflect101> }, { 0, @@ -255,6 +255,41 @@ namespace filter column_filter::caller<31, T, D, BrdColReplicate>, column_filter::caller<32, T, D, BrdColReplicate> }, + { + 0, + column_filter::caller< 1, T, D, BrdColConstant>, + column_filter::caller< 2, T, D, BrdColConstant>, + column_filter::caller< 3, T, D, BrdColConstant>, + column_filter::caller< 4, T, D, BrdColConstant>, + column_filter::caller< 5, T, D, BrdColConstant>, + column_filter::caller< 6, T, D, BrdColConstant>, + column_filter::caller< 7, T, D, BrdColConstant>, + column_filter::caller< 8, T, D, BrdColConstant>, + column_filter::caller< 9, T, D, BrdColConstant>, + column_filter::caller<10, T, D, BrdColConstant>, + column_filter::caller<11, T, D, BrdColConstant>, + column_filter::caller<12, T, D, BrdColConstant>, + column_filter::caller<13, T, D, BrdColConstant>, + column_filter::caller<14, T, D, BrdColConstant>, + column_filter::caller<15, T, D, BrdColConstant>, + column_filter::caller<16, T, D, BrdColConstant>, + column_filter::caller<17, T, D, BrdColConstant>, + column_filter::caller<18, T, D, BrdColConstant>, + column_filter::caller<19, T, D, BrdColConstant>, + column_filter::caller<20, T, D, BrdColConstant>, + column_filter::caller<21, T, D, BrdColConstant>, + column_filter::caller<22, T, D, BrdColConstant>, + column_filter::caller<23, T, D, BrdColConstant>, + column_filter::caller<24, T, D, BrdColConstant>, + column_filter::caller<25, T, D, BrdColConstant>, + column_filter::caller<26, T, D, BrdColConstant>, + column_filter::caller<27, T, D, BrdColConstant>, + column_filter::caller<28, T, D, BrdColConstant>, + column_filter::caller<29, T, D, BrdColConstant>, + column_filter::caller<30, T, D, BrdColConstant>, + column_filter::caller<31, T, D, BrdColConstant>, + column_filter::caller<32, T, D, BrdColConstant> + }, { 0, column_filter::caller< 1, T, D, BrdColReflect>, @@ -324,41 +359,6 @@ namespace filter column_filter::caller<30, T, D, BrdColWrap>, column_filter::caller<31, T, D, BrdColWrap>, column_filter::caller<32, T, D, BrdColWrap> - }, - { - 0, - column_filter::caller< 1, T, D, BrdColReflect101>, - column_filter::caller< 2, T, D, BrdColReflect101>, - column_filter::caller< 3, T, D, BrdColReflect101>, - column_filter::caller< 4, T, D, BrdColReflect101>, - column_filter::caller< 5, T, D, BrdColReflect101>, - column_filter::caller< 6, T, D, BrdColReflect101>, - column_filter::caller< 7, T, D, BrdColReflect101>, - column_filter::caller< 8, T, D, BrdColReflect101>, - column_filter::caller< 9, T, D, BrdColReflect101>, - column_filter::caller<10, T, D, BrdColReflect101>, - column_filter::caller<11, T, D, BrdColReflect101>, - column_filter::caller<12, T, D, BrdColReflect101>, - column_filter::caller<13, T, D, BrdColReflect101>, - column_filter::caller<14, T, D, BrdColReflect101>, - column_filter::caller<15, T, D, BrdColReflect101>, - column_filter::caller<16, T, D, BrdColReflect101>, - column_filter::caller<17, T, D, BrdColReflect101>, - column_filter::caller<18, T, D, BrdColReflect101>, - column_filter::caller<19, T, D, BrdColReflect101>, - column_filter::caller<20, T, D, BrdColReflect101>, - column_filter::caller<21, T, D, BrdColReflect101>, - column_filter::caller<22, T, D, BrdColReflect101>, - column_filter::caller<23, T, D, BrdColReflect101>, - column_filter::caller<24, T, D, BrdColReflect101>, - column_filter::caller<25, T, D, BrdColReflect101>, - column_filter::caller<26, T, D, BrdColReflect101>, - column_filter::caller<27, T, D, BrdColReflect101>, - column_filter::caller<28, T, D, BrdColReflect101>, - column_filter::caller<29, T, D, BrdColReflect101>, - column_filter::caller<30, T, D, BrdColReflect101>, - column_filter::caller<31, T, D, BrdColReflect101>, - column_filter::caller<32, T, D, BrdColReflect101> } }; diff --git a/modules/gpuarithm/src/cuda/copy_make_border.cu b/modules/gpu/src/cuda/copy_make_border.cu similarity index 99% rename from modules/gpuarithm/src/cuda/copy_make_border.cu rename to modules/gpu/src/cuda/copy_make_border.cu index d772e09ede..ed90e9e80d 100644 --- a/modules/gpuarithm/src/cuda/copy_make_border.cu +++ b/modules/gpu/src/cuda/copy_make_border.cu @@ -86,11 +86,11 @@ namespace cv { namespace gpu { namespace cudev static const caller_t callers[5] = { - CopyMakeBorderDispatcher::call, + CopyMakeBorderDispatcher::call, CopyMakeBorderDispatcher::call, + CopyMakeBorderDispatcher::call, CopyMakeBorderDispatcher::call, - CopyMakeBorderDispatcher::call, - CopyMakeBorderDispatcher::call + CopyMakeBorderDispatcher::call }; callers[borderMode](PtrStepSz(src), PtrStepSz(dst), top, left, borderValue, stream); diff --git a/modules/gpuimgproc/src/cuda/debayer.cu b/modules/gpu/src/cuda/debayer.cu similarity index 100% rename from modules/gpuimgproc/src/cuda/debayer.cu rename to modules/gpu/src/cuda/debayer.cu diff --git a/modules/gpustereo/src/cuda/disparity_bilateral_filter.cu b/modules/gpu/src/cuda/disp_bilateral_filter.cu similarity index 100% rename from modules/gpustereo/src/cuda/disparity_bilateral_filter.cu rename to modules/gpu/src/cuda/disp_bilateral_filter.cu diff --git a/modules/gpu/src/cuda/element_operations.cu b/modules/gpu/src/cuda/element_operations.cu new file mode 100644 index 0000000000..095d8bac06 --- /dev/null +++ b/modules/gpu/src/cuda/element_operations.cu @@ -0,0 +1,2636 @@ +/*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*/ + +#if !defined CUDA_DISABLER + +#include "opencv2/core/cuda/common.hpp" +#include "opencv2/core/cuda/functional.hpp" +#include "opencv2/core/cuda/vec_math.hpp" +#include "opencv2/core/cuda/transform.hpp" +#include "opencv2/core/cuda/limits.hpp" +#include "opencv2/core/cuda/saturate_cast.hpp" +#include "opencv2/core/cuda/simd_functions.hpp" + +using namespace cv::gpu; +using namespace cv::gpu::cudev; + +namespace arithm +{ + template struct ArithmFuncTraits + { + enum { simple_block_dim_x = 32 }; + enum { simple_block_dim_y = 8 }; + + enum { smart_block_dim_x = 32 }; + enum { smart_block_dim_y = 8 }; + enum { smart_shift = 1 }; + }; + + template <> struct ArithmFuncTraits<1, 1> + { + enum { simple_block_dim_x = 32 }; + enum { simple_block_dim_y = 8 }; + + enum { smart_block_dim_x = 32 }; + enum { smart_block_dim_y = 8 }; + enum { smart_shift = 4 }; + }; + template <> struct ArithmFuncTraits<1, 2> + { + enum { simple_block_dim_x = 32 }; + enum { simple_block_dim_y = 8 }; + + enum { smart_block_dim_x = 32 }; + enum { smart_block_dim_y = 8 }; + enum { smart_shift = 4 }; + }; + template <> struct ArithmFuncTraits<1, 4> + { + enum { simple_block_dim_x = 32 }; + enum { simple_block_dim_y = 8 }; + + enum { smart_block_dim_x = 32 }; + enum { smart_block_dim_y = 8 }; + enum { smart_shift = 4 }; + }; + + template <> struct ArithmFuncTraits<2, 1> + { + enum { simple_block_dim_x = 32 }; + enum { simple_block_dim_y = 8 }; + + enum { smart_block_dim_x = 32 }; + enum { smart_block_dim_y = 8 }; + enum { smart_shift = 4 }; + }; + template <> struct ArithmFuncTraits<2, 2> + { + enum { simple_block_dim_x = 32 }; + enum { simple_block_dim_y = 8 }; + + enum { smart_block_dim_x = 32 }; + enum { smart_block_dim_y = 8 }; + enum { smart_shift = 4 }; + }; + template <> struct ArithmFuncTraits<2, 4> + { + enum { simple_block_dim_x = 32 }; + enum { simple_block_dim_y = 8 }; + + enum { smart_block_dim_x = 32 }; + enum { smart_block_dim_y = 8 }; + enum { smart_shift = 4 }; + }; + + template <> struct ArithmFuncTraits<4, 1> + { + enum { simple_block_dim_x = 32 }; + enum { simple_block_dim_y = 8 }; + + enum { smart_block_dim_x = 32 }; + enum { smart_block_dim_y = 8 }; + enum { smart_shift = 4 }; + }; + template <> struct ArithmFuncTraits<4, 2> + { + enum { simple_block_dim_x = 32 }; + enum { simple_block_dim_y = 8 }; + + enum { smart_block_dim_x = 32 }; + enum { smart_block_dim_y = 8 }; + enum { smart_shift = 4 }; + }; + template <> struct ArithmFuncTraits<4, 4> + { + enum { simple_block_dim_x = 32 }; + enum { simple_block_dim_y = 8 }; + + enum { smart_block_dim_x = 32 }; + enum { smart_block_dim_y = 8 }; + enum { smart_shift = 4 }; + }; +} + +////////////////////////////////////////////////////////////////////////// +// addMat + +namespace arithm +{ + struct VAdd4 : binary_function + { + __device__ __forceinline__ uint operator ()(uint a, uint b) const + { + return vadd4(a, b); + } + + __device__ __forceinline__ VAdd4() {} + __device__ __forceinline__ VAdd4(const VAdd4& other) {} + }; + + //////////////////////////////////// + + struct VAdd2 : binary_function + { + __device__ __forceinline__ uint operator ()(uint a, uint b) const + { + return vadd2(a, b); + } + + __device__ __forceinline__ VAdd2() {} + __device__ __forceinline__ VAdd2(const VAdd2& other) {} + }; + + //////////////////////////////////// + + template struct AddMat : binary_function + { + __device__ __forceinline__ D operator ()(T a, T b) const + { + return saturate_cast(a + b); + } + + __device__ __forceinline__ AddMat() {} + __device__ __forceinline__ AddMat(const AddMat& other) {} + }; +} + +namespace cv { namespace gpu { namespace cudev +{ + template <> struct TransformFunctorTraits< arithm::VAdd4 > : arithm::ArithmFuncTraits + { + }; + + //////////////////////////////////// + + template <> struct TransformFunctorTraits< arithm::VAdd2 > : arithm::ArithmFuncTraits + { + }; + + //////////////////////////////////// + + template struct TransformFunctorTraits< arithm::AddMat > : arithm::ArithmFuncTraits + { + }; +}}} + +namespace arithm +{ + void addMat_v4(PtrStepSz src1, PtrStepSz src2, PtrStepSz dst, cudaStream_t stream) + { + cudev::transform(src1, src2, dst, VAdd4(), WithOutMask(), stream); + } + + void addMat_v2(PtrStepSz src1, PtrStepSz src2, PtrStepSz dst, cudaStream_t stream) + { + cudev::transform(src1, src2, dst, VAdd2(), WithOutMask(), stream); + } + + template + void addMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream) + { + if (mask.data) + cudev::transform((PtrStepSz) src1, (PtrStepSz) src2, (PtrStepSz) dst, AddMat(), mask, stream); + else + cudev::transform((PtrStepSz) src1, (PtrStepSz) src2, (PtrStepSz) dst, AddMat(), WithOutMask(), stream); + } + + template void addMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void addMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void addMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void addMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void addMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void addMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void addMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + + template void addMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void addMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void addMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void addMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void addMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void addMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void addMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + + //template void addMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + //template void addMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void addMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void addMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void addMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void addMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void addMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + + //template void addMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + //template void addMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void addMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void addMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void addMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void addMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void addMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + + //template void addMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + //template void addMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + //template void addMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + //template void addMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void addMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void addMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void addMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + + //template void addMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + //template void addMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + //template void addMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + //template void addMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + //template void addMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void addMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void addMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + + //template void addMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + //template void addMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + //template void addMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + //template void addMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + //template void addMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + //template void addMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void addMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); +} + +////////////////////////////////////////////////////////////////////////// +// addScalar + +namespace arithm +{ + template struct AddScalar : unary_function + { + S val; + + explicit AddScalar(S val_) : val(val_) {} + + __device__ __forceinline__ D operator ()(T a) const + { + return saturate_cast(a + val); + } + }; +} + +namespace cv { namespace gpu { namespace cudev +{ + template struct TransformFunctorTraits< arithm::AddScalar > : arithm::ArithmFuncTraits + { + }; +}}} + +namespace arithm +{ + template + void addScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream) + { + AddScalar op(static_cast(val)); + + if (mask.data) + cudev::transform((PtrStepSz) src1, (PtrStepSz) dst, op, mask, stream); + else + cudev::transform((PtrStepSz) src1, (PtrStepSz) dst, op, WithOutMask(), stream); + } + + template void addScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void addScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void addScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void addScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void addScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void addScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void addScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + + template void addScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void addScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void addScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void addScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void addScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void addScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void addScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + + //template void addScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + //template void addScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void addScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void addScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void addScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void addScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void addScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + + //template void addScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + //template void addScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void addScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void addScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void addScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void addScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void addScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + + //template void addScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + //template void addScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + //template void addScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + //template void addScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void addScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void addScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void addScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + + //template void addScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + //template void addScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + //template void addScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + //template void addScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + //template void addScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void addScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void addScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + + //template void addScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + //template void addScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + //template void addScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + //template void addScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + //template void addScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + //template void addScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void addScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); +} + +////////////////////////////////////////////////////////////////////////// +// subMat + +namespace arithm +{ + struct VSub4 : binary_function + { + __device__ __forceinline__ uint operator ()(uint a, uint b) const + { + return vsub4(a, b); + } + + __device__ __forceinline__ VSub4() {} + __device__ __forceinline__ VSub4(const VSub4& other) {} + }; + + //////////////////////////////////// + + struct VSub2 : binary_function + { + __device__ __forceinline__ uint operator ()(uint a, uint b) const + { + return vsub2(a, b); + } + + __device__ __forceinline__ VSub2() {} + __device__ __forceinline__ VSub2(const VSub2& other) {} + }; + + //////////////////////////////////// + + template struct SubMat : binary_function + { + __device__ __forceinline__ D operator ()(T a, T b) const + { + return saturate_cast(a - b); + } + + __device__ __forceinline__ SubMat() {} + __device__ __forceinline__ SubMat(const SubMat& other) {} + }; +} + +namespace cv { namespace gpu { namespace cudev +{ + template <> struct TransformFunctorTraits< arithm::VSub4 > : arithm::ArithmFuncTraits + { + }; + + //////////////////////////////////// + + template <> struct TransformFunctorTraits< arithm::VSub2 > : arithm::ArithmFuncTraits + { + }; + + //////////////////////////////////// + + template struct TransformFunctorTraits< arithm::SubMat > : arithm::ArithmFuncTraits + { + }; +}}} + +namespace arithm +{ + void subMat_v4(PtrStepSz src1, PtrStepSz src2, PtrStepSz dst, cudaStream_t stream) + { + cudev::transform(src1, src2, dst, VSub4(), WithOutMask(), stream); + } + + void subMat_v2(PtrStepSz src1, PtrStepSz src2, PtrStepSz dst, cudaStream_t stream) + { + cudev::transform(src1, src2, dst, VSub2(), WithOutMask(), stream); + } + + template + void subMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream) + { + if (mask.data) + cudev::transform((PtrStepSz) src1, (PtrStepSz) src2, (PtrStepSz) dst, SubMat(), mask, stream); + else + cudev::transform((PtrStepSz) src1, (PtrStepSz) src2, (PtrStepSz) dst, SubMat(), WithOutMask(), stream); + } + + template void subMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void subMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void subMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void subMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void subMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void subMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void subMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + + template void subMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void subMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void subMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void subMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void subMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void subMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void subMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + + //template void subMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + //template void subMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void subMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void subMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void subMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void subMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void subMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + + //template void subMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + //template void subMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void subMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void subMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void subMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void subMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void subMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + + //template void subMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + //template void subMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + //template void subMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + //template void subMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void subMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void subMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void subMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + + //template void subMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + //template void subMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + //template void subMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + //template void subMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + //template void subMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void subMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void subMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + + //template void subMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + //template void subMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + //template void subMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + //template void subMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + //template void subMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + //template void subMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void subMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); +} + +////////////////////////////////////////////////////////////////////////// +// subScalar + +namespace arithm +{ + template + void subScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream) + { + AddScalar op(-static_cast(val)); + + if (mask.data) + cudev::transform((PtrStepSz) src1, (PtrStepSz) dst, op, mask, stream); + else + cudev::transform((PtrStepSz) src1, (PtrStepSz) dst, op, WithOutMask(), stream); + } + + template void subScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void subScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void subScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void subScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void subScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void subScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void subScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + + template void subScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void subScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void subScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void subScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void subScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void subScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void subScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + + //template void subScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + //template void subScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void subScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void subScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void subScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void subScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void subScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + + //template void subScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + //template void subScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void subScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void subScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void subScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void subScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void subScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + + //template void subScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + //template void subScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + //template void subScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + //template void subScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void subScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void subScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void subScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + + //template void subScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + //template void subScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + //template void subScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + //template void subScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + //template void subScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void subScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void subScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + + //template void subScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + //template void subScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + //template void subScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + //template void subScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + //template void subScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + //template void subScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); + template void subScalar(PtrStepSzb src1, double val, PtrStepSzb dst, PtrStepb mask, cudaStream_t stream); +} + +////////////////////////////////////////////////////////////////////////// +// mulMat + +namespace arithm +{ + struct Mul_8uc4_32f : binary_function + { + __device__ __forceinline__ uint operator ()(uint a, float b) const + { + uint res = 0; + + res |= (saturate_cast((0xffu & (a )) * b) ); + res |= (saturate_cast((0xffu & (a >> 8)) * b) << 8); + res |= (saturate_cast((0xffu & (a >> 16)) * b) << 16); + res |= (saturate_cast((0xffu & (a >> 24)) * b) << 24); + + return res; + } + + __device__ __forceinline__ Mul_8uc4_32f() {} + __device__ __forceinline__ Mul_8uc4_32f(const Mul_8uc4_32f& other) {} + }; + + struct Mul_16sc4_32f : binary_function + { + __device__ __forceinline__ short4 operator ()(short4 a, float b) const + { + return make_short4(saturate_cast(a.x * b), saturate_cast(a.y * b), + 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) {} + }; + + template struct Mul : binary_function + { + __device__ __forceinline__ D operator ()(T a, T b) const + { + return saturate_cast(a * b); + } + + __device__ __forceinline__ Mul() {} + __device__ __forceinline__ Mul(const Mul& other) {} + }; + + template struct MulScale : binary_function + { + S scale; + + explicit MulScale(S scale_) : scale(scale_) {} + + __device__ __forceinline__ D operator ()(T a, T b) const + { + return saturate_cast(scale * a * b); + } + }; +} + +namespace cv { namespace gpu { namespace cudev +{ + template <> struct TransformFunctorTraits : arithm::ArithmFuncTraits + { + }; + + template struct TransformFunctorTraits< arithm::Mul > : arithm::ArithmFuncTraits + { + }; + + template struct TransformFunctorTraits< arithm::MulScale > : arithm::ArithmFuncTraits + { + }; +}}} + +namespace arithm +{ + void mulMat_8uc4_32f(PtrStepSz src1, PtrStepSzf src2, PtrStepSz dst, cudaStream_t stream) + { + cudev::transform(src1, src2, dst, Mul_8uc4_32f(), WithOutMask(), stream); + } + + void mulMat_16sc4_32f(PtrStepSz src1, PtrStepSzf src2, PtrStepSz dst, cudaStream_t stream) + { + cudev::transform(src1, src2, dst, Mul_16sc4_32f(), WithOutMask(), stream); + } + + template + void mulMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream) + { + if (scale == 1) + { + Mul op; + cudev::transform((PtrStepSz) src1, (PtrStepSz) src2, (PtrStepSz) dst, op, WithOutMask(), stream); + } + else + { + MulScale op(static_cast(scale)); + cudev::transform((PtrStepSz) src1, (PtrStepSz) src2, (PtrStepSz) dst, op, WithOutMask(), stream); + } + } + + template void mulMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + template void mulMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + template void mulMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + template void mulMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + template void mulMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + template void mulMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + template void mulMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + + template void mulMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + template void mulMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + template void mulMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + template void mulMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + template void mulMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + template void mulMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + template void mulMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + + //template void mulMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + //template void mulMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + template void mulMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + template void mulMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + template void mulMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + template void mulMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + template void mulMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + + //template void mulMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + //template void mulMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + template void mulMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + template void mulMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + template void mulMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + template void mulMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + template void mulMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + + //template void mulMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + //template void mulMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + //template void mulMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + //template void mulMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + template void mulMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + template void mulMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + template void mulMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + + //template void mulMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + //template void mulMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + //template void mulMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + //template void mulMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + //template void mulMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + template void mulMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + template void mulMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + + //template void mulMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + //template void mulMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + //template void mulMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + //template void mulMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + //template void mulMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + //template void mulMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + template void mulMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); +} + +////////////////////////////////////////////////////////////////////////// +// mulScalar + +namespace arithm +{ + template struct MulScalar : unary_function + { + S val; + + explicit MulScalar(S val_) : val(val_) {} + + __device__ __forceinline__ D operator ()(T a) const + { + return saturate_cast(a * val); + } + }; +} + +namespace cv { namespace gpu { namespace cudev +{ + template struct TransformFunctorTraits< arithm::MulScalar > : arithm::ArithmFuncTraits + { + }; +}}} + +namespace arithm +{ + template + void mulScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream) + { + MulScalar op(static_cast(val)); + cudev::transform((PtrStepSz) src1, (PtrStepSz) dst, op, WithOutMask(), stream); + } + + template void mulScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void mulScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void mulScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void mulScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void mulScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void mulScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void mulScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + + template void mulScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void mulScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void mulScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void mulScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void mulScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void mulScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void mulScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + + //template void mulScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + //template void mulScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void mulScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void mulScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void mulScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void mulScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void mulScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + + //template void mulScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + //template void mulScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void mulScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void mulScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void mulScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void mulScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void mulScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + + //template void mulScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + //template void mulScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + //template void mulScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + //template void mulScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void mulScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void mulScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void mulScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + + //template void mulScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + //template void mulScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + //template void mulScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + //template void mulScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + //template void mulScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void mulScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void mulScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + + //template void mulScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + //template void mulScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + //template void mulScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + //template void mulScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + //template void mulScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + //template void mulScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void mulScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); +} + +////////////////////////////////////////////////////////////////////////// +// divMat + +namespace arithm +{ + struct Div_8uc4_32f : binary_function + { + __device__ __forceinline__ uint operator ()(uint a, float b) const + { + uint res = 0; + + if (b != 0) + { + b = 1.0f / b; + res |= (saturate_cast((0xffu & (a )) * b) ); + res |= (saturate_cast((0xffu & (a >> 8)) * b) << 8); + res |= (saturate_cast((0xffu & (a >> 16)) * b) << 16); + res |= (saturate_cast((0xffu & (a >> 24)) * b) << 24); + } + + return res; + } + }; + + struct Div_16sc4_32f : binary_function + { + __device__ __forceinline__ short4 operator ()(short4 a, float b) const + { + return b != 0 ? make_short4(saturate_cast(a.x / b), saturate_cast(a.y / b), + saturate_cast(a.z / b), saturate_cast(a.w / b)) + : make_short4(0,0,0,0); + } + }; + + template struct Div : binary_function + { + __device__ __forceinline__ D operator ()(T a, T b) const + { + return b != 0 ? saturate_cast(a / b) : 0; + } + + __device__ __forceinline__ Div() {} + __device__ __forceinline__ Div(const Div& other) {} + }; + template struct Div : binary_function + { + __device__ __forceinline__ float operator ()(T a, T b) const + { + return b != 0 ? static_cast(a) / b : 0; + } + + __device__ __forceinline__ Div() {} + __device__ __forceinline__ Div(const Div& other) {} + }; + template struct Div : binary_function + { + __device__ __forceinline__ double operator ()(T a, T b) const + { + return b != 0 ? static_cast(a) / b : 0; + } + + __device__ __forceinline__ Div() {} + __device__ __forceinline__ Div(const Div& other) {} + }; + + template struct DivScale : binary_function + { + S scale; + + explicit DivScale(S scale_) : scale(scale_) {} + + __device__ __forceinline__ D operator ()(T a, T b) const + { + return b != 0 ? saturate_cast(scale * a / b) : 0; + } + }; +} + +namespace cv { namespace gpu { namespace cudev +{ + template <> struct TransformFunctorTraits : arithm::ArithmFuncTraits + { + }; + + template struct TransformFunctorTraits< arithm::Div > : arithm::ArithmFuncTraits + { + }; + + template struct TransformFunctorTraits< arithm::DivScale > : arithm::ArithmFuncTraits + { + }; +}}} + +namespace arithm +{ + void divMat_8uc4_32f(PtrStepSz src1, PtrStepSzf src2, PtrStepSz dst, cudaStream_t stream) + { + cudev::transform(src1, src2, dst, Div_8uc4_32f(), WithOutMask(), stream); + } + + void divMat_16sc4_32f(PtrStepSz src1, PtrStepSzf src2, PtrStepSz dst, cudaStream_t stream) + { + cudev::transform(src1, src2, dst, Div_16sc4_32f(), WithOutMask(), stream); + } + + template + void divMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream) + { + if (scale == 1) + { + Div op; + cudev::transform((PtrStepSz) src1, (PtrStepSz) src2, (PtrStepSz) dst, op, WithOutMask(), stream); + } + else + { + DivScale op(static_cast(scale)); + cudev::transform((PtrStepSz) src1, (PtrStepSz) src2, (PtrStepSz) dst, op, WithOutMask(), stream); + } + } + + template void divMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + template void divMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + template void divMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + template void divMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + template void divMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + template void divMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + template void divMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + + template void divMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + template void divMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + template void divMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + template void divMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + template void divMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + template void divMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + template void divMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + + //template void divMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + //template void divMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + template void divMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + template void divMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + template void divMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + template void divMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + template void divMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + + //template void divMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + //template void divMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + template void divMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + template void divMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + template void divMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + template void divMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + template void divMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + + //template void divMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + //template void divMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + //template void divMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + //template void divMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + template void divMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + template void divMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + template void divMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + + //template void divMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + //template void divMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + //template void divMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + //template void divMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + //template void divMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + template void divMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + template void divMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + + //template void divMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + //template void divMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + //template void divMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + //template void divMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + //template void divMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + //template void divMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); + template void divMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, double scale, cudaStream_t stream); +} + +////////////////////////////////////////////////////////////////////////// +// divScalar + +namespace arithm +{ + template + void divScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream) + { + MulScalar op(static_cast(1.0 / val)); + cudev::transform((PtrStepSz) src1, (PtrStepSz) dst, op, WithOutMask(), stream); + } + + template void divScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void divScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void divScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void divScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void divScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void divScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void divScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + + template void divScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void divScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void divScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void divScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void divScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void divScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void divScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + + //template void divScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + //template void divScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void divScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void divScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void divScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void divScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void divScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + + //template void divScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + //template void divScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void divScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void divScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void divScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void divScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void divScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + + //template void divScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + //template void divScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + //template void divScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + //template void divScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void divScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void divScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void divScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + + //template void divScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + //template void divScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + //template void divScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + //template void divScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + //template void divScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void divScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void divScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + + //template void divScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + //template void divScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + //template void divScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + //template void divScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + //template void divScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + //template void divScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void divScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); +} + +////////////////////////////////////////////////////////////////////////// +// divInv + +namespace arithm +{ + template struct DivInv : unary_function + { + S val; + + explicit DivInv(S val_) : val(val_) {} + + __device__ __forceinline__ D operator ()(T a) const + { + return a != 0 ? saturate_cast(val / a) : 0; + } + }; +} + +namespace cv { namespace gpu { namespace cudev +{ + template struct TransformFunctorTraits< arithm::DivInv > : arithm::ArithmFuncTraits + { + }; +}}} + +namespace arithm +{ + template + void divInv(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream) + { + DivInv op(static_cast(val)); + cudev::transform((PtrStepSz) src1, (PtrStepSz) dst, op, WithOutMask(), stream); + } + + template void divInv(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void divInv(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void divInv(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void divInv(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void divInv(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void divInv(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void divInv(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + + template void divInv(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void divInv(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void divInv(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void divInv(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void divInv(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void divInv(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void divInv(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + + //template void divInv(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + //template void divInv(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void divInv(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void divInv(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void divInv(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void divInv(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void divInv(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + + //template void divInv(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + //template void divInv(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void divInv(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void divInv(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void divInv(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void divInv(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void divInv(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + + //template void divInv(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + //template void divInv(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + //template void divInv(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + //template void divInv(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void divInv(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void divInv(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void divInv(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + + //template void divInv(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + //template void divInv(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + //template void divInv(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + //template void divInv(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + //template void divInv(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void divInv(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void divInv(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + + //template void divInv(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + //template void divInv(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + //template void divInv(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + //template void divInv(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + //template void divInv(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + //template void divInv(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); + template void divInv(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream); +} + +////////////////////////////////////////////////////////////////////////// +// absDiffMat + +namespace arithm +{ + struct VAbsDiff4 : binary_function + { + __device__ __forceinline__ uint operator ()(uint a, uint b) const + { + return vabsdiff4(a, b); + } + + __device__ __forceinline__ VAbsDiff4() {} + __device__ __forceinline__ VAbsDiff4(const VAbsDiff4& other) {} + }; + + //////////////////////////////////// + + struct VAbsDiff2 : binary_function + { + __device__ __forceinline__ uint operator ()(uint a, uint b) const + { + return vabsdiff2(a, b); + } + + __device__ __forceinline__ VAbsDiff2() {} + __device__ __forceinline__ VAbsDiff2(const VAbsDiff2& other) {} + }; + + //////////////////////////////////// + + __device__ __forceinline__ int _abs(int a) + { + return ::abs(a); + } + __device__ __forceinline__ float _abs(float a) + { + return ::fabsf(a); + } + __device__ __forceinline__ double _abs(double a) + { + return ::fabs(a); + } + + template struct AbsDiffMat : binary_function + { + __device__ __forceinline__ T operator ()(T a, T b) const + { + return saturate_cast(_abs(a - b)); + } + + __device__ __forceinline__ AbsDiffMat() {} + __device__ __forceinline__ AbsDiffMat(const AbsDiffMat& other) {} + }; +} + +namespace cv { namespace gpu { namespace cudev +{ + template <> struct TransformFunctorTraits< arithm::VAbsDiff4 > : arithm::ArithmFuncTraits + { + }; + + //////////////////////////////////// + + template <> struct TransformFunctorTraits< arithm::VAbsDiff2 > : arithm::ArithmFuncTraits + { + }; + + //////////////////////////////////// + + template struct TransformFunctorTraits< arithm::AbsDiffMat > : arithm::ArithmFuncTraits + { + }; +}}} + +namespace arithm +{ + void absDiffMat_v4(PtrStepSz src1, PtrStepSz src2, PtrStepSz dst, cudaStream_t stream) + { + cudev::transform(src1, src2, dst, VAbsDiff4(), WithOutMask(), stream); + } + + void absDiffMat_v2(PtrStepSz src1, PtrStepSz src2, PtrStepSz dst, cudaStream_t stream) + { + cudev::transform(src1, src2, dst, VAbsDiff2(), WithOutMask(), stream); + } + + template + void absDiffMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, cudaStream_t stream) + { + cudev::transform((PtrStepSz) src1, (PtrStepSz) src2, (PtrStepSz) dst, AbsDiffMat(), WithOutMask(), stream); + } + + template void absDiffMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, cudaStream_t stream); + template void absDiffMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, cudaStream_t stream); + template void absDiffMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, cudaStream_t stream); + template void absDiffMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, cudaStream_t stream); + template void absDiffMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, cudaStream_t stream); + template void absDiffMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, cudaStream_t stream); + template void absDiffMat(PtrStepSzb src1, PtrStepSzb src2, PtrStepSzb dst, cudaStream_t stream); +} + +////////////////////////////////////////////////////////////////////////// +// absDiffScalar + +namespace arithm +{ + template struct AbsDiffScalar : unary_function + { + S val; + + explicit AbsDiffScalar(S val_) : val(val_) {} + + __device__ __forceinline__ T operator ()(T a) const + { + abs_func f; + return saturate_cast(f(a - val)); + } + }; +} + +namespace cv { namespace gpu { namespace cudev +{ + template struct TransformFunctorTraits< arithm::AbsDiffScalar > : arithm::ArithmFuncTraits + { + }; +}}} + +namespace arithm +{ + template + void absDiffScalar(PtrStepSzb src1, double val, PtrStepSzb dst, cudaStream_t stream) + { + AbsDiffScalar op(static_cast(val)); + + cudev::transform((PtrStepSz) src1, (PtrStepSz) dst, op, WithOutMask(), stream); + } + + template void absDiffScalar(PtrStepSzb src1, double src2, PtrStepSzb dst, cudaStream_t stream); + template void absDiffScalar(PtrStepSzb src1, double src2, PtrStepSzb dst, cudaStream_t stream); + template void absDiffScalar(PtrStepSzb src1, double src2, PtrStepSzb dst, cudaStream_t stream); + template void absDiffScalar(PtrStepSzb src1, double src2, PtrStepSzb dst, cudaStream_t stream); + template void absDiffScalar(PtrStepSzb src1, double src2, PtrStepSzb dst, cudaStream_t stream); + template void absDiffScalar(PtrStepSzb src1, double src2, PtrStepSzb dst, cudaStream_t stream); + template void absDiffScalar(PtrStepSzb src1, double src2, PtrStepSzb dst, cudaStream_t stream); +} + +////////////////////////////////////////////////////////////////////////// +// absMat + +namespace cv { namespace gpu { namespace cudev +{ + template struct TransformFunctorTraits< abs_func > : arithm::ArithmFuncTraits + { + }; +}}} + +namespace arithm +{ + template + void absMat(PtrStepSzb src, PtrStepSzb dst, cudaStream_t stream) + { + cudev::transform((PtrStepSz) src, (PtrStepSz) dst, abs_func(), WithOutMask(), stream); + } + + template void absMat(PtrStepSzb src, PtrStepSzb dst, cudaStream_t stream); + template void absMat(PtrStepSzb src, PtrStepSzb dst, cudaStream_t stream); + template void absMat(PtrStepSzb src, PtrStepSzb dst, cudaStream_t stream); + template void absMat(PtrStepSzb src, PtrStepSzb dst, cudaStream_t stream); + template void absMat(PtrStepSzb src, PtrStepSzb dst, cudaStream_t stream); + template void absMat(PtrStepSzb src, PtrStepSzb dst, cudaStream_t stream); + template void absMat(PtrStepSzb src, PtrStepSzb dst, cudaStream_t stream); +} + +////////////////////////////////////////////////////////////////////////// +// sqrMat + +namespace arithm +{ + template struct Sqr : unary_function + { + __device__ __forceinline__ T operator ()(T x) const + { + return saturate_cast(x * x); + } + + __device__ __forceinline__ Sqr() {} + __device__ __forceinline__ Sqr(const Sqr& other) {} + }; +} + +namespace cv { namespace gpu { namespace cudev +{ + template struct TransformFunctorTraits< arithm::Sqr > : arithm::ArithmFuncTraits + { + }; +}}} + +namespace arithm +{ + template + void sqrMat(PtrStepSzb src, PtrStepSzb dst, cudaStream_t stream) + { + cudev::transform((PtrStepSz) src, (PtrStepSz) dst, Sqr(), WithOutMask(), stream); + } + + template void sqrMat(PtrStepSzb src, PtrStepSzb dst, cudaStream_t stream); + template void sqrMat(PtrStepSzb src, PtrStepSzb dst, cudaStream_t stream); + template void sqrMat(PtrStepSzb src, PtrStepSzb dst, cudaStream_t stream); + template void sqrMat(PtrStepSzb src, PtrStepSzb dst, cudaStream_t stream); + template void sqrMat(PtrStepSzb src, PtrStepSzb dst, cudaStream_t stream); + template void sqrMat(PtrStepSzb src, PtrStepSzb dst, cudaStream_t stream); + template void sqrMat(PtrStepSzb src, PtrStepSzb dst, cudaStream_t stream); +} + +////////////////////////////////////////////////////////////////////////// +// sqrtMat + +namespace cv { namespace gpu { namespace cudev +{ + template struct TransformFunctorTraits< sqrt_func > : arithm::ArithmFuncTraits + { + }; +}}} + +namespace arithm +{ + template + void sqrtMat(PtrStepSzb src, PtrStepSzb dst, cudaStream_t stream) + { + cudev::transform((PtrStepSz) src, (PtrStepSz) dst, sqrt_func(), WithOutMask(), stream); + } + + template void sqrtMat(PtrStepSzb src, PtrStepSzb dst, cudaStream_t stream); + template void sqrtMat(PtrStepSzb src, PtrStepSzb dst, cudaStream_t stream); + template void sqrtMat(PtrStepSzb src, PtrStepSzb dst, cudaStream_t stream); + template void sqrtMat(PtrStepSzb src, PtrStepSzb dst, cudaStream_t stream); + template void sqrtMat(PtrStepSzb src, PtrStepSzb dst, cudaStream_t stream); + template void sqrtMat(PtrStepSzb src, PtrStepSzb dst, cudaStream_t stream); + template void sqrtMat(PtrStepSzb src, PtrStepSzb dst, cudaStream_t stream); +} + +////////////////////////////////////////////////////////////////////////// +// logMat + +namespace cv { namespace gpu { namespace cudev +{ + template struct TransformFunctorTraits< log_func > : arithm::ArithmFuncTraits + { + }; +}}} + +namespace arithm +{ + template + void logMat(PtrStepSzb src, PtrStepSzb dst, cudaStream_t stream) + { + cudev::transform((PtrStepSz) src, (PtrStepSz) dst, log_func(), WithOutMask(), stream); + } + + template void logMat(PtrStepSzb src, PtrStepSzb dst, cudaStream_t stream); + template void logMat(PtrStepSzb src, PtrStepSzb dst, cudaStream_t stream); + template void logMat(PtrStepSzb src, PtrStepSzb dst, cudaStream_t stream); + template void logMat(PtrStepSzb src, PtrStepSzb dst, cudaStream_t stream); + template void logMat(PtrStepSzb src, PtrStepSzb dst, cudaStream_t stream); + template void logMat(PtrStepSzb src, PtrStepSzb dst, cudaStream_t stream); + template void logMat(PtrStepSzb src, PtrStepSzb dst, cudaStream_t stream); +} + +////////////////////////////////////////////////////////////////////////// +// expMat + +namespace arithm +{ + template struct Exp : unary_function + { + __device__ __forceinline__ T operator ()(T x) const + { + exp_func f; + return saturate_cast(f(x)); + } + + __device__ __forceinline__ Exp() {} + __device__ __forceinline__ Exp(const Exp& other) {} + }; +} + +namespace cv { namespace gpu { namespace cudev +{ + template struct TransformFunctorTraits< arithm::Exp > : arithm::ArithmFuncTraits + { + }; +}}} + +namespace arithm +{ + template + void expMat(PtrStepSzb src, PtrStepSzb dst, cudaStream_t stream) + { + cudev::transform((PtrStepSz) src, (PtrStepSz) dst, Exp(), WithOutMask(), stream); + } + + template void expMat(PtrStepSzb src, PtrStepSzb dst, cudaStream_t stream); + template void expMat(PtrStepSzb src, PtrStepSzb dst, cudaStream_t stream); + template void expMat(PtrStepSzb src, PtrStepSzb dst, cudaStream_t stream); + template void expMat(PtrStepSzb src, PtrStepSzb dst, cudaStream_t stream); + template void expMat(PtrStepSzb src, PtrStepSzb dst, cudaStream_t stream); + template void expMat(PtrStepSzb src, PtrStepSzb dst, cudaStream_t stream); + template void expMat(PtrStepSzb src, PtrStepSzb dst, cudaStream_t stream); +} + +////////////////////////////////////////////////////////////////////////////////////// +// cmpMat + +namespace arithm +{ + struct VCmpEq4 : binary_function + { + __device__ __forceinline__ uint operator ()(uint a, uint b) const + { + return vcmpeq4(a, b); + } + + __device__ __forceinline__ VCmpEq4() {} + __device__ __forceinline__ VCmpEq4(const VCmpEq4& other) {} + }; + struct VCmpNe4 : binary_function + { + __device__ __forceinline__ uint operator ()(uint a, uint b) const + { + return vcmpne4(a, b); + } + + __device__ __forceinline__ VCmpNe4() {} + __device__ __forceinline__ VCmpNe4(const VCmpNe4& other) {} + }; + struct VCmpLt4 : binary_function + { + __device__ __forceinline__ uint operator ()(uint a, uint b) const + { + return vcmplt4(a, b); + } + + __device__ __forceinline__ VCmpLt4() {} + __device__ __forceinline__ VCmpLt4(const VCmpLt4& other) {} + }; + struct VCmpLe4 : binary_function + { + __device__ __forceinline__ uint operator ()(uint a, uint b) const + { + return vcmple4(a, b); + } + + __device__ __forceinline__ VCmpLe4() {} + __device__ __forceinline__ VCmpLe4(const VCmpLe4& other) {} + }; + + //////////////////////////////////// + + template + struct Cmp : binary_function + { + __device__ __forceinline__ uchar operator()(T a, T b) const + { + Op op; + return -op(a, b); + } + }; +} + +namespace cv { namespace gpu { namespace cudev +{ + template <> struct TransformFunctorTraits< arithm::VCmpEq4 > : arithm::ArithmFuncTraits + { + }; + template <> struct TransformFunctorTraits< arithm::VCmpNe4 > : arithm::ArithmFuncTraits + { + }; + template <> struct TransformFunctorTraits< arithm::VCmpLt4 > : arithm::ArithmFuncTraits + { + }; + template <> struct TransformFunctorTraits< arithm::VCmpLe4 > : arithm::ArithmFuncTraits + { + }; + + //////////////////////////////////// + + template struct TransformFunctorTraits< arithm::Cmp > : arithm::ArithmFuncTraits + { + }; +}}} + +namespace arithm +{ + void cmpMatEq_v4(PtrStepSz src1, PtrStepSz src2, PtrStepSz dst, cudaStream_t stream) + { + cudev::transform(src1, src2, dst, VCmpEq4(), WithOutMask(), stream); + } + void cmpMatNe_v4(PtrStepSz src1, PtrStepSz src2, PtrStepSz dst, cudaStream_t stream) + { + cudev::transform(src1, src2, dst, VCmpNe4(), WithOutMask(), stream); + } + void cmpMatLt_v4(PtrStepSz src1, PtrStepSz src2, PtrStepSz dst, cudaStream_t stream) + { + cudev::transform(src1, src2, dst, VCmpLt4(), WithOutMask(), stream); + } + void cmpMatLe_v4(PtrStepSz src1, PtrStepSz src2, PtrStepSz dst, cudaStream_t stream) + { + cudev::transform(src1, src2, dst, VCmpLe4(), WithOutMask(), stream); + } + + template