diff --git a/3rdparty/carotene/hal/tegra_hal.hpp b/3rdparty/carotene/hal/tegra_hal.hpp index ce8fa90982..7f67ecf1a3 100644 --- a/3rdparty/carotene/hal/tegra_hal.hpp +++ b/3rdparty/carotene/hal/tegra_hal.hpp @@ -1286,7 +1286,6 @@ inline int TEGRA_SEPFILTERFREE(cvhalFilter2D *context) #undef cv_hal_sepFilterFree #define cv_hal_sepFilterFree TEGRA_SEPFILTERFREE - struct MorphCtx { int operation; @@ -1857,6 +1856,80 @@ TegraCvtColor_Invoker(bgrx2hsvf, bgrx2hsv, src_data + static_cast(range. #define cv_hal_cvtTwoPlaneYUVtoBGREx TEGRA_CVT2PYUVTOBGR_EX #endif +// The optimized branch was developed for old armv7 processors and leads to perf degradation on armv8 +#if defined(DCAROTENE_NEON_ARCH) && (DCAROTENE_NEON_ARCH == 7) +inline CAROTENE_NS::BORDER_MODE borderCV2Carotene(int borderType) +{ + switch(borderType) + { + case CV_HAL_BORDER_CONSTANT: + return CAROTENE_NS::BORDER_MODE_CONSTANT; + case CV_HAL_BORDER_REPLICATE: + return CAROTENE_NS::BORDER_MODE_REPLICATE; + case CV_HAL_BORDER_REFLECT: + return CAROTENE_NS::BORDER_MODE_REFLECT; + case CV_HAL_BORDER_WRAP: + return CAROTENE_NS::BORDER_MODE_WRAP; + case CV_HAL_BORDER_REFLECT_101: + return CAROTENE_NS::BORDER_MODE_REFLECT101; + } + + return CAROTENE_NS::BORDER_MODE_UNDEFINED; +} + +inline int TEGRA_GaussianBlurBinomial(const uchar* src_data, size_t src_step, uchar* dst_data, size_t dst_step, + int width, int height, int depth, int cn, size_t margin_left, size_t margin_top, + size_t margin_right, size_t margin_bottom, size_t ksize, int border_type) +{ + CAROTENE_NS::Size2D sz(width, height); + CAROTENE_NS::BORDER_MODE border = borderCV2Carotene(border_type); + CAROTENE_NS::Margin mg(margin_left, margin_right, margin_top, margin_bottom); + + if (ksize == 3) + { + if ((depth != CV_8U) || (cn != 1)) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + + if (CAROTENE_NS::isGaussianBlur3x3MarginSupported(sz, border, mg)) + { + CAROTENE_NS::gaussianBlur3x3Margin(sz, src_data, src_step, dst_data, dst_step, + border, 0, mg); + return CV_HAL_ERROR_OK; + } + } + else if (ksize == 5) + { + if (!CAROTENE_NS::isGaussianBlur5x5Supported(sz, cn, border)) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + + if (depth == CV_8U) + { + CAROTENE_NS::gaussianBlur5x5(sz, cn, (uint8_t*)src_data, src_step, + (uint8_t*)dst_data, dst_step, border, 0, mg); + return CV_HAL_ERROR_OK; + } + else if (depth == CV_16U) + { + CAROTENE_NS::gaussianBlur5x5(sz, cn, (uint16_t*)src_data, src_step, + (uint16_t*)dst_data, dst_step, border, 0, mg); + return CV_HAL_ERROR_OK; + } + else if (depth == CV_16S) + { + CAROTENE_NS::gaussianBlur5x5(sz, cn, (int16_t*)src_data, src_step, + (int16_t*)dst_data, dst_step, border, 0, mg); + return CV_HAL_ERROR_OK; + } + } + + return CV_HAL_ERROR_NOT_IMPLEMENTED; +} + +#undef cv_hal_gaussianBlurBinomial +#define cv_hal_gaussianBlurBinomial TEGRA_GaussianBlurBinomial + +#endif // DCAROTENE_NEON_ARCH=7 + #endif // OPENCV_IMGPROC_HAL_INTERFACE_H #endif diff --git a/CMakeLists.txt b/CMakeLists.txt index 509a6e193a..e814744aed 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -262,7 +262,7 @@ OCV_OPTION(WITH_CAP_IOS "Enable iOS video capture" ON VERIFY HAVE_CAP_IOS) OCV_OPTION(WITH_CAROTENE "Use NVidia carotene acceleration library for ARM platform" (NOT CV_DISABLE_OPTIMIZATION) VISIBLE_IF (ARM OR AARCH64) AND NOT IOS AND NOT XROS) -OCV_OPTION(WITH_KLEIDICV "Use KleidiCV library for ARM platforms" OFF +OCV_OPTION(WITH_KLEIDICV "Use KleidiCV library for ARM platforms" (ANDROID AND AARCH64 AND NOT CV_DISABLE_OPTIMIZATION) VISIBLE_IF (AARCH64 AND (ANDROID OR UNIX AND NOT IOS AND NOT XROS))) OCV_OPTION(WITH_NDSRVP "Use Andes RVP extension" (NOT CV_DISABLE_OPTIMIZATION) VISIBLE_IF RISCV) @@ -1240,7 +1240,11 @@ if(CMAKE_GENERATOR MATCHES "Xcode|Visual Studio|Multi-Config") else() status(" Configuration:" ${CMAKE_BUILD_TYPE}) endif() - +if(DEFINED OPENCV_ALGO_HINT_DEFAULT) + status(" Algorithm Hint:" ${OPENCV_ALGO_HINT_DEFAULT}) +else() + status(" Algorithm Hint:" " ALGO_HINT_ACCURATE") +endif() # ========================= CPU code generation mode ========================= status("") @@ -1446,9 +1450,12 @@ if(WITH_GTK OR HAVE_GTK) else() status(" GTK+:" "NO") endif() + if(HAVE_GTK) status( " GThread :" HAVE_GTHREAD THEN "YES (ver ${GTHREAD_VERSION})" ELSE NO) - status( " GtkGlExt:" HAVE_GTKGLEXT THEN "YES (ver ${GTKGLEXT_VERSION})" ELSE NO) + if(NOT HAVE_GTK3) + status( " GtkGlExt:" HAVE_GTKGLEXT THEN "YES (ver ${GTKGLEXT_VERSION})" ELSE NO) + endif() endif() endif() diff --git a/cmake/OpenCVFindLibsGUI.cmake b/cmake/OpenCVFindLibsGUI.cmake index 31c19b1da7..735aae3475 100644 --- a/cmake/OpenCVFindLibsGUI.cmake +++ b/cmake/OpenCVFindLibsGUI.cmake @@ -60,9 +60,10 @@ if(WITH_QT) endif() # --- OpenGl --- +ocv_update(OpenGL_GL_PREFERENCE LEGACY) ocv_clear_vars(HAVE_OPENGL HAVE_QT_OPENGL) if(WITH_OPENGL) - if(WITH_WIN32UI OR (HAVE_QT AND QT_QTOPENGL_FOUND) OR HAVE_GTKGLEXT) + if(WITH_WIN32UI OR (HAVE_QT AND QT_QTOPENGL_FOUND) OR HAVE_GTK3 OR (HAVE_GTK AND NOT HAVE_GTK3 AND HAVE_GTKGLEXT)) find_package (OpenGL QUIET) if(OPENGL_FOUND) set(HAVE_OPENGL TRUE) diff --git a/doc/opencv.bib b/doc/opencv.bib index 5601465d1d..be6721b573 100644 --- a/doc/opencv.bib +++ b/doc/opencv.bib @@ -1377,3 +1377,12 @@ volume = {60}, journal = {ISPRS Journal of Photogrammetry and Remote Sensing} } +@article{LowIlie2003, + author = {Kok-Lim Low, Adrian Ilie}, + year = {2003}, + pages = {3-15}, + title = {View Frustum Optimization to Maximize Object's Image Area}, + journal = {Journal of Graphics, (GPU, & Game) Tools (JGT)}, + volume = {8}, + url = {https://citeseerx.ist.psu.edu/document?repid=rep1&type=pdf&doi=1fbd43f3827fffeb76641a9c5ab5b625eb5a75ba} +} diff --git a/doc/tutorials/dnn/dnn_yolo/dnn_yolo.markdown b/doc/tutorials/dnn/dnn_yolo/dnn_yolo.markdown index a2d4b2a306..8406b4746c 100644 --- a/doc/tutorials/dnn/dnn_yolo/dnn_yolo.markdown +++ b/doc/tutorials/dnn/dnn_yolo/dnn_yolo.markdown @@ -24,7 +24,9 @@ model, but the methodology applies to other supported models. @note Currently, OpenCV supports the following YOLO models: - [YOLOX](https://github.com/Megvii-BaseDetection/YOLOX/blob/main), -- [YoloNas](https://github.com/Deci-AI/super-gradients/tree/master), +- [YOLONas](https://github.com/Deci-AI/super-gradients/tree/master), +- [YOLOv10](https://github.com/THU-MIG/yolov10/tree/main), +- [YOLOv9](https://github.com/WongKinYiu/yolov9), - [YOLOv8](https://github.com/ultralytics/ultralytics/tree/main), - [YOLOv7](https://github.com/WongKinYiu/yolov7/tree/main), - [YOLOv6](https://github.com/meituan/YOLOv6/blob/main), @@ -79,7 +81,7 @@ the ONNX graph, a process that we will detail further in the subsequent sections Now that we know know the parameters of the pre-precessing we can go on and export the model from Pytorch to ONNX graph. Since in this tutorial we are using YOLOX as our sample model, lets use its -export for demonstration purposes (the process is identical for the rest of the YOLO detectors). +export for demonstration purposes (the process is identical for the rest of the YOLO detectors except `YOLOv10` model, see details on how to export it later in the post). To exporting YOLOX we can just use [export script](https://github.com/Megvii-BaseDetection/YOLOX/blob/ac58e0a5e68e57454b7b9ac822aced493b553c53/tools/export_onnx.py). Particularly we need following commands: @code{.bash} @@ -125,6 +127,20 @@ than YOLOX) in case it is needed. However, usually each YOLO repository has pred onnx.save(model_simp, args.output_name) @endcode +#### Exporting YOLOv10 model + +In oder to run YOLOv10 one needs to cut off postporcessing with dynamic shapes from torch and then convert it to ONNX. If someone is looking for on how to cut off the postprocessing, there is this [forked branch](https://github.com/Abdurrahheem/yolov10/tree/ash/opencv-export) from official YOLOv10. The forked branch cuts of the postprocessing by [returning output](https://github.com/Abdurrahheem/yolov10/blob/4fdaafd912c8891642bfbe85751ea66ec20f05ad/ultralytics/nn/modules/head.py#L522) of the model before postprocessing procedure itself. To convert torch model to ONNX follow this proceduce. + +@code{.bash} +git clone git@github.com:Abdurrahheem/yolov10.git +conda create -n yolov10 python=3.9 +conda activate yolov10 +pip install -r requirements.txt +python export_opencv.py --model= --imgsz= +@endcode + +By default `--model="yolov10s"` and `--imgsz=(480,640)`. This will generate file `yolov10s.onnx`, which can be use for inference in OpenCV + ### Running Yolo ONNX detector with OpenCV Sample Once we have our ONNX graph of the model, we just simply can run with OpenCV's sample. To that we need to make sure: @@ -144,24 +160,25 @@ Once we have our ONNX graph of the model, we just simply can run with OpenCV's s --padvalue= \ --paddingmode= \ --backend= \ - --target= + --target= \ + --width= \ + --height= \ @endcode -VIDEO DEMO: -@youtube{NHtRlndE2cg} - - --input: File path to your input image or video. If omitted, it will capture frames from a camera. - --classes: File path to a text file containing class names for object detection. - --thr: Confidence threshold for detection (e.g., 0.5). - --nms: Non-maximum suppression threshold (e.g., 0.4). - --mean: Mean normalization value (e.g., 0.0 for no mean normalization). -- --scale: Scale factor for input normalization (e.g., 1.0). +- --scale: Scale factor for input normalization (e.g., 1.0, 1/255.0, etc). - --yolo: YOLO model version (e.g., YOLOv3, YOLOv4, etc.). - --padvalue: Padding value used in pre-processing (e.g., 114.0). - --paddingmode: Method for handling image resizing and padding. Options: 0 (resize without extra processing), 1 (crop after resize), 2 (resize with aspect ratio preservation). - --backend: Selection of computation backend (0 for automatic, 1 for Halide, 2 for OpenVINO, etc.). - --target: Selection of target computation device (0 for CPU, 1 for OpenCL, etc.). - --device: Camera device number (0 for default camera). If `--input` is not provided camera with index 0 will used by default. +- --width: Model input width. Not to be confused with the image width. (e.g., 416, 480, 640, 1280, etc). +- --height: Model input height. Not to be confused with the image height. (e.g., 416, 480, 640, 1280, etc). Here `mean`, `scale`, `padvalue`, `paddingmode` should exactly match those that we discussed in pre-processing section in order for the model to match result in PyTorch @@ -183,7 +200,8 @@ cd ./bin/example_dnn_yolo_detector @endcode -This will execute the YOLOX detector with your camera. For YOLOv8 (for instance), follow these additional steps: +This will execute the YOLOX detector with your camera. +For YOLOv8 (for instance), follow these additional steps: @code{.sh} cd opencv_extra/testdata/dnn @@ -195,6 +213,23 @@ cd ./bin/example_dnn_yolo_detector --model=onnx/models/yolov8n.onnx --yolo=yolov8 --mean=0.0 --scale=0.003921568627 --paddingmode=2 --padvalue=144.0 --thr=0.5 --nms=0.4 --rgb=0 @endcode +For YOLOv10, follow these steps: + +@code{.sh} +cd opencv_extra/testdata/dnn +python download_models.py yolov10 +cd .. +export OPENCV_TEST_DATA_PATH=$(pwd) +cd + +./bin/example_dnn_yolo_detector --model=onnx/models/yolov10s.onnx --yolo=yolov10 --width=640 --height=480 --scale=0.003921568627 --padvalue=114 +@endcode + +This will run `YOLOv10` detector on first camera found on your system. If you want to run it on a image/video file, you can use `--input` option to specify the path to the file. + + +VIDEO DEMO: +@youtube{NHtRlndE2cg} ### Building a Custom Pipeline diff --git a/doc/tutorials/introduction/config_reference/config_reference.markdown b/doc/tutorials/introduction/config_reference/config_reference.markdown index f44e7d8dc2..1206463baa 100644 --- a/doc/tutorials/introduction/config_reference/config_reference.markdown +++ b/doc/tutorials/introduction/config_reference/config_reference.markdown @@ -217,6 +217,7 @@ Following options can be used to produce special builds with instrumentation or | `ENABLE_BUILD_HARDENING` | GCC, Clang, MSVC | Enable compiler options which reduce possibility of code exploitation. | | `ENABLE_LTO` | GCC, Clang, MSVC | Enable Link Time Optimization (LTO). | | `ENABLE_THIN_LTO` | Clang | Enable thin LTO which incorporates intermediate bitcode to binaries allowing consumers optimize their applications later. | +| `OPENCV_ALGO_HINT_DEFAULT` | Any | Set default OpenCV implementation hint value: `ALGO_HINT_ACCURATE` or `ALGO_HINT_APROX`. Dangerous! The option changes behaviour globally and may affect accuracy of many algorithms. | @see [GCC instrumentation](https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html) @see [Build hardening](https://en.wikipedia.org/wiki/Hardening_(computing)) diff --git a/modules/calib/include/opencv2/calib.hpp b/modules/calib/include/opencv2/calib.hpp index 32463b5b5e..4ddf6aa7db 100644 --- a/modules/calib/include/opencv2/calib.hpp +++ b/modules/calib/include/opencv2/calib.hpp @@ -372,11 +372,11 @@ R & t \\ where R is the rotation matrix corresponding to the rotation vector om: R = rodrigues(om); call x, y and z the 3 coordinates of Xc: - \f[x = Xc_1 \\ y = Xc_2 \\ z = Xc_3\f] + \f[\begin{array}{l} x = Xc_1 \\ y = Xc_2 \\ z = Xc_3 \end{array} \f] The pinhole projection coordinates of P is [a; b] where - \f[a = x / z \ and \ b = y / z \\ r^2 = a^2 + b^2 \\ \theta = atan(r)\f] + \f[\begin{array}{l} a = x / z \ and \ b = y / z \\ r^2 = a^2 + b^2 \\ \theta = atan(r) \end{array} \f] Fisheye distortion: @@ -384,12 +384,12 @@ R & t \\ The distorted point coordinates are [x'; y'] where - \f[x' = (\theta_d / r) a \\ y' = (\theta_d / r) b \f] + \f[\begin{array}{l} x' = (\theta_d / r) a \\ y' = (\theta_d / r) b \end{array} \f] Finally, conversion into pixel coordinates: The final pixel coordinates vector [u; v] where: - \f[u = f_x (x' + \alpha y') + c_x \\ - v = f_y y' + c_y\f] + \f[\begin{array}{l} u = f_x (x' + \alpha y') + c_x \\ + v = f_y y' + c_y \end{array} \f] Summary: Generic camera model @cite Kannala2006 with perspective projection and without distortion correction diff --git a/modules/calib/misc/python/test/test_calibration.py b/modules/calib/misc/python/test/test_calibration.py index f2cf4cc493..c3093ce2ef 100755 --- a/modules/calib/misc/python/test/test_calibration.py +++ b/modules/calib/misc/python/test/test_calibration.py @@ -67,7 +67,22 @@ class calibration_test(NewOpenCVTests): self.assertLess(cv.norm(camera_matrix - cameraMatrixTest, cv.NORM_L1), normCamEps) self.assertLess(cv.norm(dist_coefs - distCoeffsTest, cv.NORM_L1), normDistEps) - + def test_projectPoints(self): + objectPoints = np.array([[181.24588 , 87.80361 , 11.421074], + [ 87.17948 , 184.75563 , 37.223446], + [ 22.558456, 45.495266, 246.05797 ]], dtype=np.float32) + rvec = np.array([[ 0.9357548 , -0.28316498, 0.21019171], + [ 0.30293274, 0.9505806 , -0.06803132], + [-0.18054008, 0.12733458, 0.9752903 ]], dtype=np.float32) + tvec = np.array([ 69.32692 , 17.602057, 135.77672 ], dtype=np.float32) + cameraMatrix = np.array([[214.0047 , 26.98735 , 253.37799 ], + [189.8172 , 10.038101, 18.862494], + [114.07123 , 200.87277 , 194.56332 ]], dtype=np.float32) + distCoeffs = distCoeffs = np.zeros((4, 1), dtype=np.float32) + + imagePoints, jacobian = cv.projectPoints(objectPoints, rvec, tvec, cameraMatrix, distCoeffs) + self.assertTrue(imagePoints is not None) + self.assertTrue(jacobian is not None) if __name__ == '__main__': NewOpenCVTests.bootstrap() diff --git a/modules/calib/src/chessboard.cpp b/modules/calib/src/chessboard.cpp index b1f1b82fbf..5f526addf7 100644 --- a/modules/calib/src/chessboard.cpp +++ b/modules/calib/src/chessboard.cpp @@ -1625,36 +1625,30 @@ bool Chessboard::Board::normalizeMarkerOrientation() if(!current_cell->marker || !current_cell->right || !current_cell->right->marker) continue; - if(current_cell->black) + if(current_cell->right->top && current_cell->right->top->marker) { - if(current_cell->right->top && current_cell->right->top->marker) - { - rotateLeft(); - rotateLeft(); - pcell = current_cell->right; - break; - } - if(current_cell->right->bottom && current_cell->right->bottom->marker) - { - rotateLeft(); - pcell = current_cell->right; - break; - } + rotateLeft(); + rotateLeft(); + pcell = current_cell->right; + break; } - else + if(current_cell->right->bottom && current_cell->right->bottom->marker) { - if(current_cell->top && current_cell->top->marker) - { - rotateRight(); - pcell = current_cell; - break; - } - if(current_cell->bottom && current_cell->bottom->marker) - { - // correct orientation - pcell = current_cell; - break; - } + rotateLeft(); + pcell = current_cell->right; + break; + } + if(current_cell->top && current_cell->top->marker) + { + rotateRight(); + pcell = current_cell; + break; + } + if(current_cell->bottom && current_cell->bottom->marker) + { + // correct orientation + pcell = current_cell; + break; } } } @@ -1663,7 +1657,7 @@ bool Chessboard::Board::normalizeMarkerOrientation() //check for ambiguity if(rowCount()-pcell->bottom->getRow() > 2) { - // std::cout << "FIX board " << pcell->bottom->getRow() << " " << rowCount(); + CV_LOG_DEBUG(NULL, "FIX board " << pcell->bottom->getRow() << " " << rowCount()); flipVertical(); rotateRight(); } @@ -2265,7 +2259,7 @@ int Chessboard::Board::detectMarkers(cv::InputArray image) cell->marker = noise-signal > (noise-reference)*0.5; if(cell->marker) count++; - // std::cout << x << "/" << y << " signal " << signal << " noise " << noise << " reference " << reference << " has marker " << int(cell->marker) << std::endl; + CV_LOG_DEBUG(NULL, "Cell: " << x << "/" << y << " signal " << signal << " noise " << noise << " reference " << reference << " has marker " << int(cell->marker)); } } return count; @@ -3379,7 +3373,7 @@ cv::Scalar Chessboard::Board::calcEdgeSharpness(cv::InputArray _img,float rise_d } if(count == 0) { - std::cout <<"calcEdgeSharpness: checkerboard too small for calculation." << std::endl; + CV_LOG_DEBUG(NULL, "calcEdgeSharpness: checkerboard too small for calculation."); return cv::Scalar::all(9999); } sharpness = sharpness/float(count); diff --git a/modules/calib/test/test_chesscorners.cpp b/modules/calib/test/test_chesscorners.cpp index a63d5b3e83..3d13602780 100644 --- a/modules/calib/test/test_chesscorners.cpp +++ b/modules/calib/test/test_chesscorners.cpp @@ -679,6 +679,26 @@ TEST(Calib3d_AsymmetricCirclesPatternDetector, accuracy) { CV_ChessboardDetector TEST(Calib3d_AsymmetricCirclesPatternDetectorWithClustering, accuracy) { CV_ChessboardDetectorTest test( ASYMMETRIC_CIRCLES_GRID, CALIB_CB_CLUSTERING ); test.safe_run(); } #endif +TEST(Calib3d_ChessboardWithMarkers, regression_25806_white) +{ + const cv::String dataDir = string(TS::ptr()->get_data_path()) + "cv/cameracalibration/"; + const cv::Mat image = cv::imread(dataDir + "checkerboard_marker_white.png"); + + std::vector corners; + const bool success = cv::findChessboardCornersSB(image, Size(9, 14), corners, CALIB_CB_MARKER); + ASSERT_TRUE(success); +} + +TEST(Calib3d_ChessboardWithMarkers, regression_25806_black) +{ + const cv::String dataDir = string(TS::ptr()->get_data_path()) + "cv/cameracalibration/"; + const cv::Mat image = cv::imread(dataDir + "checkerboard_marker_black.png"); + + std::vector corners; + const bool success = cv::findChessboardCornersSB(image, Size(9, 14), corners, CALIB_CB_MARKER); + ASSERT_TRUE(success); +} + TEST(Calib3d_CirclesPatternDetectorWithClustering, accuracy) { cv::String dataDir = string(TS::ptr()->get_data_path()) + "cv/cameracalibration/circles/"; diff --git a/modules/core/CMakeLists.txt b/modules/core/CMakeLists.txt index 63f33c94c1..b4003fab7f 100644 --- a/modules/core/CMakeLists.txt +++ b/modules/core/CMakeLists.txt @@ -188,6 +188,10 @@ if(OPENCV_SEMIHOSTING) ocv_target_compile_definitions(${the_module} PRIVATE "-DOPENCV_SEMIHOSTING") endif(OPENCV_SEMIHOSTING) +if(DEFINED OPENCV_ALGO_HINT_DEFAULT) + ocv_target_compile_definitions(${the_module} PRIVATE "-DOPENCV_ALGO_HINT_DEFAULT=${OPENCV_ALGO_HINT_DEFAULT}") +endif(DEFINED OPENCV_ALGO_HINT_DEFAULT) + if(HAVE_HPX) ocv_target_link_libraries(${the_module} LINK_PRIVATE "${HPX_LIBRARIES}") endif() diff --git a/modules/core/include/opencv2/core/base.hpp b/modules/core/include/opencv2/core/base.hpp index d99b2852ca..b4f8db5122 100644 --- a/modules/core/include/opencv2/core/base.hpp +++ b/modules/core/include/opencv2/core/base.hpp @@ -297,6 +297,21 @@ It is possible to alternate error processing by using redirectError(). */ CV_EXPORTS CV_NORETURN void error(int _code, const String& _err, const char* _func, const char* _file, int _line); +/*! @brief Signals an error and terminate application. + +By default the function prints information about the error to stderr, then it terminates application +with std::terminate. The function is designed for invariants check in functions and methods with +noexcept attribute. +@param _code - error code (Error::Code) +@param _err - error description +@param _func - function name. Available only when the compiler supports getting it +@param _file - source file name where the error has occurred +@param _line - line number in the source file where the error has occurred +@see CV_AssertTerminate + */ +CV_EXPORTS CV_NORETURN void terminate(int _code, const String& _err, const char* _func, const char* _file, int _line) CV_NOEXCEPT; + + #ifdef CV_STATIC_ANALYSIS // In practice, some macro are not processed correctly (noreturn is not detected). @@ -338,8 +353,11 @@ for example: The macros CV_Assert (and CV_DbgAssert(expr)) evaluate the specified expression. If it is 0, the macros raise an error (see cv::error). The macro CV_Assert checks the condition in both Debug and Release configurations while CV_DbgAssert is only retained in the Debug configuration. +CV_AssertTerminate is analog of CV_Assert for invariants check in functions with noexcept attribute. +It does not throw exception, but terminates the application. */ #define CV_Assert( expr ) do { if(!!(expr)) ; else cv::error( cv::Error::StsAssert, #expr, CV_Func, __FILE__, __LINE__ ); } while(0) +#define CV_AssertTerminate( expr ) do { if(!!(expr)) ; else cv::terminate( #expr, CV_Func, __FILE__, __LINE__ ); } while(0) #endif // CV_STATIC_ANALYSIS diff --git a/modules/core/include/opencv2/core/cvdef.h b/modules/core/include/opencv2/core/cvdef.h index 8f90c3af5c..c8a0e17cdb 100644 --- a/modules/core/include/opencv2/core/cvdef.h +++ b/modules/core/include/opencv2/core/cvdef.h @@ -481,6 +481,7 @@ Cv64suf; #define CV_OUT #define CV_PROP #define CV_PROP_RW +#define CV_ND // Indicates that input data should be parsed into Mat without channels #define CV_WRAP #define CV_WRAP_AS(synonym) #define CV_WRAP_MAPPABLE(mappable) diff --git a/modules/core/include/opencv2/core/hal/intrin.hpp b/modules/core/include/opencv2/core/hal/intrin.hpp index c8340b7168..01a2b5d297 100644 --- a/modules/core/include/opencv2/core/hal/intrin.hpp +++ b/modules/core/include/opencv2/core/hal/intrin.hpp @@ -64,7 +64,7 @@ namespace { inline unsigned int trailingZeros32(unsigned int value) { #if defined(_MSC_VER) -#if (_MSC_VER < 1700) || defined(_M_ARM) || defined(_M_ARM64) +#if (_MSC_VER < 1700) || defined(_M_ARM) || defined(_M_ARM64) || defined(_M_ARM64EC) unsigned long index = 0; _BitScanForward(&index, value); return (unsigned int)index; @@ -1305,6 +1305,7 @@ namespace CV__SIMD_NAMESPACE { typedef struct v_float64 { int dummy; } v_float64; #endif +#include "intrin_math.hpp" #include "simd_utils.impl.hpp" #ifndef CV_DOXYGEN diff --git a/modules/core/include/opencv2/core/hal/intrin_cpp.hpp b/modules/core/include/opencv2/core/hal/intrin_cpp.hpp index 1c54f650cc..fbc6ad82e5 100644 --- a/modules/core/include/opencv2/core/hal/intrin_cpp.hpp +++ b/modules/core/include/opencv2/core/hal/intrin_cpp.hpp @@ -263,7 +263,8 @@ Most of these operations return only one value. ### Other math -- Some frequent operations: @ref v_sqrt, @ref v_invsqrt, @ref v_magnitude, @ref v_sqr_magnitude +- Some frequent operations: @ref v_sqrt, @ref v_invsqrt, @ref v_magnitude, @ref v_sqr_magnitude, @ref v_exp, + @ref v_erf - Absolute values: @ref v_abs, @ref v_absdiff, @ref v_absdiffs ### Conversions @@ -363,6 +364,8 @@ Floating point: |reverse | x | x | |extract_n | x | x | |broadcast_element | x | | +|exp | x | x | +|log | x | x | @{ */ @@ -721,11 +724,56 @@ template inline v_reg<_Tp2, n> func(const v_reg<_Tp, n>& a) Only for floating point types.*/ OPENCV_HAL_IMPL_MATH_FUNC(v_sqrt, std::sqrt, _Tp) +/** + * @brief Exponential \f$ e^x \f$ of elements + * + * Only for floating point types. Core implementation steps: + * 1. Decompose Input: Convert the input to \f$ 2^{x \cdot \log_2e} \f$ and split its exponential into integer and fractional parts: + * \f$ x \cdot \log_2e = n + f \f$, where \f$ n \f$ is the integer part and \f$ f \f$ is the fractional part. + * 2. Compute \f$ 2^n \f$: Calculated by shifting the bits. + * 3. Adjust Fractional Part: Compute \f$ f \cdot \ln2 \f$ to convert the fractional part to base \f$ e \f$. + * \f$ C1 \f$ and \f$ C2 \f$ are used to adjust the fractional part. + * 4. Polynomial Approximation for \f$ e^{f \cdot \ln2} \f$: The closer the fractional part is to 0, the more accurate the result. + * - For float16 and float32, use a Taylor Series with 6 terms. + * - For float64, use Pade Polynomials Approximation with 4 terms. + * 5. Combine Results: Multiply the two parts together to get the final result: + * \f$ e^x = 2^n \cdot e^{f \cdot \ln2} \f$. + * + * @note The precision of the calculation depends on the implementation and the data type of the input vector. + */ +OPENCV_HAL_IMPL_MATH_FUNC(v_exp, std::exp, _Tp) +#define OPENCV_HAL_MATH_HAVE_EXP 1 + +/** + * @brief Natural logarithm \f$ \log(x) \f$ of elements + * + * Only for floating point types. Core implementation steps: + * 1. Decompose Input: Use binary representation to decompose the input into mantissa part \f$ m \f$ and exponent part \f$ e \f$. Such that \f$ \log(x) = \log(m \cdot 2^e) = \log(m) + e \cdot \ln(2) \f$. + * 2. Adjust Mantissa and Exponent Parts: If the mantissa is less than \f$ \sqrt{0.5} \f$, adjust the exponent and mantissa to ensure the mantissa is in the range \f$ (\sqrt{0.5}, \sqrt{2}) \f$ for better approximation. + * 3. Polynomial Approximation for \f$ \log(m) \f$: The closer the \f$ m \f$ is to 1, the more accurate the result. + * - For float16 and float32, use a Taylor Series with 9 terms. + * - For float64, use Pade Polynomials Approximation with 6 terms. + * 4. Combine Results: Add the two parts together to get the final result. + * + * @note The precision of the calculation depends on the implementation and the data type of the input. + * + * @note Similar to the behavior of std::log(), \f$ \ln(0) = -\infty \f$. + */ +OPENCV_HAL_IMPL_MATH_FUNC(v_log, std::log, _Tp) +#define OPENCV_HAL_MATH_HAVE_LOG 1 + +/** + * @brief Error function. + * + * @note Support FP32 precision for now. + */ +OPENCV_HAL_IMPL_MATH_FUNC(v_erf, std::erf, _Tp) + //! @cond IGNORED OPENCV_HAL_IMPL_MATH_FUNC(v_sin, std::sin, _Tp) +#define OPENCV_HAL_MATH_HAVE_SIN 1 OPENCV_HAL_IMPL_MATH_FUNC(v_cos, std::cos, _Tp) -OPENCV_HAL_IMPL_MATH_FUNC(v_exp, std::exp, _Tp) -OPENCV_HAL_IMPL_MATH_FUNC(v_log, std::log, _Tp) +#define OPENCV_HAL_MATH_HAVE_COS 1 //! @endcond /** @brief Absolute value of elements diff --git a/modules/core/include/opencv2/core/hal/intrin_math.hpp b/modules/core/include/opencv2/core/hal/intrin_math.hpp new file mode 100644 index 0000000000..eaf3b3b78b --- /dev/null +++ b/modules/core/include/opencv2/core/hal/intrin_math.hpp @@ -0,0 +1,467 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html + +// This header is not standalone. Don't include directly, use "intrin.hpp" instead. +#ifdef OPENCV_HAL_INTRIN_HPP // defined in intrin.hpp + +namespace CV__SIMD_NAMESPACE { + +/* Universal Intrinsics implementation of sin, cos, exp and log + + Inspired by Intel Approximate Math library, and based on the + corresponding algorithms of the cephes math library +*/ + +/* Copyright (C) 2010,2011 RJVB - extensions */ +/* Copyright (C) 2011 Julien Pommier + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + (this is the zlib license) +*/ + +#ifndef OPENCV_HAL_MATH_HAVE_EXP + +//! @name Exponential +//! @{ +#if defined(CV_SIMD_FP16) && CV_SIMD_FP16 + // Implementation is the same as float32 vector. + inline v_float16 v_exp(const v_float16 &x) { + const v_float16 _vexp_lo_f16 = vx_setall_f16(hfloat(-10.7421875f)); + const v_float16 _vexp_hi_f16 = vx_setall_f16(hfloat(11.f)); + const v_float16 _vexp_half_fp16 = vx_setall_f16(hfloat(0.5f)); + const v_float16 _vexp_one_fp16 = vx_setall_f16(hfloat(1.f)); + const v_float16 _vexp_LOG2EF_f16 = vx_setall_f16(hfloat(1.44269504088896341f)); + const v_float16 _vexp_C1_f16 = vx_setall_f16(hfloat(-6.93359375E-1f)); + const v_float16 _vexp_C2_f16 = vx_setall_f16(hfloat(2.12194440E-4f)); + const v_float16 _vexp_p0_f16 = vx_setall_f16(hfloat(1.9875691500E-4f)); + const v_float16 _vexp_p1_f16 = vx_setall_f16(hfloat(1.3981999507E-3f)); + const v_float16 _vexp_p2_f16 = vx_setall_f16(hfloat(8.3334519073E-3f)); + const v_float16 _vexp_p3_f16 = vx_setall_f16(hfloat(4.1665795894E-2f)); + const v_float16 _vexp_p4_f16 = vx_setall_f16(hfloat(1.6666665459E-1f)); + const v_float16 _vexp_p5_f16 = vx_setall_f16(hfloat(5.0000001201E-1f)); + const v_int16 _vexp_bias_s16 = vx_setall_s16(0xf); + + v_float16 _vexp_, _vexp_x, _vexp_y, _vexp_xx; + v_int16 _vexp_mm; + + // compute exponential of x + _vexp_x = v_max(x, _vexp_lo_f16); + _vexp_x = v_min(_vexp_x, _vexp_hi_f16); + + _vexp_ = v_fma(_vexp_x, _vexp_LOG2EF_f16, _vexp_half_fp16); + _vexp_mm = v_floor(_vexp_); + _vexp_ = v_cvt_f16(_vexp_mm); + _vexp_mm = v_add(_vexp_mm, _vexp_bias_s16); + _vexp_mm = v_shl(_vexp_mm, 10); + + _vexp_x = v_fma(_vexp_, _vexp_C1_f16, _vexp_x); + _vexp_x = v_fma(_vexp_, _vexp_C2_f16, _vexp_x); + _vexp_xx = v_mul(_vexp_x, _vexp_x); + + _vexp_y = v_fma(_vexp_x, _vexp_p0_f16, _vexp_p1_f16); + _vexp_y = v_fma(_vexp_y, _vexp_x, _vexp_p2_f16); + _vexp_y = v_fma(_vexp_y, _vexp_x, _vexp_p3_f16); + _vexp_y = v_fma(_vexp_y, _vexp_x, _vexp_p4_f16); + _vexp_y = v_fma(_vexp_y, _vexp_x, _vexp_p5_f16); + + _vexp_y = v_fma(_vexp_y, _vexp_xx, _vexp_x); + _vexp_y = v_add(_vexp_y, _vexp_one_fp16); + _vexp_y = v_mul(_vexp_y, v_reinterpret_as_f16(_vexp_mm)); + + // exp(NAN) -> NAN + v_float16 mask_not_nan = v_not_nan(x); + return v_select(mask_not_nan, _vexp_y, v_reinterpret_as_f16(vx_setall_s16(0x7e00))); + } +#endif + + inline v_float32 v_exp(const v_float32 &x) { + const v_float32 _vexp_lo_f32 = vx_setall_f32(-88.3762626647949f); + const v_float32 _vexp_hi_f32 = vx_setall_f32(89.f); + const v_float32 _vexp_half_fp32 = vx_setall_f32(0.5f); + const v_float32 _vexp_one_fp32 = vx_setall_f32(1.f); + const v_float32 _vexp_LOG2EF_f32 = vx_setall_f32(1.44269504088896341f); + const v_float32 _vexp_C1_f32 = vx_setall_f32(-6.93359375E-1f); + const v_float32 _vexp_C2_f32 = vx_setall_f32(2.12194440E-4f); + const v_float32 _vexp_p0_f32 = vx_setall_f32(1.9875691500E-4f); + const v_float32 _vexp_p1_f32 = vx_setall_f32(1.3981999507E-3f); + const v_float32 _vexp_p2_f32 = vx_setall_f32(8.3334519073E-3f); + const v_float32 _vexp_p3_f32 = vx_setall_f32(4.1665795894E-2f); + const v_float32 _vexp_p4_f32 = vx_setall_f32(1.6666665459E-1f); + const v_float32 _vexp_p5_f32 = vx_setall_f32(5.0000001201E-1f); + const v_int32 _vexp_bias_s32 = vx_setall_s32(0x7f); + + v_float32 _vexp_, _vexp_x, _vexp_y, _vexp_xx; + v_int32 _vexp_mm; + + // compute exponential of x + _vexp_x = v_max(x, _vexp_lo_f32); + _vexp_x = v_min(_vexp_x, _vexp_hi_f32); + + _vexp_ = v_fma(_vexp_x, _vexp_LOG2EF_f32, _vexp_half_fp32); + _vexp_mm = v_floor(_vexp_); + _vexp_ = v_cvt_f32(_vexp_mm); + _vexp_mm = v_add(_vexp_mm, _vexp_bias_s32); + _vexp_mm = v_shl(_vexp_mm, 23); + + _vexp_x = v_fma(_vexp_, _vexp_C1_f32, _vexp_x); + _vexp_x = v_fma(_vexp_, _vexp_C2_f32, _vexp_x); + _vexp_xx = v_mul(_vexp_x, _vexp_x); + + _vexp_y = v_fma(_vexp_x, _vexp_p0_f32, _vexp_p1_f32); + _vexp_y = v_fma(_vexp_y, _vexp_x, _vexp_p2_f32); + _vexp_y = v_fma(_vexp_y, _vexp_x, _vexp_p3_f32); + _vexp_y = v_fma(_vexp_y, _vexp_x, _vexp_p4_f32); + _vexp_y = v_fma(_vexp_y, _vexp_x, _vexp_p5_f32); + + _vexp_y = v_fma(_vexp_y, _vexp_xx, _vexp_x); + _vexp_y = v_add(_vexp_y, _vexp_one_fp32); + _vexp_y = v_mul(_vexp_y, v_reinterpret_as_f32(_vexp_mm)); + + // exp(NAN) -> NAN + v_float32 mask_not_nan = v_not_nan(x); + return v_select(mask_not_nan, _vexp_y, v_reinterpret_as_f32(vx_setall_s32(0x7fc00000))); + } + +#if CV_SIMD_64F || CV_SIMD_SCALABLE_64F + inline v_float64 v_exp(const v_float64 &x) { + const v_float64 _vexp_lo_f64 = vx_setall_f64(-709.43613930310391424428); + const v_float64 _vexp_hi_f64 = vx_setall_f64(710.); + const v_float64 _vexp_half_f64 = vx_setall_f64(0.5); + const v_float64 _vexp_one_f64 = vx_setall_f64(1.0); + const v_float64 _vexp_two_f64 = vx_setall_f64(2.0); + const v_float64 _vexp_LOG2EF_f64 = vx_setall_f64(1.44269504088896340736); + const v_float64 _vexp_C1_f64 = vx_setall_f64(-6.93145751953125E-1); + const v_float64 _vexp_C2_f64 = vx_setall_f64(-1.42860682030941723212E-6); + const v_float64 _vexp_p0_f64 = vx_setall_f64(1.26177193074810590878E-4); + const v_float64 _vexp_p1_f64 = vx_setall_f64(3.02994407707441961300E-2); + const v_float64 _vexp_p2_f64 = vx_setall_f64(9.99999999999999999910E-1); + const v_float64 _vexp_q0_f64 = vx_setall_f64(3.00198505138664455042E-6); + const v_float64 _vexp_q1_f64 = vx_setall_f64(2.52448340349684104192E-3); + const v_float64 _vexp_q2_f64 = vx_setall_f64(2.27265548208155028766E-1); + const v_float64 _vexp_q3_f64 = vx_setall_f64(2.00000000000000000009E0); + const v_int64 _vexp_bias_s64 = vx_setall_s64(0x3ff); + + v_float64 _vexp_, _vexp_x, _vexp_y, _vexp_z, _vexp_xx; + v_int64 _vexp_mm; + + // compute exponential of x + _vexp_x = v_max(x, _vexp_lo_f64); + _vexp_x = v_min(_vexp_x, _vexp_hi_f64); + + _vexp_ = v_fma(_vexp_x, _vexp_LOG2EF_f64, _vexp_half_f64); + _vexp_mm = v_expand_low(v_floor(_vexp_)); + _vexp_ = v_cvt_f64(_vexp_mm); + _vexp_mm = v_add(_vexp_mm, _vexp_bias_s64); + _vexp_mm = v_shl(_vexp_mm, 52); + + _vexp_x = v_fma(_vexp_, _vexp_C1_f64, _vexp_x); + _vexp_x = v_fma(_vexp_, _vexp_C2_f64, _vexp_x); + _vexp_xx = v_mul(_vexp_x, _vexp_x); + + _vexp_y = v_fma(_vexp_xx, _vexp_p0_f64, _vexp_p1_f64); + _vexp_y = v_fma(_vexp_y, _vexp_xx, _vexp_p2_f64); + _vexp_y = v_mul(_vexp_y, _vexp_x); + + _vexp_z = v_fma(_vexp_xx, _vexp_q0_f64, _vexp_q1_f64); + _vexp_z = v_fma(_vexp_xx, _vexp_z, _vexp_q2_f64); + _vexp_z = v_fma(_vexp_xx, _vexp_z, _vexp_q3_f64); + + _vexp_z = v_div(_vexp_y, v_sub(_vexp_z, _vexp_y)); + _vexp_z = v_fma(_vexp_two_f64, _vexp_z, _vexp_one_f64); + _vexp_z = v_mul(_vexp_z, v_reinterpret_as_f64(_vexp_mm)); + + // exp(NAN) -> NAN + v_float64 mask_not_nan = v_not_nan(x); + return v_select(mask_not_nan, _vexp_z, v_reinterpret_as_f64(vx_setall_s64(0x7FF8000000000000))); + } +#endif + +#define OPENCV_HAL_MATH_HAVE_EXP 1 +//! @} +#endif + +#ifndef OPENCV_HAL_MATH_HAVE_LOG + +//! @name Natural Logarithm +//! @{ +#if defined(CV_SIMD_FP16) && CV_SIMD_FP16 + inline v_float16 v_log(const v_float16 &x) { + const v_float16 _vlog_one_fp16 = vx_setall_f16(hfloat(1.0f)); + const v_float16 _vlog_SQRTHF_fp16 = vx_setall_f16(hfloat(0.707106781186547524f)); + const v_float16 _vlog_q1_fp16 = vx_setall_f16(hfloat(-2.12194440E-4f)); + const v_float16 _vlog_q2_fp16 = vx_setall_f16(hfloat(0.693359375f)); + const v_float16 _vlog_p0_fp16 = vx_setall_f16(hfloat(7.0376836292E-2f)); + const v_float16 _vlog_p1_fp16 = vx_setall_f16(hfloat(-1.1514610310E-1f)); + const v_float16 _vlog_p2_fp16 = vx_setall_f16(hfloat(1.1676998740E-1f)); + const v_float16 _vlog_p3_fp16 = vx_setall_f16(hfloat(-1.2420140846E-1f)); + const v_float16 _vlog_p4_fp16 = vx_setall_f16(hfloat(1.4249322787E-1f)); + const v_float16 _vlog_p5_fp16 = vx_setall_f16(hfloat(-1.6668057665E-1f)); + const v_float16 _vlog_p6_fp16 = vx_setall_f16(hfloat(2.0000714765E-1f)); + const v_float16 _vlog_p7_fp16 = vx_setall_f16(hfloat(-2.4999993993E-1f)); + const v_float16 _vlog_p8_fp16 = vx_setall_f16(hfloat(3.3333331174E-1f)); + const v_int16 _vlog_inv_mant_mask_s16 = vx_setall_s16(~0x7c00); + + v_float16 _vlog_x, _vlog_e, _vlog_y, _vlog_z, _vlog_tmp; + v_int16 _vlog_ux, _vlog_emm0; + + _vlog_ux = v_reinterpret_as_s16(x); + _vlog_emm0 = v_shr(_vlog_ux, 10); + + _vlog_ux = v_and(_vlog_ux, _vlog_inv_mant_mask_s16); + _vlog_ux = v_or(_vlog_ux, v_reinterpret_as_s16(vx_setall_f16(hfloat(0.5f)))); + _vlog_x = v_reinterpret_as_f16(_vlog_ux); + + _vlog_emm0 = v_sub(_vlog_emm0, vx_setall_s16(0xf)); + _vlog_e = v_cvt_f16(_vlog_emm0); + + _vlog_e = v_add(_vlog_e, _vlog_one_fp16); + + v_float16 _vlog_mask = v_lt(_vlog_x, _vlog_SQRTHF_fp16); + _vlog_tmp = v_and(_vlog_x, _vlog_mask); + _vlog_x = v_sub(_vlog_x, _vlog_one_fp16); + _vlog_e = v_sub(_vlog_e, v_and(_vlog_one_fp16, _vlog_mask)); + _vlog_x = v_add(_vlog_x, _vlog_tmp); + + _vlog_z = v_mul(_vlog_x, _vlog_x); + + _vlog_y = v_fma(_vlog_p0_fp16, _vlog_x, _vlog_p1_fp16); + _vlog_y = v_fma(_vlog_y, _vlog_x, _vlog_p2_fp16); + _vlog_y = v_fma(_vlog_y, _vlog_x, _vlog_p3_fp16); + _vlog_y = v_fma(_vlog_y, _vlog_x, _vlog_p4_fp16); + _vlog_y = v_fma(_vlog_y, _vlog_x, _vlog_p5_fp16); + _vlog_y = v_fma(_vlog_y, _vlog_x, _vlog_p6_fp16); + _vlog_y = v_fma(_vlog_y, _vlog_x, _vlog_p7_fp16); + _vlog_y = v_fma(_vlog_y, _vlog_x, _vlog_p8_fp16); + _vlog_y = v_mul(_vlog_y, _vlog_x); + _vlog_y = v_mul(_vlog_y, _vlog_z); + + _vlog_y = v_fma(_vlog_e, _vlog_q1_fp16, _vlog_y); + + _vlog_y = v_sub(_vlog_y, v_mul(_vlog_z, vx_setall_f16(hfloat(0.5f)))); + + _vlog_x = v_add(_vlog_x, _vlog_y); + _vlog_x = v_fma(_vlog_e, _vlog_q2_fp16, _vlog_x); + // log(0) -> -INF + v_float16 mask_zero = v_eq(x, vx_setzero_f16()); + _vlog_x = v_select(mask_zero, v_reinterpret_as_f16(vx_setall_s16(0xfc00)), _vlog_x); + // log(NEG), log(NAN) -> NAN + v_float16 mask_not_nan = v_ge(x, vx_setzero_f16()); + _vlog_x = v_select(mask_not_nan, _vlog_x, v_reinterpret_as_f16(vx_setall_s16(0x7e00))); + // log(INF) -> INF + v_float16 mask_inf = v_eq(x, v_reinterpret_as_f16(vx_setall_s16(0x7c00))); + _vlog_x = v_select(mask_inf, x, _vlog_x); + return _vlog_x; + } +#endif + + inline v_float32 v_log(const v_float32 &x) { + const v_float32 _vlog_one_fp32 = vx_setall_f32(1.0f); + const v_float32 _vlog_SQRTHF_fp32 = vx_setall_f32(0.707106781186547524f); + const v_float32 _vlog_q1_fp32 = vx_setall_f32(-2.12194440E-4f); + const v_float32 _vlog_q2_fp32 = vx_setall_f32(0.693359375f); + const v_float32 _vlog_p0_fp32 = vx_setall_f32(7.0376836292E-2f); + const v_float32 _vlog_p1_fp32 = vx_setall_f32(-1.1514610310E-1f); + const v_float32 _vlog_p2_fp32 = vx_setall_f32(1.1676998740E-1f); + const v_float32 _vlog_p3_fp32 = vx_setall_f32(-1.2420140846E-1f); + const v_float32 _vlog_p4_fp32 = vx_setall_f32(1.4249322787E-1f); + const v_float32 _vlog_p5_fp32 = vx_setall_f32(-1.6668057665E-1f); + const v_float32 _vlog_p6_fp32 = vx_setall_f32(2.0000714765E-1f); + const v_float32 _vlog_p7_fp32 = vx_setall_f32(-2.4999993993E-1f); + const v_float32 _vlog_p8_fp32 = vx_setall_f32(3.3333331174E-1f); + const v_int32 _vlog_inv_mant_mask_s32 = vx_setall_s32(~0x7f800000); + + v_float32 _vlog_x, _vlog_e, _vlog_y, _vlog_z, _vlog_tmp; + v_int32 _vlog_ux, _vlog_emm0; + + _vlog_ux = v_reinterpret_as_s32(x); + _vlog_emm0 = v_shr(_vlog_ux, 23); + + _vlog_ux = v_and(_vlog_ux, _vlog_inv_mant_mask_s32); + _vlog_ux = v_or(_vlog_ux, v_reinterpret_as_s32(vx_setall_f32(0.5f))); + _vlog_x = v_reinterpret_as_f32(_vlog_ux); + + _vlog_emm0 = v_sub(_vlog_emm0, vx_setall_s32(0x7f)); + _vlog_e = v_cvt_f32(_vlog_emm0); + + _vlog_e = v_add(_vlog_e, _vlog_one_fp32); + + v_float32 _vlog_mask = v_lt(_vlog_x, _vlog_SQRTHF_fp32); + _vlog_tmp = v_and(_vlog_x, _vlog_mask); + _vlog_x = v_sub(_vlog_x, _vlog_one_fp32); + _vlog_e = v_sub(_vlog_e, v_and(_vlog_one_fp32, _vlog_mask)); + _vlog_x = v_add(_vlog_x, _vlog_tmp); + + _vlog_z = v_mul(_vlog_x, _vlog_x); + + _vlog_y = v_fma(_vlog_p0_fp32, _vlog_x, _vlog_p1_fp32); + _vlog_y = v_fma(_vlog_y, _vlog_x, _vlog_p2_fp32); + _vlog_y = v_fma(_vlog_y, _vlog_x, _vlog_p3_fp32); + _vlog_y = v_fma(_vlog_y, _vlog_x, _vlog_p4_fp32); + _vlog_y = v_fma(_vlog_y, _vlog_x, _vlog_p5_fp32); + _vlog_y = v_fma(_vlog_y, _vlog_x, _vlog_p6_fp32); + _vlog_y = v_fma(_vlog_y, _vlog_x, _vlog_p7_fp32); + _vlog_y = v_fma(_vlog_y, _vlog_x, _vlog_p8_fp32); + _vlog_y = v_mul(_vlog_y, _vlog_x); + _vlog_y = v_mul(_vlog_y, _vlog_z); + + _vlog_y = v_fma(_vlog_e, _vlog_q1_fp32, _vlog_y); + + _vlog_y = v_sub(_vlog_y, v_mul(_vlog_z, vx_setall_f32(0.5))); + + _vlog_x = v_add(_vlog_x, _vlog_y); + _vlog_x = v_fma(_vlog_e, _vlog_q2_fp32, _vlog_x); + // log(0) -> -INF + v_float32 mask_zero = v_eq(x, vx_setzero_f32()); + _vlog_x = v_select(mask_zero, v_reinterpret_as_f32(vx_setall_s32(0xff800000)), _vlog_x); + // log(NEG), log(NAN) -> NAN + v_float32 mask_not_nan = v_ge(x, vx_setzero_f32()); + _vlog_x = v_select(mask_not_nan, _vlog_x, v_reinterpret_as_f32(vx_setall_s32(0x7fc00000))); + // log(INF) -> INF + v_float32 mask_inf = v_eq(x, v_reinterpret_as_f32(vx_setall_s32(0x7f800000))); + _vlog_x = v_select(mask_inf, x, _vlog_x); + return _vlog_x; + } + +#if CV_SIMD_64F || CV_SIMD_SCALABLE_64F + inline v_float64 v_log(const v_float64 &x) { + const v_float64 _vlog_one_fp64 = vx_setall_f64(1.0); + const v_float64 _vlog_SQRTHF_fp64 = vx_setall_f64(0.7071067811865475244); + const v_float64 _vlog_p0_fp64 = vx_setall_f64(1.01875663804580931796E-4); + const v_float64 _vlog_p1_fp64 = vx_setall_f64(4.97494994976747001425E-1); + const v_float64 _vlog_p2_fp64 = vx_setall_f64(4.70579119878881725854); + const v_float64 _vlog_p3_fp64 = vx_setall_f64(1.44989225341610930846E1); + const v_float64 _vlog_p4_fp64 = vx_setall_f64(1.79368678507819816313E1); + const v_float64 _vlog_p5_fp64 = vx_setall_f64(7.70838733755885391666); + const v_float64 _vlog_q0_fp64 = vx_setall_f64(1.12873587189167450590E1); + const v_float64 _vlog_q1_fp64 = vx_setall_f64(4.52279145837532221105E1); + const v_float64 _vlog_q2_fp64 = vx_setall_f64(8.29875266912776603211E1); + const v_float64 _vlog_q3_fp64 = vx_setall_f64(7.11544750618563894466E1); + const v_float64 _vlog_q4_fp64 = vx_setall_f64(2.31251620126765340583E1); + + const v_float64 _vlog_C0_fp64 = vx_setall_f64(2.121944400546905827679e-4); + const v_float64 _vlog_C1_fp64 = vx_setall_f64(0.693359375); + const v_int64 _vlog_inv_mant_mask_s64 = vx_setall_s64(~0x7ff0000000000000); + + v_float64 _vlog_x, _vlog_e, _vlog_y, _vlog_z, _vlog_tmp, _vlog_xx; + v_int64 _vlog_ux, _vlog_emm0; + + _vlog_ux = v_reinterpret_as_s64(x); + _vlog_emm0 = v_shr(_vlog_ux, 52); + + _vlog_ux = v_and(_vlog_ux, _vlog_inv_mant_mask_s64); + _vlog_ux = v_or(_vlog_ux, v_reinterpret_as_s64(vx_setall_f64(0.5))); + _vlog_x = v_reinterpret_as_f64(_vlog_ux); + + _vlog_emm0 = v_sub(_vlog_emm0, vx_setall_s64(0x3ff)); + _vlog_e = v_cvt_f64(_vlog_emm0); + + _vlog_e = v_add(_vlog_e, _vlog_one_fp64); + + v_float64 _vlog_mask = v_lt(_vlog_x, _vlog_SQRTHF_fp64); + _vlog_tmp = v_and(_vlog_x, _vlog_mask); + _vlog_x = v_sub(_vlog_x, _vlog_one_fp64); + _vlog_e = v_sub(_vlog_e, v_and(_vlog_one_fp64, _vlog_mask)); + _vlog_x = v_add(_vlog_x, _vlog_tmp); + + _vlog_xx = v_mul(_vlog_x, _vlog_x); + + _vlog_y = v_fma(_vlog_p0_fp64, _vlog_x, _vlog_p1_fp64); + _vlog_y = v_fma(_vlog_y, _vlog_x, _vlog_p2_fp64); + _vlog_y = v_fma(_vlog_y, _vlog_x, _vlog_p3_fp64); + _vlog_y = v_fma(_vlog_y, _vlog_x, _vlog_p4_fp64); + _vlog_y = v_fma(_vlog_y, _vlog_x, _vlog_p5_fp64); + _vlog_y = v_mul(_vlog_y, _vlog_x); + _vlog_y = v_mul(_vlog_y, _vlog_xx); + + _vlog_z = v_add(_vlog_x, _vlog_q0_fp64); + _vlog_z = v_fma(_vlog_z, _vlog_x, _vlog_q1_fp64); + _vlog_z = v_fma(_vlog_z, _vlog_x, _vlog_q2_fp64); + _vlog_z = v_fma(_vlog_z, _vlog_x, _vlog_q3_fp64); + _vlog_z = v_fma(_vlog_z, _vlog_x, _vlog_q4_fp64); + + _vlog_z = v_div(_vlog_y, _vlog_z); + _vlog_z = v_sub(_vlog_z, v_mul(_vlog_e, _vlog_C0_fp64)); + _vlog_z = v_sub(_vlog_z, v_mul(_vlog_xx, vx_setall_f64(0.5))); + + _vlog_z = v_add(_vlog_z, _vlog_x); + _vlog_z = v_fma(_vlog_e, _vlog_C1_fp64, _vlog_z); + + // log(0) -> -INF + v_float64 mask_zero = v_eq(x, vx_setzero_f64()); + _vlog_z = v_select(mask_zero, v_reinterpret_as_f64(vx_setall_s64(0xfff0000000000000)), _vlog_z); + // log(NEG), log(NAN) -> NAN + v_float64 mask_not_nan = v_ge(x, vx_setzero_f64()); + _vlog_z = v_select(mask_not_nan, _vlog_z, v_reinterpret_as_f64(vx_setall_s64(0x7ff8000000000000))); + // log(INF) -> INF + v_float64 mask_inf = v_eq(x, v_reinterpret_as_f64(vx_setall_s64(0x7ff0000000000000))); + _vlog_z = v_select(mask_inf, x, _vlog_z); + return _vlog_z; + } +#endif + +#define OPENCV_HAL_MATH_HAVE_LOG 1 +//! @} +#endif + +/* This implementation is derived from the approximation approach of Error Function (Erf) from PyTorch + https://github.com/pytorch/pytorch/blob/9c50ecc84b9a6e699a7f058891b889aafbf976c7/aten/src/ATen/cpu/vec/vec512/vec512_float.h#L189-L220 +*/ + +#ifndef OPENCV_HAL_MATH_HAVE_ERF + +//! @name Error Function +//! @{ + + inline v_float32 v_erf(const v_float32 &v) { + const v_float32 coef0 = vx_setall_f32(0.3275911f), + coef1 = vx_setall_f32(1.061405429f), + coef2 = vx_setall_f32(-1.453152027f), + coef3 = vx_setall_f32(1.421413741f), + coef4 = vx_setall_f32(-0.284496736f), + coef5 = vx_setall_f32(0.254829592f), + ones = vx_setall_f32(1.0f), + neg_zeros = vx_setall_f32(-0.f); + v_float32 t = v_abs(v); + // sign(v) + v_float32 sign_mask = v_and(neg_zeros, v); + + t = v_div(ones, v_fma(coef0, t, ones)); + v_float32 r = v_fma(coef1, t, coef2); + r = v_fma(r, t, coef3); + r = v_fma(r, t, coef4); + r = v_fma(r, t, coef5); + // - v * v + v_float32 pow_2 = v_mul(v, v); + v_float32 neg_pow_2 = v_xor(neg_zeros, pow_2); + // - exp(- v * v) + v_float32 exp = v_exp(neg_pow_2); + v_float32 neg_exp = v_xor(neg_zeros, exp); + v_float32 res = v_mul(t, neg_exp); + res = v_fma(r, res, ones); + return v_xor(sign_mask, res); + } + +#define OPENCV_HAL_MATH_HAVE_ERF 1 +//! @} + +#endif // OPENCV_HAL_MATH_HAVE_ERF + + +} +#endif // OPENCV_HAL_INTRIN_HPP diff --git a/modules/core/include/opencv2/core/mat.hpp b/modules/core/include/opencv2/core/mat.hpp index 0257414646..59b95e2f40 100644 --- a/modules/core/include/opencv2/core/mat.hpp +++ b/modules/core/include/opencv2/core/mat.hpp @@ -2136,7 +2136,7 @@ public: /** @overload */ template void forEach(const Functor& operation) const; - Mat(Mat&& m); + Mat(Mat&& m) CV_NOEXCEPT; Mat& operator = (Mat&& m); enum { MAGIC_VAL = 0x42FF0000, AUTO_STEP = 0, CONTINUOUS_FLAG = CV_MAT_CONT_FLAG, SUBMATRIX_FLAG = CV_SUBMAT_FLAG }; diff --git a/modules/core/include/opencv2/core/opengl.hpp b/modules/core/include/opencv2/core/opengl.hpp index fceb85bd06..5d19f81f85 100644 --- a/modules/core/include/opencv2/core/opengl.hpp +++ b/modules/core/include/opencv2/core/opengl.hpp @@ -57,7 +57,7 @@ This section describes OpenGL interoperability. To enable OpenGL support, configure OpenCV using CMake with WITH_OPENGL=ON . Currently OpenGL is supported only with WIN32, GTK and Qt backends on Windows and Linux (MacOS and Android are not -supported). For GTK backend gtkglext-1.0 library is required. +supported). For GTK-2.0 backend gtkglext-1.0 library is required. To use OpenGL functionality you should first create OpenGL context (window or frame buffer). You can do this with namedWindow function or with other OpenGL toolkit (GLUT, for example). diff --git a/modules/core/include/opencv2/core/utility.hpp b/modules/core/include/opencv2/core/utility.hpp index e491352bcf..5eed688c83 100644 --- a/modules/core/include/opencv2/core/utility.hpp +++ b/modules/core/include/opencv2/core/utility.hpp @@ -544,6 +544,18 @@ bool isAligned(const void* p1, const void* p2, const void* p3, const void* p4) return isAligned(((size_t)p1)|((size_t)p2)|((size_t)p3)|((size_t)p4)); } +/*! @brief Flags that allow to midify some functions behavior. Used as set of flags. +*/ +enum AlgorithmHint { + ALGO_HINT_DEFAULT = 0, //!< Default algorithm behaviour defined during OpenCV build + ALGO_HINT_ACCURATE = 1, //!< Use generic portable implementation + ALGO_HINT_APPROX = 2, //!< Allow alternative approximations to get faster implementation. Behaviour and result depends on a platform +}; + +/*! @brief Returns AlgorithmHint defined during OpenCV compilation. Defines #ALGO_HINT_DEFAULT behavior. + */ +CV_EXPORTS_W AlgorithmHint getDefaultAlgorithmHint(); + /** @brief Enables or disables the optimized code. The function can be used to dynamically turn on and off optimized dispatched code (code that uses SSE4.2, AVX/AVX2, diff --git a/modules/core/src/matrix.cpp b/modules/core/src/matrix.cpp index 2e851fd1ca..4b19f68eca 100644 --- a/modules/core/src/matrix.cpp +++ b/modules/core/src/matrix.cpp @@ -614,7 +614,7 @@ size_t Mat::total(int startDim, int endDim) const } -Mat::Mat(Mat&& m) +Mat::Mat(Mat&& m) CV_NOEXCEPT : flags(m.flags), dims(m.dims), rows(m.rows), cols(m.cols), data(m.data), datastart(m.datastart), dataend(m.dataend), datalimit(m.datalimit), allocator(m.allocator), u(m.u), size(&rows) diff --git a/modules/core/src/system.cpp b/modules/core/src/system.cpp index 1254014ced..91cdd65359 100644 --- a/modules/core/src/system.cpp +++ b/modules/core/src/system.cpp @@ -1316,6 +1316,12 @@ redirectError( ErrorCallback errCallback, void* userdata, void** prevUserdata) return prevCallback; } +void terminate(int _code, const String& _err, const char* _func, const char* _file, int _line) CV_NOEXCEPT +{ + dumpException(cv::Exception(_code, _err, _func, _file, _line)); + std::terminate(); +} + } @@ -2834,6 +2840,14 @@ bool restoreFPDenormalsState(const FPDenormalsModeState& state) } // namespace details +AlgorithmHint getDefaultAlgorithmHint() +{ +#ifdef OPENCV_ALGO_HINT_DEFAULT + return OPENCV_ALGO_HINT_DEFAULT; +#else + return ALGO_HINT_ACCURATE; +#endif +}; } // namespace cv diff --git a/modules/core/test/test_intrin_utils.hpp b/modules/core/test/test_intrin_utils.hpp index fe841617b6..eb1866d7c4 100644 --- a/modules/core/test/test_intrin_utils.hpp +++ b/modules/core/test/test_intrin_utils.hpp @@ -1748,6 +1748,7 @@ template struct TheTest TheTest & test_loadstore_fp16_f32() { + printf("test_loadstore_fp16_f32 ...\n"); AlignedData data; data.a.clear(); data.a.d[0] = 0x3c00; // 1.0 data.a.d[VTraits::vlanes() - 1] = (unsigned short)0xc000; // -2.0 @@ -1873,6 +1874,216 @@ template struct TheTest return *this; } + void __test_exp(LaneType dataMax, LaneType diff_thr, LaneType enlarge_factor, LaneType flt_min) { + int n = VTraits::vlanes(); + + // Test overflow and underflow values with step + const LaneType step = (LaneType) 0.01; + for (LaneType i = dataMax + 1; i <= dataMax + 11;) { + Data dataUpperBound, dataLowerBound, resOverflow, resUnderflow; + for (int j = 0; j < n; ++j) { + dataUpperBound[j] = i; + dataLowerBound[j] = -i; + i += step; + } + R upperBound = dataUpperBound, lowerBound = dataLowerBound; + resOverflow = v_exp(upperBound); + resUnderflow = v_exp(lowerBound); + for (int j = 0; j < n; ++j) { + SCOPED_TRACE(cv::format("Overflow/Underflow test value: %f", i)); + EXPECT_TRUE(resOverflow[j] > 0 && std::isinf(resOverflow[j])); + EXPECT_GE(resUnderflow[j], 0); + EXPECT_LT(resUnderflow[j], flt_min); + } + } + + // Test random values combined with special values + std::vector specialValues = {0, 1, INFINITY, -INFINITY, NAN, dataMax}; + const int testRandNum = 10000; + const double specialValueProbability = 0.1; // 10% chance to insert a special value + cv::RNG_MT19937 rng; + + for (int i = 0; i < testRandNum; i++) { + Data dataRand, resRand; + for (int j = 0; j < n; ++j) { + if (rng.uniform(0.f, 1.f) <= specialValueProbability) { + // Insert a special value + int specialValueIndex = rng.uniform(0, (int) specialValues.size()); + dataRand[j] = specialValues[specialValueIndex]; + } else { + // Generate random data in [-dataMax*1.1, dataMax*1.1] + dataRand[j] = (LaneType) rng.uniform(-dataMax * 1.1, dataMax * 1.1); + } + } + // Compare with std::exp + R x = dataRand; + resRand = v_exp(x); + for (int j = 0; j < n; ++j) { + SCOPED_TRACE(cv::format("Random test value: %f", dataRand[j])); + LaneType std_exp = std::exp(dataRand[j]); + if (dataRand[j] == 0) { + // input 0 -> output 1 + EXPECT_EQ(resRand[j], 1); + } else if (dataRand[j] == 1) { + // input 1 -> output e + EXPECT_NEAR((LaneType) M_E, resRand[j], 1e-15); + } else if (dataRand[j] > 0 && std::isinf(dataRand[j])) { + // input INF -> output INF + EXPECT_TRUE(resRand[j] > 0 && std::isinf(resRand[j])); + } else if (dataRand[j] < 0 && std::isinf(dataRand[j])) { + // input -INF -> output 0 + EXPECT_EQ(resRand[j], 0); + } else if (std::isnan(dataRand[j])) { + // input NaN -> output NaN + EXPECT_TRUE(std::isnan(resRand[j])); + } else if (dataRand[j] == dataMax) { + // input dataMax -> output less than INFINITY + EXPECT_LT(resRand[j], (LaneType) INFINITY); + } else if (std::isinf(resRand[j])) { + // output INF -> input close to edge + EXPECT_GT(dataRand[j], dataMax); + } else { + EXPECT_GE(resRand[j], 0); + EXPECT_LT(std::abs(resRand[j] - std_exp), diff_thr * (std_exp + flt_min * enlarge_factor)); + } + } + } + } + + TheTest &test_exp_fp16() { + // issue after 4.x merge: float16_t and hfloat conflict: https://github.com/opencv/opencv/issues/25922 +#if CV_SIMD_FP16 & 0 + float16_t flt16_min; + uint16_t flt16_min_hex = 0x0400; + std::memcpy(&flt16_min, &flt16_min_hex, sizeof(float16_t)); + __test_exp((float16_t) 10, (float16_t) 1e-2, (float16_t) 1e2, flt16_min); +#endif + return *this; + } + + TheTest &test_exp_fp32() { + __test_exp(88.0f, 1e-6f, 1e6f, FLT_MIN); + return *this; + } + + TheTest &test_exp_fp64() { +#if CV_SIMD_64F || CV_SIMD_SCALABLE_64F + __test_exp(709.0, 1e-15, 1e15, DBL_MIN); +#endif + return *this; + } + + void __test_log(LaneType expBound, LaneType diff_thr, LaneType flt_min) { + int n = VTraits::vlanes(); + // Test special values + std::vector specialValues = {0, 1, (LaneType) M_E, INFINITY, -INFINITY, NAN}; + const int testRandNum = 10000; + const double specialValueProbability = 0.1; // 10% chance to insert a special value + cv::RNG_MT19937 rng; + + for (int i = 0; i < testRandNum; i++) { + Data dataRand, resRand; + for (int j = 0; j < n; ++j) { + if (rng.uniform(0.f, 1.f) <= specialValueProbability) { + // Insert a special value + int specialValueIndex = rng.uniform(0, (int) specialValues.size()); + dataRand[j] = specialValues[specialValueIndex]; + } else { + // Generate uniform random data in [-expBound, expBound] + dataRand[j] = (LaneType) std::exp(rng.uniform(-expBound, expBound)); + } + } + + // Compare with std::log + R x = dataRand; + resRand = v_log(x); + for (int j = 0; j < n; ++j) { + SCOPED_TRACE(cv::format("Random test value: %f", dataRand[j])); + LaneType std_log = std::log(dataRand[j]); + if (dataRand[j] == 0) { + // input 0 -> output -INF + EXPECT_TRUE(std::isinf(resRand[j]) && resRand[j] < 0); + } else if (dataRand[j] < 0 || std::isnan(dataRand[j])) { + // input less than 0 -> output NAN + // input NaN -> output NaN + EXPECT_TRUE(std::isnan(resRand[j])); + } else if (dataRand[j] == 1) { + // input 1 -> output 0 + EXPECT_EQ((LaneType) 0, resRand[j]); + } else if (std::isinf(dataRand[j]) && dataRand[j] > 0) { + // input INF -> output INF + EXPECT_TRUE(std::isinf(resRand[j]) && resRand[j] > 0); + } else { + EXPECT_LT(std::abs(resRand[j] - std_log), diff_thr * (std::abs(std_log) + flt_min * 100)); + } + } + } + } + + TheTest &test_log_fp16() { + // issue after 4.x merge: float16_t and hfloat conflict: https://github.com/opencv/opencv/issues/25922 +#if CV_SIMD_FP16 & 0 + float16_t flt16_min; + uint16_t flt16_min_hex = 0x0400; + std::memcpy(&flt16_min, &flt16_min_hex, sizeof(float16_t)); + __test_log((float16_t) 9, (float16_t) 1e-3, flt16_min); +#endif + return *this; + } + + TheTest &test_log_fp32() { + __test_log(25.f, 1e-6f, FLT_MIN); + return *this; + } + + TheTest &test_log_fp64() { +#if CV_SIMD_64F || CV_SIMD_SCALABLE_64F + __test_log(200., 1e-15, DBL_MIN); +#endif + return *this; + } + + TheTest &test_erf_fp32() { + int n = VTraits::vlanes(); + + constexpr int num_loops = 10000; + const std::vector singular_inputs{INFINITY, -INFINITY, NAN}; + constexpr double insert_singular_input_probability = 0.1; + cv::RNG_MT19937 rng; + + for (int i = 0; i < num_loops; i++) { + Data inputs; + for (int j = 0; j < n; j++) { + if (rng.uniform(0.f, 1.f) <= insert_singular_input_probability) { + int singular_input_index = rng.uniform(0, int(singular_inputs.size())); + inputs[j] = singular_inputs[singular_input_index]; + } else { + // std::exp(float) overflows at about 88.0f. + // In v_erf, exp is called on input*input. So test range is [-sqrt(88.0f), sqrt(88.0f)] + inputs[j] = (LaneType) rng.uniform(-9.4f, 9.4f); + } + } + + Data outputs = v_erf(R(inputs)); + for (int j = 0; j < n; j++) { + SCOPED_TRACE(cv::format("Random test value: %f", inputs[j])); + if (std::isinf(inputs[j])) { + if (inputs[j] < 0) { + EXPECT_EQ(-1, outputs[j]); + } else { + EXPECT_EQ(1, outputs[j]); + } + } else if (std::isnan(inputs[j])) { + EXPECT_TRUE(std::isnan(outputs[j])); + } else { + LaneType ref_output = std::erf(inputs[j]); + EXPECT_LT(std::abs(outputs[j] - ref_output), 1e-3f * (std::abs(ref_output) + FLT_MIN * 1e4f)); + } + } + } + + return *this; + } }; #define DUMP_ENTRY(type) printf("SIMD%d: %s\n", 8*VTraits::vlanes(), CV__TRACE_FUNCTION); @@ -2186,6 +2397,9 @@ void test_hal_intrin_float32() .test_extract_highest() .test_broadcast_highest() .test_pack_triplets() + .test_exp_fp32() + .test_log_fp32() + .test_erf_fp32() #if CV_SIMD_WIDTH == 32 .test_extract<4>().test_extract<5>().test_extract<6>().test_extract<7>() .test_rotate<4>().test_rotate<5>().test_rotate<6>().test_rotate<7>() @@ -2217,6 +2431,8 @@ void test_hal_intrin_float64() .test_rotate<0>().test_rotate<1>() .test_extract_n<0>().test_extract_n<1>() .test_extract_highest() + .test_exp_fp64() + .test_log_fp64() //.test_broadcast_element<0>().test_broadcast_element<1>() #if CV_SIMD_WIDTH == 32 .test_extract<2>().test_extract<3>() @@ -2258,6 +2474,8 @@ void test_hal_intrin_float16() .test_extract_highest() .test_broadcast_element<0>().test_broadcast_element<1>() .test_extract_n<0>().test_extract_n<1>() + .test_exp_fp16() + .test_log_fp16() #endif ; #else diff --git a/modules/dnn/include/opencv2/dnn/dnn.hpp b/modules/dnn/include/opencv2/dnn/dnn.hpp index b26d213d34..311bf380b1 100644 --- a/modules/dnn/include/opencv2/dnn/dnn.hpp +++ b/modules/dnn/include/opencv2/dnn/dnn.hpp @@ -608,13 +608,13 @@ CV__DNN_INLINE_NS_BEGIN * @param outputName name for layer which output is needed to get * @details If @p outputName is empty, runs forward pass for the whole network. */ - CV_WRAP void forward(OutputArrayOfArrays outputBlobs, const String& outputName = String()); + CV_WRAP void forward(CV_ND OutputArrayOfArrays outputBlobs, const String& outputName = String()); /** @brief Runs forward pass to compute outputs of layers listed in @p outBlobNames. * @param outputBlobs contains blobs for first outputs of specified layers. * @param outBlobNames names for layers which outputs are needed to get */ - CV_WRAP void forward(OutputArrayOfArrays outputBlobs, + CV_WRAP void forward(CV_ND OutputArrayOfArrays outputBlobs, const std::vector& outBlobNames); /** @brief Runs forward pass to compute outputs of layers listed in @p outBlobNames. @@ -661,7 +661,7 @@ CV__DNN_INLINE_NS_BEGIN * as: * \f[input(n,c,h,w) = scalefactor \times (blob(n,c,h,w) - mean_c)\f] */ - CV_WRAP void setInput(InputArray blob, const String& name = "", + CV_WRAP void setInput(CV_ND InputArray blob, const String& name = "", double scalefactor = 1.0, const Scalar& mean = Scalar()); /** @brief Sets the new value for the learned param of the layer. @@ -672,8 +672,8 @@ CV__DNN_INLINE_NS_BEGIN * @note If shape of the new blob differs from the previous shape, * then the following forward pass may fail. */ - CV_WRAP void setParam(int layer, int numParam, const Mat &blob); - CV_WRAP inline void setParam(const String& layerName, int numParam, const Mat &blob) { return setParam(getLayerId(layerName), numParam, blob); } + CV_WRAP void setParam(int layer, int numParam, CV_ND const Mat &blob); + CV_WRAP inline void setParam(const String& layerName, int numParam, CV_ND const Mat &blob) { return setParam(getLayerId(layerName), numParam, blob); } /** @brief Returns parameter blob of the layer. * @param layer name or id of the layer. diff --git a/modules/dnn/misc/python/test/test_dnn.py b/modules/dnn/misc/python/test/test_dnn.py index 9a658acc2e..e9a5b1e075 100644 --- a/modules/dnn/misc/python/test/test_dnn.py +++ b/modules/dnn/misc/python/test/test_dnn.py @@ -456,10 +456,6 @@ class dnn_test(NewOpenCVTests): "Verify OPENCV_DNN_TEST_DATA_PATH configuration parameter.") input = np.load(input_file) - # we have to expand the shape of input tensor because Python bindings cut 3D tensors to 2D - # it should be fixed in future. see : https://github.com/opencv/opencv/issues/19091 - # please remove `expand_dims` after that - input = np.expand_dims(input, axis=3) gold_output = np.load(output_file) for backend, target in self.dnnBackendsAndTargets: @@ -470,10 +466,63 @@ class dnn_test(NewOpenCVTests): net.setPreferableBackend(backend) net.setPreferableTarget(target) + # Check whether 3d shape is parsed correctly for setInput net.setInput(input) - real_output = net.forward() - normAssert(self, real_output, gold_output, "", getDefaultThreshold(target)) + # Case 0: test API `forward(const String& outputName = String()` + real_output = net.forward() # Retval is a np.array of shape [2, 5, 3] + normAssert(self, real_output, gold_output, "Case 1", getDefaultThreshold(target)) + + ''' + Pre-allocate output memory with correct shape. + Normally Python users do not use in this way, + but we have to test it since we design API in this way + ''' + # Case 1: a np.array with a string of output name. + # It tests API `forward(OutputArrayOfArrays outputBlobs, const String& outputName = String()` + # when outputBlobs is a np.array and we expect it to be the only output. + real_output = np.empty([2, 5, 3], dtype=np.float32) + real_output = net.forward(real_output, "237") # Retval is a tuple with a np.array of shape [2, 5, 3] + normAssert(self, real_output, gold_output, "Case 1", getDefaultThreshold(target)) + + # Case 2: a tuple of np.array with a string of output name. + # It tests API `forward(OutputArrayOfArrays outputBlobs, const String& outputName = String()` + # when outputBlobs is a container of several np.array and we expect to save all outputs accordingly. + real_output = tuple(np.empty([2, 5, 3], dtype=np.float32)) + real_output = net.forward(real_output, "237") # Retval is a tuple with a np.array of shape [2, 5, 3] + normAssert(self, real_output, gold_output, "Case 2", getDefaultThreshold(target)) + + # Case 3: a tuple of np.array with a string of output name. + # It tests API `forward(OutputArrayOfArrays outputBlobs, const std::vector& outBlobNames)` + real_output = tuple(np.empty([2, 5, 3], dtype=np.float32)) + # Note that it does not support parsing a list , e.g. ["237"] + real_output = net.forward(real_output, ("237")) # Retval is a tuple with a np.array of shape [2, 5, 3] + normAssert(self, real_output, gold_output, "Case 3", getDefaultThreshold(target)) + + def test_set_param_3d(self): + model_path = self.find_dnn_file('dnn/onnx/models/matmul_3d_init.onnx') + input_file = self.find_dnn_file('dnn/onnx/data/input_matmul_3d_init.npy') + output_file = self.find_dnn_file('dnn/onnx/data/output_matmul_3d_init.npy') + + input = np.load(input_file) + output = np.load(output_file) + + for backend, target in self.dnnBackendsAndTargets: + printParams(backend, target) + + net = cv.dnn.readNet(model_path) + + node_name = net.getLayerNames()[0] + w = net.getParam(node_name, 0) # returns the original tensor of three-dimensional shape + net.setParam(node_name, 0, w) # set param once again to see whether tensor is converted with correct shape + + net.setPreferableBackend(backend) + net.setPreferableTarget(target) + + net.setInput(input) + res_output = net.forward() + + normAssert(self, output, res_output, "", getDefaultThreshold(target)) def test_scalefactor_assign(self): params = cv.dnn.Image2BlobParams() diff --git a/modules/dnn/perf/perf_layer.cpp b/modules/dnn/perf/perf_layer.cpp index d436f95cc6..95966240f8 100644 --- a/modules/dnn/perf/perf_layer.cpp +++ b/modules/dnn/perf/perf_layer.cpp @@ -973,4 +973,49 @@ INSTANTIATE_TEST_CASE_P(/**/, Layer_Softmax, Combine( /* withCann= */ false) // only test on CPU )); +using Layer_Elementwise = TestBaseWithParam, std::string, tuple>>; +PERF_TEST_P_(Layer_Elementwise, elementwise) { + std::vector input_shape = get<0>(GetParam()); + std::string op = get<1>(GetParam()); + int backend_id = get<0>(get<2>(GetParam())); + int target_id = get<1>(get<2>(GetParam())); + + Mat input(input_shape, CV_32F); + randn(input, 0.f, 1.f); + + LayerParams lp; + lp.type = op; + lp.name = "TestLayer"; + + Net net; + net.addLayerToPrev(lp.name, lp.type, lp); + + // Warmup + { + net.setInput(input); + net.setPreferableBackend(backend_id); + net.setPreferableTarget(target_id); + Mat out = net.forward(); + } + + TEST_CYCLE() { + net.forward(); + } + + SANITY_CHECK_NOTHING(); +} + +INSTANTIATE_TEST_CASE_P(/**/, Layer_Elementwise, testing::Combine( + testing::Values(std::vector{1, 50, 3072}), + testing::Values(std::string{"Gelu"}), + dnnBackendsAndTargets(/* withInferenceEngine= */ true, + /* withHalide= */ false, + /* withCpuOCV= */ true, + /* withVkCom= */ false, + /* withCUDA= */ true, + /* withNgraph= */ true, + /* withWebnn= */ false, + /* withCann= */ false) // only test on CPU +)); + } // namespace diff --git a/modules/dnn/src/cuda4dnn/init.hpp b/modules/dnn/src/cuda4dnn/init.hpp index f5bb7714f8..e1f0a71d2f 100644 --- a/modules/dnn/src/cuda4dnn/init.hpp +++ b/modules/dnn/src/cuda4dnn/init.hpp @@ -15,7 +15,7 @@ namespace cv { namespace dnn { namespace cuda4dnn { - void checkVersions() + inline void checkVersions() { // https://docs.nvidia.com/deeplearning/cudnn/developer-guide/index.html#programming-model // cuDNN API Compatibility @@ -23,8 +23,19 @@ namespace cv { namespace dnn { namespace cuda4dnn { // Any patch release x.y.z is forward or backward-compatible with applications built against another cuDNN patch release x.y.w (meaning, of the same major and minor version number, but having w!=z). // cuDNN minor releases beginning with cuDNN 7 are binary backward-compatible with applications built against the same or earlier patch release (meaning, an application built against cuDNN 7.x is binary compatible with cuDNN library 7.y, where y>=x). // Applications compiled with a cuDNN version 7.y are not guaranteed to work with 7.x release when y > x. - auto cudnn_bversion = cudnnGetVersion(); - auto cudnn_major_bversion = cudnn_bversion / 1000, cudnn_minor_bversion = cudnn_bversion % 1000 / 100; + int cudnn_bversion = cudnnGetVersion(); + int cudnn_major_bversion = 0, cudnn_minor_bversion = 0; + // CuDNN changed major version multiplier in 9.0 + if (cudnn_bversion >= 9*10000) + { + cudnn_major_bversion = cudnn_bversion / 10000; + cudnn_minor_bversion = cudnn_bversion % 10000 / 100; + } + else + { + cudnn_major_bversion = cudnn_bversion / 1000; + cudnn_minor_bversion = cudnn_bversion % 1000 / 100; + } if (cudnn_major_bversion != CUDNN_MAJOR || cudnn_minor_bversion < CUDNN_MINOR) { std::ostringstream oss; @@ -33,21 +44,23 @@ namespace cv { namespace dnn { namespace cuda4dnn { } } - int getDeviceCount() + inline int getDeviceCount() { return cuda::getCudaEnabledDeviceCount(); } - int getDevice() + inline int getDevice() { int device_id = -1; CUDA4DNN_CHECK_CUDA(cudaGetDevice(&device_id)); return device_id; } - bool isDeviceCompatible() + inline bool isDeviceCompatible(int device_id = -1) { - int device_id = getDevice(); + if (device_id < 0) + device_id = getDevice(); + if (device_id < 0) return false; @@ -65,9 +78,11 @@ namespace cv { namespace dnn { namespace cuda4dnn { return false; } - bool doesDeviceSupportFP16() + inline bool doesDeviceSupportFP16(int device_id = -1) { - int device_id = getDevice(); + if (device_id < 0) + device_id = getDevice(); + if (device_id < 0) return false; @@ -76,9 +91,7 @@ namespace cv { namespace dnn { namespace cuda4dnn { CUDA4DNN_CHECK_CUDA(cudaDeviceGetAttribute(&minor, cudaDevAttrComputeCapabilityMinor, device_id)); int version = major * 10 + minor; - if (version < 53) - return false; - return true; + return (version >= 53); } }}} /* namespace cv::dnn::cuda4dnn */ diff --git a/modules/dnn/src/layers/concat_layer.cpp b/modules/dnn/src/layers/concat_layer.cpp index 362280c92d..04d7c7fcc0 100644 --- a/modules/dnn/src/layers/concat_layer.cpp +++ b/modules/dnn/src/layers/concat_layer.cpp @@ -316,6 +316,8 @@ public: ranges[cAxis].start = 0; for (size_t i = 0; i < inputs.size(); i++) { + if (inputs[i].empty()) + continue; ranges[cAxis].end = ranges[cAxis].start + inputs[i].size[cAxis]; for (int j = 0; j < outMat.dims; ++j) { diff --git a/modules/dnn/src/layers/cpu_kernels/softmax.cpp b/modules/dnn/src/layers/cpu_kernels/softmax.cpp index eb258ecfa2..fd55c1c1de 100644 --- a/modules/dnn/src/layers/cpu_kernels/softmax.cpp +++ b/modules/dnn/src/layers/cpu_kernels/softmax.cpp @@ -71,48 +71,12 @@ void softmax(Mat &dst, const Mat &src, int axis, int axisBias, int axisStep){ // calculate the exp value along the axis v_float32 vs = vx_setzero_f32(); vmax = vx_setall_f32(maxVal); - // initialize vexp constant - v_float32 _vexp_lo = vx_setall_f32(-88.3762626647949f); - v_float32 _vexp_hi = vx_setall_f32(88.3762626647949f); - v_float32 _vexp_half = vx_setall_f32(0.5f); - v_float32 _vexp_one = vx_setall_f32(1.f); - v_float32 _vexp_LOG2EF = vx_setall_f32(1.44269504088896341f); - v_float32 _vexp_C1 = vx_setall_f32(-0.693359375f); - v_float32 _vexp_C2 = vx_setall_f32(2.12194440e-4f); - v_float32 _vexp_p0 = vx_setall_f32(1.9875691500E-4f); - v_float32 _vexp_p1 = vx_setall_f32(1.3981999507E-3f); - v_float32 _vexp_p2 = vx_setall_f32(8.3334519073E-3f); - v_float32 _vexp_p3 = vx_setall_f32(4.1665795894E-2f); - v_float32 _vexp_p4 = vx_setall_f32(1.6666665459E-1f); - v_float32 _vexp_p5 = vx_setall_f32(5.0000001201E-1f); - // initialize temp vectors for vexp - v_float32 val, _vexp_, _vexp_x, _vexp_y, _vexp_z; - v_int32 _vexp_mm; - + v_float32 val; // calculate and sum all data along axis for (size_t cnDim = 0; cnDim < axisStep; cnDim += nlanes) { val = vx_load(axisBuf + cnDim); val = v_sub(val, vmax); - - // compute vexp of val - _vexp_x = v_min(val, _vexp_hi); - _vexp_x = v_max(_vexp_x, _vexp_lo); - _vexp_ = v_fma(_vexp_x, _vexp_LOG2EF, _vexp_half); - _vexp_mm = v_floor(_vexp_); - _vexp_ = v_cvt_f32(_vexp_mm); - _vexp_mm = v_add(_vexp_mm, vx_setall_s32(0x7f)); - _vexp_mm = v_shl(_vexp_mm, 23); - _vexp_x = v_fma(_vexp_, _vexp_C1, _vexp_x); - _vexp_x = v_fma(_vexp_, _vexp_C2, _vexp_x); - _vexp_z = v_mul(_vexp_x, _vexp_x); - _vexp_y = v_fma(_vexp_x, _vexp_p0, _vexp_p1); - _vexp_y = v_fma(_vexp_y, _vexp_x, _vexp_p2); - _vexp_y = v_fma(_vexp_y, _vexp_x, _vexp_p3); - _vexp_y = v_fma(_vexp_y, _vexp_x, _vexp_p4); - _vexp_y = v_fma(_vexp_y, _vexp_x, _vexp_p5); - _vexp_y = v_fma(_vexp_y, _vexp_z, _vexp_x); - _vexp_y = v_add(_vexp_y, _vexp_one); - val = v_mul(_vexp_y, v_reinterpret_as_f32(_vexp_mm)); + val = v_exp(val); vs = v_add(vs, val); v_store(axisBuf + cnDim, val); diff --git a/modules/dnn/src/layers/elementwise_layers.cpp b/modules/dnn/src/layers/elementwise_layers.cpp index e7b8c38302..efd5eaa1ca 100644 --- a/modules/dnn/src/layers/elementwise_layers.cpp +++ b/modules/dnn/src/layers/elementwise_layers.cpp @@ -684,20 +684,82 @@ private: static const char* const ocl_kernel_name; }; -struct GeluFunctor : public BaseDefaultFunctor -{ - typedef GeluLayer Layer; +namespace { + // Refer to v_erf in modules/core/include/opencv2/core/hal/intrin_math.hpp + constexpr float c_erf_coef0 = 0.3275911f; + constexpr float c_erf_coef1 = 1.061405429f; + constexpr float c_erf_coef2 = -1.453152027f; + constexpr float c_erf_coef3 = 1.421413741f; + constexpr float c_erf_coef4 = -0.284496736f; + constexpr float c_erf_coef5 = 0.254829592f; + + inline float erf_approx(float v) { + float t = 1.f / fmaf(fabsf(v), c_erf_coef0, 1.f); + float r = fmaf(c_erf_coef1, t, c_erf_coef2); + r = fmaf(r, t, c_erf_coef3); + r = fmaf(r, t, c_erf_coef4); + r = fmaf(r, t, c_erf_coef5); + r = 1.f - r * t * expf(-v * v); + return std::copysignf(r, v); + } +} - explicit GeluFunctor() {} +struct GeluFunctor : public BaseFunctor { + using Layer = GeluLayer; + int vlanes; - bool supportBackend(int backendId, int) - { - return backendId == DNN_BACKEND_OPENCV || backendId == DNN_BACKEND_CUDA; + explicit GeluFunctor() { +#if (CV_SIMD || CV_SIMD_SCALABLE) + vlanes = VTraits::vlanes(); +#else + vlanes = 1; +#endif } - inline float calculate(float x) const - { - return 0.5f * x * (1.0f + erf(x * M_SQRT1_2)); + bool supportBackend(int backendId, int) { + return backendId == DNN_BACKEND_OPENCV || backendId == DNN_BACKEND_CUDA || backendId == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH; + } + + void apply(const float* srcptr, float* dstptr, int stripeStart, int len, size_t planeSize, int cn0, int cn1) const { + CV_UNUSED(stripeStart); + for (int cn = cn0; cn < cn1; cn++, srcptr += planeSize, dstptr += planeSize) { + int i = 0; +#if (CV_SIMD || CV_SIMD_SCALABLE) + // 0.5f * x * (1.0f + erf(x * M_SQRT1_2)); + v_float32 half = vx_setall_f32(0.5f), + one = vx_setall_f32(1.0f), + reciprocal_sqrt2 = vx_setall_f32(M_SQRT1_2); + for (; i <= len - vlanes; i += vlanes) { + if (i + vlanes > len) { + if (i == 0 || i == len) { + break; + } + i = len - vlanes; + } + v_float32 x0 = vx_load(srcptr + i); + + // t = x * M_SQRT1_2 + v_float32 t0 = v_mul(reciprocal_sqrt2, x0); + + // t = 1.0f + t + t0 = v_add(one, v_erf(t0)); + + // x = 0.5 * x + x0 = v_mul(half, x0); + + // x = x * t + x0 = v_mul(x0, t0); + + vx_store(dstptr + i, x0); + } +#endif + // 0.5f * x * (1.0f + erf(x * M_SQRT1_2)); + for( ; i < len; i++ ) + { + float x = srcptr[i]; + dstptr[i] = 0.5f * x * (1.0f + erf_approx(x * M_SQRT1_2)); + } + } } #ifdef HAVE_CUDA @@ -707,12 +769,55 @@ struct GeluFunctor : public BaseDefaultFunctor } #endif +#ifdef HAVE_OPENCL + bool initKernel(ocl::Kernel &ker, const UMat &src) const + { + String buildopt = oclGetTMacro(src); + + if (!ker.create("GeluForward", ocl::dnn::activations_oclsrc, buildopt)) + return false; + + return true; + } + + bool applyOCL(InputArrayOfArrays inps, OutputArrayOfArrays outs, OutputArrayOfArrays internals) + { + std::vector inputs; + std::vector outputs; + + inps.getUMatVector(inputs); + outs.getUMatVector(outputs); + + for (size_t i = 0; i < inputs.size(); i++) + { + UMat& src = inputs[i]; + UMat& dst = outputs[i]; + CV_Assert(src.isContinuous() && dst.isContinuous() && !src.offset && !dst.offset); + + ocl::Kernel kernel; + CV_Assert(initKernel(kernel, src)); + kernel.set(0, (int)src.total()); + kernel.set(1, ocl::KernelArg::PtrReadOnly(src)); + kernel.set(2, ocl::KernelArg::PtrWriteOnly(dst)); + + size_t gSize = src.total(); + CV_Assert(kernel.run(1, &gSize, NULL, false)); + } + + return true; + } +#endif + +#ifdef HAVE_DNN_NGRAPH + std::shared_ptr initNgraphAPI(const ngraph::Output& node) + { + return std::make_shared(node); + } +#endif // HAVE_DNN_NGRAPH + int64 getFLOPSPerElement() const { return 100; } }; -template<> -const char* const BaseDefaultFunctor::ocl_kernel_name = "GeluForward"; - namespace GeluApproximationConstants { static constexpr float sqrt_2_pi = 0.7978845834732056f; diff --git a/modules/dnn/src/layers/slice_layer.cpp b/modules/dnn/src/layers/slice_layer.cpp index e4e694b3dd..b6340a3efc 100644 --- a/modules/dnn/src/layers/slice_layer.cpp +++ b/modules/dnn/src/layers/slice_layer.cpp @@ -69,10 +69,12 @@ Range normalizeRange(const Range& input_range, int n) { Range range = input_range; - range.start = std::min(std::max(range.start, -n), n - 1); - if (range.start < 0) - { - range.start += n; + if (range.start != n){ + range.start = std::min(std::max(range.start, -n), n - 1); + if (range.start < 0) + { + range.start += n; + } } range.end = std::min(std::max(range.end, -n), n); @@ -630,7 +632,9 @@ public: { for (size_t i = 0; i < outputs.size(); i++) { - inpMat(finalSliceRanges[i]).copyTo(outputs[i]); + if (finalSliceRanges[i][0].start != finalSliceRanges[i][0].end){ + inpMat(finalSliceRanges[i]).copyTo(outputs[i]); + } } } else diff --git a/modules/dnn/src/net_impl_backend.cpp b/modules/dnn/src/net_impl_backend.cpp index cb39c48b49..4ec202dd8f 100644 --- a/modules/dnn/src/net_impl_backend.cpp +++ b/modules/dnn/src/net_impl_backend.cpp @@ -10,6 +10,10 @@ #include "backend.hpp" #include "factory.hpp" +#ifdef HAVE_CUDA +#include "cuda4dnn/init.hpp" +#endif + namespace cv { namespace dnn { CV__DNN_INLINE_NS_BEGIN @@ -240,6 +244,16 @@ void Net::Impl::setPreferableTarget(int targetId) #endif } + if (IS_DNN_CUDA_TARGET(targetId)) + { + preferableTarget = DNN_TARGET_CPU; +#ifdef HAVE_CUDA + if (cuda4dnn::doesDeviceSupportFP16() && targetId == DNN_TARGET_CUDA_FP16) + preferableTarget = DNN_TARGET_CUDA_FP16; + else + preferableTarget = DNN_TARGET_CUDA; +#endif + } #if !defined(__arm64__) || !__arm64__ if (targetId == DNN_TARGET_CPU_FP16) { diff --git a/modules/dnn/src/opencl/activations.cl b/modules/dnn/src/opencl/activations.cl index 96b56725fb..bbd03b2ea1 100644 --- a/modules/dnn/src/opencl/activations.cl +++ b/modules/dnn/src/opencl/activations.cl @@ -48,6 +48,10 @@ #pragma OPENCL EXTENSION cl_khr_fp16 : enable #endif +#if !defined(M_SQRT1_2) +#define M_SQRT1_2 0.707106781186547524400844362104849039 /* 1/sqrt(2) */ +#endif + __kernel void ReLUForward(const int count, __global const T* in, __global T* out #ifndef RELU_NO_SLOPE , KERNEL_ARG_DTYPE negative_slope diff --git a/modules/dnn/src/registry.cpp b/modules/dnn/src/registry.cpp index 33d2e92a95..fe3d35c1f3 100644 --- a/modules/dnn/src/registry.cpp +++ b/modules/dnn/src/registry.cpp @@ -15,6 +15,10 @@ #include "backend.hpp" #include "factory.hpp" +#ifdef HAVE_CUDA +#include "cuda4dnn/init.hpp" +#endif + namespace cv { namespace dnn { CV__DNN_INLINE_NS_BEGIN @@ -107,10 +111,28 @@ private: #endif #ifdef HAVE_CUDA - if (haveCUDA()) + cuda4dnn::checkVersions(); + + bool hasCudaCompatible = false; + bool hasCudaFP16 = false; + for (int i = 0; i < cuda4dnn::getDeviceCount(); i++) + { + if (cuda4dnn::isDeviceCompatible(i)) + { + hasCudaCompatible = true; + if (cuda4dnn::doesDeviceSupportFP16(i)) + { + hasCudaFP16 = true; + break; // we already have all we need here + } + } + } + + if (hasCudaCompatible) { backends.push_back(std::make_pair(DNN_BACKEND_CUDA, DNN_TARGET_CUDA)); - backends.push_back(std::make_pair(DNN_BACKEND_CUDA, DNN_TARGET_CUDA_FP16)); + if (hasCudaFP16) + backends.push_back(std::make_pair(DNN_BACKEND_CUDA, DNN_TARGET_CUDA_FP16)); } #endif diff --git a/modules/dnn/test/test_common.hpp b/modules/dnn/test/test_common.hpp index e9e276ce3f..0f9244324e 100644 --- a/modules/dnn/test/test_common.hpp +++ b/modules/dnn/test/test_common.hpp @@ -210,7 +210,7 @@ public: if ((!l->supportBackend(backend) || l->preferableTarget != target) && !fused) { hasFallbacks = true; - std::cout << "FALLBACK: Layer [" << l->type << "]:[" << l->name << "] is expected to has backend implementation" << endl; + std::cout << "FALLBACK: Layer [" << l->type << "]:[" << l->name << "] is expected to have backend implementation" << endl; } } if (hasFallbacks && raiseError) diff --git a/modules/dnn/test/test_onnx_conformance.cpp b/modules/dnn/test/test_onnx_conformance.cpp index c9bb674933..5e854cb17b 100644 --- a/modules/dnn/test/test_onnx_conformance.cpp +++ b/modules/dnn/test/test_onnx_conformance.cpp @@ -282,6 +282,14 @@ static const TestCase testConformanceConfig[] = { {"test_gathernd_example_float32", 2, 1}, {"test_gathernd_example_int32", 2, 1}, {"test_gathernd_example_int32_batch_dim1", 2, 1}, + {"test_gelu_default_1", 1, 1}, + {"test_gelu_default_1_expanded", 1, 1}, + {"test_gelu_default_2", 1, 1}, + {"test_gelu_default_2_expanded", 1, 1}, + {"test_gelu_tanh_1", 1, 1}, + {"test_gelu_tanh_1_expanded", 1, 1}, + {"test_gelu_tanh_2", 1, 1}, + {"test_gelu_tanh_2_expanded", 1, 1}, {"test_gemm_all_attributes", 3, 1}, {"test_gemm_alpha", 3, 1}, {"test_gemm_beta", 3, 1}, @@ -962,6 +970,9 @@ public: static std::set opencl_fp16_deny_list; static std::set opencl_deny_list; static std::set cpu_deny_list; +#ifdef HAVE_HALIDE + static std::set halide_deny_list; +#endif #ifdef HAVE_VULKAN static std::set vulkan_deny_list; #endif @@ -1005,7 +1016,7 @@ public: if ((!l->supportBackend(backend) || l->preferableTarget != target) && !fused) { hasFallbacks = true; - std::cout << "FALLBACK: Layer [" << l->type << "]:[" << l->name << "] is expected to has backend implementation" << endl; + std::cout << "FALLBACK: Layer [" << l->type << "]:[" << l->name << "] is expected to have backend implementation" << endl; } } return hasFallbacks; @@ -1037,6 +1048,12 @@ public: #include "test_onnx_conformance_layer_filter_opencv_cpu_denylist.inl.hpp" }; +#ifdef HAVE_HALIDE + halide_deny_list = { + #include "test_onnx_conformance_layer_filter__halide_denylist.inl.hpp" + }; +#endif + #ifdef HAVE_VULKAN vulkan_deny_list = { #include "test_onnx_conformance_layer_filter__vulkan_denylist.inl.hpp" @@ -1061,6 +1078,9 @@ std::set Test_ONNX_conformance::opencv_deny_list; std::set Test_ONNX_conformance::opencl_fp16_deny_list; std::set Test_ONNX_conformance::opencl_deny_list; std::set Test_ONNX_conformance::cpu_deny_list; +#ifdef HAVE_HALIDE +std::set Test_ONNX_conformance::halide_deny_list; +#endif #ifdef HAVE_VULKAN std::set Test_ONNX_conformance::vulkan_deny_list; #endif @@ -1107,7 +1127,29 @@ TEST_P(Test_ONNX_conformance, Layer_Test) { applyTestTag(CV_TEST_TAG_DNN_SKIP_CPU, CV_TEST_TAG_DNN_SKIP_OPENCV_BACKEND, CV_TEST_TAG_DNN_SKIP_ONNX_CONFORMANCE); } + if (name == "test_gelu_tanh_1") { + default_l1 = 0.00011; // Expected: (normL1) <= (l1), actual: 0.000101805 vs 1e-05 + default_lInf = 0.00016; // Expected: (normInf) <= (lInf), actual: 0.000152707 vs 0.0001 + } + if (name == "test_gelu_tanh_2") { + if (target == DNN_TARGET_OPENCL_FP16) { + default_l1 = 0.00016; // Expected: (normL1) <= (l1), actual: 0.000157223 vs 9e-05 + default_lInf = 0.0016; // Expected: (normInf) <= (lInf), actual: 0.00153041 vs 0.0005 + } else { + default_l1 = 9e-5; // Expected: (normL1) <= (l1), actual: 8.80073e-05 vs 1e-05 + default_lInf = 0.0005; // Expected: (normInf) <= (lInf), actual: 0.000455521 vs 0.0001 + } + } + } +#ifdef HAVE_HALIDE + else if (backend == DNN_BACKEND_HALIDE) + { + if (halide_deny_list.find(name) != halide_deny_list.end()) + { + applyTestTag(CV_TEST_TAG_DNN_SKIP_HALIDE, CV_TEST_TAG_DNN_SKIP_ONNX_CONFORMANCE); + } } +#endif #ifdef HAVE_INF_ENGINE else if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH) { @@ -1121,6 +1163,15 @@ TEST_P(Test_ONNX_conformance, Layer_Test) { applyTestTag(CV_TEST_TAG_DNN_SKIP_VULKAN, CV_TEST_TAG_DNN_SKIP_ONNX_CONFORMANCE); } + + if (name == "test_gelu_tanh_1") { + default_l1 = 0.00011; // Expected: (normL1) <= (l1), actual: 0.000101805 vs 1e-05 + default_lInf = 0.00016; // Expected: (normInf) <= (lInf), actual: 0.000152707 vs 0.0001 + } + if (name == "test_gelu_tanh_2") { + default_l1 = 9e-5; // Expected: (normL1) <= (l1), actual: 8.80073e-05 vs 1e-05 + default_lInf = 0.0005; // Expected: (normInf) <= (lInf), actual: 0.000455521 vs 0.0001 + } } #endif #ifdef HAVE_CUDA @@ -1134,6 +1185,20 @@ TEST_P(Test_ONNX_conformance, Layer_Test) { applyTestTag(CV_TEST_TAG_DNN_SKIP_CUDA_FP16, CV_TEST_TAG_DNN_SKIP_ONNX_CONFORMANCE); } + + if (name == "test_gelu_tanh_1") { + default_l1 = 0.00011; // Expected: (normL1) <= (l1), actual: 0.000101815 vs 1e-05 + default_lInf = 0.00016; // Expected: (normInf) <= (lInf), actual: 0.000152737 vs 0.0001 + } + if (name == "test_gelu_tanh_2") { + if (target == DNN_TARGET_CUDA_FP16) { + default_l1 = 0.00023; // Expected: (normL1) <= (l1), actual: 0.000220591 vs 9e-05 + default_lInf = 0.0023; // Expected: (normInf) <= (lInf), actual: 0.00220466 vs 0.0005 + } else { + default_l1 = 9e-5; // Expected: (normL1) <= (l1), actual: 8.80127e-05 vs 1e-05 + default_lInf = 0.0005; // Expected: (normInf) <= (lInf), actual: 0.000455445 vs 0.0001 + } + } } #endif else diff --git a/modules/dnn/test/test_onnx_conformance_layer_filter__openvino.inl.hpp b/modules/dnn/test/test_onnx_conformance_layer_filter__openvino.inl.hpp index 21fd63f868..c8c965c55b 100644 --- a/modules/dnn/test/test_onnx_conformance_layer_filter__openvino.inl.hpp +++ b/modules/dnn/test/test_onnx_conformance_layer_filter__openvino.inl.hpp @@ -624,6 +624,36 @@ CASE(test_gathernd_example_int32) // no filter CASE(test_gathernd_example_int32_batch_dim1) // no filter +CASE(test_gelu_default_1) + // no filter +CASE(test_gelu_default_1_expanded) + // no filter +CASE(test_gelu_default_2) + // no filter +CASE(test_gelu_default_2_expanded) + // no filter +CASE(test_gelu_tanh_1) + if (target == DNN_TARGET_CPU) { + default_l1 = 0.00011; // Expected: (normL1) <= (l1), actual: 0.000101805 vs 1e-05 + default_lInf = 0.00016; // Expected: (normInf) <= (lInf), actual: 0.000152707 vs 0.0001 + } + if (target == DNN_TARGET_OPENCL) { + default_l1 = 0.00011; // Expected: (normL1) <= (l1), actual: 0.000101815 vs 1e-05 + default_lInf = 0.00016; // Expected: (normInf) <= (lInf), actual: 0.000152737 vs 0.0001 + } +CASE(test_gelu_tanh_1_expanded) + // no filter +CASE(test_gelu_tanh_2) + if (target == DNN_TARGET_CPU) { + default_l1 = 9e-5; // Expected: (normL1) <= (l1), actual: 8.80057e-05 vs 1e-05 + default_lInf = 0.00046; // Expected: (normInf) <= (lInf), actual: 0.000455521 vs 0.0001 + } + if (target == DNN_TARGET_OPENCL) { + default_l1 = 9e-5; // Expected: (normL1) <= (l1), actual: 8.80144e-05 vs 1e-05 + default_lInf = 0.00046; // Expected: (normInf) <= (lInf), actual: 0.000455445 vs 0.0001 + } +CASE(test_gelu_tanh_2_expanded) + // no filter CASE(test_gemm_all_attributes) // no filter CASE(test_gemm_alpha) diff --git a/modules/dnn/test/test_onnx_conformance_layer_parser_denylist.inl.hpp b/modules/dnn/test/test_onnx_conformance_layer_parser_denylist.inl.hpp index bceebe472e..51cfe518ce 100644 --- a/modules/dnn/test/test_onnx_conformance_layer_parser_denylist.inl.hpp +++ b/modules/dnn/test/test_onnx_conformance_layer_parser_denylist.inl.hpp @@ -102,6 +102,10 @@ "test_gathernd_example_float32", // Issues::Layer::Can't create layer "test_gathernd_example_int32", // ---- same as above --- "test_gathernd_example_int32_batch_dim1", // ---- same as above --- +"test_gelu_default_1_expanded", // parser: no corresponding layer for CastLike +"test_gelu_default_2_expanded", // parser: no corresponding layer for CastLike +"test_gelu_tanh_1_expanded", // parser: no corresponding layer for CastLike +"test_gelu_tanh_2_expanded", // parser: no corresponding layer for CastLike "test_gemm_all_attributes", // Issue::Wrong output "test_gemm_alpha", // Issue::Wrong output "test_gemm_beta", // Issue::Wrong output diff --git a/modules/dnn/test/test_onnx_importer.cpp b/modules/dnn/test/test_onnx_importer.cpp index 5e850a2191..f6d05464eb 100644 --- a/modules/dnn/test/test_onnx_importer.cpp +++ b/modules/dnn/test/test_onnx_importer.cpp @@ -19,7 +19,8 @@ void yoloPostProcessing( std::vector& keep_boxes, float conf_threshold, float iou_threshold, - const std::string& test_name); + const std::string& model_name, + const int nc=80); template static std::string _tf(TString filename, bool required = true) @@ -2735,7 +2736,8 @@ void yoloPostProcessing( std::vector& keep_boxes, float conf_threshold, float iou_threshold, - const std::string& test_name + const std::string& model_name, + const int nc ){ // Retrieve @@ -2743,11 +2745,13 @@ void yoloPostProcessing( std::vector confidences; std::vector boxes; - if (test_name == "yolov8"){ + if (model_name == "yolov8" || model_name == "yolov10" || + model_name == "yolov9") + { cv::transposeND(outs[0], {0, 2, 1}, outs[0]); } - if (test_name == "yolonas"){ + if (model_name == "yolonas"){ // outs contains 2 elemets of shape [1, 8400, 80] and [1, 8400, 4]. Concat them to get [1, 8400, 84] Mat concat_out; // squeeze the first dimension @@ -2761,22 +2765,27 @@ void yoloPostProcessing( outs[0] = outs[0].reshape(0, std::vector{1, 8400, 84}); } + // assert if last dim is 85 or 84 + CV_CheckEQ(outs[0].dims, 3, "Invalid output shape. The shape should be [1, #anchors, 85 or 84]"); + CV_CheckEQ((outs[0].size[2] == nc + 5 || outs[0].size[2] == 80 + 4), true, "Invalid output shape: "); + for (auto preds : outs){ preds = preds.reshape(1, preds.size[1]); // [1, 8400, 85] -> [8400, 85] for (int i = 0; i < preds.rows; ++i) { // filter out non object - float obj_conf = (test_name == "yolov8" || test_name == "yolonas") ? 1.0f : preds.at(i, 4) ; + float obj_conf = (model_name == "yolov8" || model_name == "yolonas" || + model_name == "yolov9" || model_name == "yolov10") ? 1.0f : preds.at(i, 4) ; if (obj_conf < conf_threshold) continue; - Mat scores = preds.row(i).colRange((test_name == "yolov8" || test_name == "yolonas") ? 4 : 5, preds.cols); + Mat scores = preds.row(i).colRange((model_name == "yolov8" || model_name == "yolonas" || model_name == "yolov9" || model_name == "yolov10") ? 4 : 5, preds.cols); double conf; Point maxLoc; minMaxLoc(scores, 0, &conf, 0, &maxLoc); - conf = (test_name == "yolov8" || test_name == "yolonas") ? conf : conf * obj_conf; + conf = (model_name == "yolov8" || model_name == "yolonas" || model_name == "yolov9" || model_name == "yolov10") ? conf : conf * obj_conf; if (conf < conf_threshold) continue; @@ -2787,15 +2796,14 @@ void yoloPostProcessing( double w = det[2]; double h = det[3]; - // std::cout << "cx: " << cx << " cy: " << cy << " w: " << w << " h: " << h << " conf: " << conf << " idx: " << maxLoc.x << std::endl; // [x1, y1, x2, y2] - if (test_name == "yolonas"){ + if (model_name == "yolonas" || model_name == "yolov10"){ boxes.push_back(Rect2d(cx, cy, w, h)); } else { boxes.push_back(Rect2d(cx - 0.5 * w, cy - 0.5 * h, cx + 0.5 * w, cy + 0.5 * h)); } - classIds.push_back(maxLoc.x); + classIds.push_back(maxLoc.x); confidences.push_back(conf); } } @@ -2812,7 +2820,75 @@ void yoloPostProcessing( } } +TEST_P(Test_ONNX_nets, YOLOv10) +{ + + std::string weightPath = _tf("models/yolov10s.onnx", false); + + Size targetSize{640, 480}; + float conf_threshold = 0.50; + float iou_threshold = 0.50; + + std::vector refClassIds{1, 16, 7}; + std::vector refScores{0.9510f, 0.9454f, 0.8404f}; + + std::vector refBoxes{ + Rect2d(105.5014, 112.8838, 472.9274, 350.0603), + Rect2d(109.8231, 185.7994, 258.5916, 452.9302), + Rect2d(388.5018, 62.1034, 576.6399, 143.3986) + }; + + Image2BlobParams imgParams( + Scalar::all(1 / 255.0), + targetSize, + Scalar::all(0), + true, + CV_32F, + DNN_LAYOUT_NCHW, + DNN_PMODE_LETTERBOX, + Scalar::all(114) + ); + + testYOLO( + weightPath, refClassIds, refScores, refBoxes, + imgParams, conf_threshold, iou_threshold, + 1.0e-4, 1.0e-4, "yolov10"); +} + +TEST_P(Test_ONNX_nets, YOLOv9) +{ + + std::string weightPath = _tf("models/yolov9t.onnx", false); + Size targetSize{640, 480}; + float conf_threshold = 0.50; + float iou_threshold = 0.50; + + std::vector refClassIds{1, 16, 2}; // wrong class mapping for yolov9 + std::vector refScores{0.959274f, 0.901125f, 0.559396f}; + + std::vector refBoxes{ + Rect2d(106.255, 107.927, 472.497, 350.309), + Rect2d(108.633, 185.256, 259.287, 450.672), + Rect2d(390.701, 62.1454, 576.928, 141.795) + }; + + Image2BlobParams imgParams( + Scalar::all(1 / 255.0), + targetSize, + Scalar::all(0), + true, + CV_32F, + DNN_LAYOUT_NCHW, + DNN_PMODE_LETTERBOX, + Scalar::all(114) + ); + + testYOLO( + weightPath, refClassIds, refScores, refBoxes, + imgParams, conf_threshold, iou_threshold, + 1.0e-4, 1.0e-4, "yolov9"); +} TEST_P(Test_ONNX_nets, YOLOX) { applyTestTag(CV_TEST_TAG_DEBUG_VERYLONG); @@ -3099,6 +3175,20 @@ TEST_P(Test_ONNX_layers, Attention) { TEST_P(Test_ONNX_layers, AttentionSingleHead) { testONNXModels("attention_single_head"); } +TEST_P(Test_ONNX_layers, PyTorchAttentionSingleHead) { + // 5.x specific bug: https://github.com/opencv/opencv/issues/25921 + if (target == DNN_TARGET_OPENCL) + applyTestTag(CV_TEST_TAG_DNN_SKIP_OPENCL); + + if (target == DNN_TARGET_OPENCL_FP16) + applyTestTag(CV_TEST_TAG_DNN_SKIP_OPENCL_FP16); + + testONNXModels("pytorch_attention_single_head"); +} + +TEST_P(Test_ONNX_layers, PyTorchUnflatten){ + testONNXModels("unflatten"); +} TEST_P(Test_ONNX_nets, ViT_B_32) { applyTestTag(CV_TEST_TAG_LONG, CV_TEST_TAG_DEBUG_LONG); @@ -3131,6 +3221,15 @@ TEST_P(Test_ONNX_nets, ViT_B_32) { l1 = 0.008; lInf = 0.04; } + if (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH) { + if (target == DNN_TARGET_CPU) { + l1 = 4.4e-5; // Expected: (normL1) <= (l1), actual: 4.31208e-05 vs 1e-05 + lInf = 0.0002; // Expected: (normInf) <= (lInf), actual: 0.000194907 vs 0.0001 + } else if (target == DNN_TARGET_OPENCL || target == DNN_TARGET_OPENCL_FP16) { + l1 = 0.0092; // Expected: (normL1) <= (l1), actual: 0.00918349 vs 4.4e-05 + lInf = 0.056; // Expected: (normInf) <= (lInf), actual: 0.0556431 vs 0.0002 + } + } normAssert(ref, out, "ViTB_32", l1, lInf); } diff --git a/modules/gapi/src/backends/onnx/gonnxbackend.cpp b/modules/gapi/src/backends/onnx/gonnxbackend.cpp index e465a1ccc7..0d9a16a7bd 100644 --- a/modules/gapi/src/backends/onnx/gonnxbackend.cpp +++ b/modules/gapi/src/backends/onnx/gonnxbackend.cpp @@ -123,7 +123,7 @@ class ONNXCompiled { std::vector out_data; void Run(const std::vector& ins, - const std::vector& outs); + std::vector& outs); std::vector in_names_without_const; public: @@ -322,22 +322,20 @@ inline std::vector toORT(const cv::MatSize &sz) { inline void preprocess(const cv::Mat& src, const cv::gimpl::onnx::TensorInfo& ti, cv::Mat& dst) { - GAPI_Assert(src.depth() == CV_32F || src.depth() == CV_8U); // CNN input type const auto type = toCV(ti.type); - if (src.depth() == CV_32F) { + if (src.depth() != CV_8U) { // Just pass the tensor as-is. // No layout or dimension transformations done here! // TODO: This needs to be aligned across all NN backends. - GAPI_Assert(type == CV_32F && "Only 32F model input is supported for 32F input data"); const auto tensor_dims = toORT(src.size); if (tensor_dims.size() == ti.dims.size()) { for (size_t i = 0; i < ti.dims.size(); ++i) { GAPI_Assert((ti.dims[i] == -1 || ti.dims[i] == tensor_dims[i]) && - "32F tensor dimensions should match with all non-dynamic NN input dimensions"); + "Non-U8 tensor dimensions should match with all non-dynamic NN input dimensions"); } } else { - GAPI_Error("32F tensor size should match with NN input"); + GAPI_Error("Non-U8 tensor size should match with NN input"); } dst = src; @@ -471,6 +469,25 @@ inline Ort::Value createTensor(const Ort::MemoryInfo& memory_info, return createTensor(memory_info, tensor_params, data); case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT32: return createTensor(memory_info, tensor_params, data); + case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64:{ + // cv::Mat does not support int64 data directly. + // Following steps are applied to create an ONNX tensor from cv::Mat data: + // - First create a new ONNX tensor 'i64_tensor' with data type int64_t using the default allocator + // - Next retrieve a pointer to the mutable data buffer of 'i64_tensor' + // - Convert the data from int32 (see toCV function) to int64 and deep copy it into 'i64_tensor' + auto ort_dims = toORT(data.size); + + Ort::AllocatorWithDefaultOptions allocator; + Ort::Value i64_tensor = Ort::Value::CreateTensor(allocator, + ort_dims.data(), + ort_dims.size()); + int64_t* tensor_data = i64_tensor.GetTensorMutableData(); + + cv::gimpl::convertInt32ToInt64(data.ptr(), + tensor_data, + data.total()); + return i64_tensor; + } default: GAPI_Error("ONNX. Unsupported data type"); } @@ -747,9 +764,11 @@ ONNXCompiled::ONNXCompiled(const gapi::onnx::detail::ParamDesc &pp) in_tensor_info.end(), [](const cv::gimpl::onnx::TensorInfo &p) { return p.type == ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT - || p.type == ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT8; + || p.type == ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT8 + || p.type == ONNX_TENSOR_ELEMENT_DATA_TYPE_INT32 + || p.type == ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64; }) - && "Only FP32 and U8 inputs for NN are supported"); + && "Only FP32, INT32, INT64 and U8 inputs for NN are supported"); // Put mean and std in appropriate tensor params if (!params.mean.empty() || !params.stdev.empty()) { @@ -864,7 +883,7 @@ cv::Mat ONNXCompiled::allocOutput(int i) const { } void ONNXCompiled::Run(const std::vector& ins, - const std::vector& outs) { + std::vector& outs) { std::vector in_tensors, out_tensors; // Layer names order for run @@ -909,6 +928,17 @@ void ONNXCompiled::Run(const std::vector& ins, out_run_names.data(), &out_tensors.front(), params.output_names.size()); + if (out_tensor_info[0].type == ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64) { + // cv::Mat does not support int64 output data. + // Conversion from int 64 to int 32 is carried in the copyFromONNX function + // The output is written to out_mat + for (auto &&iter : ade::util::zip(ade::util::toRange(out_tensors), + ade::util::toRange(outs))) { + auto &out_tensor = std::get<0>(iter); + auto &out_mat = std::get<1>(iter); + copyFromONNX(out_tensor, out_mat); + } + } } else { // Hard path - run session & user-defined post-processing // NOTE: use another list of output names here diff --git a/modules/highgui/CMakeLists.txt b/modules/highgui/CMakeLists.txt index 0108626dfd..c1d42444c5 100644 --- a/modules/highgui/CMakeLists.txt +++ b/modules/highgui/CMakeLists.txt @@ -218,6 +218,9 @@ if(TARGET ocv.3rdparty.gtk3 OR TARGET ocv.3rdparty.gtk2) ) if(__gtk_dependency STREQUAL "ocv.3rdparty.gtk3") set(OPENCV_HIGHGUI_BUILTIN_BACKEND "GTK3") + if(OPENGL_LIBRARIES) + list(APPEND HIGHGUI_LIBRARIES "${OPENGL_LIBRARIES}") + endif() elseif(__gtk_dependency STREQUAL "ocv.3rdparty.gtk2") set(OPENCV_HIGHGUI_BUILTIN_BACKEND "GTK2") else() diff --git a/modules/highgui/cmake/detect_gtk.cmake b/modules/highgui/cmake/detect_gtk.cmake index 5e9412d427..fdca580aa9 100644 --- a/modules/highgui/cmake/detect_gtk.cmake +++ b/modules/highgui/cmake/detect_gtk.cmake @@ -26,17 +26,27 @@ if(WITH_GTK) else() ocv_add_external_target(gthread "${GTHREAD_INCLUDE_DIRS}" "${GTHREAD_LIBRARIES}" "HAVE_GTHREAD") endif() - if((WITH_OPENGL OR HAVE_OPENGL) AND HAVE_GTK2) - ocv_check_modules(GTKGLEXT gtkglext-1.0) - if(HAVE_GTKGLEXT) - ocv_add_external_target(gtkglext "${GTKGLEXT_INCLUDE_DIRS}" "${GTKGLEXT_LIBRARIES}" "HAVE_GTKGLEXT") + if((WITH_OPENGL OR HAVE_OPENGL) AND (HAVE_GTK2 OR HAVE_GTK3)) + if(HAVE_GTK2) + ocv_check_modules(GTKGLEXT gtkglext-1.0) + if(HAVE_GTKGLEXT) + # HACK for https://github.com/opencv/opencv/issues/20850 + # pkg-config reports some include directories that do not exist. Just filter them out. + set(GTKGLEXT_INCLUDE_DIRS_EXISTS "") + foreach(p ${GTKGLEXT_INCLUDE_DIRS}) + if (EXISTS "${p}") + list(APPEND GTKGLEXT_INCLUDE_DIRS_EXISTS "${p}") + endif() + endforeach() + ocv_add_external_target(gtkglext "${GTKGLEXT_INCLUDE_DIRS}" "${GTKGLEXT_LIBRARIES}" "HAVE_GTKGLEXT") + endif() endif() endif() elseif(HAVE_GTK) ocv_add_external_target(gtk "${GTK_INCLUDE_DIRS}" "${GTK_LIBRARIES}" "${GTK_DEFINES};HAVE_GTK") endif() -if(WITH_OPENGL AND HAVE_GTKGLEXT) +if(WITH_OPENGL) find_package(OpenGL QUIET) if(OPENGL_FOUND) set(HAVE_OPENGL TRUE) diff --git a/modules/highgui/src/window_gtk.cpp b/modules/highgui/src/window_gtk.cpp index 708a97b50a..76fe4b0447 100644 --- a/modules/highgui/src/window_gtk.cpp +++ b/modules/highgui/src/window_gtk.cpp @@ -46,10 +46,7 @@ #include -#if (GTK_MAJOR_VERSION == 3) && defined(HAVE_OPENGL) - #undef HAVE_OPENGL // no support with GTK3 -#endif -#if defined(HAVE_OPENGL) && !defined(HAVE_GTKGLEXT) +#if (GTK_MAJOR_VERSION == 2) && defined(HAVE_OPENGL) && !defined(HAVE_GTKGLEXT) #undef HAVE_OPENGL // gtkglext is required #endif @@ -68,9 +65,13 @@ #endif #ifdef HAVE_OPENGL + #ifdef GTK_VERSION3 + #include + #else #include - #include #include + #endif + #include #endif #include @@ -575,7 +576,7 @@ struct CvWindow : CvUIBase last_key(0), flags(0), status(0), on_mouse(NULL), on_mouse_param(NULL) #ifdef HAVE_OPENGL - ,useGl(false), glDrawCallback(NULL), glDrawData(NULL) + ,useGl(false), glDrawCallback(NULL), glDrawData(NULL), glArea(NULL) #endif { CV_LOG_INFO(NULL, "OpenCV/UI: creating GTK window: " << window_name); @@ -602,6 +603,7 @@ struct CvWindow : CvUIBase CvOpenGlDrawCallback glDrawCallback; void* glDrawData; + GtkWidget* glArea; #endif }; @@ -645,7 +647,7 @@ static int gtk_InitSystem( int argc, char** argv ) setlocale(LC_NUMERIC,"C"); - #ifdef HAVE_OPENGL + #if defined(HAVE_OPENGL) && not defined(GTK_VERSION3) // GTK3+ uses GtkGLArea so no need to check for GtkGLExt if (!gtk_gl_init_check(&argc, &argv)) { hasError = true; @@ -900,11 +902,42 @@ double cvGetOpenGlProp_GTK(const char* name) // OpenGL support #ifdef HAVE_OPENGL - namespace { + +#ifdef GTK_VERSION3 + + void glRealizeCallback(GtkGLArea* area, gpointer user_data) { + CV_UNUSED(user_data); + gtk_gl_area_make_current(area); + if (gtk_gl_area_get_error(area) != NULL) + CV_Error(cv::Error::OpenGlApiCallError, "OpenGL context is not initialized"); + } + + gboolean glRenderCallback(GtkGLArea* area, GdkGLContext* context, gpointer user_data) { + CV_UNUSED(context); + CvWindow* window = (CvWindow*)user_data; + gtk_gl_area_make_current(area); + if (gtk_gl_area_get_error(area) != NULL) { + CV_Error(cv::Error::OpenGlApiCallError, "OpenGL context is not initialized"); + return FALSE; + } + if(window->glDrawCallback) { + window->glDrawCallback(window->glDrawData); + } +// gtk_gl_area_queue_render(area); + return TRUE; + } + +#endif + void createGlContext(CvWindow* window) { + #ifdef GTK_VERSION3 + g_signal_connect(window->glArea, "realize", G_CALLBACK(glRealizeCallback), window); + g_signal_connect(window->glArea, "render", G_CALLBACK(glRenderCallback), window); + #else + GdkGLConfig* glconfig; // Try double-buffered visual @@ -916,11 +949,24 @@ namespace if (!gtk_widget_set_gl_capability(window->widget, glconfig, NULL, TRUE, GDK_GL_RGBA_TYPE)) CV_Error( cv::Error::OpenGlApiCallError, "Can't Create A GL Device Context" ); + #endif + window->useGl = true; } void drawGl(CvWindow* window) { + #ifdef GTK_VERSION3 + + GtkGLArea* gtkGlArea = GTK_GL_AREA(window->glArea); + if (gtk_gl_area_get_error(gtkGlArea) != NULL) + CV_Error(cv::Error::OpenGlApiCallError, "Can't Activate The GL Rendering Context"); + + if (window->glDrawCallback) + window->glDrawCallback(window->glDrawData); + + #else + GdkGLContext* glcontext = gtk_widget_get_gl_context(window->widget); GdkGLDrawable* gldrawable = gtk_widget_get_gl_drawable(window->widget); @@ -940,6 +986,8 @@ namespace glFlush(); gdk_gl_drawable_gl_end(gldrawable); + + #endif } } @@ -1035,12 +1083,27 @@ static std::shared_ptr namedWindow_(const std::string& name, int flags window->frame = gtk_window_new( GTK_WINDOW_TOPLEVEL ); - window->paned = gtk_vbox_new( FALSE, 0 ); window->widget = cvImageWidgetNew( flags ); + +#if defined(HAVE_OPENGL) && defined(GTK_VERSION3) + if (flags & cv::WINDOW_OPENGL) { + window->glArea = gtk_gl_area_new(); + gtk_container_add(GTK_CONTAINER(window->frame), window->glArea); + gtk_widget_show(window->glArea); + } else { + window->paned = gtk_vbox_new( FALSE, 0 ); + gtk_box_pack_end( GTK_BOX(window->paned), window->widget, TRUE, TRUE, 0 ); + gtk_widget_show( window->widget ); + gtk_container_add( GTK_CONTAINER(window->frame), window->paned ); + gtk_widget_show( window->paned ); + } +#else + window->paned = gtk_vbox_new( FALSE, 0 ); gtk_box_pack_end( GTK_BOX(window->paned), window->widget, TRUE, TRUE, 0 ); gtk_widget_show( window->widget ); gtk_container_add( GTK_CONTAINER(window->frame), window->paned ); gtk_widget_show( window->paned ); +#endif #ifndef HAVE_OPENGL if (flags & cv::WINDOW_OPENGL) @@ -1116,9 +1179,6 @@ static std::shared_ptr namedWindow_(const std::string& name, int flags void setOpenGLContextImpl(const char* name) { - GdkGLContext* glcontext; - GdkGLDrawable* gldrawable; - CV_Assert(name && "NULL name string"); CV_LOCK_MUTEX(); @@ -1130,11 +1190,24 @@ void setOpenGLContextImpl(const char* name) if (!window->useGl) CV_Error( cv::Error::OpenGlNotSupported, "Window doesn't support OpenGL" ); +#ifdef GTK_VERSION3 + + if(gtk_gl_area_get_error(GTK_GL_AREA(window->glArea)) != NULL) + CV_Error( cv::Error::OpenGlApiCallError, "Can't Activate The GL Rendering Context"); + +#else + + GdkGLContext* glcontext; + GdkGLDrawable* gldrawable; + glcontext = gtk_widget_get_gl_context(window->widget); gldrawable = gtk_widget_get_gl_drawable(window->widget); if (!gdk_gl_drawable_make_current(gldrawable, glcontext)) CV_Error( cv::Error::OpenGlApiCallError, "Can't Activate The GL Rendering Context" ); + +#endif + } void updateWindowImpl(const char* name) @@ -1148,7 +1221,20 @@ void updateWindowImpl(const char* name) return; // window does not refresh without this +#ifdef GTK_VERSION3 + + if ( GTK_IS_GL_AREA(window->glArea) ){ + gtk_gl_area_queue_render(GTK_GL_AREA(window->glArea)); + } else { + gtk_widget_queue_draw( GTK_WIDGET(window->widget)); + } + +#else + gtk_widget_queue_draw( GTK_WIDGET(window->widget) ); + +#endif + } void setOpenGLDrawCallbackImpl(const char* name, CvOpenGlDrawCallback callback, void* userdata) diff --git a/modules/imgcodecs/include/opencv2/imgcodecs.hpp b/modules/imgcodecs/include/opencv2/imgcodecs.hpp index eba25ce1cf..735b24db91 100644 --- a/modules/imgcodecs/include/opencv2/imgcodecs.hpp +++ b/modules/imgcodecs/include/opencv2/imgcodecs.hpp @@ -68,7 +68,8 @@ namespace cv enum ImreadModes { IMREAD_UNCHANGED = -1, //!< If set, return the loaded image as is (with alpha channel, otherwise it gets cropped). Ignore EXIF orientation. IMREAD_GRAYSCALE = 0, //!< If set, always convert image to the single channel grayscale image (codec internal conversion). - IMREAD_COLOR = 1, //!< If set, always convert image to the 3 channel BGR color image. + IMREAD_COLOR_BGR = 1, //!< If set, always convert image to the 3 channel BGR color image. + IMREAD_COLOR = 1, //!< Same as IMREAD_COLOR_BGR. IMREAD_ANYDEPTH = 2, //!< If set, return 16-bit/32-bit image when the input has the corresponding depth, otherwise convert it to 8-bit. IMREAD_ANYCOLOR = 4, //!< If set, the image is read in any possible color format. IMREAD_LOAD_GDAL = 8, //!< If set, use the gdal driver for loading the image. @@ -78,7 +79,8 @@ enum ImreadModes { IMREAD_REDUCED_COLOR_4 = 33, //!< If set, always convert image to the 3 channel BGR color image and the image size reduced 1/4. IMREAD_REDUCED_GRAYSCALE_8 = 64, //!< If set, always convert image to the single channel grayscale image and the image size reduced 1/8. IMREAD_REDUCED_COLOR_8 = 65, //!< If set, always convert image to the 3 channel BGR color image and the image size reduced 1/8. - IMREAD_IGNORE_ORIENTATION = 128 //!< If set, do not rotate the image according to EXIF's orientation flag. + IMREAD_IGNORE_ORIENTATION = 128, //!< If set, do not rotate the image according to EXIF's orientation flag. + IMREAD_COLOR_RGB = 256, //!< If set, always convert image to the 3 channel RGB color image. }; //! Imwrite flags @@ -268,7 +270,7 @@ Currently, the following file formats are supported: @param filename Name of file to be loaded. @param flags Flag that can take values of cv::ImreadModes */ -CV_EXPORTS_W Mat imread( const String& filename, int flags = IMREAD_COLOR ); +CV_EXPORTS_W Mat imread( const String& filename, int flags = IMREAD_COLOR_BGR ); /** @brief Loads an image from a file. @@ -279,7 +281,7 @@ This is an overloaded member function, provided for convenience. It differs from @note The image passing through the img parameter can be pre-allocated. The memory is reused if the shape and the type match with the load image. */ -CV_EXPORTS_W void imread( const String& filename, OutputArray dst, int flags = IMREAD_COLOR ); +CV_EXPORTS_W void imread( const String& filename, OutputArray dst, int flags = IMREAD_COLOR_BGR ); /** @brief Loads a multi-page image from a file. @@ -291,7 +293,7 @@ The function imreadmulti loads a multi-page image from the specified file into a */ CV_EXPORTS_W bool imreadmulti(const String& filename, CV_OUT std::vector& mats, int flags = IMREAD_ANYCOLOR); -/** @brief Loads a of images of a multi-page image from a file. +/** @brief Loads images of a multi-page image from a file. The function imreadmulti loads a specified range from a multi-page image from the specified file into a vector of Mat objects. @param filename Name of file to be loaded. @@ -303,7 +305,7 @@ The function imreadmulti loads a specified range from a multi-page image from th */ CV_EXPORTS_W bool imreadmulti(const String& filename, CV_OUT std::vector& mats, int start, int count, int flags = IMREAD_ANYCOLOR); -/** @brief Returns the number of images inside the give file +/** @brief Returns the number of images inside the given file The function imcount will return the number of pages in a multi-page image, or 1 for single-page images @param filename Name of file to be loaded. @@ -410,27 +412,47 @@ CV_EXPORTS_W bool imencode( const String& ext, InputArray img, CV_OUT std::vector& buf, const std::vector& params = std::vector()); -/** @brief Returns true if the specified image can be decoded by OpenCV +/** @brief Checks if the specified image file can be decoded by OpenCV. -@param filename File name of the image +The function haveImageReader checks if OpenCV is capable of reading the specified file. +This can be useful for verifying support for a given image format before attempting to load an image. + +@param filename The name of the file to be checked. +@return true if an image reader for the specified file is available and the file can be opened, false otherwise. + +@note The function checks the availability of image codecs that are either built into OpenCV or dynamically loaded. +It does not check for the actual existence of the file but rather the ability to read the specified file type. +If the file cannot be opened or the format is unsupported, the function will return false. + +@sa cv::haveImageWriter, cv::imread, cv::imdecode */ CV_EXPORTS_W bool haveImageReader( const String& filename ); -/** @brief Returns true if an image with the specified filename can be encoded by OpenCV +/** @brief Checks if the specified image file or specified file extension can be encoded by OpenCV. - @param filename File name of the image - */ +The function haveImageWriter checks if OpenCV is capable of writing images with the specified file extension. +This can be useful for verifying support for a given image format before attempting to save an image. + +@param filename The name of the file or the file extension (e.g., ".jpg", ".png"). +It is recommended to provide the file extension rather than the full file name. +@return true if an image writer for the specified extension is available, false otherwise. + +@note The function checks the availability of image codecs that are either built into OpenCV or dynamically loaded. +It does not check for the actual existence of the file but rather the ability to write files of the given type. + +@sa cv::haveImageReader, cv::imwrite, cv::imencode +*/ CV_EXPORTS_W bool haveImageWriter( const String& filename ); -/** @brief To read Multi Page images on demand +/** @brief To read multi-page images on demand -The ImageCollection class provides iterator API to read multi page images on demand. Create iterator +The ImageCollection class provides iterator API to read multi-page images on demand. Create iterator to the collection of the images and iterate over the collection. Decode the necessary page with operator*. The performance of page decoding is O(1) if collection is increment sequentially. If the user wants to access random page, then the time Complexity is O(n) because the collection has to be reinitialized every time in order to go to the correct page. However, the intermediate pages are not decoded during the process, so typically it's quite fast. -This is required because multipage codecs does not support going backwards. +This is required because multi-page codecs does not support going backwards. After decoding the one page, it is stored inside the collection cache. Hence, trying to get Mat object from already decoded page is O(1). If you need memory, you can use .releaseCache() method to release cached index. The space complexity is O(n) if all pages are decoded into memory. The user is able to decode and release images on demand. diff --git a/modules/imgcodecs/perf/perf_jpeg.cpp b/modules/imgcodecs/perf/perf_jpeg.cpp index 7063ca909c..16610a3044 100644 --- a/modules/imgcodecs/perf/perf_jpeg.cpp +++ b/modules/imgcodecs/perf/perf_jpeg.cpp @@ -27,6 +27,23 @@ PERF_TEST(JPEG, Decode) SANITY_CHECK_NOTHING(); } +PERF_TEST(JPEG, Decode_rgb) +{ + String filename = getDataPath("stitching/boat1.jpg"); + + FILE *f = fopen(filename.c_str(), "rb"); + fseek(f, 0, SEEK_END); + long len = ftell(f); + fseek(f, 0, SEEK_SET); + vector file_buf((size_t)len); + EXPECT_EQ(len, (long)fread(&file_buf[0], 1, (size_t)len, f)); + fclose(f); f = NULL; + + TEST_CYCLE() imdecode(file_buf, IMREAD_COLOR_RGB); + + SANITY_CHECK_NOTHING(); +} + PERF_TEST(JPEG, Encode) { String filename = getDataPath("stitching/boat1.jpg"); diff --git a/modules/imgcodecs/perf/perf_png.cpp b/modules/imgcodecs/perf/perf_png.cpp index 1af4780882..5bbef590bd 100644 --- a/modules/imgcodecs/perf/perf_png.cpp +++ b/modules/imgcodecs/perf/perf_png.cpp @@ -30,6 +30,23 @@ PERF_TEST(PNG, decode) SANITY_CHECK_NOTHING(); } +PERF_TEST(PNG, decode_rgb) +{ + String filename = getDataPath("perf/2560x1600.png"); + + FILE *f = fopen(filename.c_str(), "rb"); + fseek(f, 0, SEEK_END); + long len = ftell(f); + fseek(f, 0, SEEK_SET); + vector file_buf((size_t)len); + EXPECT_EQ(len, (long)fread(&file_buf[0], 1, (size_t)len, f)); + fclose(f); f = NULL; + + TEST_CYCLE() imdecode(file_buf, IMREAD_COLOR_RGB); + + SANITY_CHECK_NOTHING(); +} + PERF_TEST(PNG, encode) { String filename = getDataPath("perf/2560x1600.png"); diff --git a/modules/imgcodecs/src/grfmt_avif.cpp b/modules/imgcodecs/src/grfmt_avif.cpp index 4b39ada60a..d71843c3f0 100644 --- a/modules/imgcodecs/src/grfmt_avif.cpp +++ b/modules/imgcodecs/src/grfmt_avif.cpp @@ -33,7 +33,7 @@ struct AvifImageDeleter { using AvifImageUniquePtr = std::unique_ptr; -avifResult CopyToMat(const avifImage *image, int channels, Mat *mat) { +avifResult CopyToMat(const avifImage *image, int channels, bool useRGB , Mat *mat) { CV_Assert((int)image->height == mat->rows); CV_Assert((int)image->width == mat->cols); if (channels == 1) { @@ -53,7 +53,10 @@ avifResult CopyToMat(const avifImage *image, int channels, Mat *mat) { avifRGBImage rgba; avifRGBImageSetDefaults(&rgba, image); if (channels == 3) { - rgba.format = AVIF_RGB_FORMAT_BGR; + if (useRGB) + rgba.format = AVIF_RGB_FORMAT_RGB; + else + rgba.format = AVIF_RGB_FORMAT_BGR; } else { CV_Assert(channels == 4); rgba.format = AVIF_RGB_FORMAT_BGRA; @@ -227,7 +230,7 @@ bool AvifDecoder::readData(Mat &img) { is_first_image_ = false; } - if (CopyToMat(decoder_->image, channels_, &read_img) != AVIF_RESULT_OK) { + if (CopyToMat(decoder_->image, channels_, m_use_rgb, &read_img) != AVIF_RESULT_OK) { CV_Error(Error::StsInternal, "Cannot convert from AVIF to Mat"); return false; } diff --git a/modules/imgcodecs/src/grfmt_base.cpp b/modules/imgcodecs/src/grfmt_base.cpp index 88f1c04f30..da343941bd 100644 --- a/modules/imgcodecs/src/grfmt_base.cpp +++ b/modules/imgcodecs/src/grfmt_base.cpp @@ -53,6 +53,7 @@ BaseImageDecoder::BaseImageDecoder() m_type = -1; m_buf_supported = false; m_scale_denom = 1; + m_use_rgb = false; } @@ -94,6 +95,11 @@ int BaseImageDecoder::setScale( const int& scale_denom ) return temp; } +void BaseImageDecoder::setRGB(bool useRGB) +{ + m_use_rgb = useRGB; +} + ImageDecoder BaseImageDecoder::newDecoder() const { return ImageDecoder(); diff --git a/modules/imgcodecs/src/grfmt_base.hpp b/modules/imgcodecs/src/grfmt_base.hpp index 816bef98fb..0d98c51ae2 100644 --- a/modules/imgcodecs/src/grfmt_base.hpp +++ b/modules/imgcodecs/src/grfmt_base.hpp @@ -73,6 +73,8 @@ public: virtual bool readHeader() = 0; virtual bool readData( Mat& img ) = 0; + virtual void setRGB(bool useRGB); + /// Called after readData to advance to the next page, if any. virtual bool nextPage() { return false; } @@ -89,6 +91,7 @@ protected: String m_signature; Mat m_buf; bool m_buf_supported; + bool m_use_rgb; // flag of decode image as RGB order instead of BGR. ExifReader m_exif; }; diff --git a/modules/imgcodecs/src/grfmt_bmp.cpp b/modules/imgcodecs/src/grfmt_bmp.cpp index 9c76597908..91ef23cc3f 100644 --- a/modules/imgcodecs/src/grfmt_bmp.cpp +++ b/modules/imgcodecs/src/grfmt_bmp.cpp @@ -544,6 +544,11 @@ decode_rle8_bad: ; throw; } + if (m_use_rgb && color && img.channels() == 3) + { + cv::cvtColor(img, img, cv::COLOR_BGR2RGB); + } + return result; } diff --git a/modules/imgcodecs/src/grfmt_exr.cpp b/modules/imgcodecs/src/grfmt_exr.cpp index 65a0e5e03b..d48e067edd 100644 --- a/modules/imgcodecs/src/grfmt_exr.cpp +++ b/modules/imgcodecs/src/grfmt_exr.cpp @@ -373,18 +373,35 @@ bool ExrDecoder::readData( Mat& img ) if( m_iscolor ) { - if( m_blue && (m_blue->xSampling != 1 || m_blue->ySampling != 1) ) - UpSample( data, channelstoread, step / xstep, m_blue->xSampling, m_blue->ySampling ); - if( m_green && (m_green->xSampling != 1 || m_green->ySampling != 1) ) - UpSample( data + xstep, channelstoread, step / xstep, m_green->xSampling, m_green->ySampling ); - if( m_red && (m_red->xSampling != 1 || m_red->ySampling != 1) ) - UpSample( data + 2 * xstep, channelstoread, step / xstep, m_red->xSampling, m_red->ySampling ); + if (m_use_rgb) + { + if( m_red && (m_red->xSampling != 1 || m_red->ySampling != 1) ) + UpSample( data, channelstoread, step / xstep, m_red->xSampling, m_red->ySampling ); + if( m_green && (m_green->xSampling != 1 || m_green->ySampling != 1) ) + UpSample( data + xstep, channelstoread, step / xstep, m_green->xSampling, m_green->ySampling ); + if( m_blue && (m_blue->xSampling != 1 || m_blue->ySampling != 1) ) + UpSample( data + 2 * xstep, channelstoread, step / xstep, m_blue->xSampling, m_blue->ySampling ); + } + else + { + if( m_blue && (m_blue->xSampling != 1 || m_blue->ySampling != 1) ) + UpSample( data, channelstoread, step / xstep, m_blue->xSampling, m_blue->ySampling ); + if( m_green && (m_green->xSampling != 1 || m_green->ySampling != 1) ) + UpSample( data + xstep, channelstoread, step / xstep, m_green->xSampling, m_green->ySampling ); + if( m_red && (m_red->xSampling != 1 || m_red->ySampling != 1) ) + UpSample( data + 2 * xstep, channelstoread, step / xstep, m_red->xSampling, m_red->ySampling ); + } } else if( m_green && (m_green->xSampling != 1 || m_green->ySampling != 1) ) UpSample( data, channelstoread, step / xstep, m_green->xSampling, m_green->ySampling ); if( chromatorgb ) - ChromaToBGR( (float *)data, m_height, channelstoread, step / xstep ); + { + if (m_use_rgb) + ChromaToRGB( (float *)data, m_height, channelstoread, step / xstep ); + else + ChromaToBGR( (float *)data, m_height, channelstoread, step / xstep ); + } } else { @@ -406,7 +423,12 @@ bool ExrDecoder::readData( Mat& img ) else { if( chromatorgb ) - ChromaToBGR( (float *)buffer, 1, defaultchannels, step ); + { + if (m_use_rgb) + ChromaToRGB( (float *)buffer, 1, defaultchannels, step ); + else + ChromaToBGR( (float *)buffer, 1, defaultchannels, step ); + } if( m_type == FLOAT ) { @@ -430,12 +452,24 @@ bool ExrDecoder::readData( Mat& img ) } if( color ) { - if( m_blue && (m_blue->xSampling != 1 || m_blue->ySampling != 1) ) - UpSampleY( data, defaultchannels, step / xstep, m_blue->ySampling ); - if( m_green && (m_green->xSampling != 1 || m_green->ySampling != 1) ) - UpSampleY( data + xstep, defaultchannels, step / xstep, m_green->ySampling ); - if( m_red && (m_red->xSampling != 1 || m_red->ySampling != 1) ) - UpSampleY( data + 2 * xstep, defaultchannels, step / xstep, m_red->ySampling ); + if (m_use_rgb) + { + if( m_red && (m_red->xSampling != 1 || m_red->ySampling != 1) ) + UpSampleY( data, defaultchannels, step / xstep, m_red->ySampling ); + if( m_green && (m_green->xSampling != 1 || m_green->ySampling != 1) ) + UpSampleY( data + xstep, defaultchannels, step / xstep, m_green->ySampling ); + if( m_blue && (m_blue->xSampling != 1 || m_blue->ySampling != 1) ) + UpSampleY( data + 2 * xstep, defaultchannels, step / xstep, m_blue->ySampling ); + } + else + { + if( m_blue && (m_blue->xSampling != 1 || m_blue->ySampling != 1) ) + UpSampleY( data, defaultchannels, step / xstep, m_blue->ySampling ); + if( m_green && (m_green->xSampling != 1 || m_green->ySampling != 1) ) + UpSampleY( data + xstep, defaultchannels, step / xstep, m_green->ySampling ); + if( m_red && (m_red->xSampling != 1 || m_red->ySampling != 1) ) + UpSampleY( data + 2 * xstep, defaultchannels, step / xstep, m_red->ySampling ); + } } else if( m_green && (m_green->xSampling != 1 || m_green->ySampling != 1) ) UpSampleY( data, 1, step / xstep, m_green->ySampling ); @@ -558,6 +592,47 @@ void ExrDecoder::ChromaToBGR( float *data, int numlines, int xstep, int ystep ) } } +void ExrDecoder::ChromaToRGB(float *data, int numlines, int xstep, int ystep) +{ + for( int y = 0; y < numlines; y++ ) + { + for( int x = 0; x < m_width; x++ ) + { + double b, Y, r; + if( m_type == FLOAT ) + { + b = data[y * ystep + x * xstep]; + Y = data[y * ystep + x * xstep + 1]; + r = data[y * ystep + x * xstep + 2]; + } + else + { + b = ((unsigned *)data)[y * ystep + x * xstep]; + Y = ((unsigned *)data)[y * ystep + x * xstep + 1]; + r = ((unsigned *)data)[y * ystep + x * xstep + 2]; + } + r = (r + 1) * Y; + b = (b + 1) * Y; + Y = (Y - b * m_chroma.blue[1] - r * m_chroma.red[1]) / m_chroma.green[1]; + + if( m_type == FLOAT ) + { + data[y * ystep + x * xstep] = (float)r; + data[y * ystep + x * xstep + 1] = (float)Y; + data[y * ystep + x * xstep + 2] = (float)b; + } + else + { + int t = cvRound(r); + ((unsigned *)data)[y * ystep + x * xstep + 0] = (unsigned)MAX(t, 0); + t = cvRound(Y); + ((unsigned *)data)[y * ystep + x * xstep + 1] = (unsigned)MAX(t, 0); + t = cvRound(b); + ((unsigned *)data)[y * ystep + x * xstep + 2] = (unsigned)MAX(t, 0); + } + } + } +} /** // convert one row to gray diff --git a/modules/imgcodecs/src/grfmt_exr.hpp b/modules/imgcodecs/src/grfmt_exr.hpp index a86874d228..48ca09acd8 100644 --- a/modules/imgcodecs/src/grfmt_exr.hpp +++ b/modules/imgcodecs/src/grfmt_exr.hpp @@ -83,6 +83,7 @@ protected: void UpSampleX( float *data, int xstep, int xsample ); void UpSampleY( uchar *data, int xstep, int ystep, int ysample ); void ChromaToBGR( float *data, int numlines, int xstep, int ystep ); + void ChromaToRGB( float *data, int numlines, int xstep, int ystep ); void RGBToGray( float *in, float *out ); InputFile *m_file; diff --git a/modules/imgcodecs/src/grfmt_gdal.cpp b/modules/imgcodecs/src/grfmt_gdal.cpp index 17581b576a..ff059338cf 100644 --- a/modules/imgcodecs/src/grfmt_gdal.cpp +++ b/modules/imgcodecs/src/grfmt_gdal.cpp @@ -397,13 +397,13 @@ bool GdalDecoder::readData( Mat& img ){ case GCI_PaletteIndex: case GCI_GrayIndex: case GCI_BlueBand: - color = 0; + color = m_use_rgb ? 2 : 0; break; case GCI_GreenBand: color = 1; break; case GCI_RedBand: - color = 2; + color = m_use_rgb ? 0 : 2; break; case GCI_AlphaBand: color = 3; diff --git a/modules/imgcodecs/src/grfmt_hdr.cpp b/modules/imgcodecs/src/grfmt_hdr.cpp index c9fec94aa3..3039626121 100644 --- a/modules/imgcodecs/src/grfmt_hdr.cpp +++ b/modules/imgcodecs/src/grfmt_hdr.cpp @@ -106,7 +106,13 @@ bool HdrDecoder::readData(Mat& _img) switch (_img.channels()) { case 1: cvtColor(img, _img, COLOR_BGR2GRAY); break; - case 3: img.copyTo(_img); break; + case 3: + // TODO, try to modify RGBE_ReadPixels_RLE to load rgb data directly. + if (m_use_rgb) + cv::cvtColor(img, _img, cv::COLOR_BGR2RGB); + else + img.copyTo(_img); + break; default: CV_Error(Error::StsError, "Wrong expected image channels, allowed: 1 and 3"); } return true; diff --git a/modules/imgcodecs/src/grfmt_jpeg.cpp b/modules/imgcodecs/src/grfmt_jpeg.cpp index 4e3b1df48d..a3a7f70c3c 100644 --- a/modules/imgcodecs/src/grfmt_jpeg.cpp +++ b/modules/imgcodecs/src/grfmt_jpeg.cpp @@ -437,13 +437,13 @@ bool JpegDecoder::readData( Mat& img ) if( cinfo->num_components != 4 ) { #ifdef JCS_EXTENSIONS - cinfo->out_color_space = JCS_EXT_BGR; + cinfo->out_color_space = m_use_rgb ? JCS_EXT_RGB : JCS_EXT_BGR; cinfo->out_color_components = 3; doDirectRead = true; // BGR -> BGR #else cinfo->out_color_space = JCS_RGB; cinfo->out_color_components = 3; - doDirectRead = false; // RGB -> BGR + doDirectRead = m_use_rgb ? true : false; // RGB -> BGR #endif } else @@ -499,7 +499,7 @@ bool JpegDecoder::readData( Mat& img ) for( int iy = 0 ; iy < m_height; iy ++ ) { uchar* data = img.ptr(iy); - jpeg_read_scanlines( cinfo, &data, 1 ); + if (jpeg_read_scanlines( cinfo, &data, 1 ) != 1) return false; } } else @@ -510,14 +510,24 @@ bool JpegDecoder::readData( Mat& img ) for( int iy = 0 ; iy < m_height; iy ++ ) { uchar* data = img.ptr(iy); - jpeg_read_scanlines( cinfo, buffer, 1 ); + if (jpeg_read_scanlines( cinfo, buffer, 1 ) != 1) return false; if( color ) { - if( cinfo->out_color_components == 3 ) - icvCvt_RGB2BGR_8u_C3R( buffer[0], 0, data, 0, Size(m_width,1) ); + if (m_use_rgb) + { + if( cinfo->out_color_components == 3 ) + icvCvt_BGR2RGB_8u_C3R( buffer[0], 0, data, 0, Size(m_width,1) ); + else + icvCvt_CMYK2RGB_8u_C4C3R( buffer[0], 0, data, 0, Size(m_width,1) ); + } else - icvCvt_CMYK2BGR_8u_C4C3R( buffer[0], 0, data, 0, Size(m_width,1) ); + { + if( cinfo->out_color_components == 3 ) + icvCvt_RGB2BGR_8u_C3R( buffer[0], 0, data, 0, Size(m_width,1) ); + else + icvCvt_CMYK2BGR_8u_C4C3R( buffer[0], 0, data, 0, Size(m_width,1) ); + } } else { diff --git a/modules/imgcodecs/src/grfmt_jpeg2000.cpp b/modules/imgcodecs/src/grfmt_jpeg2000.cpp index 0f80d89c8d..9b8680ac1f 100644 --- a/modules/imgcodecs/src/grfmt_jpeg2000.cpp +++ b/modules/imgcodecs/src/grfmt_jpeg2000.cpp @@ -286,11 +286,12 @@ bool Jpeg2KDecoder::readData( Mat& img ) { int ncmpts; int cmptlut[3]; + int swap_rb = m_use_rgb ? 0 : 2; if( color ) { - cmptlut[0] = jas_image_getcmptbytype( image, JAS_IMAGE_CT_RGB_B ); - cmptlut[1] = jas_image_getcmptbytype( image, JAS_IMAGE_CT_RGB_G ); - cmptlut[2] = jas_image_getcmptbytype( image, JAS_IMAGE_CT_RGB_R ); + cmptlut[0] = jas_image_getcmptbytype( image, swap_rb ); + cmptlut[1] = jas_image_getcmptbytype( image, 1 ); + cmptlut[2] = jas_image_getcmptbytype( image, swap_rb^2 ); if( cmptlut[0] < 0 || cmptlut[1] < 0 || cmptlut[2] < 0 ) result = false; ncmpts = 3; diff --git a/modules/imgcodecs/src/grfmt_jpeg2000_openjpeg.cpp b/modules/imgcodecs/src/grfmt_jpeg2000_openjpeg.cpp index c5b1a292cc..c05c0bca0e 100644 --- a/modules/imgcodecs/src/grfmt_jpeg2000_openjpeg.cpp +++ b/modules/imgcodecs/src/grfmt_jpeg2000_openjpeg.cpp @@ -350,7 +350,7 @@ opj_cparameters setupEncoderParameters(const std::vector& params) return parameters; } -bool decodeSRGBData(const opj_image_t& inImg, cv::Mat& outImg, uint8_t shift) +bool decodeSRGBData(const opj_image_t& inImg, cv::Mat& outImg, uint8_t shift, bool use_rgb) { using ImageComponents = std::vector; @@ -377,8 +377,9 @@ bool decodeSRGBData(const opj_image_t& inImg, cv::Mat& outImg, uint8_t shift) if (inChannels >= 3) { + int swap_rb = use_rgb ? 0 : 2; // Assume RGB (+ alpha) for 3 channels -> BGR - ImageComponents incomps { inImg.comps[2].data, inImg.comps[1].data, inImg.comps[0].data }; + ImageComponents incomps { inImg.comps[swap_rb].data, inImg.comps[1].data, inImg.comps[swap_rb^2].data }; // Assume RGBA for 4 channels -> BGRA if (outChannels > 3) { @@ -393,7 +394,7 @@ bool decodeSRGBData(const opj_image_t& inImg, cv::Mat& outImg, uint8_t shift) return false; } -bool decodeGrayscaleData(const opj_image_t& inImg, cv::Mat& outImg, uint8_t shift) +bool decodeGrayscaleData(const opj_image_t& inImg, cv::Mat& outImg, uint8_t shift, bool) { using ImageComponents = std::vector; @@ -411,7 +412,7 @@ bool decodeGrayscaleData(const opj_image_t& inImg, cv::Mat& outImg, uint8_t shif return false; } -bool decodeSYCCData(const opj_image_t& inImg, cv::Mat& outImg, uint8_t shift) +bool decodeSYCCData(const opj_image_t& inImg, cv::Mat& outImg, uint8_t shift, bool use_rgb) { using ImageComponents = std::vector; @@ -426,7 +427,10 @@ bool decodeSYCCData(const opj_image_t& inImg, cv::Mat& outImg, uint8_t shift) if (outChannels == 3 && inChannels >= 3) { copyToMat(ImageComponents { inImg.comps[0].data, inImg.comps[1].data, inImg.comps[2].data }, outImg, shift); - cvtColor(outImg, outImg, COLOR_YUV2BGR); + if (use_rgb) + cvtColor(outImg, outImg, COLOR_YUV2RGB); + else + cvtColor(outImg, outImg, COLOR_YUV2BGR); return true; } @@ -585,7 +589,7 @@ bool Jpeg2KOpjDecoderBase::readHeader() bool Jpeg2KOpjDecoderBase::readData( Mat& img ) { - using DecodeFunc = bool(*)(const opj_image_t&, cv::Mat&, uint8_t shift); + using DecodeFunc = bool(*)(const opj_image_t&, cv::Mat&, uint8_t shift, bool use_rgb); if (!opj_decode(codec_.get(), stream_.get(), image_.get())) { @@ -647,7 +651,7 @@ bool Jpeg2KOpjDecoderBase::readData( Mat& img ) CV_Assert(comp.data && "OpenJPEG2000: missing component data (unsupported / broken input)"); } - return decode(*image_, img, shift); + return decode(*image_, img, shift, m_use_rgb); } } // namespace detail diff --git a/modules/imgcodecs/src/grfmt_pam.cpp b/modules/imgcodecs/src/grfmt_pam.cpp index 979a31ca91..2c15ab244c 100644 --- a/modules/imgcodecs/src/grfmt_pam.cpp +++ b/modules/imgcodecs/src/grfmt_pam.cpp @@ -90,7 +90,7 @@ const static struct pam_header_field fields[] = { #define PAM_FIELDS_NO (sizeof (fields) / sizeof ((fields)[0])) typedef bool (*cvtFunc) (void *src, void *target, int width, int target_channels, - int target_depth); + int target_depth, bool use_rgb); struct channel_layout { uint rchan, gchan, bchan, graychan; @@ -108,7 +108,7 @@ struct pam_format { }; static bool rgb_convert (void *src, void *target, int width, int target_channels, - int target_depth); + int target_depth, bool use_rgb); const static struct pam_format formats[] = { {IMWRITE_PAM_FORMAT_NULL, "", NULL, {0, 0, 0, 0} }, @@ -125,19 +125,25 @@ const static struct pam_format formats[] = { */ static bool -rgb_convert (void *src, void *target, int width, int target_channels, int target_depth) +rgb_convert (void *src, void *target, int width, int target_channels, int target_depth, bool use_rgb) { bool ret = false; if (target_channels == 3) { switch (target_depth) { case CV_8U: - icvCvt_RGB2BGR_8u_C3R( (uchar*) src, 0, (uchar*) target, 0, - Size(width,1) ); + if (use_rgb) + memcpy(target, src, sizeof(uchar) * width); + else + icvCvt_RGB2BGR_8u_C3R( (uchar*) src, 0, (uchar*) target, 0, + Size(width,1) ); ret = true; break; case CV_16U: - icvCvt_RGB2BGR_16u_C3R( (ushort *)src, 0, (ushort *)target, 0, - Size(width,1) ); + if (use_rgb) + memcpy(target, src, sizeof(ushort) * width); + else + icvCvt_RGB2BGR_16u_C3R( (ushort *)src, 0, (ushort *)target, 0, + Size(width,1) ); ret = true; break; default: @@ -169,7 +175,7 @@ rgb_convert (void *src, void *target, int width, int target_channels, int target static void basic_conversion (void *src, const struct channel_layout *layout, int src_sampe_size, - int src_width, void *target, int target_channels, int target_depth) + int src_width, void *target, int target_channels, int target_depth, bool use_rgb) { switch (target_depth) { case CV_8U: @@ -182,11 +188,18 @@ basic_conversion (void *src, const struct channel_layout *layout, int src_sampe_ d[0] = d[1] = d[2] = s[layout->graychan]; break; case 3: - for( ; s < end; d += 3, s += src_sampe_size ) { - d[0] = s[layout->bchan]; - d[1] = s[layout->gchan]; - d[2] = s[layout->rchan]; - } + if (use_rgb) + for( ; s < end; d += 3, s += src_sampe_size ) { + d[0] = s[layout->rchan]; + d[1] = s[layout->gchan]; + d[2] = s[layout->bchan]; + } + else + for( ; s < end; d += 3, s += src_sampe_size ) { + d[0] = s[layout->bchan]; + d[1] = s[layout->gchan]; + d[2] = s[layout->rchan]; + } break; default: CV_Error(Error::StsInternal, ""); @@ -203,11 +216,18 @@ basic_conversion (void *src, const struct channel_layout *layout, int src_sampe_ d[0] = d[1] = d[2] = s[layout->graychan]; break; case 3: - for( ; s < end; d += 3, s += src_sampe_size ) { - d[0] = s[layout->bchan]; - d[1] = s[layout->gchan]; - d[2] = s[layout->rchan]; - } + if (use_rgb) + for( ; s < end; d += 3, s += src_sampe_size ) { + d[0] = s[layout->rchan]; + d[1] = s[layout->gchan]; + d[2] = s[layout->bchan]; + } + else + for( ; s < end; d += 3, s += src_sampe_size ) { + d[0] = s[layout->bchan]; + d[1] = s[layout->gchan]; + d[2] = s[layout->rchan]; + } break; default: CV_Error(Error::StsInternal, ""); @@ -610,18 +630,18 @@ bool PAMDecoder::readData(Mat& img) bool funcout = false; if (fmt->cvt_func) funcout = fmt->cvt_func (src, data, m_width, target_channels, - img.depth()); + img.depth(), m_use_rgb); /* fall back to default if there is no conversion function or it * can't handle the specified characteristics */ if (!funcout) basic_conversion (src, &fmt->layout, m_channels, - m_width, data, target_channels, img.depth()); + m_width, data, target_channels, img.depth(), m_use_rgb); /* default to selecting the first available channels */ } else { basic_conversion (src, &layout, m_channels, - m_width, data, target_channels, img.depth()); + m_width, data, target_channels, img.depth(), m_use_rgb); } } } diff --git a/modules/imgcodecs/src/grfmt_pfm.cpp b/modules/imgcodecs/src/grfmt_pfm.cpp index addae34b4f..b213d18fde 100644 --- a/modules/imgcodecs/src/grfmt_pfm.cpp +++ b/modules/imgcodecs/src/grfmt_pfm.cpp @@ -142,7 +142,7 @@ bool PFMDecoder::readData(Mat& mat) } } - if (buffer.channels() == 3) { + if (buffer.channels() == 3 && !m_use_rgb) { cv::cvtColor(buffer, buffer, cv::COLOR_BGR2RGB); } diff --git a/modules/imgcodecs/src/grfmt_png.cpp b/modules/imgcodecs/src/grfmt_png.cpp index aca73bd000..726c8b90b7 100644 --- a/modules/imgcodecs/src/grfmt_png.cpp +++ b/modules/imgcodecs/src/grfmt_png.cpp @@ -261,7 +261,7 @@ bool PngDecoder::readData( Mat& img ) png_set_gray_1_2_4_to_8( png_ptr ); #endif - if( (m_color_type & PNG_COLOR_MASK_COLOR) && color ) + if( (m_color_type & PNG_COLOR_MASK_COLOR) && color && !m_use_rgb) png_set_bgr( png_ptr ); // convert RGB to BGR else if( color ) png_set_gray_to_rgb( png_ptr ); // Gray->RGB diff --git a/modules/imgcodecs/src/grfmt_pxm.cpp b/modules/imgcodecs/src/grfmt_pxm.cpp index 76290c43de..d2ce60c743 100644 --- a/modules/imgcodecs/src/grfmt_pxm.cpp +++ b/modules/imgcodecs/src/grfmt_pxm.cpp @@ -340,7 +340,9 @@ bool PxMDecoder::readData( Mat& img ) { if( color ) { - if( img.depth() == CV_8U ) + if (m_use_rgb) + memcpy(data, src, m_width * CV_ELEM_SIZE(img.type())); + else if( img.depth() == CV_8U ) icvCvt_RGB2BGR_8u_C3R( src, 0, data, 0, Size(m_width,1) ); else icvCvt_RGB2BGR_16u_C3R( (ushort *)src, 0, (ushort *)data, 0, Size(m_width,1) ); diff --git a/modules/imgcodecs/src/grfmt_spng.cpp b/modules/imgcodecs/src/grfmt_spng.cpp index fa15bd46c7..59b2decc6e 100644 --- a/modules/imgcodecs/src/grfmt_spng.cpp +++ b/modules/imgcodecs/src/grfmt_spng.cpp @@ -381,14 +381,14 @@ bool SPngDecoder::readData(Mat &img) break; ret = spng_decode_row(png_ptr, buffer[row_info.row_num], image_width); - if (ihdr.interlace_method == 0) + if (ihdr.interlace_method == 0 && !m_use_rgb) { icvCvt_RGBA2BGRA_16u_C4R(reinterpret_cast(buffer[row_info.row_num]), 0, reinterpret_cast(buffer[row_info.row_num]), 0, Size(m_width, 1)); } } while (ret == SPNG_OK); - if (ihdr.interlace_method) + if (ihdr.interlace_method && !m_use_rgb) { icvCvt_RGBA2BGRA_16u_C4R(reinterpret_cast(img.data), step * 2, reinterpret_cast(img.data), step * 2, Size(m_width, m_height)); } @@ -402,12 +402,12 @@ bool SPngDecoder::readData(Mat &img) break; ret = spng_decode_row(png_ptr, buffer[row_info.row_num], image_width); - if (ihdr.interlace_method == 0) + if (ihdr.interlace_method == 0 && !m_use_rgb) { icvCvt_RGBA2BGRA_8u_C4R(buffer[row_info.row_num], 0, buffer[row_info.row_num], 0, Size(m_width, 1)); } } while (ret == SPNG_OK); - if (ihdr.interlace_method) + if (ihdr.interlace_method && !m_use_rgb) { icvCvt_RGBA2BGRA_8u_C4R(img.data, step, img.data, step, Size(m_width, m_height)); } @@ -421,13 +421,13 @@ bool SPngDecoder::readData(Mat &img) break; ret = spng_decode_row(png_ptr, buffer[row_info.row_num], image_width); - if (ihdr.interlace_method == 0) + if (ihdr.interlace_method == 0 && !m_use_rgb) { icvCvt_RGB2BGR_16u_C3R(reinterpret_cast(buffer[row_info.row_num]), 0, reinterpret_cast(buffer[row_info.row_num]), 0, Size(m_width, 1)); } } while (ret == SPNG_OK); - if (ihdr.interlace_method) + if (ihdr.interlace_method && !m_use_rgb) { icvCvt_RGB2BGR_16u_C3R(reinterpret_cast(img.data), step, reinterpret_cast(img.data), step, Size(m_width, m_height)); @@ -442,12 +442,12 @@ bool SPngDecoder::readData(Mat &img) break; ret = spng_decode_row(png_ptr, buffer[row_info.row_num], image_width); - if (ihdr.interlace_method == 0) + if (ihdr.interlace_method == 0 && !m_use_rgb) { icvCvt_RGB2BGR_8u_C3R(buffer[row_info.row_num], 0, buffer[row_info.row_num], 0, Size(m_width, 1)); } } while (ret == SPNG_OK); - if (ihdr.interlace_method) + if (ihdr.interlace_method && !m_use_rgb) { icvCvt_RGB2BGR_8u_C3R(img.data, step, img.data, step, Size(m_width, m_height)); } diff --git a/modules/imgcodecs/src/grfmt_sunras.cpp b/modules/imgcodecs/src/grfmt_sunras.cpp index 31c0286248..798f295376 100644 --- a/modules/imgcodecs/src/grfmt_sunras.cpp +++ b/modules/imgcodecs/src/grfmt_sunras.cpp @@ -342,7 +342,7 @@ bad_decoding_end: if( color ) { - if( m_type == RAS_FORMAT_RGB ) + if( m_type == RAS_FORMAT_RGB || m_use_rgb) icvCvt_RGB2BGR_8u_C3R(src, 0, data, 0, Size(m_width,1) ); else memcpy(data, src, std::min(step, (size_t)src_pitch)); @@ -365,7 +365,7 @@ bad_decoding_end: if( color ) icvCvt_BGRA2BGR_8u_C4C3R( src + 4, 0, data, 0, Size(m_width,1), - m_type == RAS_FORMAT_RGB ? 2 : 0 ); + (m_type == RAS_FORMAT_RGB || m_use_rgb) ? 2 : 0 ); else icvCvt_BGRA2Gray_8u_C4C1R( src + 4, 0, data, 0, Size(m_width,1), m_type == RAS_FORMAT_RGB ? 2 : 0 ); diff --git a/modules/imgcodecs/src/grfmt_tiff.cpp b/modules/imgcodecs/src/grfmt_tiff.cpp index 3890df96bd..41607006e2 100644 --- a/modules/imgcodecs/src/grfmt_tiff.cpp +++ b/modules/imgcodecs/src/grfmt_tiff.cpp @@ -865,9 +865,14 @@ bool TiffDecoder::readData( Mat& img ) break; case MAKE_FLAG( 3, 3 ): // RGB to BGR - icvCvt_BGR2RGB_8u_C3R( bstart, 0, - img_line_buffer, 0, - Size(tile_width, 1) ); + if (m_use_rgb) + memcpy( (void*) img_line_buffer, + (void*) bstart, + tile_width * sizeof(uchar) ); + else + icvCvt_BGR2RGB_8u_C3R( bstart, 0, + img_line_buffer, 0, + Size(tile_width, 1) ); break; case MAKE_FLAG( 4, 1 ): // RGBA to GRAY @@ -879,7 +884,7 @@ bool TiffDecoder::readData( Mat& img ) case MAKE_FLAG( 4, 3 ): // RGBA to BGR icvCvt_BGRA2BGR_8u_C4C3R( bstart, 0, img_line_buffer, 0, - Size(tile_width, 1), 2 ); + Size(tile_width, 1), m_use_rgb ? 0 : 2); break; case MAKE_FLAG( 4, 4 ): // RGBA to BGRA @@ -909,7 +914,7 @@ bool TiffDecoder::readData( Mat& img ) CV_CheckEQ(wanted_channels, 3, "TIFF-8bpp: BGR/BGRA images are supported only"); icvCvt_BGRA2BGR_8u_C4C3R(bstart + i*tile_width0*4, 0, img.ptr(img_y + tile_height - i - 1, x), 0, - Size(tile_width, 1), 2); + Size(tile_width, 1), m_use_rgb ? 0 : 2); } } else @@ -972,9 +977,12 @@ bool TiffDecoder::readData( Mat& img ) else if (ncn == 3) { CV_CheckEQ(wanted_channels, 3, ""); - icvCvt_RGB2BGR_16u_C3R(buffer16, 0, - img.ptr(img_y + i, x), 0, - Size(tile_width, 1)); + if (m_use_rgb) + memcpy(buffer16, img.ptr(img_y + i, x), tile_width * sizeof(ushort)); + else + icvCvt_RGB2BGR_16u_C3R(buffer16, 0, + img.ptr(img_y + i, x), 0, + Size(tile_width, 1)); } else if (ncn == 4) { @@ -989,7 +997,7 @@ bool TiffDecoder::readData( Mat& img ) CV_CheckEQ(wanted_channels, 3, "TIFF-16bpp: BGR/BGRA images are supported only"); icvCvt_BGRA2BGR_16u_C4C3R(buffer16, 0, img.ptr(img_y + i, x), 0, - Size(tile_width, 1), 2); + Size(tile_width, 1), m_use_rgb ? 0 : 2); } } else @@ -1032,7 +1040,7 @@ bool TiffDecoder::readData( Mat& img ) Mat m_tile(Size(tile_width0, tile_height0), CV_MAKETYPE((dst_bpp == 32) ? (depth == CV_32S ? CV_32S : CV_32F) : CV_64F, ncn), src_buffer); Rect roi_tile(0, 0, tile_width, tile_height); Rect roi_img(x, img_y, tile_width, tile_height); - if (!m_hdr && ncn == 3) + if (!m_hdr && ncn == 3 && !m_use_rgb) extend_cvtColor(m_tile(roi_tile), img(roi_img), COLOR_RGB2BGR); else if (!m_hdr && ncn == 4) extend_cvtColor(m_tile(roi_tile), img(roi_img), COLOR_RGBA2BGRA); @@ -1060,7 +1068,10 @@ bool TiffDecoder::readData( Mat& img ) if (m_hdr && depth >= CV_32F) { CV_Assert(photometric == PHOTOMETRIC_LOGLUV); - cvtColor(img, img, COLOR_XYZ2BGR); + if (m_use_rgb) + cvtColor(img, img, COLOR_XYZ2RGB); + else + cvtColor(img, img, COLOR_XYZ2BGR); } return true; } diff --git a/modules/imgcodecs/src/grfmt_webp.cpp b/modules/imgcodecs/src/grfmt_webp.cpp index 6f5cdfb6ab..ca54effe86 100644 --- a/modules/imgcodecs/src/grfmt_webp.cpp +++ b/modules/imgcodecs/src/grfmt_webp.cpp @@ -184,14 +184,22 @@ bool WebPDecoder::readData(Mat &img) if (channels == 3) { CV_CheckTypeEQ(read_img.type(), CV_8UC3, ""); - res_ptr = WebPDecodeBGRInto(data.ptr(), data.total(), out_data, - (int)out_data_size, (int)read_img.step); + if (m_use_rgb) + res_ptr = WebPDecodeRGBInto(data.ptr(), data.total(), out_data, + (int)out_data_size, (int)read_img.step); + else + res_ptr = WebPDecodeBGRInto(data.ptr(), data.total(), out_data, + (int)out_data_size, (int)read_img.step); } else if (channels == 4) { CV_CheckTypeEQ(read_img.type(), CV_8UC4, ""); - res_ptr = WebPDecodeBGRAInto(data.ptr(), data.total(), out_data, - (int)out_data_size, (int)read_img.step); + if (m_use_rgb) + res_ptr = WebPDecodeRGBAInto(data.ptr(), data.total(), out_data, + (int)out_data_size, (int)read_img.step); + else + res_ptr = WebPDecodeBGRAInto(data.ptr(), data.total(), out_data, + (int)out_data_size, (int)read_img.step); } if (res_ptr != out_data) diff --git a/modules/imgcodecs/src/loadsave.cpp b/modules/imgcodecs/src/loadsave.cpp index 87354b12f8..e21716b1f2 100644 --- a/modules/imgcodecs/src/loadsave.cpp +++ b/modules/imgcodecs/src/loadsave.cpp @@ -88,7 +88,7 @@ static inline int calcType(int type, int flags) if( (flags & IMREAD_ANYDEPTH) == 0 ) type = CV_MAKETYPE(CV_8U, CV_MAT_CN(type)); - if( (flags & IMREAD_COLOR) != 0 || + if( (flags & IMREAD_COLOR) != 0 || (flags & IMREAD_COLOR_RGB) != 0 || ((flags & IMREAD_ANYCOLOR) != 0 && CV_MAT_CN(type) > 1) ) type = CV_MAKETYPE(CV_MAT_DEPTH(type), 3); else @@ -432,6 +432,12 @@ imread_( const String& filename, int flags, OutputArray mat ) scale_denom = 8; } + // Try to decode image by RGB instead of BGR. + if (flags & IMREAD_COLOR_RGB && flags != IMREAD_UNCHANGED) + { + decoder->setRGB(true); + } + /// set the scale_denom in the driver decoder->setScale( scale_denom ); @@ -542,6 +548,9 @@ imreadmulti_(const String& filename, int flags, std::vector& mats, int star count = std::numeric_limits::max(); } + if (flags & IMREAD_COLOR_RGB && flags != IMREAD_UNCHANGED) + decoder->setRGB(true); + /// set the filename in the driver decoder->setSource(filename); @@ -811,6 +820,12 @@ imdecode_( const Mat& buf, int flags, Mat& mat ) scale_denom = 8; } + // Try to decode image by RGB instead of BGR. + if (flags & IMREAD_COLOR_RGB && flags != IMREAD_UNCHANGED) + { + decoder->setRGB(true); + } + /// set the scale_denom in the driver decoder->setScale( scale_denom ); @@ -947,6 +962,12 @@ imdecodemulti_(const Mat& buf, int flags, std::vector& mats, int start, int if (!decoder) return 0; + // Try to decode image by RGB instead of BGR. + if (flags & IMREAD_COLOR_RGB && flags != IMREAD_UNCHANGED) + { + decoder->setRGB(true); + } + if (count < 0) { count = std::numeric_limits::max(); } diff --git a/modules/imgcodecs/src/utils.cpp b/modules/imgcodecs/src/utils.cpp index 0962ebea62..41fd9f5041 100644 --- a/modules/imgcodecs/src/utils.cpp +++ b/modules/imgcodecs/src/utils.cpp @@ -352,6 +352,25 @@ void icvCvt_CMYK2BGR_8u_C4C3R( const uchar* cmyk, int cmyk_step, } } +void icvCvt_CMYK2RGB_8u_C4C3R( const uchar* cmyk, int cmyk_step, + uchar* rgb, int rgb_step, Size size ) +{ + int i; + for( ; size.height--; ) + { + for( i = 0; i < size.width; i++, rgb += 3, cmyk += 4 ) + { + int c = cmyk[0], m = cmyk[1], y = cmyk[2], k = cmyk[3]; + c = k - ((255 - c)*k>>8); + m = k - ((255 - m)*k>>8); + y = k - ((255 - y)*k>>8); + rgb[0] = (uchar)c; rgb[1] = (uchar)m; rgb[2] = (uchar)y; + } + rgb += rgb_step - size.width*3; + cmyk += cmyk_step - size.width*4; + } +} + void icvCvt_CMYK2Gray_8u_C4C1R( const uchar* cmyk, int cmyk_step, uchar* gray, int gray_step, Size size ) diff --git a/modules/imgcodecs/src/utils.hpp b/modules/imgcodecs/src/utils.hpp index 43eb907f76..2a9451422e 100644 --- a/modules/imgcodecs/src/utils.hpp +++ b/modules/imgcodecs/src/utils.hpp @@ -115,6 +115,8 @@ void icvCvt_BGR5652BGR_8u_C2C3R( const uchar* bgr565, int bgr565_step, uchar* bgr, int bgr_step, Size size ); void icvCvt_CMYK2BGR_8u_C4C3R( const uchar* cmyk, int cmyk_step, uchar* bgr, int bgr_step, Size size ); +void icvCvt_CMYK2RGB_8u_C4C3R( const uchar* cmyk, int cmyk_step, + uchar* rgb, int rgb_step, Size size ); void icvCvt_CMYK2Gray_8u_C4C1R( const uchar* ycck, int ycck_step, uchar* gray, int gray_step, Size size ); diff --git a/modules/imgcodecs/test/test_avif.cpp b/modules/imgcodecs/test/test_avif.cpp index 72b7f54fea..227c69556d 100644 --- a/modules/imgcodecs/test/test_avif.cpp +++ b/modules/imgcodecs/test/test_avif.cpp @@ -150,7 +150,7 @@ INSTANTIATE_TEST_CASE_P( ::testing::ValuesIn({1, 3, 4}), ::testing::ValuesIn({0, 50, 100}), ::testing::ValuesIn({IMREAD_UNCHANGED, IMREAD_GRAYSCALE, - IMREAD_COLOR}))); + IMREAD_COLOR, IMREAD_COLOR_RGB}))); class Imgcodecs_Avif_Image_EncodeDecodeSuite : public Imgcodecs_Avif_Image_RoundTripSuite {}; @@ -183,7 +183,7 @@ INSTANTIATE_TEST_CASE_P( ::testing::ValuesIn({1, 3, 4}), ::testing::ValuesIn({0, 50, 100}), ::testing::ValuesIn({IMREAD_UNCHANGED, IMREAD_GRAYSCALE, - IMREAD_COLOR}))); + IMREAD_COLOR, IMREAD_COLOR_RGB}))); //////////////////////////////////////////////////////////////////////////////// @@ -311,7 +311,7 @@ INSTANTIATE_TEST_CASE_P( ::testing::Combine(::testing::ValuesIn({8, 10, 12}), ::testing::ValuesIn({1, 3}), ::testing::ValuesIn({50}), ::testing::ValuesIn({IMREAD_UNCHANGED, IMREAD_GRAYSCALE, - IMREAD_COLOR}))); + IMREAD_COLOR, IMREAD_COLOR_RGB}))); class Imgcodecs_Avif_Animation_WriteDecodeSuite : public Imgcodecs_Avif_Animation_RoundTripSuite {}; @@ -347,7 +347,7 @@ INSTANTIATE_TEST_CASE_P( ::testing::Combine(::testing::ValuesIn({8, 10, 12}), ::testing::ValuesIn({1, 3}), ::testing::ValuesIn({50}), ::testing::ValuesIn({IMREAD_UNCHANGED, IMREAD_GRAYSCALE, - IMREAD_COLOR}))); + IMREAD_COLOR, IMREAD_COLOR_RGB}))); } // namespace } // namespace opencv_test diff --git a/modules/imgcodecs/test/test_exr.impl.hpp b/modules/imgcodecs/test/test_exr.impl.hpp index 2418d9d817..c8cda11a63 100644 --- a/modules/imgcodecs/test/test_exr.impl.hpp +++ b/modules/imgcodecs/test/test_exr.impl.hpp @@ -192,6 +192,15 @@ TEST(Imgcodecs_EXR, read_YC_changeDepth) ASSERT_FALSE(img.empty()); ASSERT_EQ(CV_8UC3, img.type()); + const Mat img_rgb = cv::imread(filenameInput, IMREAD_COLOR_RGB); + + ASSERT_FALSE(img_rgb.empty()); + ASSERT_EQ(CV_8UC3, img_rgb.type()); + + cvtColor(img_rgb, img_rgb, COLOR_RGB2BGR); + + EXPECT_TRUE(cvtest::norm(img, img_rgb, NORM_INF) == 0); + // Cannot test writing, EXR encoder doesn't support 8U depth } diff --git a/modules/imgcodecs/test/test_grfmt.cpp b/modules/imgcodecs/test/test_grfmt.cpp index 1e0bf47b47..947e560c81 100644 --- a/modules/imgcodecs/test/test_grfmt.cpp +++ b/modules/imgcodecs/test/test_grfmt.cpp @@ -108,6 +108,7 @@ const int basic_modes[] = IMREAD_UNCHANGED, IMREAD_GRAYSCALE, IMREAD_COLOR, + IMREAD_COLOR_RGB, IMREAD_ANYDEPTH, IMREAD_ANYCOLOR }; @@ -356,6 +357,10 @@ TEST(Imgcodecs_Bmp, rgba_scale) ASSERT_FALSE(img.empty()); ASSERT_EQ(CV_8UC3, img.type()); + img = cv::imread(filenameInput, IMREAD_COLOR_RGB); + ASSERT_FALSE(img.empty()); + ASSERT_EQ(CV_8UC3, img.type()); + data = img.ptr(); ASSERT_EQ(data[0], 255); ASSERT_EQ(data[1], 255); diff --git a/modules/imgcodecs/test/test_jpeg.cpp b/modules/imgcodecs/test/test_jpeg.cpp index ee9da01aa7..503848068a 100644 --- a/modules/imgcodecs/test/test_jpeg.cpp +++ b/modules/imgcodecs/test/test_jpeg.cpp @@ -217,6 +217,7 @@ TEST_P(Imgcodecs_Jpeg_decode_cmyk, regression25274) INSTANTIATE_TEST_CASE_P( /* nothing */, Imgcodecs_Jpeg_decode_cmyk, testing::Values(cv::IMREAD_COLOR, + cv::IMREAD_COLOR_RGB, cv::IMREAD_GRAYSCALE, cv::IMREAD_ANYCOLOR)); @@ -327,6 +328,13 @@ TEST_P(Imgcodecs_Jpeg_encode_withLumaChromaQuality, basic) cv::Mat src = imread(fname, cv::IMREAD_COLOR); ASSERT_FALSE(src.empty()); + // Add imread RGB test + cv::Mat src_rgb = imread(fname, cv::IMREAD_COLOR_RGB); + ASSERT_FALSE(src_rgb.empty()); + + cvtColor(src_rgb, src_rgb, COLOR_RGB2BGR); + EXPECT_TRUE(cvtest::norm(src, src_rgb, NORM_INF) == 0); + std::vector jpegNormal; ASSERT_NO_THROW(cv::imencode(".jpg", src, jpegNormal)); diff --git a/modules/imgcodecs/test/test_png.cpp b/modules/imgcodecs/test/test_png.cpp index cdc7da39b2..13aca2e396 100644 --- a/modules/imgcodecs/test/test_png.cpp +++ b/modules/imgcodecs/test/test_png.cpp @@ -83,6 +83,14 @@ TEST(Imgcodecs_Png, read_color_palette_with_alpha) EXPECT_EQ(img.at(0, 0), Vec3b(0, 0, 255)); EXPECT_EQ(img.at(0, 1), Vec3b(0, 0, 255)); + img = imread(root + "readwrite/color_palette_alpha.png", IMREAD_COLOR_RGB); + ASSERT_FALSE(img.empty()); + ASSERT_TRUE(img.channels() == 3); + + // pixel is red in RGB + EXPECT_EQ(img.at(0, 0), Vec3b(255, 0, 0)); + EXPECT_EQ(img.at(0, 1), Vec3b(255, 0, 0)); + // Fourth Test : Read PNG without alpha, imread flag 1 img = imread(root + "readwrite/color_palette_no_alpha.png", IMREAD_COLOR); ASSERT_FALSE(img.empty()); @@ -91,6 +99,14 @@ TEST(Imgcodecs_Png, read_color_palette_with_alpha) // pixel is red in BGR EXPECT_EQ(img.at(0, 0), Vec3b(0, 0, 255)); EXPECT_EQ(img.at(0, 1), Vec3b(0, 0, 255)); + + img = imread(root + "readwrite/color_palette_no_alpha.png", IMREAD_COLOR_RGB); + ASSERT_FALSE(img.empty()); + ASSERT_TRUE(img.channels() == 3); + + // pixel is red in RGB + EXPECT_EQ(img.at(0, 0), Vec3b(255, 0, 0)); + EXPECT_EQ(img.at(0, 1), Vec3b(255, 0, 0)); } /** diff --git a/modules/imgcodecs/test/test_precomp.hpp b/modules/imgcodecs/test/test_precomp.hpp index 7e9a8edd71..f5fb5c82e4 100644 --- a/modules/imgcodecs/test/test_precomp.hpp +++ b/modules/imgcodecs/test/test_precomp.hpp @@ -50,6 +50,11 @@ void PrintTo(const ImreadModes& val, std::ostream* os) v &= ~IMREAD_IGNORE_ORIENTATION; *os << "IMREAD_IGNORE_ORIENTATION" << (v == 0 ? "" : " | "); } + if ((v & IMREAD_COLOR_RGB) != 0) + { + v &= ~IMREAD_COLOR_RGB; + *os << "IMREAD_COLOR_RGB" << (v == 0 ? "" : " | "); + } switch (v) { case IMREAD_UNCHANGED: return; @@ -65,6 +70,7 @@ void PrintTo(const ImreadModes& val, std::ostream* os) case IMREAD_REDUCED_GRAYSCALE_8: // fallthru case IMREAD_REDUCED_COLOR_8: *os << "REDUCED_8"; return; case IMREAD_IGNORE_ORIENTATION: return; + case IMREAD_COLOR_RGB: return; } // don't use "default:" to emit compiler warnings *os << "IMREAD_UNKNOWN(" << (int)v << ")"; } diff --git a/modules/imgcodecs/test/test_read_write.cpp b/modules/imgcodecs/test/test_read_write.cpp index 824688b366..255f819a9a 100644 --- a/modules/imgcodecs/test/test_read_write.cpp +++ b/modules/imgcodecs/test/test_read_write.cpp @@ -196,9 +196,19 @@ void test_image_io(const Mat& image, const std::string& fname, const std::string Mat buf_loaded = imdecode(Mat(buf), imreadFlag); EXPECT_FALSE(buf_loaded.empty()); + if (imreadFlag & IMREAD_COLOR_RGB && imreadFlag != -1) + { + cvtColor(buf_loaded, buf_loaded, COLOR_RGB2BGR); + } + Mat loaded = imread(fname, imreadFlag); EXPECT_FALSE(loaded.empty()); + if (imreadFlag & IMREAD_COLOR_RGB && imreadFlag != -1) + { + cvtColor(loaded, loaded, COLOR_RGB2BGR); + } + EXPECT_EQ(0, cv::norm(loaded, buf_loaded, NORM_INF)) << "imread() and imdecode() calls must provide the same result (bit-exact)"; double psnr = cvtest::PSNR(loaded, image); @@ -238,6 +248,7 @@ TEST_P(Imgcodecs_Image, read_write_BGR) Mat image = generateTestImageBGR(); EXPECT_NO_THROW(test_image_io(image, fname, ext, IMREAD_COLOR, psnrThreshold)); + EXPECT_NO_THROW(test_image_io(image, fname, ext, IMREAD_COLOR_RGB, psnrThreshold)); EXPECT_EQ(0, remove(fname.c_str())); } diff --git a/modules/imgcodecs/test/test_tiff.cpp b/modules/imgcodecs/test/test_tiff.cpp index ee40c54b59..fb607bf18f 100644 --- a/modules/imgcodecs/test/test_tiff.cpp +++ b/modules/imgcodecs/test/test_tiff.cpp @@ -53,7 +53,7 @@ enum ImreadMixModes { IMREAD_MIX_UNCHANGED = IMREAD_UNCHANGED , IMREAD_MIX_GRAYSCALE = IMREAD_GRAYSCALE , - IMREAD_MIX_COLOR = IMREAD_COLOR , + IMREAD_MIX_COLOR = IMREAD_COLOR | IMREAD_COLOR_RGB , IMREAD_MIX_GRAYSCALE_ANYDEPTH = IMREAD_GRAYSCALE | IMREAD_ANYDEPTH , IMREAD_MIX_GRAYSCALE_ANYCOLOR = IMREAD_GRAYSCALE | IMREAD_ANYCOLOR, IMREAD_MIX_GRAYSCALE_ANYDEPTH_ANYCOLOR = IMREAD_GRAYSCALE | IMREAD_ANYDEPTH | IMREAD_ANYCOLOR, @@ -125,7 +125,7 @@ TEST_P(Imgcodecs_Tiff_decode_Huge, regression) case IMREAD_GRAYSCALE | IMREAD_ANYCOLOR | IMREAD_ANYDEPTH: ncn = (ncn == 1)?1:3; break; - case IMREAD_COLOR: + case IMREAD_COLOR | IMREAD_COLOR_RGB: ncn = 3; depth = 1; break; @@ -818,6 +818,24 @@ TEST(Imgcodecs_Tiff, read_palette_color_image) ASSERT_EQ(CV_8UC3, img.type()); } +TEST(Imgcodecs_Tiff, read_palette_color_image_rgb_and_bgr) +{ + const string root = cvtest::TS::ptr()->get_data_path(); + const string filenameInput = root + "readwrite/test_palette_color_image.tif"; + + Mat img_rgb, img_bgr; + ASSERT_NO_THROW(img_rgb = cv::imread(filenameInput, IMREAD_COLOR_RGB)); + ASSERT_NO_THROW(img_bgr = cv::imread(filenameInput, IMREAD_COLOR_BGR)); + ASSERT_FALSE(img_rgb.empty()); + ASSERT_EQ(CV_8UC3, img_rgb.type()); + + ASSERT_FALSE(img_bgr.empty()); + ASSERT_EQ(CV_8UC3, img_bgr.type()); + + EXPECT_EQ(img_rgb.at(32, 24), Vec3b(255, 0, 0)); + EXPECT_EQ(img_bgr.at(32, 24), Vec3b(0, 0, 255)); +} + TEST(Imgcodecs_Tiff, read_4_bit_palette_color_image) { const string root = cvtest::TS::ptr()->get_data_path(); @@ -1066,6 +1084,7 @@ const int all_modes[] = IMREAD_UNCHANGED, IMREAD_GRAYSCALE, IMREAD_COLOR, + IMREAD_COLOR_RGB, IMREAD_ANYDEPTH, IMREAD_ANYCOLOR }; diff --git a/modules/imgcodecs/test/test_webp.cpp b/modules/imgcodecs/test/test_webp.cpp index 1f2cad7d89..3647714039 100644 --- a/modules/imgcodecs/test/test_webp.cpp +++ b/modules/imgcodecs/test/test_webp.cpp @@ -51,6 +51,12 @@ TEST(Imgcodecs_WebP, encode_decode_lossless_webp) ASSERT_FALSE(decode.empty()); EXPECT_TRUE(cvtest::norm(decode, img_webp, NORM_INF) == 0); + cv::Mat decode_rgb = cv::imdecode(buf, IMREAD_COLOR_RGB); + ASSERT_FALSE(decode_rgb.empty()); + + cvtColor(decode_rgb, decode_rgb, COLOR_RGB2BGR); + EXPECT_TRUE(cvtest::norm(decode_rgb, img_webp, NORM_INF) == 0); + ASSERT_FALSE(img_webp.empty()); EXPECT_TRUE(cvtest::norm(img, img_webp, NORM_INF) == 0); diff --git a/modules/imgproc/include/opencv2/imgproc.hpp b/modules/imgproc/include/opencv2/imgproc.hpp index 287888fa19..c33b833b0f 100644 --- a/modules/imgproc/include/opencv2/imgproc.hpp +++ b/modules/imgproc/include/opencv2/imgproc.hpp @@ -1541,12 +1541,14 @@ respectively (see #getGaussianKernel for details); to fully control the result r possible future modifications of all this semantics, it is recommended to specify all of ksize, sigmaX, and sigmaY. @param borderType pixel extrapolation method, see #BorderTypes. #BORDER_WRAP is not supported. +@param hint Implementation modfication flags. See #AlgorithmHint @sa sepFilter2D, filter2D, blur, boxFilter, bilateralFilter, medianBlur */ CV_EXPORTS_W void GaussianBlur( InputArray src, OutputArray dst, Size ksize, double sigmaX, double sigmaY = 0, - int borderType = BORDER_DEFAULT ); + int borderType = BORDER_DEFAULT, + AlgorithmHint hint = cv::ALGO_HINT_DEFAULT ); /** @brief Applies the bilateral filter to an image. @@ -4084,6 +4086,28 @@ CV_EXPORTS_W void approxPolyDP( InputArray curve, OutputArray approxCurve, double epsilon, bool closed ); +/** @brief Approximates a polygon with a convex hull with a specified accuracy and number of sides. + +The cv::approxPolyN function approximates a polygon with a convex hull +so that the difference between the contour area of the original contour and the new polygon is minimal. +It uses a greedy algorithm for contracting two vertices into one in such a way that the additional area is minimal. +Straight lines formed by each edge of the convex contour are drawn and the areas of the resulting triangles are considered. +Each vertex will lie either on the original contour or outside it. + +The algorithm based on the paper @cite LowIlie2003 . + +@param curve Input vector of a 2D points stored in std::vector or Mat, points must be float or integer. +@param approxCurve Result of the approximation. The type is vector of a 2D point (Point2f or Point) in std::vector or Mat. +@param nsides The parameter defines the number of sides of the result polygon. +@param epsilon_percentage defines the percentage of the maximum of additional area. +If it equals -1, it is not used. Otherwise algorighm stops if additional area is greater than contourArea(_curve) * percentage. +If additional area exceeds the limit, algorithm returns as many vertices as there were at the moment the limit was exceeded. +@param ensure_convex If it is true, algorithm creates a convex hull of input contour. Otherwise input vector should be convex. + */ +CV_EXPORTS_W void approxPolyN(InputArray curve, OutputArray approxCurve, + int nsides, float epsilon_percentage = -1.0, + bool ensure_convex = true); + /** @brief Calculates a contour perimeter or a curve length. The function computes a curve length or a closed contour perimeter. diff --git a/modules/imgproc/src/approx.cpp b/modules/imgproc/src/approx.cpp index 442e1ce4f7..ffc45c2624 100644 --- a/modules/imgproc/src/approx.cpp +++ b/modules/imgproc/src/approx.cpp @@ -39,6 +39,7 @@ // //M*/ #include "precomp.hpp" +#include /****************************************************************************************\ * Polygonal Approximation * @@ -284,3 +285,241 @@ void cv::approxPolyDP( InputArray _curve, OutputArray _approxCurve, Mat(nout, 1, CV_MAKETYPE(depth, 2), buf).copyTo(_approxCurve); } + +enum class PointStatus : int8_t +{ + REMOVED = -1, + RECALCULATE = 0, + CALCULATED = 1 +}; + +struct neighbours +{ + PointStatus pointStatus; + cv::Point2f point; + int next; + int prev; + + explicit neighbours(int next_ = -1, int prev_ = -1, const cv::Point2f& point_ = { -1, -1 }) + { + next = next_; + prev = prev_; + point = point_; + pointStatus = PointStatus::CALCULATED; + } +}; + +struct changes +{ + float area; + int vertex; + cv::Point2f intersection; + + explicit changes(float area_, int vertex_, const cv::Point2f& intersection_) + { + area = area_; + vertex = vertex_; + intersection = intersection_; + } + + bool operator < (const changes& elem) const + { + return (area < elem.area) || ((area == elem.area) && (vertex < elem.vertex)); + } + bool operator > (const changes& elem) const + { + return (area > elem.area) || ((area == elem.area) && (vertex > elem.vertex)); + } +}; + +/* + returns intersection point and extra area +*/ +static void recalculation(std::vector& hull, int vertex_id, float& area_, float& x, float& y) +{ + cv::Point2f vertex = hull[vertex_id].point, + next_vertex = hull[hull[vertex_id].next].point, + extra_vertex_1 = hull[hull[vertex_id].prev].point, + extra_vertex_2 = hull[hull[hull[vertex_id].next].next].point; + + cv::Point2f curr_edge = next_vertex - vertex, + prev_edge = vertex - extra_vertex_1, + next_edge = extra_vertex_2 - next_vertex; + + float cross = prev_edge.x * next_edge.y - prev_edge.y * next_edge.x; + if (abs(cross) < 1e-8) + { + area_ = FLT_MAX; + x = -1; + y = -1; + return; + } + + float t = (curr_edge.x * next_edge.y - curr_edge.y * next_edge.x) / cross; + cv::Point2f intersection = vertex + cv::Point2f(prev_edge.x * t, prev_edge.y * t); + + float area = 0.5f * abs((next_vertex.x - vertex.x) * (intersection.y - vertex.y) + - (intersection.x - vertex.x) * (next_vertex.y - vertex.y)); + + area_ = area; + x = intersection.x; + y = intersection.y; +} + +static void update(std::vector& hull, int vertex_id) +{ + neighbours& v1 = hull[vertex_id], & removed = hull[v1.next], & v2 = hull[removed.next]; + + removed.pointStatus = PointStatus::REMOVED; + v1.pointStatus = PointStatus::RECALCULATE; + v2.pointStatus = PointStatus::RECALCULATE; + hull[v1.prev].pointStatus = PointStatus::RECALCULATE; + v1.next = removed.next; + v2.prev = removed.prev; +} + +/* + A greedy algorithm based on contraction of vertices for approximating a convex contour by a bounding polygon +*/ +void cv::approxPolyN(InputArray _curve, OutputArray _approxCurve, + int nsides, float epsilon_percentage, bool ensure_convex) +{ + CV_INSTRUMENT_REGION(); + + CV_Assert(epsilon_percentage > 0 || epsilon_percentage == -1); + CV_Assert(nsides > 2); + + if (_approxCurve.fixedType()) + { + CV_Assert(_approxCurve.type() == CV_32FC2 || _approxCurve.type() == CV_32SC2); + } + + Mat curve; + int depth = _curve.depth(); + + CV_Assert(depth == CV_32F || depth == CV_32S); + + if (ensure_convex) + { + cv::convexHull(_curve, curve); + } + else + { + CV_Assert(isContourConvex(_curve)); + curve = _curve.getMat(); + } + + CV_Assert((curve.cols == 1 && curve.rows >= nsides) + || (curve.rows == 1 && curve.cols >= nsides)); + + if (curve.rows == 1) + { + curve = curve.reshape(0, curve.cols); + } + + std::vector hull(curve.rows); + int size = curve.rows; + std::priority_queue, std::greater> areas; + float extra_area = 0, max_extra_area = epsilon_percentage * static_cast(contourArea(_curve)); + + if (curve.depth() == CV_32S) + { + for (int i = 0; i < size; ++i) + { + Point t = curve.at(i, 0); + hull[i] = neighbours(i + 1, i - 1, Point2f(static_cast(t.x), static_cast(t.y))); + } + } + else + { + for (int i = 0; i < size; ++i) + { + Point2f t = curve.at(i, 0); + hull[i] = neighbours(i + 1, i - 1, t); + } + } + hull[0].prev = size - 1; + hull[size - 1].next = 0; + + if (size > nsides) + { + for (int vertex_id = 0; vertex_id < size; ++vertex_id) + { + float area, new_x, new_y; + recalculation(hull, vertex_id, area, new_x, new_y); + + areas.push(changes(area, vertex_id, Point2f(new_x, new_y))); + } + } + + while (size > nsides) + { + changes base = areas.top(); + int vertex_id = base.vertex; + + if (hull[vertex_id].pointStatus == PointStatus::REMOVED) + { + areas.pop(); + } + else if (hull[vertex_id].pointStatus == PointStatus::RECALCULATE) + { + float area, new_x, new_y; + areas.pop(); + recalculation(hull, vertex_id, area, new_x, new_y); + + areas.push(changes(area, vertex_id, Point2f(new_x, new_y))); + hull[vertex_id].pointStatus = PointStatus::CALCULATED; + } + else + { + if (epsilon_percentage != -1) + { + extra_area += base.area; + if (extra_area > max_extra_area) + { + break; + } + } + + size--; + hull[vertex_id].point = base.intersection; + update(hull, vertex_id); + } + } + + if (_approxCurve.fixedType()) + { + depth = _approxCurve.depth(); + } + _approxCurve.create(1, size, CV_MAKETYPE(depth, 2)); + Mat buf = _approxCurve.getMat(); + int last_free = 0; + + if (depth == CV_32S) + { + for (int i = 0; i < curve.rows; ++i) + { + if (hull[i].pointStatus != PointStatus::REMOVED) + { + Point t = Point(static_cast(round(hull[i].point.x)), + static_cast(round(hull[i].point.y))); + + buf.at(0, last_free) = t; + last_free++; + } + } + } + else + { + for (int i = 0; i < curve.rows; ++i) + { + if (hull[i].pointStatus != PointStatus::REMOVED) + { + buf.at(0, last_free) = hull[i].point; + last_free++; + } + } + } +} + +/* End of file. */ diff --git a/modules/imgproc/src/smooth.dispatch.cpp b/modules/imgproc/src/smooth.dispatch.cpp index c893150d1c..52581b4e42 100644 --- a/modules/imgproc/src/smooth.dispatch.cpp +++ b/modules/imgproc/src/smooth.dispatch.cpp @@ -384,7 +384,7 @@ static bool ocl_GaussianBlur_8UC1(InputArray _src, OutputArray _dst, Size ksize, #endif -#if defined ENABLE_IPP_GAUSSIAN_BLUR // see CMake's OPENCV_IPP_GAUSSIAN_BLUR option +#ifdef ENABLE_IPP_GAUSSIAN_BLUR // see CMake's OPENCV_IPP_GAUSSIAN_BLUR option #define IPP_DISABLE_GAUSSIAN_BLUR_LARGE_KERNELS_1TH 1 #define IPP_DISABLE_GAUSSIAN_BLUR_16SC4_1TH 1 @@ -442,14 +442,14 @@ private: #endif -static bool ipp_GaussianBlur(InputArray _src, OutputArray _dst, Size ksize, +static bool ipp_GaussianBlur(cv::Mat& src, cv::Mat& dst, Size ksize, double sigma1, double sigma2, int borderType ) { #ifdef HAVE_IPP_IW CV_INSTRUMENT_REGION_IPP(); #if IPP_VERSION_X100 < 201800 && ((defined _MSC_VER && defined _M_IX86) || (defined __GNUC__ && defined __i386__)) - CV_UNUSED(_src); CV_UNUSED(_dst); CV_UNUSED(ksize); CV_UNUSED(sigma1); CV_UNUSED(sigma2); CV_UNUSED(borderType); + CV_UNUSED(src); CV_UNUSED(dst); CV_UNUSED(ksize); CV_UNUSED(sigma1); CV_UNUSED(sigma2); CV_UNUSED(borderType); return false; // bug on ia32 #else if(sigma1 != sigma2) @@ -464,8 +464,6 @@ static bool ipp_GaussianBlur(InputArray _src, OutputArray _dst, Size ksize, // Acquire data and begin processing try { - Mat src = _src.getMat(); - Mat dst = _dst.getMat(); ::ipp::IwiImage iwSrc = ippiGetImage(src); ::ipp::IwiImage iwDst = ippiGetImage(dst); ::ipp::IwiBorderSize borderSize = ::ipp::iwiSizeToBorderSize(ippiGetSize(ksize)); @@ -505,7 +503,7 @@ static bool ipp_GaussianBlur(InputArray _src, OutputArray _dst, Size ksize, return true; #endif #else - CV_UNUSED(_src); CV_UNUSED(_dst); CV_UNUSED(ksize); CV_UNUSED(sigma1); CV_UNUSED(sigma2); CV_UNUSED(borderType); + CV_UNUSED(src); CV_UNUSED(dst); CV_UNUSED(ksize); CV_UNUSED(sigma1); CV_UNUSED(sigma2); CV_UNUSED(borderType); return false; #endif } @@ -526,10 +524,13 @@ static bool validateGaussianBlurKernel(std::vector& kernel) void GaussianBlur(InputArray _src, OutputArray _dst, Size ksize, double sigma1, double sigma2, - int borderType) + int borderType, AlgorithmHint hint) { CV_INSTRUMENT_REGION(); + if (hint == cv::ALGO_HINT_DEFAULT) + hint = cv::getDefaultAlgorithmHint(); + CV_Assert(!_src.empty()); int type = _src.type(); @@ -609,7 +610,25 @@ void GaussianBlur(InputArray _src, OutputArray _dst, Size ksize, src2.locateROI( wsz, ofs ); CALL_HAL(gaussianBlurBinomial, cv_hal_gaussianBlurBinomial, src2.ptr(), src2.step, dst.ptr(), dst.step, src2.cols, src2.rows, sdepth, cn, - ofs.x, ofs.y, wsz.width - src2.cols - ofs.x, wsz.height - src2.rows - ofs.y, ksize.width, borderType&~BORDER_ISOLATED); + ofs.x, ofs.y, wsz.width - src2.cols - ofs.x, wsz.height - src2.rows - ofs.y, ksize.width, + borderType & ~BORDER_ISOLATED); + } + + if (hint == ALGO_HINT_APPROX) + { + Point ofs; + Size wsz(src.cols, src.rows); + if(!(borderType & BORDER_ISOLATED)) + src.locateROI( wsz, ofs ); + + CALL_HAL(gaussianBlur, cv_hal_gaussianBlur, src.ptr(), src.step, dst.ptr(), dst.step, src.cols, src.rows, sdepth, cn, + ofs.x, ofs.y, wsz.width - src.cols - ofs.x, wsz.height - src.rows - ofs.y, ksize.width, ksize.height, + sigma1, sigma2, borderType & ~BORDER_ISOLATED); + +#ifdef ENABLE_IPP_GAUSSIAN_BLUR + // IPP is not bit-exact to OpenCV implementation + CV_IPP_RUN_FAST(ipp_GaussianBlur(src, dst, ksize, sigma1, sigma2, borderType)); +#endif } CV_CPU_DISPATCH(GaussianBlurFixedPoint, (src, dst, (const uint16_t*)&fkx[0], (int)fkx.size(), (const uint16_t*)&fky[0], (int)fky.size(), borderType), @@ -663,6 +682,23 @@ void GaussianBlur(InputArray _src, OutputArray _dst, Size ksize, ofs.x, ofs.y, wsz.width - src2.cols - ofs.x, wsz.height - src2.rows - ofs.y, ksize.width, borderType&~BORDER_ISOLATED); } + if (hint == ALGO_HINT_APPROX) + { + Point ofs; + Size wsz(src.cols, src.rows); + if(!(borderType & BORDER_ISOLATED)) + src.locateROI( wsz, ofs ); + + CALL_HAL(gaussianBlur, cv_hal_gaussianBlur, src.ptr(), src.step, dst.ptr(), dst.step, src.cols, src.rows, sdepth, cn, + ofs.x, ofs.y, wsz.width - src.cols - ofs.x, wsz.height - src.rows - ofs.y, ksize.width, ksize.height, + sigma1, sigma2, borderType & ~BORDER_ISOLATED); + +#ifdef ENABLE_IPP_GAUSSIAN_BLUR + // IPP is not bit-exact to OpenCV implementation + CV_IPP_RUN_FAST(ipp_GaussianBlur(src, dst, ksize, sigma1, sigma2, borderType)); +#endif + } + CV_CPU_DISPATCH(GaussianBlurFixedPoint, (src, dst, (const uint32_t*)&fkx[0], (int)fkx.size(), (const uint32_t*)&fky[0], (int)fky.size(), borderType), CV_CPU_DISPATCH_MODES_ALL); @@ -688,7 +724,7 @@ void GaussianBlur(InputArray _src, OutputArray _dst, Size ksize, CALL_HAL(gaussianBlur, cv_hal_gaussianBlur, src.ptr(), src.step, dst.ptr(), dst.step, src.cols, src.rows, sdepth, cn, ofs.x, ofs.y, wsz.width - src.cols - ofs.x, wsz.height - src.rows - ofs.y, ksize.width, ksize.height, - sigma1, sigma2, borderType&~BORDER_ISOLATED); + sigma1, sigma2, borderType & ~BORDER_ISOLATED); #if defined ENABLE_IPP_GAUSSIAN_BLUR // IPP is not bit-exact to OpenCV implementation diff --git a/modules/imgproc/test/test_approxpoly.cpp b/modules/imgproc/test/test_approxpoly.cpp index 482c1f10b3..410673ad53 100644 --- a/modules/imgproc/test/test_approxpoly.cpp +++ b/modules/imgproc/test/test_approxpoly.cpp @@ -76,4 +76,79 @@ TEST(Imgproc_ApproxPoly, bad_epsilon) ASSERT_ANY_THROW(approxPolyDP(inputPoints, outputPoints, eps, false)); } +struct ApproxPolyN: public testing::Test +{ + void SetUp() + { + vector> inputPoints = { + { {87, 103}, {100, 112}, {96, 138}, {80, 169}, {60, 183}, {38, 176}, {41, 145}, {56, 118}, {76, 104} }, + { {196, 102}, {205, 118}, {174, 196}, {152, 207}, {102, 194}, {100, 175}, {131, 109} }, + { {372, 101}, {377, 119}, {337, 238}, {324, 248}, {240, 229}, {199, 214}, {232, 123}, {245, 103} }, + { {463, 86}, {563, 112}, {574, 135}, {596, 221}, {518, 298}, {412, 266}, {385, 164}, {462, 86} } + }; + + Mat image(600, 600, CV_8UC1, Scalar(0)); + + for (vector& polygon : inputPoints) { + polylines(image, { polygon }, true, Scalar(255), 1); + } + + findContours(image, contours, RETR_LIST, CHAIN_APPROX_NONE); + } + + vector> contours; +}; + +TEST_F(ApproxPolyN, accuracyInt) +{ + vector> rightCorners = { + { {72, 187}, {37, 176}, {42, 127}, {133, 64} }, + { {168, 212}, {92, 192}, {131, 109}, {213, 100} }, + { {72, 187}, {37, 176}, {42, 127}, {133, 64} }, + { {384, 100}, {333, 251}, {197, 220}, {239, 103} }, + { {168, 212}, {92, 192}, {131, 109}, {213, 100} }, + { {333, 251}, {197, 220}, {239, 103}, {384, 100} }, + { {542, 6}, {596, 221}, {518, 299}, {312, 236} }, + { {596, 221}, {518, 299}, {312, 236}, {542, 6} } + }; + EXPECT_EQ(rightCorners.size(), contours.size()); + + for (size_t i = 0; i < contours.size(); ++i) { + std::vector corners; + approxPolyN(contours[i], corners, 4, -1, true); + ASSERT_EQ(rightCorners[i], corners ); + } +} + +TEST_F(ApproxPolyN, accuracyFloat) +{ + vector> rightCorners = { + { {72.f, 187.f}, {37.f, 176.f}, {42.f, 127.f}, {133.f, 64.f} }, + { {168.f, 212.f}, {92.f, 192.f}, {131.f, 109.f}, {213.f, 100.f} }, + { {72.f, 187.f}, {37.f, 176.f}, {42.f, 127.f}, {133.f, 64.f} }, + { {384.f, 100.f}, {333.f, 251.f}, {197.f, 220.f}, {239.f, 103.f} }, + { {168.f, 212.f}, {92.f, 192.f}, {131.f, 109.f}, {213.f, 100.f} }, + { {333.f, 251.f}, {197.f, 220.f}, {239.f, 103.f}, {384.f, 100.f} }, + { {542.f, 6.f}, {596.f, 221.f}, {518.f, 299.f}, {312.f, 236.f} }, + { {596.f, 221.f}, {518.f, 299.f}, {312.f, 236.f}, {542.f, 6.f} } + }; + EXPECT_EQ(rightCorners.size(), contours.size()); + + for (size_t i = 0; i < contours.size(); ++i) { + std::vector corners; + approxPolyN(contours[i], corners, 4, -1, true); + EXPECT_LT(cvtest::norm(rightCorners[i], corners, NORM_INF), .5f); + } +} + +TEST_F(ApproxPolyN, bad_args) +{ + Mat contour(10, 1, CV_32FC2); + vector> bad_contours; + vector corners; + ASSERT_ANY_THROW(approxPolyN(contour, corners, 0)); + ASSERT_ANY_THROW(approxPolyN(contour, corners, 3, 0)); + ASSERT_ANY_THROW(approxPolyN(bad_contours, corners, 4)); +} + }} // namespace diff --git a/modules/imgproc/test/test_canny.cpp b/modules/imgproc/test/test_canny.cpp index 806a6f3cbd..86bc0d7da9 100644 --- a/modules/imgproc/test/test_canny.cpp +++ b/modules/imgproc/test/test_canny.cpp @@ -40,134 +40,10 @@ //M*/ #include "test_precomp.hpp" -#include "opencv2/core/core_c.h" namespace opencv_test { namespace { -class CV_CannyTest : public cvtest::ArrayTest -{ -public: - CV_CannyTest(bool custom_deriv = false); - -protected: - void get_test_array_types_and_sizes( int test_case_idx, vector >& sizes, vector >& types ); - double get_success_error_level( int test_case_idx, int i, int j ); - int prepare_test_case( int test_case_idx ); - void run_func(); - void prepare_to_validation( int ); - int validate_test_results( int /*test_case_idx*/ ); - - int aperture_size; - bool use_true_gradient; - double threshold1, threshold2; - bool test_cpp; - bool test_custom_deriv; - - Mat img; -}; - - -CV_CannyTest::CV_CannyTest(bool custom_deriv) -{ - test_array[INPUT].push_back(NULL); - test_array[OUTPUT].push_back(NULL); - test_array[REF_OUTPUT].push_back(NULL); - element_wise_relative_error = true; - aperture_size = 0; - use_true_gradient = false; - threshold1 = threshold2 = 0; - test_custom_deriv = custom_deriv; - - const char imgPath[] = "shared/fruits.png"; - img = cv::imread(cvtest::TS::ptr()->get_data_path() + imgPath, IMREAD_GRAYSCALE); -} - - -void CV_CannyTest::get_test_array_types_and_sizes( int test_case_idx, - vector >& sizes, - vector >& types ) -{ - RNG& rng = ts->get_rng(); - double thresh_range; - - cvtest::ArrayTest::get_test_array_types_and_sizes( test_case_idx, sizes, types ); - types[INPUT][0] = types[OUTPUT][0] = types[REF_OUTPUT][0] = CV_8U; - - aperture_size = cvtest::randInt(rng) % 2 ? 5 : 3; - thresh_range = aperture_size == 3 ? 300 : 1000; - - threshold1 = cvtest::randReal(rng)*thresh_range; - threshold2 = cvtest::randReal(rng)*thresh_range*0.3; - - if( cvtest::randInt(rng) % 2 ) - CV_SWAP( threshold1, threshold2, thresh_range ); - - use_true_gradient = cvtest::randInt(rng) % 2 != 0; - test_cpp = (cvtest::randInt(rng) & 256) == 0; - - ts->printf(cvtest::TS::LOG, "Canny(size = %d x %d, aperture_size = %d, threshold1 = %g, threshold2 = %g, L2 = %s) test_cpp = %s (test case #%d)\n", - sizes[0][0].width, sizes[0][0].height, aperture_size, threshold1, threshold2, use_true_gradient ? "TRUE" : "FALSE", test_cpp ? "TRUE" : "FALSE", test_case_idx); -} - - -int CV_CannyTest::prepare_test_case( int test_case_idx ) -{ - int code = cvtest::ArrayTest::prepare_test_case( test_case_idx ); - if( code > 0 ) - { - RNG& rng = ts->get_rng(); - Mat& src = test_mat[INPUT][0]; - //GaussianBlur(src, src, Size(11, 11), 5, 5); - if(src.cols > img.cols || src.rows > img.rows) - resize(img, src, src.size(), 0, 0, INTER_LINEAR_EXACT); - else - img( - Rect( - cvtest::randInt(rng) % (img.cols-src.cols), - cvtest::randInt(rng) % (img.rows-src.rows), - src.cols, - src.rows - ) - ).copyTo(src); - GaussianBlur(src, src, Size(5, 5), 0); - } - - return code; -} - - -double CV_CannyTest::get_success_error_level( int /*test_case_idx*/, int /*i*/, int /*j*/ ) -{ - return 0; -} - - -void CV_CannyTest::run_func() -{ - if (test_custom_deriv) - { - cv::Mat _out = cv::cvarrToMat(test_array[OUTPUT][0]); - cv::Mat src = cv::cvarrToMat(test_array[INPUT][0]); - cv::Mat dx, dy; - int m = aperture_size; - Point anchor(m/2, m/2); - Mat dxkernel = cvtest::calcSobelKernel2D( 1, 0, m, 0 ); - Mat dykernel = cvtest::calcSobelKernel2D( 0, 1, m, 0 ); - cvtest::filter2D(src, dx, CV_16S, dxkernel, anchor, 0, BORDER_REPLICATE); - cvtest::filter2D(src, dy, CV_16S, dykernel, anchor, 0, BORDER_REPLICATE); - cv::Canny(dx, dy, _out, threshold1, threshold2, use_true_gradient); - } - else - { - cv::Mat _out = cv::cvarrToMat(test_array[OUTPUT][0]); - cv::Canny(cv::cvarrToMat(test_array[INPUT][0]), _out, threshold1, threshold2, - aperture_size, use_true_gradient); - } -} - - -static void -cannyFollow( int x, int y, float lowThreshold, const Mat& mag, Mat& dst ) +static void Canny_reference_follow( int x, int y, float lowThreshold, const Mat& mag, Mat& dst ) { static const int ofs[][2] = {{1,0},{1,-1},{0,-1},{-1,-1},{-1,0},{-1,1},{0,1},{1,1}}; int i; @@ -182,16 +58,15 @@ cannyFollow( int x, int y, float lowThreshold, const Mat& mag, Mat& dst ) (unsigned)y1 < (unsigned)mag.rows && mag.at(y1, x1) > lowThreshold && !dst.at(y1, x1) ) - cannyFollow( x1, y1, lowThreshold, mag, dst ); + Canny_reference_follow( x1, y1, lowThreshold, mag, dst ); } } - -static void -test_Canny( const Mat& src, Mat& dst, +static void Canny_reference( const Mat& src, Mat& dst, double threshold1, double threshold2, int aperture_size, bool use_true_gradient ) { + dst.create(src.size(), src.type()); int m = aperture_size; Point anchor(m/2, m/2); const double tan_pi_8 = tan(CV_PI/8.); @@ -274,47 +149,80 @@ test_Canny( const Mat& src, Mat& dst, { for( x = 0; x < width; x++ ) if( mag.at(y, x) > highThreshold && !dst.at(y, x) ) - cannyFollow( x, y, lowThreshold, mag, dst ); + Canny_reference_follow( x, y, lowThreshold, mag, dst ); } } +//============================================================================== -void CV_CannyTest::prepare_to_validation( int ) +// aperture, true gradient +typedef testing::TestWithParam> Canny_Modes; + +TEST_P(Canny_Modes, accuracy) { - Mat src = test_mat[INPUT][0], dst = test_mat[REF_OUTPUT][0]; - test_Canny( src, dst, threshold1, threshold2, aperture_size, use_true_gradient ); -} + const int aperture = get<0>(GetParam()); + const bool trueGradient = get<1>(GetParam()); + const double range = aperture == 3 ? 300. : 1000.; + RNG & rng = TS::ptr()->get_rng(); + for (int ITER = 0; ITER < 20; ++ITER) + { + SCOPED_TRACE(cv::format("iteration %d", ITER)); -int CV_CannyTest::validate_test_results( int test_case_idx ) -{ - int code = cvtest::TS::OK, nz0; - prepare_to_validation(test_case_idx); + const std::string fname = cvtest::findDataFile("shared/fruits.png"); + const Mat original = cv::imread(fname, IMREAD_GRAYSCALE); - double err = cvtest::norm(test_mat[OUTPUT][0], test_mat[REF_OUTPUT][0], NORM_L1); - if( err == 0 ) - return code; + const double thresh1 = rng.uniform(0., range); + const double thresh2 = rng.uniform(0., range * 0.3); + const Size sz(rng.uniform(127, 800), rng.uniform(127, 600)); + const Size osz = original.size(); - if( err != cvRound(err) || cvRound(err)%255 != 0 ) - { - ts->printf( cvtest::TS::LOG, "Some of the pixels, produced by Canny, are not 0's or 255's; the difference is %g\n", err ); - ts->set_failed_test_info( cvtest::TS::FAIL_INVALID_OUTPUT ); - return code; - } + // preparation + Mat img; + if (sz.width >= osz.width || sz.height >= osz.height) + { + // larger image -> scale + resize(original, img, sz, 0, 0, INTER_LINEAR_EXACT); + } + else + { + // smaller image -> crop + Point origin(rng.uniform(0, osz.width - sz.width), rng.uniform(0, osz.height - sz.height)); + Rect roi(origin, sz); + original(roi).copyTo(img); + } + GaussianBlur(img, img, Size(5, 5), 0); - nz0 = cvRound(cvtest::norm(test_mat[REF_OUTPUT][0], NORM_L1)/255); - err = (err/255/MAX(nz0,100))*100; - if( err > 1 ) - { - ts->printf( cvtest::TS::LOG, "Too high percentage of non-matching edge pixels = %g%%\n", err); - ts->set_failed_test_info( cvtest::TS::FAIL_BAD_ACCURACY ); - } + // regular function + Mat result; + { + cv::Canny(img, result, thresh1, thresh2, aperture, trueGradient); + } - return code; + // custom derivatives + Mat customResult; + { + Mat dxkernel = cvtest::calcSobelKernel2D(1, 0, aperture, 0); + Mat dykernel = cvtest::calcSobelKernel2D(0, 1, aperture, 0); + Point anchor(aperture / 2, aperture / 2); + cv::Mat dx, dy; + cvtest::filter2D(img, dx, CV_16S, dxkernel, anchor, 0, BORDER_REPLICATE); + cvtest::filter2D(img, dy, CV_16S, dykernel, anchor, 0, BORDER_REPLICATE); + cv::Canny(dx, dy, customResult, thresh1, thresh2, trueGradient); + } + + Mat reference; + Canny_reference(img, reference, thresh1, thresh2, aperture, trueGradient); + + EXPECT_MAT_NEAR(result, reference, 0); + EXPECT_MAT_NEAR(customResult, reference, 0); + } } -TEST(Imgproc_Canny, accuracy) { CV_CannyTest test; test.safe_run(); } -TEST(Imgproc_Canny, accuracy_deriv) { CV_CannyTest test(true); test.safe_run(); } +INSTANTIATE_TEST_CASE_P(/**/, Canny_Modes, + testing::Combine( + testing::Values(3, 5), + testing::Values(true, false))); }} // namespace /* End of file. */ diff --git a/modules/imgproc/test/test_color.cpp b/modules/imgproc/test/test_color.cpp index d4ff3c387e..a3cc2ab35e 100644 --- a/modules/imgproc/test/test_color.cpp +++ b/modules/imgproc/test/test_color.cpp @@ -159,17 +159,17 @@ int CV_ColorCvtBaseTest::prepare_test_case( int test_case_idx ) void CV_ColorCvtBaseTest::run_func() { - CvArr* out0 = test_array[OUTPUT][0]; - cv::Mat _out0 = cv::cvarrToMat(out0), _out1 = cv::cvarrToMat(test_array[OUTPUT][1]); + cv::Mat out0 = test_mat[OUTPUT][0]; + cv::Mat _out0 = out0, _out1 = test_mat[OUTPUT][1]; - cv::cvtColor( cv::cvarrToMat(inplace ? out0 : test_array[INPUT][0]), _out0, fwd_code, _out0.channels()); + cv::cvtColor( inplace ? out0 : test_mat[INPUT][0], _out0, fwd_code, _out0.channels()); if( inplace ) { - cvCopy( out0, test_array[OUTPUT][1] ); - out0 = test_array[OUTPUT][1]; + out0.copyTo(test_mat[OUTPUT][1]); + out0 = test_mat[OUTPUT][1]; } - cv::cvtColor(cv::cvarrToMat(out0), _out1, inv_code, _out1.channels()); + cv::cvtColor(out0, _out1, inv_code, _out1.channels()); } @@ -1722,8 +1722,8 @@ double CV_ColorBayerTest::get_success_error_level( int /*test_case_idx*/, int /* void CV_ColorBayerTest::run_func() { - cv::Mat _out = cv::cvarrToMat(test_array[OUTPUT][0]); - cv::cvtColor(cv::cvarrToMat(test_array[INPUT][0]), _out, fwd_code, _out.channels()); + cv::Mat _out = test_mat[OUTPUT][0]; + cv::cvtColor(test_mat[INPUT][0], _out, fwd_code, _out.channels()); } diff --git a/modules/imgproc/test/test_convhull.cpp b/modules/imgproc/test/test_convhull.cpp index 1697ced954..7e10757514 100644 --- a/modules/imgproc/test/test_convhull.cpp +++ b/modules/imgproc/test/test_convhull.cpp @@ -39,907 +39,18 @@ // //M*/ +#include "opencv2/core/hal/interface.h" +#include "opencv2/ts.hpp" +#include "opencv2/ts/cuda_test.hpp" #include "test_precomp.hpp" -#include "opencv2/core/core_c.h" namespace opencv_test { namespace { -CV_INLINE double -cvTsDist( const Point2f& a, const Point2f& b ) -{ - double dx = a.x - b.x; - double dy = a.y - b.y; - return sqrt(dx*dx + dy*dy); -} - -CV_INLINE double -cvTsPtLineDist( const Point2f& pt, const Point2f& a, const Point2f& b ) -{ - double d0 = cvTsDist( pt, a ), d1; - double dd = cvTsDist( a, b ); - if( dd < FLT_EPSILON ) - return d0; - d1 = cvTsDist( pt, b ); - dd = fabs((double)(pt.x - a.x)*(b.y - a.y) - (double)(pt.y - a.y)*(b.x - a.x))/dd; - d0 = MIN( d0, d1 ); - return MIN( d0, dd ); -} - -static double -cvTsPointPolygonTest( Point2f pt, const Point2f* vv, int n, int* _idx=0, int* _on_edge=0 ) -{ - int i; - Point2f v = vv[n-1], v0; - double min_dist_num = FLT_MAX, min_dist_denom = 1; - int min_dist_idx = -1, min_on_edge = 0; - int counter = 0; - double result; - - for( i = 0; i < n; i++ ) - { - double dx, dy, dx1, dy1, dx2, dy2, dist_num, dist_denom = 1; - int on_edge = 0, idx = i; - - v0 = v; v = vv[i]; - dx = v.x - v0.x; dy = v.y - v0.y; - dx1 = pt.x - v0.x; dy1 = pt.y - v0.y; - dx2 = pt.x - v.x; dy2 = pt.y - v.y; - - if( dx2*dx + dy2*dy >= 0 ) - dist_num = dx2*dx2 + dy2*dy2; - else if( dx1*dx + dy1*dy <= 0 ) - { - dist_num = dx1*dx1 + dy1*dy1; - idx = i - 1; - if( idx < 0 ) idx = n-1; - } - else - { - dist_num = (dy1*dx - dx1*dy); - dist_num *= dist_num; - dist_denom = dx*dx + dy*dy; - on_edge = 1; - } - - if( dist_num*min_dist_denom < min_dist_num*dist_denom ) - { - min_dist_num = dist_num; - min_dist_denom = dist_denom; - min_dist_idx = idx; - min_on_edge = on_edge; - if( min_dist_num == 0 ) - break; - } - - if( (v0.y <= pt.y && v.y <= pt.y) || - (v0.y > pt.y && v.y > pt.y) || - (v0.x < pt.x && v.x < pt.x) ) - continue; - - dist_num = dy1*dx - dx1*dy; - if( dy < 0 ) - dist_num = -dist_num; - counter += dist_num > 0; - } - - result = sqrt(min_dist_num/min_dist_denom); - if( counter % 2 == 0 ) - result = -result; - - if( _idx ) - *_idx = min_dist_idx; - if( _on_edge ) - *_on_edge = min_on_edge; - - return result; -} - -static cv::Point2f -cvTsMiddlePoint(const cv::Point2f &a, const cv::Point2f &b) -{ - return cv::Point2f((a.x + b.x) / 2, (a.y + b.y) / 2); -} - -static bool -cvTsIsPointOnLineSegment(const cv::Point2f &x, const cv::Point2f &a, const cv::Point2f &b) -{ - double d1 = cvTsDist(x, a); - double d2 = cvTsDist(x, b); - double d3 = cvTsDist(a, b); - - return (abs(d1 + d2 - d3) <= (1E-4)); -} - - -/****************************************************************************************\ -* Base class for shape descriptor tests * -\****************************************************************************************/ - -class CV_BaseShapeDescrTest : public cvtest::BaseTest -{ -public: - CV_BaseShapeDescrTest(); - virtual ~CV_BaseShapeDescrTest(); - void clear(); - -protected: - int read_params( const cv::FileStorage& fs ); - void run_func(void); - int prepare_test_case( int test_case_idx ); - int validate_test_results( int test_case_idx ); - virtual void generate_point_set( void* points ); - virtual void extract_points(); - - int min_log_size; - int max_log_size; - int dims; - bool enable_flt_points; - - CvSeq* points1; - CvMat* points2; - void* points; - void* result; - double low_high_range; - Scalar low, high; -}; - - -CV_BaseShapeDescrTest::CV_BaseShapeDescrTest() -{ - points1 = 0; - points2 = 0; - points = 0; - test_case_count = 500; - min_log_size = 0; - max_log_size = 10; - low = high = cvScalarAll(0); - low_high_range = 50; - dims = 2; - enable_flt_points = true; -} - - -CV_BaseShapeDescrTest::~CV_BaseShapeDescrTest() -{ - clear(); -} - - -void CV_BaseShapeDescrTest::clear() -{ - cvtest::BaseTest::clear(); - cvReleaseMat( &points2 ); - points1 = 0; - points = 0; -} - - -int CV_BaseShapeDescrTest::read_params( const cv::FileStorage& fs ) -{ - int code = cvtest::BaseTest::read_params( fs ); - if( code < 0 ) - return code; - - read( find_param( fs, "struct_count" ), test_case_count, test_case_count ); - read( find_param( fs, "min_log_size" ), min_log_size, min_log_size ); - read( find_param( fs, "max_log_size" ), max_log_size, max_log_size ); - - min_log_size = cvtest::clipInt( min_log_size, 0, 8 ); - max_log_size = cvtest::clipInt( max_log_size, 0, 10 ); - if( min_log_size > max_log_size ) - { - int t; - CV_SWAP( min_log_size, max_log_size, t ); - } - - return 0; -} - - -void CV_BaseShapeDescrTest::generate_point_set( void* pointsSet ) -{ - RNG& rng = ts->get_rng(); - int i, k, n, total, point_type; - CvSeqReader reader; - uchar* data = 0; - double a[4], b[4]; - - for( k = 0; k < 4; k++ ) - { - a[k] = high.val[k] - low.val[k]; - b[k] = low.val[k]; - } - memset( &reader, 0, sizeof(reader) ); - - if( CV_IS_SEQ(pointsSet) ) - { - CvSeq* ptseq = (CvSeq*)pointsSet; - total = ptseq->total; - point_type = CV_SEQ_ELTYPE(ptseq); - cvStartReadSeq( ptseq, &reader ); - } - else - { - CvMat* ptm = (CvMat*)pointsSet; - CV_Assert( CV_IS_MAT(ptm) && CV_IS_MAT_CONT(ptm->type) ); - total = ptm->rows + ptm->cols - 1; - point_type = CV_MAT_TYPE(ptm->type); - data = ptm->data.ptr; - } - - n = CV_MAT_CN(point_type); - point_type = CV_MAT_DEPTH(point_type); - - CV_Assert( (point_type == CV_32S || point_type == CV_32F) && n <= 4 ); - - for( i = 0; i < total; i++ ) - { - int* pi; - float* pf; - if( reader.ptr ) - { - pi = (int*)reader.ptr; - pf = (float*)reader.ptr; - CV_NEXT_SEQ_ELEM( reader.seq->elem_size, reader ); - } - else - { - pi = (int*)data + i*n; - pf = (float*)data + i*n; - } - if( point_type == CV_32S ) - for( k = 0; k < n; k++ ) - pi[k] = cvRound(cvtest::randReal(rng)*a[k] + b[k]); - else - for( k = 0; k < n; k++ ) - pf[k] = (float)(cvtest::randReal(rng)*a[k] + b[k]); - } -} - - -int CV_BaseShapeDescrTest::prepare_test_case( int test_case_idx ) -{ - int size; - int point_type; - int i; - RNG& rng = ts->get_rng(); - - cvtest::BaseTest::prepare_test_case( test_case_idx ); - - clear(); - size = cvRound( exp((cvtest::randReal(rng) * (max_log_size - min_log_size) + min_log_size)*CV_LOG2) ); - point_type = CV_MAKETYPE(cvtest::randInt(rng) % - (enable_flt_points ? 2 : 1) ? CV_32F : CV_32S, dims); - - int rows = 1, cols = size; - if( cvtest::randInt(rng) % 2 ) - rows = size, cols = 1; - - points2 = cvCreateMat( rows, cols, point_type ); - points = points2; - - for( i = 0; i < 4; i++ ) - { - low.val[i] = (cvtest::randReal(rng)-0.5)*low_high_range*2; - high.val[i] = (cvtest::randReal(rng)-0.5)*low_high_range*2; - if( low.val[i] > high.val[i] ) - { - double t; - CV_SWAP( low.val[i], high.val[i], t ); - } - if( high.val[i] < low.val[i] + 1 ) - high.val[i] += 1; - } - - generate_point_set( points ); - return 1; -} - - -void CV_BaseShapeDescrTest::extract_points() -{ - if( points1 ) - { - points2 = cvCreateMat( 1, points1->total, CV_SEQ_ELTYPE(points1) ); - cvCvtSeqToArray( points1, points2->data.ptr ); - } - - if( CV_MAT_DEPTH(points2->type) != CV_32F && enable_flt_points ) - { - CvMat tmp = cvMat( points2->rows, points2->cols, - (points2->type & ~CV_MAT_DEPTH_MASK) | CV_32F, points2->data.ptr ); - cvConvert( points2, &tmp ); - } -} - - -void CV_BaseShapeDescrTest::run_func(void) -{ -} - - -int CV_BaseShapeDescrTest::validate_test_results( int /*test_case_idx*/ ) -{ - extract_points(); - return 0; -} - - -/****************************************************************************************\ -* Convex Hull Test * -\****************************************************************************************/ - -class CV_ConvHullTest : public CV_BaseShapeDescrTest -{ -public: - CV_ConvHullTest(); - virtual ~CV_ConvHullTest(); - void clear(); - -protected: - void run_func(void); - int prepare_test_case( int test_case_idx ); - int validate_test_results( int test_case_idx ); - - CvSeq* hull1; - CvMat* hull2; - bool clockwise; - int return_points; -}; - - -CV_ConvHullTest::CV_ConvHullTest() -{ - hull1 = 0; - hull2 = 0; - return_points = 0; - clockwise = false; -} - - -CV_ConvHullTest::~CV_ConvHullTest() -{ - clear(); -} - - -void CV_ConvHullTest::clear() -{ - CV_BaseShapeDescrTest::clear(); - cvReleaseMat( &hull2 ); - hull1 = 0; -} - - -int CV_ConvHullTest::prepare_test_case( int test_case_idx ) -{ - int code = CV_BaseShapeDescrTest::prepare_test_case( test_case_idx ); - RNG& rng = ts->get_rng(); - - if( code <= 0 ) - return code; - - clockwise = cvtest::randInt(rng) % 2 != 0; - return_points = cvtest::randInt(rng) % 2; - - int rows, cols; - int sz = points1 ? points1->total : points2->cols + points2->rows - 1; - int point_type = points1 ? CV_SEQ_ELTYPE(points1) : CV_MAT_TYPE(points2->type); - - if( cvtest::randInt(rng) % 2 ) - rows = sz, cols = 1; - else - rows = 1, cols = sz; - - hull2 = cvCreateMat( rows, cols, return_points ? point_type : CV_32SC1 ); - - return code; -} - - -void CV_ConvHullTest::run_func() -{ - cv::Mat _points = cv::cvarrToMat(points); - size_t n = 0; - if( !return_points ) - { - std::vector _hull; - cv::convexHull(_points, _hull, clockwise); - n = _hull.size(); - memcpy(hull2->data.ptr, &_hull[0], n*sizeof(_hull[0])); - } - else if(_points.type() == CV_32SC2) - { - std::vector _hull; - cv::convexHull(_points, _hull, clockwise); - n = _hull.size(); - memcpy(hull2->data.ptr, &_hull[0], n*sizeof(_hull[0])); - } - else if(_points.type() == CV_32FC2) - { - std::vector _hull; - cv::convexHull(_points, _hull, clockwise); - n = _hull.size(); - memcpy(hull2->data.ptr, &_hull[0], n*sizeof(_hull[0])); - } - if(hull2->rows > hull2->cols) - hull2->rows = (int)n; - else - hull2->cols = (int)n; -} - - -int CV_ConvHullTest::validate_test_results( int test_case_idx ) -{ - int code = CV_BaseShapeDescrTest::validate_test_results( test_case_idx ); - CvMat* hull = 0; - CvMat* mask = 0; - int i, point_count, hull_count; - Point2f *p, *h; - CvSeq header, hheader, *ptseq, *hseq; - CvSeqBlock block, hblock; - - if( points1 ) - ptseq = points1; - else - ptseq = cvMakeSeqHeaderForArray( CV_MAT_TYPE(points2->type), - sizeof(CvSeq), CV_ELEM_SIZE(points2->type), points2->data.ptr, - points2->rows + points2->cols - 1, &header, &block ); - point_count = ptseq->total; - p = (Point2f*)(points2->data.ptr); - - if( hull1 ) - hseq = hull1; - else - hseq = cvMakeSeqHeaderForArray( CV_MAT_TYPE(hull2->type), - sizeof(CvSeq), CV_ELEM_SIZE(hull2->type), hull2->data.ptr, - hull2->rows + hull2->cols - 1, &hheader, &hblock ); - hull_count = hseq->total; - hull = cvCreateMat( 1, hull_count, CV_32FC2 ); - mask = cvCreateMat( 1, hull_count, CV_8UC1 ); - cvZero( mask ); - Mat _mask = cvarrToMat(mask); - - h = (Point2f*)(hull->data.ptr); - - // extract convex hull points - if( return_points ) - { - cvCvtSeqToArray( hseq, hull->data.ptr ); - if( CV_SEQ_ELTYPE(hseq) != CV_32FC2 ) - { - CvMat tmp = cvMat( hull->rows, hull->cols, CV_32SC2, hull->data.ptr ); - cvConvert( &tmp, hull ); - } - } - else - { - CvSeqReader reader; - cvStartReadSeq( hseq, &reader ); - - for( i = 0; i < hull_count; i++ ) - { - schar* ptr = reader.ptr; - int idx; - CV_NEXT_SEQ_ELEM( hseq->elem_size, reader ); - - if( hull1 ) - idx = cvSeqElemIdx( ptseq, *(uchar**)ptr ); - else - idx = *(int*)ptr; - - if( idx < 0 || idx >= point_count ) - { - ts->printf( cvtest::TS::LOG, "Invalid convex hull point #%d\n", i ); - code = cvtest::TS::FAIL_INVALID_OUTPUT; - goto _exit_; - } - h[i] = p[idx]; - } - } - - // check that the convex hull is a convex polygon - if( hull_count >= 3 ) - { - Point2f pt0 = h[hull_count-1]; - for( i = 0; i < hull_count; i++ ) - { - int j = i+1; - Point2f pt1 = h[i], pt2 = h[j < hull_count ? j : 0]; - float dx0 = pt1.x - pt0.x, dy0 = pt1.y - pt0.y; - float dx1 = pt2.x - pt1.x, dy1 = pt2.y - pt1.y; - double t = (double)dx0*dy1 - (double)dx1*dy0; - if( (t < 0) ^ clockwise ) - { - ts->printf( cvtest::TS::LOG, "The convex hull is not convex or has a wrong orientation (vtx %d)\n", i ); - code = cvtest::TS::FAIL_INVALID_OUTPUT; - goto _exit_; - } - pt0 = pt1; - } - } - - // check that all the points are inside the hull or on the hull edge - // and at least hull_point points are at the hull vertices - for( i = 0; i < point_count; i++ ) - { - int idx = 0, on_edge = 0; - double pptresult = cvTsPointPolygonTest( p[i], h, hull_count, &idx, &on_edge ); - - if( pptresult < 0 ) - { - ts->printf( cvtest::TS::LOG, "The point #%d is outside of the convex hull\n", i ); - code = cvtest::TS::FAIL_BAD_ACCURACY; - goto _exit_; - } - - if( pptresult < FLT_EPSILON && !on_edge ) - mask->data.ptr[idx] = (uchar)1; - } - - if( cvtest::norm( _mask, Mat::zeros(_mask.dims, _mask.size, _mask.type()), NORM_L1 ) != hull_count ) - { - ts->printf( cvtest::TS::LOG, "Not every convex hull vertex coincides with some input point\n" ); - code = cvtest::TS::FAIL_BAD_ACCURACY; - goto _exit_; - } - -_exit_: - - cvReleaseMat( &hull ); - cvReleaseMat( &mask ); - if( code < 0 ) - ts->set_failed_test_info( code ); - return code; -} - - -/****************************************************************************************\ -* MinAreaRect Test * -\****************************************************************************************/ - -class CV_MinAreaRectTest : public CV_BaseShapeDescrTest -{ -public: - CV_MinAreaRectTest(); - -protected: - void run_func(void); - int validate_test_results( int test_case_idx ); - - Point2f box_pt[4]; -}; - - -CV_MinAreaRectTest::CV_MinAreaRectTest() -{ -} - - -void CV_MinAreaRectTest::run_func() -{ - cv::RotatedRect r = cv::minAreaRect(cv::cvarrToMat(points)); - r.points(box_pt); -} - - -int CV_MinAreaRectTest::validate_test_results( int test_case_idx ) -{ - double eps = 1e-1; - int code = CV_BaseShapeDescrTest::validate_test_results( test_case_idx ); - int i, j, point_count = points2->rows + points2->cols - 1; - CvPoint2D32f *p = (CvPoint2D32f*)(points2->data.ptr); - int mask[] = {0,0,0,0}; - - // check that the bounding box is a rotated rectangle: - // 1. diagonals should be equal - // 2. they must intersect in their middle points - { - double d0 = cvTsDist( box_pt[0], box_pt[2] ); - double d1 = cvTsDist( box_pt[1], box_pt[3] ); - - double x0 = (box_pt[0].x + box_pt[2].x)*0.5; - double y0 = (box_pt[0].y + box_pt[2].y)*0.5; - double x1 = (box_pt[1].x + box_pt[3].x)*0.5; - double y1 = (box_pt[1].y + box_pt[3].y)*0.5; - - if( fabs(d0 - d1) + fabs(x0 - x1) + fabs(y0 - y1) > eps*MAX(d0,d1) ) - { - ts->printf( cvtest::TS::LOG, "The bounding box is not a rectangle\n" ); - code = cvtest::TS::FAIL_INVALID_OUTPUT; - goto _exit_; - } - } - - // check that the box includes all the points - // and there is at least one point at (or very close to) every box side - for( i = 0; i < point_count; i++ ) - { - int idx = 0, on_edge = 0; - double pptresult = cvTsPointPolygonTest( p[i], box_pt, 4, &idx, &on_edge ); - if( pptresult < -eps ) - { - ts->printf( cvtest::TS::LOG, "The point #%d is outside of the box\n", i ); - code = cvtest::TS::FAIL_BAD_ACCURACY; - goto _exit_; - } - - if( pptresult < eps ) - { - for( j = 0; j < 4; j++ ) - { - double d = cvTsPtLineDist( p[i], box_pt[(j-1)&3], box_pt[j] ); - if( d < eps ) - mask[j] = (uchar)1; - } - } - } - - if( mask[0] + mask[1] + mask[2] + mask[3] != 4 ) - { - ts->printf( cvtest::TS::LOG, "Not every box side has a point nearby\n" ); - code = cvtest::TS::FAIL_BAD_ACCURACY; - goto _exit_; - } - -_exit_: - - if( code < 0 ) - ts->set_failed_test_info( code ); - return code; -} - - -/****************************************************************************************\ -* MinEnclosingTriangle Test * -\****************************************************************************************/ - -class CV_MinTriangleTest : public CV_BaseShapeDescrTest -{ -public: - CV_MinTriangleTest(); - -protected: - void run_func(void); - int validate_test_results( int test_case_idx ); - std::vector getTriangleMiddlePoints(); - - std::vector convexPolygon; - std::vector triangle; -}; - - -CV_MinTriangleTest::CV_MinTriangleTest() -{ -} - -std::vector CV_MinTriangleTest::getTriangleMiddlePoints() -{ - std::vector triangleMiddlePoints; - - for (int i = 0; i < 3; i++) { - triangleMiddlePoints.push_back(cvTsMiddlePoint(triangle[i], triangle[(i + 1) % 3])); - } - - return triangleMiddlePoints; -} - - -void CV_MinTriangleTest::run_func() -{ - std::vector pointsAsVector; - - cv::cvarrToMat(points).convertTo(pointsAsVector, CV_32F); - - cv::minEnclosingTriangle(pointsAsVector, triangle); - cv::convexHull(pointsAsVector, convexPolygon, true, true); -} - - -int CV_MinTriangleTest::validate_test_results( int test_case_idx ) -{ - bool errorEnclosed = false, errorMiddlePoints = false, errorFlush = true; - double eps = 1e-4; - int code = CV_BaseShapeDescrTest::validate_test_results( test_case_idx ); - - int polygonVertices = (int) convexPolygon.size(); - - if (polygonVertices > 2) { - // Check if all points are enclosed by the triangle - for (int i = 0; (i < polygonVertices) && (!errorEnclosed); i++) - { - if (cv::pointPolygonTest(triangle, cv::Point2f(convexPolygon[i].x, convexPolygon[i].y), true) < (-eps)) - errorEnclosed = true; - } - - // Check if triangle edges middle points touch the polygon - std::vector middlePoints = getTriangleMiddlePoints(); - - for (int i = 0; (i < 3) && (!errorMiddlePoints); i++) - { - bool isTouching = false; - - for (int j = 0; (j < polygonVertices) && (!isTouching); j++) - { - if (cvTsIsPointOnLineSegment(middlePoints[i], convexPolygon[j], - convexPolygon[(j + 1) % polygonVertices])) - isTouching = true; - } - - errorMiddlePoints = (isTouching) ? false : true; - } - - // Check if at least one of the edges is flush - for (int i = 0; (i < 3) && (errorFlush); i++) - { - for (int j = 0; (j < polygonVertices) && (errorFlush); j++) - { - if ((cvTsIsPointOnLineSegment(convexPolygon[j], triangle[i], - triangle[(i + 1) % 3])) && - (cvTsIsPointOnLineSegment(convexPolygon[(j + 1) % polygonVertices], triangle[i], - triangle[(i + 1) % 3]))) - errorFlush = false; - } - } - - // Report any found errors - if (errorEnclosed) - { - ts->printf( cvtest::TS::LOG, - "All points should be enclosed by the triangle.\n" ); - code = cvtest::TS::FAIL_BAD_ACCURACY; - } - else if (errorMiddlePoints) - { - ts->printf( cvtest::TS::LOG, - "All triangle edges middle points should touch the convex hull of the points.\n" ); - code = cvtest::TS::FAIL_INVALID_OUTPUT; - } - else if (errorFlush) - { - ts->printf( cvtest::TS::LOG, - "At least one edge of the enclosing triangle should be flush with one edge of the polygon.\n" ); - code = cvtest::TS::FAIL_INVALID_OUTPUT; - } - } - - if ( code < 0 ) - ts->set_failed_test_info( code ); - - return code; -} - - -/****************************************************************************************\ -* MinEnclosingCircle Test * -\****************************************************************************************/ - -class CV_MinCircleTest : public CV_BaseShapeDescrTest -{ -public: - CV_MinCircleTest(); - -protected: - void run_func(void); - int validate_test_results( int test_case_idx ); - - Point2f center; - float radius; -}; - - -CV_MinCircleTest::CV_MinCircleTest() -{ -} - - -void CV_MinCircleTest::run_func() -{ - cv::minEnclosingCircle(cv::cvarrToMat(points), center, radius); -} - - -int CV_MinCircleTest::validate_test_results( int test_case_idx ) -{ - double eps = 1.03; - int code = CV_BaseShapeDescrTest::validate_test_results( test_case_idx ); - int i, j = 0, point_count = points2->rows + points2->cols - 1; - Point2f *p = (Point2f*)(points2->data.ptr); - Point2f v[3]; - - // check that the circle contains all the points inside and - // remember at most 3 points that are close to the boundary - for( i = 0; i < point_count; i++ ) - { - double d = cvTsDist(p[i], center); - if( d > radius ) - { - ts->printf( cvtest::TS::LOG, "The point #%d is outside of the circle\n", i ); - code = cvtest::TS::FAIL_BAD_ACCURACY; - goto _exit_; - } - - if( radius - d < eps*radius && j < 3 ) - v[j++] = p[i]; - } - - if( point_count >= 2 && (j < 2 || (j == 2 && cvTsDist(v[0],v[1]) < (radius-1)*2/eps)) ) - { - ts->printf( cvtest::TS::LOG, - "There should be at least 3 points near the circle boundary or 2 points on the diameter\n" ); - code = cvtest::TS::FAIL_BAD_ACCURACY; - goto _exit_; - } - -_exit_: - - if( code < 0 ) - ts->set_failed_test_info( code ); - return code; -} - -/****************************************************************************************\ -* MinEnclosingCircle Test 2 * -\****************************************************************************************/ - -class CV_MinCircleTest2 : public CV_BaseShapeDescrTest -{ -public: - CV_MinCircleTest2(); -protected: - RNG rng; - void run_func(void); - int validate_test_results( int test_case_idx ); - float delta; -}; - - -CV_MinCircleTest2::CV_MinCircleTest2() -{ - rng = ts->get_rng(); -} - - -void CV_MinCircleTest2::run_func() -{ - Point2f center = Point2f(rng.uniform(0.0f, 1000.0f), rng.uniform(0.0f, 1000.0f));; - float radius = rng.uniform(0.0f, 500.0f); - float angle = (float)rng.uniform(0.0f, (float)(CV_2PI)); - vector pts; - pts.push_back(center + Point2f(radius * cos(angle), radius * sin(angle))); - angle += (float)CV_PI; - pts.push_back(center + Point2f(radius * cos(angle), radius * sin(angle))); - float radius2 = radius * radius; - float x = rng.uniform(center.x - radius, center.x + radius); - float deltaX = x - center.x; - float upperBoundY = sqrt(radius2 - deltaX * deltaX); - float y = rng.uniform(center.y - upperBoundY, center.y + upperBoundY); - pts.push_back(Point2f(x, y)); - // Find the minimum area enclosing circle - Point2f calcCenter; - float calcRadius; - minEnclosingCircle(pts, calcCenter, calcRadius); - delta = (float)cv::norm(calcCenter - center) + abs(calcRadius - radius); -} - -int CV_MinCircleTest2::validate_test_results( int test_case_idx ) -{ - float eps = 1.0F; - int code = CV_BaseShapeDescrTest::validate_test_results( test_case_idx ); - if (delta > eps) - { - ts->printf( cvtest::TS::LOG, "Delta center and calcCenter > %f\n", eps ); - code = cvtest::TS::FAIL_BAD_ACCURACY; - ts->set_failed_test_info( code ); - } - return code; -} - /****************************************************************************************\ * minEnclosingCircle Test 3 * \****************************************************************************************/ -TEST(Imgproc_minEnclosingCircle, basic_test) +TEST(minEnclosingCircle, basic_test) { vector pts; pts.push_back(Point2f(0, 0)); @@ -947,546 +58,74 @@ TEST(Imgproc_minEnclosingCircle, basic_test) pts.push_back(Point2f(5, 1)); const float EPS = 1.0e-3f; Point2f center; - float radius; - - // pts[2] is within the circle with diameter pts[0] - pts[1]. - // 2 - // 0 1 - // NB: The triangle is obtuse, so the only pts[0] and pts[1] are on the circle. - minEnclosingCircle(pts, center, radius); - EXPECT_NEAR(center.x, 5, EPS); - EXPECT_NEAR(center.y, 0, EPS); - EXPECT_NEAR(5, radius, EPS); - - // pts[2] is on the circle with diameter pts[0] - pts[1]. - // 2 - // 0 1 - pts[2] = Point2f(5, 5); - minEnclosingCircle(pts, center, radius); - EXPECT_NEAR(center.x, 5, EPS); - EXPECT_NEAR(center.y, 0, EPS); - EXPECT_NEAR(5, radius, EPS); - - // pts[2] is outside the circle with diameter pts[0] - pts[1]. - // 2 - // - // - // 0 1 - // NB: The triangle is acute, so all 3 points are on the circle. - pts[2] = Point2f(5, 10); - minEnclosingCircle(pts, center, radius); - EXPECT_NEAR(center.x, 5, EPS); - EXPECT_NEAR(center.y, 3.75, EPS); - EXPECT_NEAR(6.25f, radius, EPS); - - // The 3 points are colinear. - pts[2] = Point2f(3, 0); - minEnclosingCircle(pts, center, radius); - EXPECT_NEAR(center.x, 5, EPS); - EXPECT_NEAR(center.y, 0, EPS); - EXPECT_NEAR(5, radius, EPS); - - // 2 points are the same. - pts[2] = pts[1]; - minEnclosingCircle(pts, center, radius); - EXPECT_NEAR(center.x, 5, EPS); - EXPECT_NEAR(center.y, 0, EPS); - EXPECT_NEAR(5, radius, EPS); - - // 3 points are the same. - pts[0] = pts[1]; - minEnclosingCircle(pts, center, radius); - EXPECT_NEAR(center.x, 10, EPS); - EXPECT_NEAR(center.y, 0, EPS); - EXPECT_NEAR(0, radius, EPS); -} - -TEST(Imgproc_minEnclosingCircle, regression_16051) { - vector pts; - pts.push_back(Point2f(85, 1415)); - pts.push_back(Point2f(87, 1415)); - pts.push_back(Point2f(89, 1414)); - pts.push_back(Point2f(89, 1414)); - pts.push_back(Point2f(87, 1412)); - Point2f center; - float radius; - minEnclosingCircle(pts, center, radius); - EXPECT_NEAR(center.x, 86.9f, 1e-3); - EXPECT_NEAR(center.y, 1414.1f, 1e-3); - EXPECT_NEAR(2.1024551f, radius, 1e-3); -} - -/****************************************************************************************\ -* FitEllipse Test * -\****************************************************************************************/ - -class CV_FitEllipseTest : public CV_BaseShapeDescrTest -{ -public: - CV_FitEllipseTest(); - -protected: - int prepare_test_case( int test_case_idx ); - void generate_point_set( void* points ); - void run_func(void); - int validate_test_results( int test_case_idx ); - RotatedRect box0, box; - double min_ellipse_size, max_noise; -}; - - -CV_FitEllipseTest::CV_FitEllipseTest() -{ - min_log_size = 5; // for robust ellipse fitting a dozen of points is needed at least - max_log_size = 10; - min_ellipse_size = 10; - max_noise = 0.05; -} - - -void CV_FitEllipseTest::generate_point_set( void* pointsSet ) -{ - RNG& rng = ts->get_rng(); - int i, total, point_type; - CvSeqReader reader; - uchar* data = 0; - double a, b; - - box0.center.x = (float)((low.val[0] + high.val[0])*0.5); - box0.center.y = (float)((low.val[1] + high.val[1])*0.5); - box0.size.width = (float)(MAX(high.val[0] - low.val[0], min_ellipse_size)*2); - box0.size.height = (float)(MAX(high.val[1] - low.val[1], min_ellipse_size)*2); - box0.angle = (float)(cvtest::randReal(rng)*180); - a = cos(box0.angle*CV_PI/180.); - b = sin(box0.angle*CV_PI/180.); - - if( box0.size.width > box0.size.height ) - { - float t; - CV_SWAP( box0.size.width, box0.size.height, t ); - } - memset( &reader, 0, sizeof(reader) ); - - if( CV_IS_SEQ(pointsSet) ) - { - CvSeq* ptseq = (CvSeq*)pointsSet; - total = ptseq->total; - point_type = CV_SEQ_ELTYPE(ptseq); - cvStartReadSeq( ptseq, &reader ); - } - else - { - CvMat* ptm = (CvMat*)pointsSet; - CV_Assert( CV_IS_MAT(ptm) && CV_IS_MAT_CONT(ptm->type) ); - total = ptm->rows + ptm->cols - 1; - point_type = CV_MAT_TYPE(ptm->type); - data = ptm->data.ptr; - } - - CV_Assert(point_type == CV_32SC2 || point_type == CV_32FC2); - - for( i = 0; i < total; i++ ) - { - CvPoint* pp; - CvPoint2D32f p = {0, 0}; - double angle = cvtest::randReal(rng)*CV_PI*2; - double x = box0.size.height*0.5*(cos(angle) + (cvtest::randReal(rng)-0.5)*2*max_noise); - double y = box0.size.width*0.5*(sin(angle) + (cvtest::randReal(rng)-0.5)*2*max_noise); - p.x = (float)(box0.center.x + a*x + b*y); - p.y = (float)(box0.center.y - b*x + a*y); - - if( reader.ptr ) - { - pp = (CvPoint*)reader.ptr; - CV_NEXT_SEQ_ELEM( sizeof(*pp), reader ); - } - else - pp = ((CvPoint*)data) + i; - if( point_type == CV_32SC2 ) - { - pp->x = cvRound(p.x); - pp->y = cvRound(p.y); - } - else - *(CvPoint2D32f*)pp = p; - } -} - - -int CV_FitEllipseTest::prepare_test_case( int test_case_idx ) -{ - min_log_size = MAX(min_log_size,4); - max_log_size = MAX(min_log_size,max_log_size); - return CV_BaseShapeDescrTest::prepare_test_case( test_case_idx ); -} - - -void CV_FitEllipseTest::run_func() -{ - box = cv::fitEllipse(cv::cvarrToMat(points)); -} - -int CV_FitEllipseTest::validate_test_results( int test_case_idx ) -{ - int code = CV_BaseShapeDescrTest::validate_test_results( test_case_idx ); - double diff_angle; - - if( cvIsNaN(box.center.x) || cvIsInf(box.center.x) || - cvIsNaN(box.center.y) || cvIsInf(box.center.y) || - cvIsNaN(box.size.width) || cvIsInf(box.size.width) || - cvIsNaN(box.size.height) || cvIsInf(box.size.height) || - cvIsNaN(box.angle) || cvIsInf(box.angle) ) - { - ts->printf( cvtest::TS::LOG, "Some of the computed ellipse parameters are invalid (x=%g,y=%g,w=%g,h=%g,angle=%g)\n", - box.center.x, box.center.y, box.size.width, box.size.height, box.angle ); - code = cvtest::TS::FAIL_INVALID_OUTPUT; - goto _exit_; - } - - box.angle = (float)(90-box.angle); - if( box.angle < 0 ) - box.angle += 360; - if( box.angle > 360 ) - box.angle -= 360; - - if( fabs(box.center.x - box0.center.x) > 3 || - fabs(box.center.y - box0.center.y) > 3 || - fabs(box.size.width - box0.size.width) > 0.1*fabs(box0.size.width) || - fabs(box.size.height - box0.size.height) > 0.1*fabs(box0.size.height) ) - { - ts->printf( cvtest::TS::LOG, "The computed ellipse center and/or size are incorrect:\n\t" - "(x=%.1f,y=%.1f,w=%.1f,h=%.1f), while it should be (x=%.1f,y=%.1f,w=%.1f,h=%.1f)\n", - box.center.x, box.center.y, box.size.width, box.size.height, - box0.center.x, box0.center.y, box0.size.width, box0.size.height ); - code = cvtest::TS::FAIL_BAD_ACCURACY; - goto _exit_; - } - - diff_angle = fabs(box0.angle - box.angle); - diff_angle = MIN( diff_angle, fabs(diff_angle - 360)); - diff_angle = MIN( diff_angle, fabs(diff_angle - 180)); - - if( box0.size.height >= 1.3*box0.size.width && diff_angle > 30 ) - { - ts->printf( cvtest::TS::LOG, "Incorrect ellipse angle (=%1.f, should be %1.f)\n", - box.angle, box0.angle ); - code = cvtest::TS::FAIL_BAD_ACCURACY; - goto _exit_; - } - -_exit_: - - if( code < 0 ) - { - ts->set_failed_test_info( code ); - } - return code; -} - - -class CV_FitEllipseSmallTest : public cvtest::BaseTest -{ -public: - CV_FitEllipseSmallTest() {} - ~CV_FitEllipseSmallTest() {} -protected: - void run(int) - { - Size sz(50, 50); - vector > c; - c.push_back(vector()); - int scale = 1; - Point ofs = Point(0,0);//sz.width/2, sz.height/2) - Point(4,4)*scale; - c[0].push_back(Point(2, 0)*scale+ofs); - c[0].push_back(Point(0, 2)*scale+ofs); - c[0].push_back(Point(0, 6)*scale+ofs); - c[0].push_back(Point(2, 8)*scale+ofs); - c[0].push_back(Point(6, 8)*scale+ofs); - c[0].push_back(Point(8, 6)*scale+ofs); - c[0].push_back(Point(8, 2)*scale+ofs); - c[0].push_back(Point(6, 0)*scale+ofs); - - RotatedRect e = fitEllipse(c[0]); - CV_Assert( fabs(e.center.x - 4) <= 1. && - fabs(e.center.y - 4) <= 1. && - fabs(e.size.width - 9) <= 1. && - fabs(e.size.height - 9) <= 1. ); - } -}; - - -// Regression test for incorrect fitEllipse result reported in Bug #3989 -// Check edge cases for rotation angles of ellipse ([-180, 90, 0, 90, 180] degrees) -class CV_FitEllipseParallelTest : public CV_FitEllipseTest -{ -public: - CV_FitEllipseParallelTest(); - ~CV_FitEllipseParallelTest(); -protected: - void generate_point_set( void* points ); - void run_func(void); - Mat pointsMat; -}; - -CV_FitEllipseParallelTest::CV_FitEllipseParallelTest() -{ - min_ellipse_size = 5; -} - -void CV_FitEllipseParallelTest::generate_point_set( void* ) -{ - RNG& rng = ts->get_rng(); - int height = (int)(MAX(high.val[0] - low.val[0], min_ellipse_size)); - int width = (int)(MAX(high.val[1] - low.val[1], min_ellipse_size)); - const int angle = ( (cvtest::randInt(rng) % 5) - 2 ) * 90; - const int dim = max(height, width); - const Point center = Point(dim*2, dim*2); - - if( width > height ) - { - int t; - CV_SWAP( width, height, t ); - } - - Mat image = Mat::zeros(dim*4, dim*4, CV_8UC1); - ellipse(image, center, Size(height, width), angle, - 0, 360, Scalar(255, 0, 0), 1, 8); - - box0.center.x = (float)center.x; - box0.center.y = (float)center.y; - box0.size.width = (float)width*2; - box0.size.height = (float)height*2; - box0.angle = (float)angle; - - vector > contours; - findContours(image, contours, RETR_EXTERNAL, CHAIN_APPROX_NONE); - Mat(contours[0]).convertTo(pointsMat, CV_32F); -} - -void CV_FitEllipseParallelTest::run_func() -{ - box = cv::fitEllipse(pointsMat); -} - -CV_FitEllipseParallelTest::~CV_FitEllipseParallelTest(){ - pointsMat.release(); -} - -/****************************************************************************************\ -* FitLine Test * -\****************************************************************************************/ - -class CV_FitLineTest : public CV_BaseShapeDescrTest -{ -public: - CV_FitLineTest(); - -protected: - int prepare_test_case( int test_case_idx ); - void generate_point_set( void* points ); - void run_func(void); - int validate_test_results( int test_case_idx ); - double max_noise; - AutoBuffer line, line0; - int dist_type; - double reps, aeps; -}; - - -CV_FitLineTest::CV_FitLineTest() -{ - min_log_size = 5; // for robust line fitting a dozen of points is needed at least - max_log_size = 10; - max_noise = 0.05; -} - -void CV_FitLineTest::generate_point_set( void* pointsSet ) -{ - RNG& rng = ts->get_rng(); - int i, k, n, total, point_type; - CvSeqReader reader; - uchar* data = 0; - double s = 0; - - n = dims; - for( k = 0; k < n; k++ ) - { - line0[k+n] = (float)((low.val[k] + high.val[k])*0.5); - line0[k] = (float)(high.val[k] - low.val[k]); - if( cvtest::randInt(rng) % 2 ) - line0[k] = -line0[k]; - s += (double)line0[k]*line0[k]; - } - - s = 1./sqrt(s); - for( k = 0; k < n; k++ ) - line0[k] = (float)(line0[k]*s); - - memset( &reader, 0, sizeof(reader) ); - - if( CV_IS_SEQ(pointsSet) ) - { - CvSeq* ptseq = (CvSeq*)pointsSet; - total = ptseq->total; - point_type = CV_MAT_DEPTH(CV_SEQ_ELTYPE(ptseq)); - cvStartReadSeq( ptseq, &reader ); - } - else - { - CvMat* ptm = (CvMat*)pointsSet; - CV_Assert( CV_IS_MAT(ptm) && CV_IS_MAT_CONT(ptm->type) ); - total = ptm->rows + ptm->cols - 1; - point_type = CV_MAT_DEPTH(CV_MAT_TYPE(ptm->type)); - data = ptm->data.ptr; - } - - for( i = 0; i < total; i++ ) - { - int* pi; - float* pf; - float p[4], t; - if( reader.ptr ) - { - pi = (int*)reader.ptr; - pf = (float*)reader.ptr; - CV_NEXT_SEQ_ELEM( reader.seq->elem_size, reader ); - } - else - { - pi = (int*)data + i*n; - pf = (float*)data + i*n; - } - - t = (float)((cvtest::randReal(rng)-0.5)*low_high_range*2); - - for( k = 0; k < n; k++ ) - { - p[k] = (float)((cvtest::randReal(rng)-0.5)*max_noise*2 + t*line0[k] + line0[k+n]); - - if( point_type == CV_32S ) - pi[k] = cvRound(p[k]); - else - pf[k] = p[k]; - } - } -} - -int CV_FitLineTest::prepare_test_case( int test_case_idx ) -{ - RNG& rng = ts->get_rng(); - dims = cvtest::randInt(rng) % 2 + 2; - line.allocate(dims * 2); - line0.allocate(dims * 2); - min_log_size = MAX(min_log_size,5); - max_log_size = MAX(min_log_size,max_log_size); - int code = CV_BaseShapeDescrTest::prepare_test_case( test_case_idx ); - dist_type = cvtest::randInt(rng) % 6 + 1; - dist_type += dist_type == cv::DIST_C; - reps = 0.1; aeps = 0.01; - return code; -} - - -void CV_FitLineTest::run_func() -{ - if(dims == 2) - cv::fitLine(cv::cvarrToMat(points), (cv::Vec4f&)line[0], dist_type, 0, reps, aeps); - else - cv::fitLine(cv::cvarrToMat(points), (cv::Vec6f&)line[0], dist_type, 0, reps, aeps); -} - -int CV_FitLineTest::validate_test_results( int test_case_idx ) -{ - int code = CV_BaseShapeDescrTest::validate_test_results( test_case_idx ); - int k, max_k = 0; - double vec_diff = 0, t; - - //std::cout << dims << " " << Mat(1, dims*2, CV_32FC1, line.data()) << " " << Mat(1, dims, CV_32FC1, line0.data()) << std::endl; - - for( k = 0; k < dims*2; k++ ) - { - if( cvIsNaN(line[k]) || cvIsInf(line[k]) ) - { - ts->printf( cvtest::TS::LOG, "Some of the computed line parameters are invalid (line[%d]=%g)\n", - k, line[k] ); - code = cvtest::TS::FAIL_INVALID_OUTPUT; - goto _exit_; - } - } + float radius; - if( fabs(line0[1]) > fabs(line0[0]) ) - max_k = 1; - if( fabs(line0[dims-1]) > fabs(line0[max_k]) ) - max_k = dims-1; - if( line0[max_k] < 0 ) - for( k = 0; k < dims; k++ ) - line0[k] = -line0[k]; - if( line[max_k] < 0 ) - for( k = 0; k < dims; k++ ) - line[k] = -line[k]; - - for( k = 0; k < dims; k++ ) - { - double dt = line[k] - line0[k]; - vec_diff += dt*dt; - } + // pts[2] is within the circle with diameter pts[0] - pts[1]. + // 2 + // 0 1 + // NB: The triangle is obtuse, so the only pts[0] and pts[1] are on the circle. + minEnclosingCircle(pts, center, radius); + EXPECT_NEAR(center.x, 5, EPS); + EXPECT_NEAR(center.y, 0, EPS); + EXPECT_NEAR(5, radius, EPS); - if( sqrt(vec_diff) > 0.05 ) - { - if( dims == 2 ) - ts->printf( cvtest::TS::LOG, - "The computed line vector (%.2f,%.2f) is different from the actual (%.2f,%.2f)\n", - line[0], line[1], line0[0], line0[1] ); - else - ts->printf( cvtest::TS::LOG, - "The computed line vector (%.2f,%.2f,%.2f) is different from the actual (%.2f,%.2f,%.2f)\n", - line[0], line[1], line[2], line0[0], line0[1], line0[2] ); - code = cvtest::TS::FAIL_BAD_ACCURACY; - goto _exit_; - } + // pts[2] is on the circle with diameter pts[0] - pts[1]. + // 2 + // 0 1 + pts[2] = Point2f(5, 5); + minEnclosingCircle(pts, center, radius); + EXPECT_NEAR(center.x, 5, EPS); + EXPECT_NEAR(center.y, 0, EPS); + EXPECT_NEAR(5, radius, EPS); - t = (line[max_k+dims] - line0[max_k+dims])/line0[max_k]; - for( k = 0; k < dims; k++ ) - { - double p = line0[k+dims] + t*line0[k] - line[k+dims]; - vec_diff += p*p; - } + // pts[2] is outside the circle with diameter pts[0] - pts[1]. + // 2 + // + // + // 0 1 + // NB: The triangle is acute, so all 3 points are on the circle. + pts[2] = Point2f(5, 10); + minEnclosingCircle(pts, center, radius); + EXPECT_NEAR(center.x, 5, EPS); + EXPECT_NEAR(center.y, 3.75, EPS); + EXPECT_NEAR(6.25f, radius, EPS); - if( sqrt(vec_diff) > 1*MAX(fabs(t),1) ) - { - if( dims == 2 ) - ts->printf( cvtest::TS::LOG, - "The computed line point (%.2f,%.2f) is too far from the actual line\n", - line[2]+line0[2], line[3]+line0[3] ); - else - ts->printf( cvtest::TS::LOG, - "The computed line point (%.2f,%.2f,%.2f) is too far from the actual line\n", - line[3]+line0[3], line[4]+line0[4], line[5]+line0[5] ); - code = cvtest::TS::FAIL_BAD_ACCURACY; - goto _exit_; - } + // The 3 points are colinear. + pts[2] = Point2f(3, 0); + minEnclosingCircle(pts, center, radius); + EXPECT_NEAR(center.x, 5, EPS); + EXPECT_NEAR(center.y, 0, EPS); + EXPECT_NEAR(5, radius, EPS); -_exit_: + // 2 points are the same. + pts[2] = pts[1]; + minEnclosingCircle(pts, center, radius); + EXPECT_NEAR(center.x, 5, EPS); + EXPECT_NEAR(center.y, 0, EPS); + EXPECT_NEAR(5, radius, EPS); - if( code < 0 ) - { - ts->set_failed_test_info( code ); - } - return code; + // 3 points are the same. + pts[0] = pts[1]; + minEnclosingCircle(pts, center, radius); + EXPECT_NEAR(center.x, 10, EPS); + EXPECT_NEAR(center.y, 0, EPS); + EXPECT_NEAR(0, radius, EPS); } - -TEST(Imgproc_ConvexHull, accuracy) { CV_ConvHullTest test; test.safe_run(); } -TEST(Imgproc_MinAreaRect, accuracy) { CV_MinAreaRectTest test; test.safe_run(); } -TEST(Imgproc_MinTriangle, accuracy) { CV_MinTriangleTest test; test.safe_run(); } -TEST(Imgproc_MinCircle, accuracy) { CV_MinCircleTest test; test.safe_run(); } -TEST(Imgproc_MinCircle2, accuracy) { CV_MinCircleTest2 test; test.safe_run(); } -TEST(Imgproc_FitEllipse, accuracy) { CV_FitEllipseTest test; test.safe_run(); } -TEST(Imgproc_FitEllipse, parallel) { CV_FitEllipseParallelTest test; test.safe_run(); } -TEST(Imgproc_FitLine, accuracy) { CV_FitLineTest test; test.safe_run(); } -TEST(Imgproc_FitEllipse, small) { CV_FitEllipseSmallTest test; test.safe_run(); } - - +TEST(Imgproc_minEnclosingCircle, regression_16051) { + vector pts; + pts.push_back(Point2f(85, 1415)); + pts.push_back(Point2f(87, 1415)); + pts.push_back(Point2f(89, 1414)); + pts.push_back(Point2f(89, 1414)); + pts.push_back(Point2f(87, 1412)); + Point2f center; + float radius; + minEnclosingCircle(pts, center, radius); + EXPECT_NEAR(center.x, 86.9f, 1e-3); + EXPECT_NEAR(center.y, 1414.1f, 1e-3); + EXPECT_NEAR(2.1024551f, radius, 1e-3); +} PARAM_TEST_CASE(ConvexityDefects_regression_5908, bool, int) { @@ -1912,5 +551,523 @@ TEST(Imgproc_minEnclosingTriangle, regression_mat_with_diff_channels) EXPECT_NO_THROW(minEnclosingTriangle(pointsNx1, triangle)); } +//============================================================================== + +typedef testing::TestWithParam> fitLine_Modes; + +TEST_P(fitLine_Modes, accuracy) +{ + const int data_type = get<0>(GetParam()); + const int dist_type = get<1>(GetParam()); + const int CN = CV_MAT_CN(data_type); + const int res_type = CV_32FC(CN); + + for (int ITER = 0; ITER < 20; ++ITER) + { + SCOPED_TRACE(cv::format("iteration %d", ITER)); + + Mat v0(1, 1, data_type), v1(1, 1, data_type); // pt = v0 + v1 * t + Mat v1n; + + RNG& rng = TS::ptr()->get_rng(); + cvtest::randUni(rng, v0, Scalar::all(1), Scalar::all(100)); + cvtest::randUni(rng, v1, Scalar::all(1), Scalar::all(100)); + normalize(v1, v1n, 1, 0, NORM_L2, res_type); + v0.convertTo(v0, res_type); + v1.convertTo(v1, res_type); + + const int NUM = rng.uniform(30, 100); + Mat points(NUM, 1, data_type, Scalar::all(0)); + for (int i = 0; i < NUM; ++i) + { + Mat pt = v0 + v1 * i; + if (CV_MAT_DEPTH(data_type) == CV_32F) + { + Mat noise = cvtest::randomMat(rng, Size(1, 1), res_type, -0.01, 0.01, false); + pt += noise; + + } + pt.copyTo(points.row(i)); + } + + Mat line_; + cv::fitLine(points, line_, dist_type, 0, 0.1, 0.01); + Mat line = line_.reshape(points.channels(), 1); + + // check result type and size + EXPECT_EQ(res_type, line.type()); + EXPECT_EQ(Size(2, 1), line.size()); + + // check result pt1 + const double angle = line.col(0).dot(v1n); + EXPECT_NEAR(abs(angle), 1, 1e-2); + + // put result pt0 to the original equation (pt = v0 + v1 * t) and find "t" + Mat diff = line.col(1) - v0; + cv::divide(diff, v1, diff); + cv::divide(diff, diff.at(0, 0), diff); + const Mat unit(1, 1, res_type, Scalar::all(1)); + EXPECT_NEAR(cvtest::norm(diff, unit, NORM_L1), 0, 0.01); + } +} + +INSTANTIATE_TEST_CASE_P(/**/, + fitLine_Modes, + testing::Combine( + testing::Values(CV_32FC2, CV_32FC3, CV_32SC2, CV_32SC3), + testing::Values(DIST_L1, DIST_L2, DIST_L12, DIST_FAIR, DIST_WELSCH, DIST_HUBER))); + +//============================================================================== + +inline float normAngle(float angle_deg) +{ + while (angle_deg < 0.f) + angle_deg += 180.f; + while (angle_deg > 180.f) + angle_deg -= 180.f; + if (abs(angle_deg - 180.f) < 0.01) // border case + angle_deg = 0.f; + return angle_deg; +} + +inline float angleToDeg(float angle_rad) +{ + return angle_rad * 180.f / (float)M_PI; +} + +inline float angleDiff(float a, float b) +{ + float res = a - b; + return normAngle(res); +} + +typedef testing::TestWithParam fitEllipse_Modes; + +TEST_P(fitEllipse_Modes, accuracy) +{ + const int data_type = GetParam(); + const float int_scale = 1000.f; + const Size sz(1, 2); + const Matx22f rot {0.f, -1.f, 1.f, 0.f}; + RNG& rng = TS::ptr()->get_rng(); + + for (int ITER = 0; ITER < 20; ++ITER) + { + SCOPED_TRACE(cv::format("iteration %d", ITER)); + + Mat f0(sz, CV_32FC1), f1(sz, CV_32FC1), f2(sz, CV_32FC1); + cvtest::randUni(rng, f0, Scalar::all(-100), Scalar::all(100)); + cvtest::randUni(rng, f1, Scalar::all(-100), Scalar::all(100)); + if (ITER % 4 == 0) + { + // 0/90 degrees case + f1.at(0, 0) = 0.; + } + // f2 is orthogonal to f1 and scaled + f2 = rot * f1 * cvtest::randomDouble(0.01, 3); + + const Point2f ref_center(f0.at(0), f0.at(1)); + const Size2f ref_size( + (float)cvtest::norm(f1, NORM_L2) * 2.f, + (float)cvtest::norm(f2, NORM_L2) * 2.f); + const float ref_angle1 = angleToDeg(atan(f1.at(1) / f1.at(0))); + const float ref_angle2 = angleToDeg(atan(f2.at(1) / f2.at(0))); + + const int NUM = rng.uniform(10, 30); + Mat points(NUM, 1, data_type, Scalar::all(0)); + for (int i = 0; i < NUM; ++i) + { + Mat pt = f0 + f1 * sin(i) + f2 * cos(i); + pt = pt.reshape(2); + if (data_type == CV_32SC2) + { + pt.convertTo(points.row(i), CV_32SC2, int_scale); + } + else if (data_type == CV_32FC2) + { + pt.copyTo(points.row(i)); + } + else + { + FAIL() << "unsupported data type: " << data_type; + } + } + + RotatedRect res = cv::fitEllipse(points); + + if (data_type == CV_32SC2) + { + res.center /= int_scale; + res.size = Size2f(res.size.width / int_scale, res.size.height / int_scale); + } + const bool sizeSwap = (res.size.width < res.size.height) != (ref_size.width < ref_size.height); + if (sizeSwap) + { + std::swap(res.size.width, res.size.height); + } + EXPECT_FALSE(res.size.empty()); + EXPECT_POINT2_NEAR(res.center, ref_center, 0.01); + const float sizeDiff = (data_type == CV_32FC2) ? 0.1f : 1.f; + EXPECT_NEAR(min(res.size.width, res.size.height), min(ref_size.width, ref_size.height), sizeDiff); + EXPECT_NEAR(max(res.size.width, res.size.height), max(ref_size.width, ref_size.height), sizeDiff); + if (sizeSwap) + { + EXPECT_LE(angleDiff(ref_angle2, res.angle), 0.1); + } + else + { + EXPECT_LE(angleDiff(ref_angle1, res.angle), 0.1); + } + } +} + +INSTANTIATE_TEST_CASE_P(/**/, + fitEllipse_Modes, + testing::Values(CV_32FC2, CV_32SC2)); + +//============================================================================== + +TEST(fitEllipse, small) +{ + Size sz(50, 50); + vector > c; + c.push_back(vector()); + int scale = 1; + Point ofs = Point(0,0);//sz.width/2, sz.height/2) - Point(4,4)*scale; + c[0].push_back(Point(2, 0)*scale+ofs); + c[0].push_back(Point(0, 2)*scale+ofs); + c[0].push_back(Point(0, 6)*scale+ofs); + c[0].push_back(Point(2, 8)*scale+ofs); + c[0].push_back(Point(6, 8)*scale+ofs); + c[0].push_back(Point(8, 6)*scale+ofs); + c[0].push_back(Point(8, 2)*scale+ofs); + c[0].push_back(Point(6, 0)*scale+ofs); + + RotatedRect e = cv::fitEllipse(c[0]); + + EXPECT_NEAR(e.center.x, 4, 1.f); + EXPECT_NEAR(e.center.y, 4, 1.f); + EXPECT_NEAR(e.size.width, 9, 1.); + EXPECT_NEAR(e.size.height, 9, 1.f); +} + +//============================================================================== + +// points stored in rows +inline static int findPointInMat(const Mat & data, const Mat & point) +{ + for (int i = 0; i < data.rows; ++i) + if (cvtest::norm(data.row(i), point, NORM_L1) == 0) + return i; + return -1; +} + +// > 0 - "pt" is to the right of AB +// < 0 - "pt" is to the left of AB +// points stored in rows +inline static double getSide(const Mat & ptA, const Mat & ptB, const Mat & pt) +{ + Mat d0 = pt - ptA, d1 = ptB - pt, prod; + vconcat(d0, d1, prod); + prod = prod.reshape(1); + if (prod.depth() == CV_32S) + prod.convertTo(prod, CV_32F); + return determinant(prod); +} + +typedef testing::TestWithParam convexHull_Modes; + +TEST_P(convexHull_Modes, accuracy) +{ + const int data_type = CV_MAKE_TYPE(GetParam(), 2); + RNG & rng = TS::ptr()->get_rng(); + + for (int ITER = 0; ITER < 20; ++ITER) + { + SCOPED_TRACE(cv::format("iteration %d", ITER)); + + const int NUM = cvtest::randomInt(5, 100); + Mat points(NUM, 1, data_type, Scalar::all(0)); + cvtest::randUni(rng, points, Scalar(-10), Scalar::all(10)); + + Mat hull, c_hull, indexes; + cv::convexHull(points, hull, false, true); // default parameters + cv::convexHull(points, c_hull, true, true); // counter-clockwise + cv::convexHull(points, indexes, false, false); // point indexes + + ASSERT_EQ(hull.size().width, 1); + ASSERT_GE(hull.size().height, 3); + ASSERT_EQ(hull.size(), c_hull.size()); + ASSERT_EQ(hull.size(), indexes.size()); + + // find shift between hull and counter-clockwise hull + const int c_diff = findPointInMat(hull, c_hull.row(0)); + ASSERT_NE(c_diff, -1); + + const int sz = (int)hull.total(); + for (int i = 0; i < sz; ++i) + { + SCOPED_TRACE(cv::format("vertex %d", i)); + + Mat prev = (i == 0) ? hull.row(sz - 1) : hull.row(i - 1); + Mat cur = hull.row(i); + Mat next = (i != sz - 1) ? hull.row(i + 1) : hull.row(0); + // 1. "cur' is one of points + EXPECT_NE(findPointInMat(points, cur), -1); + // 2. convexity: "cur" is on right side of "prev - next" edge + EXPECT_GE(getSide(prev, next, cur), 0); + // 3. all points are inside polygon - on the left side of "cur - next" edge + for (int j = 0; j < points.rows; ++j) + { + SCOPED_TRACE(cv::format("point %d", j)); + EXPECT_LE(getSide(cur, next, points.row(j)), 0); + } + // check counter-clockwise hull + const int c_idx = (sz - i + c_diff) % sz; + Mat c_cur = c_hull.row(c_idx); + EXPECT_MAT_NEAR(cur, c_cur, 0); + // check indexed hull + const int pt_index = indexes.at(i); + EXPECT_MAT_NEAR(cur, points.row(pt_index), 0); + } + } +} + +INSTANTIATE_TEST_CASE_P(/**/, + convexHull_Modes, + testing::Values(CV_32F, CV_32S)); + + +//============================================================================== + +typedef testing::TestWithParam minAreaRect_Modes; + +TEST_P(minAreaRect_Modes, accuracy) +{ + const int data_type = CV_MAKE_TYPE(GetParam(), 2); + RNG & rng = TS::ptr()->get_rng(); + for (int ITER = 0; ITER < 20; ++ITER) + { + SCOPED_TRACE(cv::format("iteration %d", ITER)); + + const int NUM = cvtest::randomInt(5, 100); + Mat points(NUM, 1, data_type, Scalar::all(0)); + cvtest::randUni(rng, points, Scalar(-10), Scalar::all(10)); + + const RotatedRect res = cv::minAreaRect(points); + Point2f box_pts[4] {}; + res.points(box_pts); + + // check that the box contains all the points - all on one side + double common_side = 0.; + bool edgeHasPoint[4] {0}; + for (int i = 0; i < 4; ++i) + { + const int j = (i == 3) ? 0 : i + 1; + Mat cur(1, 1, CV_32FC2, box_pts + i); + Mat next(1, 1, CV_32FC2, box_pts + j); + for (int k = 0; k < points.rows; ++k) + { + SCOPED_TRACE(cv::format("point %d", j)); + Mat one_point; + points.row(k).convertTo(one_point, CV_32FC2); + const double side = getSide(cur, next, one_point); + if (abs(side) < 0.01) // point on edge - no need to check + { + edgeHasPoint[i] = true; + continue; + } + if (common_side == 0.) // initial state + { + common_side = side > 0 ? 1. : -1.; // only sign matters + } + else + { + EXPECT_EQ(common_side > 0, side > 0) << common_side << ", " << side; + } + } + } + EXPECT_TRUE(edgeHasPoint[0] && edgeHasPoint[1] && edgeHasPoint[2] && edgeHasPoint[3]); + } + +} + +INSTANTIATE_TEST_CASE_P(/**/, + minAreaRect_Modes, + testing::Values(CV_32F, CV_32S)); + + +//============================================================================== + +// true if "point" is on one of hull's edges +inline static bool isPointOnHull(const Mat &hull, const Mat &point, const double thresh = 0.01) +{ + const int sz = hull.rows; + for (int k = 0; k < sz; ++k) + { + const double side = getSide(hull.row(k), hull.row(k == sz - 1 ? 0 : k + 1), point); + if (abs(side) < thresh) + return true; + } + return false; +} + +// true if one of hull's edges touches "A-B" +inline static bool isEdgeOnHull(const Mat &hull, const Mat &ptA, const Mat &ptB, const double thresh = 0.01) +{ + const int sz = hull.rows; + double prev_side = getSide(ptA, ptB, hull.row(sz - 1)); + for (int k = 0; k < sz; ++k) + { + Mat cur = hull.row(k); + const double cur_side = getSide(ptA, ptB, cur); + if (abs(prev_side) < thresh && abs(cur_side) < thresh) + return true; + prev_side = cur_side; + } + return false; +} + +typedef testing::TestWithParam minEnclosingTriangle_Modes; + +TEST_P(minEnclosingTriangle_Modes, accuracy) +{ + const int data_type = CV_MAKETYPE(GetParam(), 2); + RNG & rng = TS::ptr()->get_rng(); + for (int ITER = 0; ITER < 20; ++ITER) + { + SCOPED_TRACE(cv::format("iteration %d", ITER)); + + const int NUM = cvtest::randomInt(5, 100); + Mat points(NUM, 1, data_type, Scalar::all(0)); + cvtest::randUni(rng, points, Scalar::all(-100), Scalar::all(100)); + + Mat triangle; + const double area = cv::minEnclosingTriangle(points, triangle); + + ASSERT_GT(area, 0.0001); + ASSERT_EQ(triangle.type(), CV_32FC2); + triangle = triangle.reshape(2, 1); + ASSERT_EQ(triangle.size(), Size(3, 1)); + + Mat hull; + cv::convexHull(points, hull); + hull.convertTo(hull, CV_32FC2); + + // check that all points are enclosed by triangle sides + double commonSide = 0.; + bool hasEdgeOnHull = false; + for (int i = 0; i < 3; ++i) + { + SCOPED_TRACE(cv::format("edge %d", i)); + const int j = (i == 2) ? 0 : i + 1; + Mat cur = triangle.col(i); + Mat next = triangle.col(j); + for (int k = 0; k < points.rows; ++k) + { + SCOPED_TRACE(cv::format("point %d", k)); + Mat pt; + points.row(k).convertTo(pt, CV_32FC2); + const double side = getSide(cur, next, pt); + if (abs(side) < 0.01) // point on edge - no need to check + continue; + if (commonSide == 0.f) // initial state + { + commonSide = side > 0 ? 1.f : -1.f; // only sign matters + } + else + { + // either on the same side or close to zero + EXPECT_EQ(commonSide > 0, side > 0) << commonSide << ", side=" << side; + } + } + + // triangle mid-points must be on the hull edges + const Mat midPoint = (cur + next) / 2; + EXPECT_TRUE(isPointOnHull(hull, midPoint)); + + // at least one of hull edges must be on tirangle edge + hasEdgeOnHull = hasEdgeOnHull || isEdgeOnHull(hull, cur, next); + } + EXPECT_TRUE(hasEdgeOnHull); + } +} + +INSTANTIATE_TEST_CASE_P(/**/, + minEnclosingTriangle_Modes, + testing::Values(CV_32F, CV_32S)); + +//============================================================================== + +typedef testing::TestWithParam minEnclosingCircle_Modes; + +TEST_P(minEnclosingCircle_Modes, accuracy) +{ + const int data_type = CV_MAKETYPE(GetParam(), 2); + RNG & rng = TS::ptr()->get_rng(); + for (int ITER = 0; ITER < 20; ++ITER) + { + SCOPED_TRACE(cv::format("iteration %d", ITER)); + + const int NUM = cvtest::randomInt(5, 100); + Mat points(NUM, 1, data_type, Scalar::all(0)), fpoints; + cvtest::randUni(rng, points, Scalar::all(-100), Scalar::all(100)); + points.convertTo(fpoints, CV_32FC2); + + Point2f center {}; + float radius = 0.f; + cv::minEnclosingCircle(points, center, radius); + + vector boundPts; // indexes + for (int i = 0; i < NUM; ++i) + { + Point2f pt = fpoints.at(i); + const double dist = cv::norm(pt - center); + EXPECT_LE(dist, radius); + if (abs(dist - radius) < 0.01) + boundPts.push_back(i); + } + // 2 points on diameter or at least 3 points on circle + EXPECT_GE(boundPts.size(), 2llu); + + // 2 points on diameter + if (boundPts.size() == 2llu) + { + const Point2f diff = fpoints.at(boundPts[0]) - fpoints.at(boundPts[1]); + EXPECT_NEAR(cv::norm(diff), 2 * radius, 0.001); + } + } +} + +INSTANTIATE_TEST_CASE_P(/**/, + minEnclosingCircle_Modes, + testing::Values(CV_32F, CV_32S)); + +//============================================================================== + +TEST(minEnclosingCircle, three_points) +{ + RNG & rng = TS::ptr()->get_rng(); + Point2f center = Point2f(rng.uniform(0.0f, 1000.0f), rng.uniform(0.0f, 1000.0f));; + float radius = rng.uniform(0.0f, 500.0f); + float angle = (float)rng.uniform(0.0f, (float)(CV_2PI)); + vector pts; + pts.push_back(center + Point2f(radius * cos(angle), radius * sin(angle))); + angle += (float)CV_PI; + pts.push_back(center + Point2f(radius * cos(angle), radius * sin(angle))); + float radius2 = radius * radius; + float x = rng.uniform(center.x - radius, center.x + radius); + float deltaX = x - center.x; + float upperBoundY = sqrt(radius2 - deltaX * deltaX); + float y = rng.uniform(center.y - upperBoundY, center.y + upperBoundY); + pts.push_back(Point2f(x, y)); + // Find the minimum area enclosing circle + Point2f calcCenter; + float calcRadius; + cv::minEnclosingCircle(pts, calcCenter, calcRadius); + const float delta = (float)cv::norm(calcCenter - center) + abs(calcRadius - radius); + EXPECT_LE(delta, 1.f); +} + }} // namespace /* End of file. */ diff --git a/modules/imgproc/test/test_filter.cpp b/modules/imgproc/test/test_filter.cpp index 4c86e535ec..948e21e8bf 100644 --- a/modules/imgproc/test/test_filter.cpp +++ b/modules/imgproc/test/test_filter.cpp @@ -40,7 +40,6 @@ //M*/ #include "test_precomp.hpp" -#include "opencv2/core/core_c.h" namespace opencv_test { namespace { @@ -200,7 +199,7 @@ void CV_DerivBaseTest::get_test_array_types_and_sizes( int test_case_idx, int sameDepth = cvtest::randInt(rng) % 2; types[OUTPUT][0] = types[REF_OUTPUT][0] = sameDepth ? depth : CV_MAKETYPE(depth==CV_8U?CV_16S:CV_32F,1); _aperture_size = (cvtest::randInt(rng)%5)*2 - 1; - sizes[INPUT][1] = aperture_size = cvSize(_aperture_size, _aperture_size); + sizes[INPUT][1] = aperture_size = Size(_aperture_size, _aperture_size); } @@ -249,21 +248,21 @@ void CV_SobelTest::get_test_array_types_and_sizes( int test_case_idx, } if( _aperture_size < 0 ) - aperture_size = cvSize(3, 3); + aperture_size = Size(3, 3); else if( _aperture_size == 1 ) { if( dx == 0 ) - aperture_size = cvSize(1, 3); + aperture_size = Size(1, 3); else if( dy == 0 ) - aperture_size = cvSize(3, 1); + aperture_size = Size(3, 1); else { _aperture_size = 3; - aperture_size = cvSize(3, 3); + aperture_size = Size(3, 3); } } else - aperture_size = cvSize(_aperture_size, _aperture_size); + aperture_size = Size(_aperture_size, _aperture_size); sizes[INPUT][1] = aperture_size; anchor.x = aperture_size.width / 2; @@ -377,10 +376,10 @@ void CV_LaplaceTest::get_test_array_types_and_sizes( int test_case_idx, { if( _aperture_size < 0 ) _aperture_size = 1; - aperture_size = cvSize(3, 3); + aperture_size = Size(3, 3); } else - aperture_size = cvSize(_aperture_size, _aperture_size); + aperture_size = Size(_aperture_size, _aperture_size); sizes[INPUT][1] = aperture_size; anchor.x = aperture_size.width / 2; @@ -522,7 +521,7 @@ CV_PreCornerDetectTest::CV_PreCornerDetectTest() : CV_FeatureSelBaseTest( 1 ) void CV_PreCornerDetectTest::run_func() { - preCornerDetect( cvarrToMat( test_array[INPUT][0] ), cvarrToMat( test_array[OUTPUT][0] ), aperture_size, BORDER_REPLICATE ); + cv::preCornerDetect( test_mat[INPUT][0], test_mat[OUTPUT][0], aperture_size, BORDER_REPLICATE ); } diff --git a/modules/imgproc/test/test_imgwarp.cpp b/modules/imgproc/test/test_imgwarp.cpp index 4a17abd36d..deb5791f29 100644 --- a/modules/imgproc/test/test_imgwarp.cpp +++ b/modules/imgproc/test/test_imgwarp.cpp @@ -39,8 +39,9 @@ // //M*/ +#include "opencv2/ts/ocl_test.hpp" +#include "opencv2/ts/ts_gtest.h" #include "test_precomp.hpp" -#include "opencv2/core/core_c.h" namespace opencv_test { namespace { @@ -360,8 +361,8 @@ void CV_RemapTest::fill_array( int test_case_idx, int i, int j, Mat& arr ) void CV_RemapTest::run_func() { - remap( cvarrToMat( test_array[INPUT][0] ), cvarrToMat( test_array[INPUT_OUTPUT][0] ), - cvarrToMat( test_array[INPUT][1] ), cvarrToMat( test_array[INPUT][2] ), interpolation ); + cv::remap(test_mat[INPUT][0], test_mat[INPUT_OUTPUT][0], + test_mat[INPUT][1], test_mat[INPUT][2], interpolation ); } @@ -465,7 +466,7 @@ protected: double get_success_error_level( int test_case_idx, int i, int j ); void fill_array( int test_case_idx, int i, int j, Mat& arr ); - CvPoint2D32f center; + Point2f center; bool test_cpp; }; @@ -517,8 +518,8 @@ void CV_GetRectSubPixTest::fill_array( int test_case_idx, int i, int j, Mat& arr void CV_GetRectSubPixTest::run_func() { - cv::Mat _out = cv::cvarrToMat(test_array[INPUT_OUTPUT][0]); - cv::getRectSubPix( cv::cvarrToMat(test_array[INPUT][0]), _out.size(), center, _out, _out.type()); + cv::Mat _out = test_mat[INPUT_OUTPUT][0]; + cv::getRectSubPix(test_mat[INPUT][0], _out.size(), center, _out, _out.type()); } diff --git a/modules/imgproc/test/test_pc.cpp b/modules/imgproc/test/test_pc.cpp index 13f56189f1..64f634c693 100644 --- a/modules/imgproc/test/test_pc.cpp +++ b/modules/imgproc/test/test_pc.cpp @@ -43,6 +43,8 @@ #include "test_precomp.hpp" #include "opencv2/core/core_c.h" +#define CV_DXT_MUL_CONJ 8 + namespace opencv_test { namespace { /// phase correlation @@ -182,7 +184,7 @@ void CV_DivSpectrumsTest::get_test_array_types_and_sizes( int test_case_idx, vec // Get the flag of the input. const int rand_int_flags = cvtest::randInt(rng); - flags = rand_int_flags & (CV_DXT_MUL_CONJ | CV_DXT_ROWS); + flags = rand_int_flags & (CV_DXT_MUL_CONJ | DFT_ROWS); // Get input type. const int rand_int_type = cvtest::randInt(rng); diff --git a/modules/imgproc/test/test_smooth_bitexact.cpp b/modules/imgproc/test/test_smooth_bitexact.cpp index d4ae2af833..4546296877 100644 --- a/modules/imgproc/test/test_smooth_bitexact.cpp +++ b/modules/imgproc/test/test_smooth_bitexact.cpp @@ -244,7 +244,7 @@ static void checkGaussianBlur_8Uvs32F(const Mat& src8u, const Mat& src32f, int N TEST(GaussianBlur_Bitexact, regression_9863) { Mat src8u = imread(cvtest::findDataFile("shared/lena.png")); - Mat src32f; src8u.convertTo(src32f, CV_32F); + Mat src32f; src8u.convertTo(src32f, CV_32F); checkGaussianBlur_8Uvs32F(src8u, src32f, 151, 30); } @@ -260,4 +260,50 @@ TEST(GaussianBlur_Bitexact, overflow_20792) EXPECT_GT(count, nintyPercent); } +CV_ENUM(GaussInputType, CV_8U, CV_16S); +CV_ENUM(GaussBorder, BORDER_CONSTANT, BORDER_REPLICATE, BORDER_REFLECT_101); + +struct GaussianBlurVsBitexact: public testing::TestWithParam> +{ + virtual void SetUp() + { + orig = imread(findDataFile("shared/lena.png")); + EXPECT_FALSE(orig.empty()) << "Cannot find test image shared/lena.png"; + } + + Mat orig; +}; + +// NOTE: The test was designed for IPP (-DOPENCV_IPP_GAUSSIAN_BLUR=ON) +// Should be extended after new HAL integration +TEST_P(GaussianBlurVsBitexact, approx) +{ + auto testParams = GetParam(); + int dtype = get<0>(testParams); + int ksize = get<1>(testParams); + double sigma = get<2>(testParams); + int border = get<3>(testParams); + + Mat src; + orig.convertTo(src, dtype); + + cv::Mat gt; + GaussianBlur(src, gt, Size(ksize, ksize), sigma, sigma, border, ALGO_HINT_ACCURATE); + + cv::Mat dst; + GaussianBlur(src, dst, Size(ksize, ksize), sigma, sigma, border, ALGO_HINT_APPROX); + + EXPECT_LE(cvtest::norm(dst, gt, NORM_INF), 1); + EXPECT_LE(cvtest::norm(dst, gt, NORM_L1 | NORM_RELATIVE), 0.06); // Less 6% of different pixels +} + +INSTANTIATE_TEST_CASE_P(/*nothing*/, GaussianBlurVsBitexact, + testing::Combine( + GaussInputType::all(), + testing::Values(3, 5, 7), + testing::Values(0.75, 1.25), + GaussBorder::all() + ) +); + }} // namespace diff --git a/modules/imgproc/test/test_templmatch.cpp b/modules/imgproc/test/test_templmatch.cpp index 0eb7e57940..a59ef04500 100644 --- a/modules/imgproc/test/test_templmatch.cpp +++ b/modules/imgproc/test/test_templmatch.cpp @@ -40,166 +40,167 @@ //M*/ #include "test_precomp.hpp" -#include "opencv2/core/core_c.h" namespace opencv_test { namespace { -class CV_TemplMatchTest : public cvtest::ArrayTest -{ -public: - CV_TemplMatchTest(); - -protected: - int read_params( const cv::FileStorage& fs ); - void get_test_array_types_and_sizes( int test_case_idx, vector >& sizes, vector >& types ); - void get_minmax_bounds( int i, int j, int type, Scalar& low, Scalar& high ); - double get_success_error_level( int test_case_idx, int i, int j ); - void run_func(); - void prepare_to_validation( int ); - - int max_template_size; - int method; - bool test_cpp; -}; - - -CV_TemplMatchTest::CV_TemplMatchTest() -{ - test_array[INPUT].push_back(NULL); - test_array[INPUT].push_back(NULL); - test_array[OUTPUT].push_back(NULL); - test_array[REF_OUTPUT].push_back(NULL); - element_wise_relative_error = false; - max_template_size = 100; - method = 0; - test_cpp = false; -} - - -int CV_TemplMatchTest::read_params( const cv::FileStorage& fs ) -{ - int code = cvtest::ArrayTest::read_params( fs ); - if( code < 0 ) - return code; - - read( find_param( fs, "max_template_size" ), max_template_size, max_template_size ); - max_template_size = cvtest::clipInt( max_template_size, 1, 100 ); - - return code; -} - - -void CV_TemplMatchTest::get_minmax_bounds( int i, int j, int type, Scalar& low, Scalar& high ) -{ - cvtest::ArrayTest::get_minmax_bounds( i, j, type, low, high ); - int depth = CV_MAT_DEPTH(type); - if( depth == CV_32F ) - { - low = Scalar::all(-10.); - high = Scalar::all(10.); - } -} - - -void CV_TemplMatchTest::get_test_array_types_and_sizes( int test_case_idx, - vector >& sizes, vector >& types ) -{ - RNG& rng = ts->get_rng(); - int depth = cvtest::randInt(rng) % 2, cn = cvtest::randInt(rng) & 1 ? 3 : 1; - cvtest::ArrayTest::get_test_array_types_and_sizes( test_case_idx, sizes, types ); - depth = depth == 0 ? CV_8U : CV_32F; - - types[INPUT][0] = types[INPUT][1] = CV_MAKETYPE(depth,cn); - types[OUTPUT][0] = types[REF_OUTPUT][0] = CV_32FC1; - - sizes[INPUT][1].width = cvtest::randInt(rng)%MIN(sizes[INPUT][1].width,max_template_size) + 1; - sizes[INPUT][1].height = cvtest::randInt(rng)%MIN(sizes[INPUT][1].height,max_template_size) + 1; - sizes[OUTPUT][0].width = sizes[INPUT][0].width - sizes[INPUT][1].width + 1; - sizes[OUTPUT][0].height = sizes[INPUT][0].height - sizes[INPUT][1].height + 1; - sizes[REF_OUTPUT][0] = sizes[OUTPUT][0]; - - method = cvtest::randInt(rng)%6; - test_cpp = (cvtest::randInt(rng) & 256) == 0; -} - - -double CV_TemplMatchTest::get_success_error_level( int /*test_case_idx*/, int /*i*/, int /*j*/ ) -{ - if( test_mat[INPUT][1].depth() == CV_8U || - (method >= cv::TM_CCOEFF && test_mat[INPUT][1].cols*test_mat[INPUT][1].rows <= 2) ) - return 1e-2; - else - return 1e-3; -} - - -void CV_TemplMatchTest::run_func() -{ - cv::Mat _out = cv::cvarrToMat(test_array[OUTPUT][0]); - cv::matchTemplate(cv::cvarrToMat(test_array[INPUT][0]), cv::cvarrToMat(test_array[INPUT][1]), _out, method); +TEST(Imgproc_MatchTemplate, bug_9597) { + const uint8_t img[] = { + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 246, 246, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 246, 246, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 246, 246, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 246, 246, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 246, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 246, 246, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 246, 246, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 246, 246, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 246, 246, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 247, 247, 247, 247, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 247, 247, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245}; + const uint8_t tmpl[] = { + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, + 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245 }; + cv::Mat cvimg(cv::Size(61, 82), CV_8UC1, (void*)img, cv::Mat::AUTO_STEP); + cv::Mat cvtmpl(cv::Size(17, 17), CV_8UC1, (void*)tmpl, cv::Mat::AUTO_STEP); + cv::Mat result; + cv::matchTemplate(cvimg, cvtmpl, result, cv::TM_SQDIFF); + double minValue; + cv::minMaxLoc(result, &minValue, NULL, NULL, NULL); + ASSERT_GE(minValue, 0); } +//============================================================================== -static void cvTsMatchTemplate( const CvMat* img, const CvMat* templ, CvMat* result, int method ) +static void matchTemplate_reference(Mat & img, Mat & templ, Mat & result, const int method) { - int i, j, k, l; - int depth = CV_MAT_DEPTH(img->type), cn = CV_MAT_CN(img->type); - int width_n = templ->cols*cn, height = templ->rows; - int a_step = img->step / CV_ELEM_SIZE(img->type & CV_MAT_DEPTH_MASK); - int b_step = templ->step / CV_ELEM_SIZE(templ->type & CV_MAT_DEPTH_MASK); - CvScalar b_mean = CV_STRUCT_INITIALIZER, b_sdv = CV_STRUCT_INITIALIZER; - double b_denom = 1., b_sum2 = 0; - int area = templ->rows*templ->cols; - - cvAvgSdv(templ, &b_mean, &b_sdv); - - for( i = 0; i < cn; i++ ) - b_sum2 += (b_sdv.val[i]*b_sdv.val[i] + b_mean.val[i]*b_mean.val[i])*area; - - if( b_sdv.val[0]*b_sdv.val[0] + b_sdv.val[1]*b_sdv.val[1] + - b_sdv.val[2]*b_sdv.val[2] + b_sdv.val[3]*b_sdv.val[3] < DBL_EPSILON && - method == cv::TM_CCOEFF_NORMED ) + CV_Assert(cv::TM_SQDIFF <= method && method <= cv::TM_CCOEFF_NORMED); + + const Size res_sz(img.cols - templ.cols + 1, img.rows - templ.rows + 1); + result.create(res_sz, CV_32FC1); + + const int depth = img.depth(); + const int cn = img.channels(); + const int area = templ.size().area(); + const int width_n = templ.cols * cn; + const int height = templ.rows; + int a_step = (int)(img.step / img.elemSize1()); + int b_step = (int)(templ.step / templ.elemSize1()); + + Scalar b_mean = Scalar::all(0); + Scalar b_sdv = Scalar::all(0); + cv::meanStdDev(templ, b_mean, b_sdv); + + double b_sum2 = 0.; + for (int i = 0; i < cn; i++ ) + b_sum2 += (b_sdv.val[i] * b_sdv.val[i] + b_mean.val[i] * b_mean.val[i]) * area; + + if (b_sdv.val[0] * b_sdv.val[0] + b_sdv.val[1] * b_sdv.val[1] + + b_sdv.val[2] * b_sdv.val[2] + b_sdv.val[3] * b_sdv.val[3] < DBL_EPSILON && + method == cv::TM_CCOEFF_NORMED) { - cvSet( result, cvScalarAll(1.) ); + result = Scalar::all(1.); return; } - if( method & 1 ) + double b_denom = 1.; + if (method & 1) // _NORMED { b_denom = 0; - if( method != cv::TM_CCOEFF_NORMED ) + if (method != cv::TM_CCOEFF_NORMED) { b_denom = b_sum2; } else { - for( i = 0; i < cn; i++ ) - b_denom += b_sdv.val[i]*b_sdv.val[i]*area; + for (int i = 0; i < cn; i++) + b_denom += b_sdv.val[i] * b_sdv.val[i] * area; } b_denom = sqrt(b_denom); - if( b_denom == 0 ) + if (b_denom == 0) b_denom = 1.; } - CV_Assert( cv::TM_SQDIFF <= method && method <= cv::TM_CCOEFF_NORMED ); - - for( i = 0; i < result->rows; i++ ) + for (int i = 0; i < result.rows; i++) { - for( j = 0; j < result->cols; j++ ) + for (int j = 0; j < result.cols; j++) { Scalar a_sum(0), a_sum2(0); Scalar ccorr(0); double value = 0.; - if( depth == CV_8U ) + if (depth == CV_8U) { - const uchar* a = img->data.ptr + i*img->step + j*cn; - const uchar* b = templ->data.ptr; + const uchar* a = img.ptr(i, j); // ??? ->data.ptr + i*img->step + j*cn; + const uchar* b = templ.ptr(); if( cn == 1 || method < cv::TM_CCOEFF ) { - for( k = 0; k < height; k++, a += a_step, b += b_step ) - for( l = 0; l < width_n; l++ ) + for (int k = 0; k < height; k++, a += a_step, b += b_step) + for (int l = 0; l < width_n; l++) { ccorr.val[0] += a[l]*b[l]; a_sum.val[0] += a[l]; @@ -208,8 +209,8 @@ static void cvTsMatchTemplate( const CvMat* img, const CvMat* templ, CvMat* resu } else { - for( k = 0; k < height; k++, a += a_step, b += b_step ) - for( l = 0; l < width_n; l += 3 ) + for (int k = 0; k < height; k++, a += a_step, b += b_step) + for (int l = 0; l < width_n; l += 3) { ccorr.val[0] += a[l]*b[l]; ccorr.val[1] += a[l+1]*b[l+1]; @@ -223,15 +224,15 @@ static void cvTsMatchTemplate( const CvMat* img, const CvMat* templ, CvMat* resu } } } - else + else // CV_32F { - const float* a = (const float*)(img->data.ptr + i*img->step) + j*cn; - const float* b = (const float*)templ->data.ptr; + const float* a = img.ptr(i, j); // ???? (const float*)(img->data.ptr + i*img->step) + j*cn; + const float* b = templ.ptr(); if( cn == 1 || method < cv::TM_CCOEFF ) { - for( k = 0; k < height; k++, a += a_step, b += b_step ) - for( l = 0; l < width_n; l++ ) + for (int k = 0; k < height; k++, a += a_step, b += b_step) + for (int l = 0; l < width_n; l++) { ccorr.val[0] += a[l]*b[l]; a_sum.val[0] += a[l]; @@ -240,8 +241,8 @@ static void cvTsMatchTemplate( const CvMat* img, const CvMat* templ, CvMat* resu } else { - for( k = 0; k < height; k++, a += a_step, b += b_step ) - for( l = 0; l < width_n; l += 3 ) + for (int k = 0; k < height; k++, a += a_step, b += b_step) + for (int l = 0; l < width_n; l += 3) { ccorr.val[0] += a[l]*b[l]; ccorr.val[1] += a[l+1]*b[l+1]; @@ -295,130 +296,49 @@ static void cvTsMatchTemplate( const CvMat* img, const CvMat* templ, CvMat* resu else value = method != cv::TM_SQDIFF_NORMED ? 0 : 1; } - - ((float*)(result->data.ptr + result->step*i))[j] = (float)value; + result.at(i, j) = (float)value; } } } +//============================================================================== -void CV_TemplMatchTest::prepare_to_validation( int /*test_case_idx*/ ) -{ - CvMat _input = cvMat(test_mat[INPUT][0]), _templ = cvMat(test_mat[INPUT][1]); - CvMat _output = cvMat(test_mat[REF_OUTPUT][0]); - cvTsMatchTemplate( &_input, &_templ, &_output, method ); +CV_ENUM(MatchModes, TM_SQDIFF, TM_SQDIFF_NORMED, TM_CCORR, TM_CCORR_NORMED, TM_CCOEFF, TM_CCOEFF_NORMED); + +typedef testing::TestWithParam> matchTemplate_Modes; - //if( ts->get_current_test_info()->test_case_idx == 0 ) - /*{ - CvFileStorage* fs = cvOpenFileStorage( "_match_template.yml", 0, CV_STORAGE_WRITE ); - cvWrite( fs, "image", &test_mat[INPUT][0] ); - cvWrite( fs, "template", &test_mat[INPUT][1] ); - cvWrite( fs, "ref", &test_mat[REF_OUTPUT][0] ); - cvWrite( fs, "opencv", &test_mat[OUTPUT][0] ); - cvWriteInt( fs, "method", method ); - cvReleaseFileStorage( &fs ); - }*/ +TEST_P(matchTemplate_Modes, accuracy) +{ + const int data_type = CV_MAKE_TYPE(get<0>(GetParam()), get<1>(GetParam())); + const int method = get<2>(GetParam()); + RNG & rng = TS::ptr()->get_rng(); - if( method >= cv::TM_CCOEFF ) + for (int ITER = 0; ITER < 20; ++ITER) { - // avoid numerical stability problems in singular cases (when the results are near to 0) - const double delta = 10.; - test_mat[REF_OUTPUT][0] += Scalar::all(delta); - test_mat[OUTPUT][0] += Scalar::all(delta); - } -} + SCOPED_TRACE(cv::format("iteration %d", ITER)); -TEST(Imgproc_MatchTemplate, accuracy) { CV_TemplMatchTest test; test.safe_run(); } + const Size imgSize(rng.uniform(128, 320), rng.uniform(128, 240)); + const Size templSize(rng.uniform(1, 30), rng.uniform(1, 30)); + Mat img(imgSize, data_type, Scalar::all(0)); + Mat templ(templSize, data_type, Scalar::all(0)); + cvtest::randUni(rng, img, Scalar::all(0), Scalar::all(255)); + cvtest::randUni(rng, templ, Scalar::all(0), Scalar::all(255)); -} + Mat result; + cv::matchTemplate(img, templ, result, method); -TEST(Imgproc_MatchTemplate, bug_9597) { - const uint8_t img[] = { - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 246, 246, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 246, 246, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 246, 246, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 246, 246, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 246, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 246, 246, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 246, 246, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 246, 246, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 246, 246, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 247, 247, 247, 247, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 247, 247, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245}; - const uint8_t tmpl[] = { - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, - 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245, 245 }; - cv::Mat cvimg(cv::Size(61, 82), CV_8UC1, (void*)img, cv::Mat::AUTO_STEP); - cv::Mat cvtmpl(cv::Size(17, 17), CV_8UC1, (void*)tmpl, cv::Mat::AUTO_STEP); - cv::Mat result; - cv::matchTemplate(cvimg, cvtmpl, result, cv::TM_SQDIFF); - double minValue; - cv::minMaxLoc(result, &minValue, NULL, NULL, NULL); - ASSERT_GE(minValue, 0); + Mat reference; + matchTemplate_reference(img, templ, reference, method); + + EXPECT_MAT_NEAR_RELATIVE(result, reference, 1e-3); + } } -} // namespace + +INSTANTIATE_TEST_CASE_P(/**/, + matchTemplate_Modes, + testing::Combine( + testing::Values(CV_8U, CV_32F), + testing::Values(1, 3), + testing::Values(TM_SQDIFF, TM_SQDIFF_NORMED, TM_CCORR, TM_CCORR_NORMED, TM_CCOEFF, TM_CCOEFF_NORMED))); + +}} // namespace diff --git a/modules/java/android_sdk/build.gradle.in b/modules/java/android_sdk/build.gradle.in index a14f042496..e61ad66708 100644 --- a/modules/java/android_sdk/build.gradle.in +++ b/modules/java/android_sdk/build.gradle.in @@ -90,7 +90,12 @@ apply plugin: 'com.android.library' apply plugin: 'maven-publish' -@KOTLIN_PLUGIN_DECLARATION@ +try { + @KOTLIN_PLUGIN_DECLARATION@ + println "Configure OpenCV with Kotlin" +} catch (Exception e) { + println "Configure OpenCV without Kotlin" +} def openCVersionName = "@OPENCV_VERSION@" def openCVersionCode = ((@OPENCV_VERSION_MAJOR@ * 100 + @OPENCV_VERSION_MINOR@) * 100 + @OPENCV_VERSION_PATCH@) * 10 + 0 diff --git a/modules/photo/include/opencv2/photo.hpp b/modules/photo/include/opencv2/photo.hpp index 24f68f60c0..4e0256b97e 100644 --- a/modules/photo/include/opencv2/photo.hpp +++ b/modules/photo/include/opencv2/photo.hpp @@ -209,7 +209,7 @@ size. @param imgToDenoiseIndex Target image to denoise index in srcImgs sequence @param temporalWindowSize Number of surrounding images to use for target image denoising. Should be odd. Images from imgToDenoiseIndex - temporalWindowSize / 2 to -imgToDenoiseIndex - temporalWindowSize / 2 from srcImgs will be used to denoise +imgToDenoiseIndex + temporalWindowSize / 2 from srcImgs will be used to denoise srcImgs[imgToDenoiseIndex] image. @param dst Output image with the same size and type as srcImgs images. @param templateWindowSize Size in pixels of the template patch that is used to compute weights. @@ -236,7 +236,7 @@ have the same type and size. @param imgToDenoiseIndex Target image to denoise index in srcImgs sequence @param temporalWindowSize Number of surrounding images to use for target image denoising. Should be odd. Images from imgToDenoiseIndex - temporalWindowSize / 2 to -imgToDenoiseIndex - temporalWindowSize / 2 from srcImgs will be used to denoise +imgToDenoiseIndex + temporalWindowSize / 2 from srcImgs will be used to denoise srcImgs[imgToDenoiseIndex] image. @param dst Output image with the same size and type as srcImgs images. @param templateWindowSize Size in pixels of the template patch that is used to compute weights. @@ -263,7 +263,7 @@ size. @param imgToDenoiseIndex Target image to denoise index in srcImgs sequence @param temporalWindowSize Number of surrounding images to use for target image denoising. Should be odd. Images from imgToDenoiseIndex - temporalWindowSize / 2 to -imgToDenoiseIndex - temporalWindowSize / 2 from srcImgs will be used to denoise +imgToDenoiseIndex + temporalWindowSize / 2 from srcImgs will be used to denoise srcImgs[imgToDenoiseIndex] image. @param dst Output image with the same size and type as srcImgs images. @param templateWindowSize Size in pixels of the template patch that is used to compute weights. diff --git a/modules/python/src2/cv2.hpp b/modules/python/src2/cv2.hpp index 06080f1aa1..8cd0c2e4b1 100644 --- a/modules/python/src2/cv2.hpp +++ b/modules/python/src2/cv2.hpp @@ -46,19 +46,22 @@ private: static const uint32_t arg_outputarg_flag = 0x1; static const uint32_t arg_arithm_op_src_flag = 0x2; static const uint32_t arg_pathlike_flag = 0x4; + static const uint32_t arg_nd_mat_flag = 0x8; public: const char* name; bool outputarg; bool arithm_op_src; bool pathlike; + bool nd_mat; // more fields may be added if necessary ArgInfo(const char* name_, uint32_t arg_) : name(name_), outputarg((arg_ & arg_outputarg_flag) != 0), arithm_op_src((arg_ & arg_arithm_op_src_flag) != 0), - pathlike((arg_ & arg_pathlike_flag) != 0) {} + pathlike((arg_ & arg_pathlike_flag) != 0), + nd_mat((arg_ & arg_nd_mat_flag) != 0) {} private: ArgInfo(const ArgInfo&) = delete; diff --git a/modules/python/src2/cv2_convert.cpp b/modules/python/src2/cv2_convert.cpp index 35766b47c9..0626e42e53 100644 --- a/modules/python/src2/cv2_convert.cpp +++ b/modules/python/src2/cv2_convert.cpp @@ -173,7 +173,7 @@ bool pyopencv_to(PyObject* o, Mat& m, const ArgInfo& info) CV_LOG_DEBUG(NULL, "Incoming ndarray '" << info.name << "': ndims=" << ndims << " _sizes=" << pycv_dumpArray(_sizes, ndims) << " _strides=" << pycv_dumpArray(_strides, ndims)); - bool ismultichannel = ndims == 3 && _sizes[2] <= CV_CN_MAX; + bool ismultichannel = ndims == 3 && _sizes[2] <= CV_CN_MAX && !info.nd_mat; if (pyopencv_Mat_TypePtr && PyObject_TypeCheck(o, pyopencv_Mat_TypePtr)) { bool wrapChannels = false; diff --git a/modules/python/src2/cv2_convert.hpp b/modules/python/src2/cv2_convert.hpp index 0c0fbd7b96..979425c3f9 100644 --- a/modules/python/src2/cv2_convert.hpp +++ b/modules/python/src2/cv2_convert.hpp @@ -340,21 +340,37 @@ static bool pyopencv_to_generic_vec(PyObject* obj, std::vector& value, const { return true; } - if (!PySequence_Check(obj)) + if (info.nd_mat && PyArray_Check(obj)) { - failmsg("Can't parse '%s'. Input argument doesn't provide sequence protocol", info.name); - return false; + /* + If obj is marked as nd mat and of array type, it is parsed to a single + mat in the target vector to avoid being split into multiple mats + */ + value.resize(1); + if (!pyopencv_to(obj, value.front(), info)) + { + failmsg("Can't parse '%s'. Array item has a wrong type", info.name); + return false; + } } - const size_t n = static_cast(PySequence_Size(obj)); - value.resize(n); - for (size_t i = 0; i < n; i++) + else // parse as sequence { - SafeSeqItem item_wrap(obj, i); - if (!pyopencv_to(item_wrap.item, value[i], info)) + if (!PySequence_Check(obj)) { - failmsg("Can't parse '%s'. Sequence item with index %lu has a wrong type", info.name, i); + failmsg("Can't parse '%s'. Input argument doesn't provide sequence protocol", info.name); return false; } + const size_t n = static_cast(PySequence_Size(obj)); + value.resize(n); + for (size_t i = 0; i < n; i++) + { + SafeSeqItem item_wrap(obj, i); + if (!pyopencv_to(item_wrap.item, value[i], info)) + { + failmsg("Can't parse '%s'. Sequence item with index %lu has a wrong type", info.name, i); + return false; + } + } } return true; } diff --git a/modules/python/src2/gen2.py b/modules/python/src2/gen2.py index e6fec5510e..e58ff63ae6 100755 --- a/modules/python/src2/gen2.py +++ b/modules/python/src2/gen2.py @@ -488,6 +488,10 @@ class ArgInfo(object): return self.name + '_' return self.name + @property + def nd_mat(self): + return '/ND' in self._modifiers + @property def inputarg(self): return '/O' not in self._modifiers @@ -528,6 +532,7 @@ class ArgInfo(object): arg = 0x01 if self.outputarg else 0x0 arg += 0x02 if self.arithm_op_src_arg else 0x0 arg += 0x04 if self.pathlike else 0x0 + arg += 0x08 if self.nd_mat else 0x0 return "ArgInfo(\"%s\", %d)" % (self.name, arg) diff --git a/modules/python/src2/hdr_parser.py b/modules/python/src2/hdr_parser.py index f4801de6b8..c547eae3cd 100755 --- a/modules/python/src2/hdr_parser.py +++ b/modules/python/src2/hdr_parser.py @@ -84,6 +84,10 @@ class CppHeaderParser(object): modlist = [] # pass 0: extracts the modifiers + if "CV_ND" in arg_str: + modlist.append("/ND") + arg_str = arg_str.replace("CV_ND", "") + if "CV_OUT" in arg_str: modlist.append("/O") arg_str = arg_str.replace("CV_OUT", "") diff --git a/modules/python/test/test_misc.py b/modules/python/test/test_misc.py index 6e3d826bb4..3677f45bd4 100644 --- a/modules/python/test/test_misc.py +++ b/modules/python/test/test_misc.py @@ -972,6 +972,10 @@ class SamplesFindFile(NewOpenCVTests): except cv.error as _e: pass +class AlgorithmImplHit(NewOpenCVTests): + def test_callable(self): + res = cv.getDefaultAlgorithmHint() + self.assertTrue(res is not None) if __name__ == '__main__': NewOpenCVTests.bootstrap() diff --git a/modules/ts/src/ts.cpp b/modules/ts/src/ts.cpp index 96a1b2b952..95fcc0c08c 100644 --- a/modules/ts/src/ts.cpp +++ b/modules/ts/src/ts.cpp @@ -1126,6 +1126,7 @@ void SystemInfoCollector::OnTestProgramStart(const testing::UnitTest&) recordPropertyVerbose("cv_vcs_version", "OpenCV VCS version", getSnippetFromConfig("Version control:", "\n")); recordPropertyVerbose("cv_build_type", "Build type", getSnippetFromConfig("Configuration:", "\n"), CV_TEST_BUILD_CONFIG); recordPropertyVerbose("cv_compiler", "Compiler", getSnippetFromConfig("C++ Compiler:", "\n")); + recordPropertyVerbose("implementation_hint", "Algorithm hint", getSnippetFromConfig("Algorithm Hint:", "\n")); const char* parallelFramework = cv::currentParallelFramework(); if (parallelFramework) { diff --git a/modules/videoio/cmake/detect_obsensor.cmake b/modules/videoio/cmake/detect_obsensor.cmake index cf5a9063a9..f0b66015eb 100644 --- a/modules/videoio/cmake/detect_obsensor.cmake +++ b/modules/videoio/cmake/detect_obsensor.cmake @@ -1,10 +1,5 @@ # --- obsensor --- if(NOT HAVE_OBSENSOR) - if(APPLE) - # force to use orbbec sdk on mac - set(OBSENSOR_USE_ORBBEC_SDK ON) - endif() - if(OBSENSOR_USE_ORBBEC_SDK) include(${CMAKE_SOURCE_DIR}/3rdparty/orbbecsdk/orbbecsdk.cmake) download_orbbec_sdk(ORBBEC_SDK_ROOT_DIR) diff --git a/modules/videoio/src/cap_ffmpeg_impl.hpp b/modules/videoio/src/cap_ffmpeg_impl.hpp index 0546fbdfbe..1a4aa36d22 100644 --- a/modules/videoio/src/cap_ffmpeg_impl.hpp +++ b/modules/videoio/src/cap_ffmpeg_impl.hpp @@ -1485,10 +1485,6 @@ bool CvCapture_FFMPEG::grabFrame() if( !ic || !video_st || (!rawMode && !context) ) return false; - if( ic->streams[video_stream]->nb_frames > 0 && - frame_number > ic->streams[video_stream]->nb_frames ) - return false; - picture_pts = AV_NOPTS_VALUE_; #if USE_AV_INTERRUPT_CALLBACK diff --git a/samples/dnn/yolo_detector.cpp b/samples/dnn/yolo_detector.cpp index b439b0d4bc..bd82acff4a 100644 --- a/samples/dnn/yolo_detector.cpp +++ b/samples/dnn/yolo_detector.cpp @@ -27,7 +27,8 @@ void yoloPostProcessing( std::vector& keep_boxes, float conf_threshold, float iou_threshold, - const std::string& test_name + const std::string& model_name, + const int nc ); std::vector classes; @@ -40,6 +41,7 @@ std::string keys = "{ yolo | yolox | yolo model version. }" "{ input i | | Path to input image or video file. Skip this argument to capture frames from a camera. }" "{ classes | | Optional path to a text file with names of classes to label detected objects. }" + "{ nc | 80 | Number of classes. Default is 80 (coming from COCO dataset). }" "{ thr | .5 | Confidence threshold. }" "{ nms | .4 | Non-maximum suppression threshold. }" "{ mean | 0.0 | Normalization constant. }" @@ -107,19 +109,21 @@ void yoloPostProcessing( std::vector& keep_boxes, float conf_threshold, float iou_threshold, - const std::string& test_name) + const std::string& model_name, + const int nc=80) { // Retrieve std::vector classIds; std::vector confidences; std::vector boxes; - if (test_name == "yolov8") + if (model_name == "yolov8" || model_name == "yolov10" || + model_name == "yolov9") { cv::transposeND(outs[0], {0, 2, 1}, outs[0]); } - if (test_name == "yolonas") + if (model_name == "yolonas") { // outs contains 2 elemets of shape [1, 8400, 80] and [1, 8400, 4]. Concat them to get [1, 8400, 84] Mat concat_out; @@ -131,25 +135,30 @@ void yoloPostProcessing( // remove the second element outs.pop_back(); // unsqueeze the first dimension - outs[0] = outs[0].reshape(0, std::vector{1, 8400, 84}); + outs[0] = outs[0].reshape(0, std::vector{1, 8400, nc + 4}); } + // assert if last dim is 85 or 84 + CV_CheckEQ(outs[0].dims, 3, "Invalid output shape. The shape should be [1, #anchors, 85 or 84]"); + CV_CheckEQ((outs[0].size[2] == nc + 5 || outs[0].size[2] == 80 + 4), true, "Invalid output shape: "); + for (auto preds : outs) { preds = preds.reshape(1, preds.size[1]); // [1, 8400, 85] -> [8400, 85] for (int i = 0; i < preds.rows; ++i) { // filter out non object - float obj_conf = (test_name == "yolov8" || test_name == "yolonas") ? 1.0f : preds.at(i, 4) ; + float obj_conf = (model_name == "yolov8" || model_name == "yolonas" || + model_name == "yolov9" || model_name == "yolov10") ? 1.0f : preds.at(i, 4) ; if (obj_conf < conf_threshold) continue; - Mat scores = preds.row(i).colRange((test_name == "yolov8" || test_name == "yolonas") ? 4 : 5, preds.cols); + Mat scores = preds.row(i).colRange((model_name == "yolov8" || model_name == "yolonas" || model_name == "yolov9" || model_name == "yolov10") ? 4 : 5, preds.cols); double conf; Point maxLoc; minMaxLoc(scores, 0, &conf, 0, &maxLoc); - conf = (test_name == "yolov8" || test_name == "yolonas") ? conf : conf * obj_conf; + conf = (model_name == "yolov8" || model_name == "yolonas" || model_name == "yolov9" || model_name == "yolov10") ? conf : conf * obj_conf; if (conf < conf_threshold) continue; @@ -161,7 +170,7 @@ void yoloPostProcessing( double h = det[3]; // [x1, y1, x2, y2] - if (test_name == "yolonas"){ + if (model_name == "yolonas" || model_name == "yolov10"){ boxes.push_back(Rect2d(cx, cy, w, h)); } else { boxes.push_back(Rect2d(cx - 0.5 * w, cy - 0.5 * h, @@ -203,6 +212,7 @@ int main(int argc, char** argv) // if model is default, use findFile to get the full path otherwise use the given path std::string weightPath = findFile(parser.get("model")); std::string yolo_model = parser.get("yolo"); + int nc = parser.get("nc"); float confThreshold = parser.get("thr"); float nmsThreshold = parser.get("nms"); @@ -219,6 +229,7 @@ int main(int argc, char** argv) // check if yolo model is valid if (yolo_model != "yolov5" && yolo_model != "yolov6" && yolo_model != "yolov7" && yolo_model != "yolov8" + && yolo_model != "yolov10" && yolo_model !="yolov9" && yolo_model != "yolox" && yolo_model != "yolonas") CV_Error(Error::StsError, "Invalid yolo model: " + yolo_model); @@ -331,7 +342,8 @@ int main(int argc, char** argv) yoloPostProcessing( outs, keep_classIds, keep_confidences, keep_boxes, confThreshold, nmsThreshold, - yolo_model); + yolo_model, + nc); //![postprocess] // covert Rect2d to Rect diff --git a/samples/opengl/CMakeLists.txt b/samples/opengl/CMakeLists.txt index 419e83a66c..7ba102e856 100644 --- a/samples/opengl/CMakeLists.txt +++ b/samples/opengl/CMakeLists.txt @@ -6,6 +6,9 @@ if(UNIX) find_package(X11 QUIET) endif() +find_package(PkgConfig QUIET) +pkg_search_module(EPOXY QUIET epoxy) + SET(OPENCV_OPENGL_SAMPLES_REQUIRED_DEPS opencv_core opencv_imgproc @@ -22,6 +25,9 @@ if(BUILD_EXAMPLES AND OCV_DEPENDENCIES_FOUND) if(NOT X11_FOUND) ocv_list_filterout(all_samples "opengl_interop") endif() + if(NOT EPOXY_FOUND) + ocv_list_filterout(all_samples "opengl3_2") + endif() foreach(sample_filename ${all_samples}) ocv_define_sample(tgt ${sample_filename} opengl) ocv_target_link_libraries(${tgt} PRIVATE "${OPENGL_LIBRARIES}" "${OPENCV_OPENGL_SAMPLES_REQUIRED_DEPS}") @@ -29,6 +35,10 @@ if(BUILD_EXAMPLES AND OCV_DEPENDENCIES_FOUND) ocv_target_link_libraries(${tgt} PRIVATE ${X11_LIBRARIES}) ocv_target_include_directories(${tgt} ${X11_INCLUDE_DIR}) endif() + if(sample_filename STREQUAL "opengl3_2.cpp") + ocv_target_link_libraries(${tgt} PRIVATE ${EPOXY_LIBRARIES}) + ocv_target_include_directories(${tgt} PRIVATE ${EPOXY_INCLUDE_DIRS}) + endif() endforeach() endif() diff --git a/samples/opengl/opengl3_2.cpp b/samples/opengl/opengl3_2.cpp new file mode 100644 index 0000000000..1a8e9a804c --- /dev/null +++ b/samples/opengl/opengl3_2.cpp @@ -0,0 +1,169 @@ +#include + +#include + +#ifdef _WIN32 + #define WIN32_LEAN_AND_MEAN 1 + #define NOMINMAX 1 + #include +#endif + +#if defined(__APPLE__) + #include + #include +#else + #include + #include +#endif + +#include "opencv2/core.hpp" +#include "opencv2/core/opengl.hpp" +#include "opencv2/core/cuda.hpp" +#include "opencv2/highgui.hpp" + +using namespace std; +using namespace cv; +using namespace cv::cuda; + +const int win_width = 800; +const int win_height = 640; + +struct DrawData +{ + GLuint vao, vbo, program, textureID; +}; + +static cv::Mat rot(float angle) +{ + cv::Mat R_y = (cv::Mat_(4,4) << + cos(angle), 0, sin(angle), 0, + 0, 1, 0, 0, + -sin(angle), 0, cos(angle), 0, + 0, 0, 0, 1); + + return R_y; +} + +static GLuint create_shader(const char* source, GLenum type) { + GLuint shader = glCreateShader(type); + glShaderSource(shader, 1, &source, NULL); + glCompileShader(shader); + return shader; +} + +static void draw(void* userdata) { + DrawData* data = static_cast(userdata); + static float angle = 0.0f; + angle += 1.f; + + cv::Mat trans = rot(CV_PI * angle / 360.f); + + glClearColor(0.0, 0.0, 0.0, 1.0); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glUseProgram(data->program); + glUniformMatrix4fv(glGetUniformLocation(data->program, "transform"), 1, GL_FALSE, trans.ptr()); + glBindTexture(GL_TEXTURE_2D, data->textureID); + glBindVertexArray(data->vao); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + glBindVertexArray(0); +} + +int main(int argc, char* argv[]) +{ + string filename; + if (argc < 2) + { + cout << "Usage: " << argv[0] << " image" << endl; + filename = "baboon.jpg"; + } + else + filename = argv[1]; + + Mat img = imread(samples::findFile(filename)); + if (img.empty()) + { + cerr << "Can't open image " << filename << endl; + return -1; + } + flip(img, img, 0); + + namedWindow("OpenGL", WINDOW_OPENGL); + resizeWindow("OpenGL", win_width, win_height); + + DrawData data; + + glEnable(GL_DEPTH_TEST); + const char *vertex_shader_source = + "#version 330 core\n" + "layout (location = 0) in vec3 position;\n" + "layout (location = 1) in vec2 texCoord;\n" + "out vec2 TexCoord;\n" + "uniform mat4 transform;\n" + "void main() {\n" + " gl_Position = transform * vec4(position, 1.0);\n" + " TexCoord = texCoord;\n" + "}\n"; + const char *fragment_shader_source = + "#version 330 core\n" + "in vec2 TexCoord;\n" + "out vec4 color;\n" + "uniform sampler2D ourTexture;\n" + "void main() {\n" + " color = texture(ourTexture, TexCoord);\n" + "}\n"; + data.program = glCreateProgram(); + GLuint vertex_shader = create_shader(vertex_shader_source, GL_VERTEX_SHADER); + GLuint fragment_shader = create_shader(fragment_shader_source, GL_FRAGMENT_SHADER); + glAttachShader(data.program, vertex_shader); + glAttachShader(data.program, fragment_shader); + glLinkProgram(data.program); + glUseProgram(data.program); + + GLfloat vertices[] = { + // Positions // Texture Coords + 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, // Top Right + 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, // Bottom Right + -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, // Top Left + -1.0f, -1.0f, 0.0f, 0.0f, 0.0f // Bottom Left + }; + + glGenVertexArrays(1, &data.vao); + glGenBuffers(1, &data.vbo); + glBindVertexArray(data.vao); + glBindBuffer(GL_ARRAY_BUFFER, data.vbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); + + // Position attribute + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)0); + glEnableVertexAttribArray(0); + // Texture Coord attribute + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat))); + glEnableVertexAttribArray(1); + glBindVertexArray(0); // Unbind VAO + + +// Image to texture + glGenTextures(1, &data.textureID); + glBindTexture(GL_TEXTURE_2D, data.textureID); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, img.cols, img.rows, 0, GL_BGR, GL_UNSIGNED_BYTE, img.data); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glBindTexture(GL_TEXTURE_2D, 0); + + setOpenGlDrawCallback("OpenGL", draw, &data); + + for (;;) + { + updateWindow("OpenGL"); + char key = (char)waitKey(40); + if (key == 27) + break; + } + + setOpenGlDrawCallback("OpenGL", 0, 0); + destroyAllWindows(); + return 0; +}