diff --git a/3rdparty/fastcv/fastcv.cmake b/3rdparty/fastcv/fastcv.cmake index 8a82995bf3..215c73af16 100644 --- a/3rdparty/fastcv/fastcv.cmake +++ b/3rdparty/fastcv/fastcv.cmake @@ -1,7 +1,7 @@ function(download_fastcv root_dir) # Commit SHA in the opencv_3rdparty repo - set(FASTCV_COMMIT "abe340d0fb7f19fa9315080e3c8616642e98a296") + set(FASTCV_COMMIT "2265e79b3b9a8512a9c615b8c4d0244e88f45a9d") # Define actual FastCV versions if(ANDROID) @@ -16,8 +16,8 @@ function(download_fastcv root_dir) endif() elseif(UNIX AND NOT APPLE AND NOT IOS AND NOT XROS) if(AARCH64) - set(FCV_PACKAGE_NAME "fastcv_linux_aarch64_2025_04_29.tgz") - set(FCV_PACKAGE_HASH "e2ce60e25c8e4113a7af2bd243118f4c") + set(FCV_PACKAGE_NAME "fastcv_linux_aarch64_2025_05_29.tgz") + set(FCV_PACKAGE_HASH "decd490524f786e103125b8b948151f3") else() message("FastCV: fastcv lib for 32-bit Linux is not supported for now!") endif() diff --git a/3rdparty/ippicv/ippicv.cmake b/3rdparty/ippicv/ippicv.cmake index 7bfcf66993..48901a1547 100644 --- a/3rdparty/ippicv/ippicv.cmake +++ b/3rdparty/ippicv/ippicv.cmake @@ -17,9 +17,15 @@ function(download_ippicv root_var) set(OPENCV_ICV_NAME "ippicv_2022.0.0_lnx_intel64_20240904_general.tgz") set(OPENCV_ICV_HASH "63717ee0f918ad72fb5a737992a206d1") else() - set(IPPICV_COMMIT "7f55c0c26be418d494615afca15218566775c725") - set(OPENCV_ICV_NAME "ippicv_2021.12.0_lnx_ia32_20240425_general.tgz") - set(OPENCV_ICV_HASH "85ffa2b9ed7802b93c23fa27b0097d36") + if(ANDROID) + set(IPPICV_COMMIT "c7c6d527dde5fee7cb914ee9e4e20f7436aab3a1") + set(OPENCV_ICV_NAME "ippicv_2021.10.1_lnx_ia32_20231206_general.tgz") + set(OPENCV_ICV_HASH "d9510f3ce08f6074aac472a5c19a3b53") + else() + set(IPPICV_COMMIT "7f55c0c26be418d494615afca15218566775c725") + set(OPENCV_ICV_NAME "ippicv_2021.12.0_lnx_ia32_20240425_general.tgz") + set(OPENCV_ICV_HASH "85ffa2b9ed7802b93c23fa27b0097d36") + endif() endif() elseif(WIN32 AND NOT ARM) set(OPENCV_ICV_PLATFORM "windows") diff --git a/doc/opencv.bib b/doc/opencv.bib index 7da68ae65c..5b5bd98fbe 100644 --- a/doc/opencv.bib +++ b/doc/opencv.bib @@ -276,6 +276,13 @@ publisher = {Walter de Gruyter}, url = {https://hal.science/hal-00437581v1} } +@misc{Chatfield2017, + author = {Chatfield, Carl}, + title = {A Simple Method for Distance to Ellipse}, + year = {2017}, + publisher = {GitHub}, + howpublished = {\url{https://blog.chatfield.io/simple-method-for-distance-to-ellipse/}}, +} @article{Chaumette06, author = {Chaumette, Fran{\c c}ois and Hutchinson, S.}, title = {{Visual servo control, Part I: Basic approaches}}, diff --git a/doc/tutorials/introduction/building_fastcv/building_fastcv.markdown b/doc/tutorials/introduction/building_fastcv/building_fastcv.markdown index a8322191c0..0ec478b2f1 100644 --- a/doc/tutorials/introduction/building_fastcv/building_fastcv.markdown +++ b/doc/tutorials/introduction/building_fastcv/building_fastcv.markdown @@ -137,11 +137,12 @@ HAL and Extension list of APIs | | |fcvFilterSobel7x7u8s16 | | |boxFilter |fcvBoxFilter3x3u8_v3 | | | |fcvBoxFilter5x5u8_v2 | +| | |fcvBoxFilterNxNf32 | | |adaptiveThreshold |fcvAdaptiveThresholdGaussian3x3u8_v2 | | | |fcvAdaptiveThresholdGaussian5x5u8_v2 | | | |fcvAdaptiveThresholdMean3x3u8_v2 | | | |fcvAdaptiveThresholdMean5x5u8_v2 | -| |pyrUp & pyrDown |fcvPyramidCreateu8_v4 | +| |pyrDown |fcvPyramidCreateu8_v4 | | |cvtColor |fcvColorRGB888toYCrCbu8_v3 | | | |fcvColorRGB888ToHSV888u8 | | |gaussianBlur |fcvFilterGaussian5x5u8_v3 | @@ -167,10 +168,15 @@ HAL and Extension list of APIs | |addWeighted |fcvAddWeightedu8_v2 | | |subtract |fcvImageDiffu8f32_v2 | | |SVD & solve |fcvSVDf32_v2 | +| |gemm |fcvMatrixMultiplyf32_v2 | +| | |fcvMultiplyScalarf32 | +| | |fcvAddf32_v2 | **FastCV based OpenCV Extensions APIs list :** +These OpenCV extension APIs are implemented under the **cv::fastcv** namespace. + |OpenCV Extension APIs |Underlying FastCV API for OpenCV acceleration | |----------------------|----------------------------------------------| |matmuls8s32 |fcvMatrixMultiplys8s32 | @@ -195,8 +201,10 @@ HAL and Extension list of APIs |remap |fcvRemapu8_v2 | |remapRGBA |fcvRemapRGBA8888BLu8 | | |fcvRemapRGBA8888NNu8 | -|resizeDownBy2 |fcvScaleDownBy2u8_v2 | -|resizeDownBy4 |fcvScaleDownBy4u8_v2 | +|resizeDown |fcvScaleDownBy2u8_v2 | +| |fcvScaleDownBy4u8_v2 | +| |fcvScaleDownMNInterleaveu8 | +| |fcvScaleDownMNu8 | |meanShift |fcvMeanShiftu8 | | |fcvMeanShifts32 | | |fcvMeanShiftf32 | @@ -246,3 +254,65 @@ HAL and Extension list of APIs | |fcvTrackLKOpticalFlowu8 | |warpPerspective2Plane |fcv2PlaneWarpPerspectiveu8 | |warpPerspective |fcvWarpPerspectiveu8_v5 | +|arithmetic_op |fcvAddu8 | +| |fcvAdds16_v2 | +| |fcvAddf32 | +| |fcvSubtractu8 | +| |fcvSubtracts16 | +|integrateYUV |fcvIntegrateImageYCbCr420PseudoPlanaru8 | +|normalizeLocalBox |fcvNormalizeLocalBoxu8 | +| |fcvNormalizeLocalBoxf32 | +|merge |fcvChannelCombine2Planesu8 | +| |fcvChannelCombine3Planesu8 | +| |fcvChannelCombine4Planesu8 | +|split |fcvDeinterleaveu8 | +| |fcvChannelExtractu8 | +|warpAffine |fcvTransformAffineu8_v2 | +| |fcvTransformAffineClippedu8_v3 | +| |fcv3ChannelTransformAffineClippedBCu8 | + + +**FastCV QDSP based OpenCV Extension APIs list :** +These OpenCV extension APIs are implemented under the **cv::fastcv::dsp** namespace. +This namespace provides optimized implementations that leverage QDSP (**Qualcomm's Digital Signal Processor**) acceleration using FastCV's Q-suffixed APIs. These functions require DSP initialization (fcvQ6Init). + +|OpenCV Extension APIs |Underlying FastCV API for OpenCV acceleration | +|----------------------|----------------------------------------------| +|filter2D |fcvFilterCorr3x3s8_v2Q | +| |fcvFilterCorrNxNu8Q | +| |fcvFilterCorrNxNu8s16Q | +| |fcvFilterCorrNxNu8f32Q | +|FFT |fcvFFTu8Q | +|IFFT |fcvIFFTf32Q | +|fcvdspinit |fcvQ6Init | +|fcvdspdeinit |fcvQ6DeInit | +|Canny |fcvFilterCannyu8Q | +|sumOfAbsoluteDiffs |fcvSumOfAbsoluteDiffs8x8u8_v2Q | +|thresholdOtsu |fcvFilterThresholdOtsuu8Q | + +**How to Use FastCV QDSP based OpenCV Extension APIs** + +This section outlines the essential steps required to use OpenCV Extension APIs that are accelerated using FastCV on QDSP(**Qualcomm's Digital Signal Processor**). + +1. Initialize QDSP: + - Call **cv::fastcv::dsp::fcvdspinit()** to initialize the QDSP. + +2. Allocate memory using **Qualcomm's memory allocator** for all buffers that are being fed to the OpenCV extension API.: + - Use **cv::fastcv::getQcAllocator()** to assign the allocator to the buffers. + - Example: + cv::Mat src; + src.allocator = cv::fastcv::getQcAllocator(); **// Set Qualcomm's memory allocator** + \ + After setting Qualcomm's memory allocator, any buffer created using methods like src.create(...), cv::imread(...) etc., will have its memory allocated using Qualcomm's memory allocator. + +3. Call the OpenCV extension API from 'cv::fastcv::dsp': + - Example: **cv::fastcv::dsp::thresholdOtsu(src, dst, binaryType);** + where 'src' and 'dst' are 'cv::Mat' objects with the Qualcomm's memory allocator, +     and 'binaryType' is a boolean indicating the thresholding mode. + +4. Deinitialize QDSP: + - Call **cv::fastcv::dsp::fcvdspdeinit()** to deinitialize the QDSP. + + +**Reference Example**: +Refer to a working test case using the OpenCV Extension APIs in the opencv_contrib repository:[opencv_contrib/modules/fastcv/test/test_thresh_dsp.cpp](https://github.com/opencv/opencv_contrib/blob/4.x/modules/fastcv/test/test_thresh_dsp.cpp) diff --git a/doc/tutorials/others/images/ldr.png b/doc/tutorials/others/images/ldr.png index 2e1a188e6b..ac420886fa 100644 Binary files a/doc/tutorials/others/images/ldr.png and b/doc/tutorials/others/images/ldr.png differ diff --git a/hal/riscv-rvv/src/imgproc/warp.cpp b/hal/riscv-rvv/src/imgproc/warp.cpp index 2a9cef6934..f2a048f519 100644 --- a/hal/riscv-rvv/src/imgproc/warp.cpp +++ b/hal/riscv-rvv/src/imgproc/warp.cpp @@ -231,7 +231,7 @@ private: for (int i = 0; i < 8; i++) coeffs[t*8+i] = 0; coeffs[t*8+3] = 1; - return; + continue; } float sum = 0; diff --git a/modules/3d/test/test_solvepnp_ransac.cpp b/modules/3d/test/test_solvepnp_ransac.cpp index 39706000a1..e60f3db333 100644 --- a/modules/3d/test/test_solvepnp_ransac.cpp +++ b/modules/3d/test/test_solvepnp_ransac.cpp @@ -344,7 +344,11 @@ protected: } } - solvePnPRansac(points, projectedPoints, intrinsics, distCoeffs, rvec, tvec, false, pointsCount, 0.5f, 0.99, inliers, method); + bool isEstimateSuccess = solvePnPRansac(points, projectedPoints, intrinsics, distCoeffs, rvec, tvec, false, pointsCount, 0.5f, 0.99, inliers, method); + if (!isEstimateSuccess) + { + return false; + } bool isTestSuccess = inliers.size() + numOutliers >= points.size(); diff --git a/modules/core/src/matrix.cpp b/modules/core/src/matrix.cpp index eb136d9593..1a731eaa02 100644 --- a/modules/core/src/matrix.cpp +++ b/modules/core/src/matrix.cpp @@ -722,7 +722,7 @@ void finalizeHdr(Mat& m) m.rows = m.cols = -1; if(m.u) m.datastart = m.data = m.u->data; - if( m.data ) + if( m.data && d > 0 ) { m.datalimit = m.datastart + m.size[0]*m.step[0]; if( m.size[0] > 0 ) @@ -736,7 +736,7 @@ void finalizeHdr(Mat& m) m.dataend = m.datalimit; } else - m.dataend = m.datalimit = 0; + m.dataend = m.datalimit = m.data; } //======================================= Mat ====================================================== diff --git a/modules/core/test/test_mat.cpp b/modules/core/test/test_mat.cpp index 9166e1a0c5..286cddf808 100644 --- a/modules/core/test/test_mat.cpp +++ b/modules/core/test/test_mat.cpp @@ -1351,6 +1351,25 @@ TEST(Core_Mat, regression_9507) EXPECT_EQ(25u, m2.total()); } +TEST(Core_Mat, empty) +{ + // Should not crash. + uint8_t data[2] = {0, 1}; + cv::Mat mat_nd(/*ndims=*/0, /*sizes=*/nullptr, CV_8UC1, /*data=*/data); + cv::Mat1b mat(0, 0, /*data=*/data, /*steps=*/1); + EXPECT_EQ(mat_nd.dims, 0); + EXPECT_EQ(mat.dims, 2); +#if CV_VERSION_MAJOR < 5 + EXPECT_LE(mat_nd.total(), 0u); + EXPECT_TRUE(mat_nd.empty()); +#else + EXPECT_LE(mat_nd.total(), 1u); + EXPECT_FALSE(mat_nd.empty()); +#endif + EXPECT_EQ(mat.total(), 0u); + EXPECT_TRUE(mat.empty()); +} + TEST(Core_InputArray, empty) { vector > data; diff --git a/modules/dnn/CMakeLists.txt b/modules/dnn/CMakeLists.txt index 48915954c6..3af4aa48a5 100644 --- a/modules/dnn/CMakeLists.txt +++ b/modules/dnn/CMakeLists.txt @@ -115,8 +115,22 @@ if(HAVE_PROTOBUF) if(PROTOBUF_UPDATE_FILES) file(GLOB proto_files "${CMAKE_CURRENT_LIST_DIR}/src/tensorflow/*.proto" "${CMAKE_CURRENT_LIST_DIR}/src/caffe/opencv-caffe.proto" "${CMAKE_CURRENT_LIST_DIR}/src/onnx/opencv-onnx.proto") - set(PROTOBUF_GENERATE_CPP_APPEND_PATH ON) # required for tensorflow - protobuf_generate_cpp(fw_srcs fw_hdrs ${proto_files}) + if(CMAKE_VERSION VERSION_LESS "3.13.0") + set(PROTOBUF_GENERATE_CPP_APPEND_PATH ON) # required for tensorflow + protobuf_generate_cpp(fw_srcs fw_hdrs ${proto_files}) + else() + protobuf_generate( + APPEND_PATH # required for tensorflow + LANGUAGE cpp + IMPORT_DIRS ${Protobuf_IMPORT_DIRS} + OUT_VAR fw_srcs + PROTOC_EXE ${Protobuf_PROTOC_EXECUTABLE} + PROTOS ${proto_files}) + set(fw_hdrs "${fw_srcs}") + # separate the header files and source files + list(FILTER fw_srcs EXCLUDE REGEX ".+\.h$") + list(FILTER fw_hdrs INCLUDE REGEX ".+\.h$") + endif() else() file(GLOB fw_srcs "${CMAKE_CURRENT_LIST_DIR}/misc/tensorflow/*.cc" "${CMAKE_CURRENT_LIST_DIR}/misc/caffe/opencv-caffe.pb.cc" "${CMAKE_CURRENT_LIST_DIR}/misc/onnx/opencv-onnx.pb.cc") file(GLOB fw_hdrs "${CMAKE_CURRENT_LIST_DIR}/misc/tensorflow/*.h" "${CMAKE_CURRENT_LIST_DIR}/misc/caffe/opencv-caffe.pb.h" "${CMAKE_CURRENT_LIST_DIR}/misc/onnx/opencv-onnx.pb.h") diff --git a/modules/imgcodecs/src/grfmt_jpegxl.cpp b/modules/imgcodecs/src/grfmt_jpegxl.cpp index 060174eb02..72118184f6 100644 --- a/modules/imgcodecs/src/grfmt_jpegxl.cpp +++ b/modules/imgcodecs/src/grfmt_jpegxl.cpp @@ -518,11 +518,34 @@ bool JpegXLEncoder::write(const Mat& img, const std::vector& params) return false; } + // get distance param for JxlBasicInfo. + float distance = -1.0; // Negative means not set + for( size_t i = 0; i < params.size(); i += 2 ) + { + if( params[i] == IMWRITE_JPEGXL_QUALITY ) + { +#if JPEGXL_MAJOR_VERSION > 0 || JPEGXL_MINOR_VERSION >= 10 + int quality = params[i+1]; + quality = MIN(MAX(quality, 0), 100); + distance = JxlEncoderDistanceFromQuality(static_cast(quality)); +#else + CV_LOG_ONCE_WARNING(NULL, "Quality parameter is supported with libjxl v0.10.0 or later"); +#endif + } + if( params[i] == IMWRITE_JPEGXL_DISTANCE ) + { + int distanceInt = params[i+1]; + distanceInt = MIN(MAX(distanceInt, 0), 25); + distance = static_cast(distanceInt); + } + } + JxlBasicInfo info; JxlEncoderInitBasicInfo(&info); info.xsize = img.cols; info.ysize = img.rows; - info.uses_original_profile = JXL_FALSE; + // Lossless encoding requires uses_original_profile = true. + info.uses_original_profile = (distance == 0.0) ? JXL_TRUE : JXL_FALSE; if( img.channels() == 4 ) { @@ -576,30 +599,26 @@ bool JpegXLEncoder::write(const Mat& img, const std::vector& params) return false; JxlEncoderFrameSettings* frame_settings = JxlEncoderFrameSettingsCreate(encoder.get(), nullptr); + + // set frame settings with distance params + if(distance == 0.0) // lossless + { + if( JXL_ENC_SUCCESS != JxlEncoderSetFrameLossless(frame_settings, JXL_TRUE) ) + { + CV_LOG_WARNING(NULL, "Failed to call JxlEncoderSetFrameLossless()"); + } + } + else if(distance > 0.0) // lossy + { + if( JXL_ENC_SUCCESS != JxlEncoderSetFrameDistance(frame_settings, distance) ) + { + CV_LOG_WARNING(NULL, "Failed to call JxlEncoderSetFrameDistance()"); + } + } + // set frame settings from params if available for( size_t i = 0; i < params.size(); i += 2 ) { - if( params[i] == IMWRITE_JPEGXL_QUALITY ) - { -#if JPEGXL_MAJOR_VERSION > 0 || JPEGXL_MINOR_VERSION >= 10 - int quality = params[i+1]; - quality = MIN(MAX(quality, 0), 100); - const float distance = JxlEncoderDistanceFromQuality(static_cast(quality)); - JxlEncoderSetFrameDistance(frame_settings, distance); - if (distance == 0) - JxlEncoderSetFrameLossless(frame_settings, JXL_TRUE); -#else - CV_LOG_ONCE_WARNING(NULL, "Quality parameter is supported with libjxl v0.10.0 or later"); -#endif - } - if( params[i] == IMWRITE_JPEGXL_DISTANCE ) - { - int distance = params[i+1]; - distance = MIN(MAX(distance, 0), 25); - JxlEncoderSetFrameDistance(frame_settings, distance); - if (distance == 0) - JxlEncoderSetFrameLossless(frame_settings, JXL_TRUE); - } if( params[i] == IMWRITE_JPEGXL_EFFORT ) { int effort = params[i+1]; diff --git a/modules/imgcodecs/test/test_jpegxl.cpp b/modules/imgcodecs/test/test_jpegxl.cpp index d43b625364..fecc325080 100644 --- a/modules/imgcodecs/test/test_jpegxl.cpp +++ b/modules/imgcodecs/test/test_jpegxl.cpp @@ -7,6 +7,8 @@ namespace opencv_test { namespace { #ifdef HAVE_JPEGXL +#include // For JPEGXL_MAJOR_VERSION and JPEGXL_MINOR_VERSION + typedef tuple MatType_and_Distance; typedef testing::TestWithParam Imgcodecs_JpegXL_MatType; @@ -16,8 +18,8 @@ TEST_P(Imgcodecs_JpegXL_MatType, write_read) const int distanceParam = get<1>(GetParam()); cv::Scalar col; - // Jpeg XL is lossy compression. - // There may be small differences in decoding results by environments. + // Jpeg XL supports lossy and lossless compressions. + // Lossy compression may be small differences in decoding results by environments. double th; switch( CV_MAT_DEPTH(matType) ) @@ -38,7 +40,7 @@ TEST_P(Imgcodecs_JpegXL_MatType, write_read) } // If increasing distanceParam, threshold should be increased. - th *= (distanceParam >= 25) ? 5 : ( distanceParam > 2 ) ? 3 : (distanceParam == 2) ? 2: 1; + th *= (distanceParam >= 25) ? 5 : (distanceParam > 2) ? 3 : distanceParam; bool ret = false; string tmp_fname = cv::tempfile(".jxl"); @@ -63,8 +65,8 @@ TEST_P(Imgcodecs_JpegXL_MatType, encode_decode) const int distanceParam = get<1>(GetParam()); cv::Scalar col; - // Jpeg XL is lossy compression. - // There may be small differences in decoding results by environments. + // Jpeg XL supports lossy and lossless compressions. + // Lossy compression may be small differences in decoding results by environments. double th; // If alpha=0, libjxl modify color channels(BGR). So do not set it. @@ -86,7 +88,7 @@ TEST_P(Imgcodecs_JpegXL_MatType, encode_decode) } // If increasing distanceParam, threshold should be increased. - th *= (distanceParam >= 25) ? 5 : ( distanceParam > 2 ) ? 3 : (distanceParam == 2) ? 2: 1; + th *= (distanceParam >= 25) ? 5 : (distanceParam > 2) ? 3 : distanceParam; bool ret = false; vector buff; @@ -130,8 +132,8 @@ TEST_P(Imgcodecs_JpegXL_Effort_DecodingSpeed, encode_decode) const int speed = get<1>(GetParam()); cv::Scalar col = cv::Scalar(124,76,42); - // Jpeg XL is lossy compression. - // There may be small differences in decoding results by environments. + // Jpeg XL supports lossy and lossless compression. + // Lossy compression may be small differences in decoding results by environments. double th = 3; // = 255 / 100 (1%); bool ret = false; @@ -305,6 +307,56 @@ TEST(Imgcodecs_JpegXL, imread_truncated_stream) remove(tmp_fname.c_str()); } +// See https://github.com/opencv/opencv/issues/27382 +TEST(Imgcodecs_JpegXL, imencode_regression27382) +{ + cv::Mat image(1024, 1024, CV_16U); + cv::RNG rng(1024); + rng.fill(image, cv::RNG::NORMAL, 0, 65535); + + std::vector buffer; + std::vector params = {cv::IMWRITE_JPEGXL_DISTANCE, 0}; // lossless + + EXPECT_NO_THROW(cv::imencode(".jxl", image, buffer, params)); + + cv::Mat decoded; + EXPECT_NO_THROW(decoded = cv::imdecode(buffer, cv::IMREAD_UNCHANGED)); + EXPECT_FALSE(decoded.empty()); + + cv::Mat diff; + cv::absdiff(image, decoded, diff); + double max_diff = 0.0; + cv::minMaxLoc(diff, nullptr, &max_diff); + EXPECT_EQ(max_diff, 0 ); +} + +TEST(Imgcodecs_JpegXL, imencode_regression27382_2) +{ + cv::Mat image(1024, 1024, CV_16U); + cv::RNG rng(1024); + rng.fill(image, cv::RNG::NORMAL, 0, 65535); + + std::vector buffer; + std::vector params = {cv::IMWRITE_JPEGXL_QUALITY, 100}; // lossless + + EXPECT_NO_THROW(cv::imencode(".jxl", image, buffer, params)); + + cv::Mat decoded; + EXPECT_NO_THROW(decoded = cv::imdecode(buffer, cv::IMREAD_UNCHANGED)); + EXPECT_FALSE(decoded.empty()); + + cv::Mat diff; + cv::absdiff(image, decoded, diff); + double max_diff = 0.0; + cv::minMaxLoc(diff, nullptr, &max_diff); +#if JPEGXL_MAJOR_VERSION > 0 || JPEGXL_MINOR_VERSION >= 10 + // Quality parameter is supported with libjxl v0.10.0 or later + EXPECT_EQ(max_diff, 0); // Lossless +#else + EXPECT_NE(max_diff, 0); // Lossy +#endif +} + #endif // HAVE_JPEGXL diff --git a/modules/imgproc/include/opencv2/imgproc.hpp b/modules/imgproc/include/opencv2/imgproc.hpp index 01ff3aa17f..55b69c6821 100644 --- a/modules/imgproc/include/opencv2/imgproc.hpp +++ b/modules/imgproc/include/opencv2/imgproc.hpp @@ -118,7 +118,7 @@ This module offers a comprehensive suite of image processing functions, enabling coordinates needs to be retrieved. In the simplest case, the coordinates can be just rounded to the nearest integer coordinates and the corresponding pixel can be used. This is called a nearest-neighbor interpolation. However, a better result can be achieved by using more - sophisticated [interpolation methods](http://en.wikipedia.org/wiki/Multivariate_interpolation) , + sophisticated [interpolation methods](https://en.wikipedia.org/wiki/Multivariate_interpolation) , where a polynomial function is fit into some neighborhood of the computed pixel \f$(f_x(x,y), f_y(x,y))\f$, and then the value of the polynomial at \f$(f_x(x,y), f_y(x,y))\f$ is taken as the interpolated pixel value. In OpenCV, you can choose between several interpolation methods. See @@ -1467,7 +1467,7 @@ CV_EXPORTS_W void getDerivKernels( OutputArray kx, OutputArray ky, /** @brief Returns Gabor filter coefficients. For more details about gabor filter equations and parameters, see: [Gabor -Filter](http://en.wikipedia.org/wiki/Gabor_filter). +Filter](https://en.wikipedia.org/wiki/Gabor_filter). @param ksize Size of the filter returned. @param sigma Standard deviation of the gaussian envelope. @@ -1549,7 +1549,7 @@ CV_EXPORTS_W void GaussianBlur( InputArray src, OutputArray dst, Size ksize, /** @brief Applies the bilateral filter to an image. The function applies bilateral filtering to the input image, as described in -http://www.dai.ed.ac.uk/CVonline/LOCAL_COPIES/MANDUCHI1/Bilateral_Filtering.html +https://homepages.inf.ed.ac.uk/rbf/CVonline/LOCAL_COPIES/MANDUCHI1/Bilateral_Filtering.html bilateralFilter can reduce unwanted noise very well while keeping edges fairly sharp. However, it is very slow compared to most filters. @@ -1659,7 +1659,7 @@ stackBlur can generate similar results as Gaussian blur, and the time consumptio It creates a kind of moving stack of colors whilst scanning through the image. Thereby it just has to add one new block of color to the right side of the stack and remove the leftmost color. The remaining colors on the topmost layer of the stack are either added on or reduced by one, depending on if they are on the right or on the left side of the stack. The only supported borderType is BORDER_REPLICATE. -Original paper was proposed by Mario Klingemann, which can be found http://underdestruction.com/2004/02/25/stackblur-2004. +Original paper was proposed by Mario Klingemann, which can be found https://underdestruction.com/2004/02/25/stackblur-2004. @param src input image. The number of channels can be arbitrary, but the depth should be one of CV_8U, CV_16U, CV_16S or CV_32F. @@ -1892,7 +1892,7 @@ Check @ref tutorial_canny_detector "the corresponding tutorial" for more details The function finds edges in the input image and marks them in the output map edges using the Canny algorithm. The smallest value between threshold1 and threshold2 is used for edge linking. The largest value is used to find initial segments of strong edges. See - + @param image 8-bit input image. @param edges output edge map; single channels 8-bit image, which has the same size as image . @@ -2168,7 +2168,7 @@ An example using the Hough line detector in python /** @brief Finds lines in a binary image using the standard Hough transform. The function implements the standard or standard multi-scale Hough transform algorithm for line -detection. See for a good explanation of Hough +detection. See for a good explanation of Hough transform. @param image 8-bit, single-channel binary source image. The image may be modified by the function. @@ -2938,13 +2938,13 @@ An example using the phaseCorrelate function The operation takes advantage of the Fourier shift theorem for detecting the translational shift in the frequency domain. It can be used for fast image registration as well as motion estimation. For -more information please see +more information please see Calculates the cross-power spectrum of two supplied source arrays. The arrays are padded if needed with getOptimalDFTSize. The function performs the following equations: -- First it applies a Hanning window (see ) to each +- First it applies a Hanning window (see ) to each image to remove possible edge effects. This window is cached until the array size changes to speed up processing time. - Next it computes the forward DFTs of each source array: @@ -2974,7 +2974,7 @@ CV_EXPORTS_W Point2d phaseCorrelate(InputArray src1, InputArray src2, /** @brief This function computes a Hanning window coefficients in two dimensions. -See (http://en.wikipedia.org/wiki/Hann_function) and (http://en.wikipedia.org/wiki/Window_function) +See (https://en.wikipedia.org/wiki/Hann_function) and (https://en.wikipedia.org/wiki/Window_function) for more information. An example is shown below: @@ -3462,7 +3462,7 @@ An example using the GrabCut algorithm /** @brief Runs the GrabCut algorithm. -The function implements the [GrabCut image segmentation algorithm](http://en.wikipedia.org/wiki/GrabCut). +The function implements the [GrabCut image segmentation algorithm](https://en.wikipedia.org/wiki/GrabCut). @param img Input 8-bit 3-channel image. @param mask Input/output 8-bit single-channel mask. The mask is initialized by the function when @@ -3792,7 +3792,7 @@ CV_EXPORTS_W Moments moments( InputArray array, bool binaryImage = false ); /** @brief Calculates seven Hu invariants. The function calculates seven Hu invariants (introduced in @cite Hu62; see also -) defined as: +) defined as: \f[\begin{array}{l} hu[0]= \eta _{20}+ \eta _{02} \\ hu[1]=( \eta _{20}- \eta _{02})^{2}+4 \eta _{11}^{2} \\ hu[2]=( \eta _{30}-3 \eta _{12})^{2}+ (3 \eta _{21}- \eta _{03})^{2} \\ hu[3]=( \eta _{30}+ \eta _{12})^{2}+ ( \eta _{21}+ \eta _{03})^{2} \\ hu[4]=( \eta _{30}-3 \eta _{12})( \eta _{30}+ \eta _{12})[( \eta _{30}+ \eta _{12})^{2}-3( \eta _{21}+ \eta _{03})^{2}]+(3 \eta _{21}- \eta _{03})( \eta _{21}+ \eta _{03})[3( \eta _{30}+ \eta _{12})^{2}-( \eta _{21}+ \eta _{03})^{2}] \\ hu[5]=( \eta _{20}- \eta _{02})[( \eta _{30}+ \eta _{12})^{2}- ( \eta _{21}+ \eta _{03})^{2}]+4 \eta _{11}( \eta _{30}+ \eta _{12})( \eta _{21}+ \eta _{03}) \\ hu[6]=(3 \eta _{21}- \eta _{03})( \eta _{21}+ \eta _{03})[3( \eta _{30}+ \eta _{12})^{2}-( \eta _{21}+ \eta _{03})^{2}]-( \eta _{30}-3 \eta _{12})( \eta _{21}+ \eta _{03})[3( \eta _{30}+ \eta _{12})^{2}-( \eta _{21}+ \eta _{03})^{2}] \\ \end{array}\f] @@ -4037,7 +4037,7 @@ An example using approxPolyDP function in python. The function cv::approxPolyDP approximates a curve or a polygon with another curve/polygon with less vertices so that the distance between them is less or equal to the specified precision. It uses the -Douglas-Peucker algorithm +Douglas-Peucker algorithm @param curve Input vector of a 2D point stored in std::vector or Mat @param approxCurve Result of the approximation. The type should match the type of the input curve. @@ -4286,6 +4286,9 @@ ellipse/rotatedRect data contains negative indices, due to the data points being border of the containing Mat element. @param points Input 2D point set, stored in std::vector\<\> or Mat + +@note Input point types are @ref Point2i or @ref Point2f and at least 5 points are required. +@note @ref getClosestEllipsePoints function can be used to compute the ellipse fitting error. */ CV_EXPORTS_W RotatedRect fitEllipse( InputArray points ); @@ -4323,6 +4326,9 @@ CV_EXPORTS_W RotatedRect fitEllipse( InputArray points ); \f} @param points Input 2D point set, stored in std::vector\<\> or Mat + + @note Input point types are @ref Point2i or @ref Point2f and at least 5 points are required. + @note @ref getClosestEllipsePoints function can be used to compute the ellipse fitting error. */ CV_EXPORTS_W RotatedRect fitEllipseAMS( InputArray points ); @@ -4368,6 +4374,9 @@ CV_EXPORTS_W RotatedRect fitEllipseAMS( InputArray points ); The scaling factor guarantees that \f$A^T C A =1\f$. @param points Input 2D point set, stored in std::vector\<\> or Mat + + @note Input point types are @ref Point2i or @ref Point2f and at least 5 points are required. + @note @ref getClosestEllipsePoints function can be used to compute the ellipse fitting error. */ CV_EXPORTS_W RotatedRect fitEllipseDirect( InputArray points ); @@ -4375,6 +4384,20 @@ CV_EXPORTS_W RotatedRect fitEllipseDirect( InputArray points ); An example for fitting line in python */ +/** @brief Compute for each 2d point the nearest 2d point located on a given ellipse. + + The function computes the nearest 2d location on a given ellipse for a vector of 2d points and is based on @cite Chatfield2017 code. + This function can be used to compute for instance the ellipse fitting error. + + @param ellipse_params Ellipse parameters + @param points Input 2d points + @param closest_pts For each 2d point, their corresponding closest 2d point located on a given ellipse + + @note Input point types are @ref Point2i or @ref Point2f + @see fitEllipse, fitEllipseAMS, fitEllipseDirect + */ +CV_EXPORTS_W void getClosestEllipsePoints( const RotatedRect& ellipse_params, InputArray points, OutputArray closest_pts ); + /** @brief Fits a line to a 2D or 3D point set. The function fitLine fits a line to a 2D or 3D point set by minimizing \f$\sum_i \rho(r_i)\f$ where @@ -4393,7 +4416,7 @@ of the following: - DIST_HUBER \f[\rho (r) = \fork{r^2/2}{if \(r < C\)}{C \cdot (r-C/2)}{otherwise} \quad \text{where} \quad C=1.345\f] -The algorithm is based on the M-estimator ( ) technique +The algorithm is based on the M-estimator ( ) technique that iteratively fits the line using the weighted least-squares algorithm. After each iteration the weights \f$w_i\f$ are adjusted to be inversely proportional to \f$\rho(r_i)\f$ . diff --git a/modules/imgproc/include/opencv2/imgproc/bindings.hpp b/modules/imgproc/include/opencv2/imgproc/bindings.hpp index c69527a779..ad507a5bf9 100644 --- a/modules/imgproc/include/opencv2/imgproc/bindings.hpp +++ b/modules/imgproc/include/opencv2/imgproc/bindings.hpp @@ -21,14 +21,34 @@ void HoughLinesWithAccumulator( InputArray image, OutputArray lines, double rho, double theta, int threshold, double srn = 0, double stn = 0, - double min_theta = 0, double max_theta = CV_PI + double min_theta = 0, double max_theta = CV_PI, + bool use_edgeval = false ) { std::vector lines_acc; - HoughLines(image, lines_acc, rho, theta, threshold, srn, stn, min_theta, max_theta); + HoughLines(image, lines_acc, rho, theta, threshold, srn, stn, min_theta, max_theta, use_edgeval); Mat(lines_acc).copyTo(lines); } +/** @brief Finds circles in a grayscale image using the Hough transform and get accumulator. + * + * @note This function is for bindings use only. Use original function in C++ code + * + * @sa HoughCircles + */ +CV_WRAP static inline +void HoughCirclesWithAccumulator( + InputArray image, OutputArray circles, + int method, double dp, double minDist, + double param1 = 100, double param2 = 100, + int minRadius = 0, int maxRadius = 0 +) +{ + std::vector circles_acc; + HoughCircles(image, circles_acc, method, dp, minDist, param1, param2, minRadius, maxRadius); + Mat(1, static_cast(circles_acc.size()), CV_32FC4, &circles_acc.front()).copyTo(circles); +} + } // namespace #endif // OPENCV_IMGPROC_BINDINGS_HPP diff --git a/modules/imgproc/src/shapedescr.cpp b/modules/imgproc/src/shapedescr.cpp index 8ec073d386..eb6cfe2933 100644 --- a/modules/imgproc/src/shapedescr.cpp +++ b/modules/imgproc/src/shapedescr.cpp @@ -876,3 +876,120 @@ cv::RotatedRect cv::fitEllipseDirect( InputArray _points ) } return box; } + +namespace cv +{ +// @misc{Chatfield2017, +// author = {Chatfield, Carl}, +// title = {A Simple Method for Distance to Ellipse}, +// year = {2017}, +// publisher = {GitHub}, +// howpublished = {\url{https://blog.chatfield.io/simple-method-for-distance-to-ellipse/}}, +// } +// https://github.com/0xfaded/ellipse_demo/blob/master/ellipse_trig_free.py +static void solveFast(float semi_major, float semi_minor, const cv::Point2f& pt, cv::Point2f& closest_pt) +{ + float px = std::abs(pt.x); + float py = std::abs(pt.y); + + float tx = 0.707f; + float ty = 0.707f; + + float a = semi_major; + float b = semi_minor; + + for (int iter = 0; iter < 3; iter++) + { + float x = a * tx; + float y = b * ty; + + float ex = (a*a - b*b) * tx*tx*tx / a; + float ey = (b*b - a*a) * ty*ty*ty / b; + + float rx = x - ex; + float ry = y - ey; + + float qx = px - ex; + float qy = py - ey; + + float r = std::hypotf(rx, ry); + float q = std::hypotf(qx, qy); + + tx = std::min(1.0f, std::max(0.0f, (qx * r / q + ex) / a)); + ty = std::min(1.0f, std::max(0.0f, (qy * r / q + ey) / b)); + float t = std::hypotf(tx, ty); + tx /= t; + ty /= t; + } + + closest_pt.x = std::copysign(a * tx, pt.x); + closest_pt.y = std::copysign(b * ty, pt.y); +} +} // namespace cv + +void cv::getClosestEllipsePoints( const RotatedRect& ellipse_params, InputArray _points, OutputArray closest_pts ) +{ + CV_INSTRUMENT_REGION(); + + Mat points = _points.getMat(); + int n = points.checkVector(2); + int depth = points.depth(); + CV_Assert(depth == CV_32F || depth == CV_32S); + CV_Assert(n > 0); + + bool is_float = (depth == CV_32F); + const Point* ptsi = points.ptr(); + const Point2f* ptsf = points.ptr(); + + float semi_major = ellipse_params.size.width / 2.0f; + float semi_minor = ellipse_params.size.height / 2.0f; + float angle_deg = ellipse_params.angle; + if (semi_major < semi_minor) + { + std::swap(semi_major, semi_minor); + angle_deg += 90; + } + + Matx23f align_T_ori_f32; + float theta_rad = static_cast(angle_deg * M_PI / 180); + float co = std::cos(theta_rad); + float si = std::sin(theta_rad); + float shift_x = ellipse_params.center.x; + float shift_y = ellipse_params.center.y; + + align_T_ori_f32(0,0) = co; + align_T_ori_f32(0,1) = si; + align_T_ori_f32(0,2) = -co*shift_x - si*shift_y; + align_T_ori_f32(1,0) = -si; + align_T_ori_f32(1,1) = co; + align_T_ori_f32(1,2) = si*shift_x - co*shift_y; + + Matx23f ori_T_align_f32; + ori_T_align_f32(0,0) = co; + ori_T_align_f32(0,1) = -si; + ori_T_align_f32(0,2) = shift_x; + ori_T_align_f32(1,0) = si; + ori_T_align_f32(1,1) = co; + ori_T_align_f32(1,2) = shift_y; + + std::vector closest_pts_list; + closest_pts_list.reserve(n); + for (int i = 0; i < n; i++) + { + Point2f p = is_float ? ptsf[i] : Point2f((float)ptsi[i].x, (float)ptsi[i].y); + Matx31f pmat(p.x, p.y, 1); + + Matx21f X_align = align_T_ori_f32 * pmat; + Point2f closest_pt; + solveFast(semi_major, semi_minor, Point2f(X_align(0,0), X_align(1,0)), closest_pt); + + pmat(0,0) = closest_pt.x; + pmat(1,0) = closest_pt.y; + Matx21f closest_pt_ori = ori_T_align_f32 * pmat; + closest_pts_list.push_back(Point2f(closest_pt_ori(0,0), closest_pt_ori(1,0))); + } + + cv::Mat(closest_pts_list).convertTo(closest_pts, CV_32F); +} + +/* End of file. */ diff --git a/modules/imgproc/src/subdivision2d.cpp b/modules/imgproc/src/subdivision2d.cpp index 2a2ab1dfeb..86a0f72852 100644 --- a/modules/imgproc/src/subdivision2d.cpp +++ b/modules/imgproc/src/subdivision2d.cpp @@ -493,7 +493,7 @@ void Subdiv2D::initDelaunay( Rect rect ) { CV_INSTRUMENT_REGION(); - float big_coord = 3.f * MAX( rect.width, rect.height ); + float big_coord = 6.f * MAX( rect.width, rect.height ); float rx = (float)rect.x; float ry = (float)rect.y; diff --git a/modules/imgproc/test/ocl/test_warp.cpp b/modules/imgproc/test/ocl/test_warp.cpp index 7b130f0f06..30f1df68d3 100644 --- a/modules/imgproc/test/ocl/test_warp.cpp +++ b/modules/imgproc/test/ocl/test_warp.cpp @@ -500,10 +500,10 @@ PARAM_TEST_CASE(RemapRelative, MatDepth, Channels, Interpolation, BorderType, bo data64FC1.reshape(nChannels, size.height).convertTo(src, srcType); cv::Mat mapRelativeX32F(size, CV_32FC1); - mapRelativeX32F.setTo(cv::Scalar::all(-0.33)); + mapRelativeX32F.setTo(cv::Scalar::all(-0.25)); cv::Mat mapRelativeY32F(size, CV_32FC1); - mapRelativeY32F.setTo(cv::Scalar::all(-0.33)); + mapRelativeY32F.setTo(cv::Scalar::all(-0.25)); cv::Mat mapAbsoluteX32F = mapRelativeX32F.clone(); mapAbsoluteX32F.forEach([&](float& pixel, const int* position) { diff --git a/modules/imgproc/test/test_cornersubpix.cpp b/modules/imgproc/test/test_cornersubpix.cpp index 5a2e633397..eebd76afea 100644 --- a/modules/imgproc/test/test_cornersubpix.cpp +++ b/modules/imgproc/test/test_cornersubpix.cpp @@ -69,6 +69,8 @@ TEST(Imgproc_CornerSubPix, out_of_image_corners) TEST(Imgproc_CornerSubPix, corners_on_the_edge) { cv::Mat image(500, 500, CV_8UC1); + RNG& rng = TS::ptr()->get_rng(); + cvtest::randUni(rng, image, 0, 255); cv::Size win(1, 1); cv::Size zeroZone(-1, -1); cv::TermCriteria criteria; diff --git a/modules/imgproc/test/test_fitellipse.cpp b/modules/imgproc/test/test_fitellipse.cpp index 169751acab..8b5c6a97c5 100644 --- a/modules/imgproc/test/test_fitellipse.cpp +++ b/modules/imgproc/test/test_fitellipse.cpp @@ -113,4 +113,197 @@ TEST(Imgproc_FitEllipse_HorizontalLine, accuracy) { EXPECT_NEAR(el.angle, 90, 0.1); } +template +static float get_ellipse_fitting_error(const std::vector& points, const Mat& closest_points) { + float mse = 0.0f; + for (int i = 0; i < static_cast(points.size()); i++) + { + Point2f pt_err = Point2f(static_cast(points[i].x), static_cast(points[i].y)) - closest_points.at(i); + mse += pt_err.x*pt_err.x + pt_err.y*pt_err.y; + } + return mse / points.size(); +} + +TEST(Imgproc_getClosestEllipsePoints, ellipse_mse) { + // https://github.com/opencv/opencv/issues/26078 + std::vector points_list; + + // [1434, 308], [1434, 309], [1433, 310], [1427, 310], [1427, 312], [1426, 313], [1422, 313], [1422, 314], + points_list.push_back(Point2i(1434, 308)); + points_list.push_back(Point2i(1434, 309)); + points_list.push_back(Point2i(1433, 310)); + points_list.push_back(Point2i(1427, 310)); + points_list.push_back(Point2i(1427, 312)); + points_list.push_back(Point2i(1426, 313)); + points_list.push_back(Point2i(1422, 313)); + points_list.push_back(Point2i(1422, 314)); + + // [1421, 315], [1415, 315], [1415, 316], [1414, 317], [1408, 317], [1408, 319], [1407, 320], [1403, 320], + points_list.push_back(Point2i(1421, 315)); + points_list.push_back(Point2i(1415, 315)); + points_list.push_back(Point2i(1415, 316)); + points_list.push_back(Point2i(1414, 317)); + points_list.push_back(Point2i(1408, 317)); + points_list.push_back(Point2i(1408, 319)); + points_list.push_back(Point2i(1407, 320)); + points_list.push_back(Point2i(1403, 320)); + + // [1403, 321], [1402, 322], [1396, 322], [1396, 323], [1395, 324], [1389, 324], [1389, 326], [1388, 327], + points_list.push_back(Point2i(1403, 321)); + points_list.push_back(Point2i(1402, 322)); + points_list.push_back(Point2i(1396, 322)); + points_list.push_back(Point2i(1396, 323)); + points_list.push_back(Point2i(1395, 324)); + points_list.push_back(Point2i(1389, 324)); + points_list.push_back(Point2i(1389, 326)); + points_list.push_back(Point2i(1388, 327)); + + // [1382, 327], [1382, 328], [1381, 329], [1376, 329], [1376, 330], [1375, 331], [1369, 331], [1369, 333], + points_list.push_back(Point2i(1382, 327)); + points_list.push_back(Point2i(1382, 328)); + points_list.push_back(Point2i(1381, 329)); + points_list.push_back(Point2i(1376, 329)); + points_list.push_back(Point2i(1376, 330)); + points_list.push_back(Point2i(1375, 331)); + points_list.push_back(Point2i(1369, 331)); + points_list.push_back(Point2i(1369, 333)); + + // [1368, 334], [1362, 334], [1362, 335], [1361, 336], [1359, 336], [1359, 1016], [1365, 1016], [1366, 1017], + points_list.push_back(Point2i(1368, 334)); + points_list.push_back(Point2i(1362, 334)); + points_list.push_back(Point2i(1362, 335)); + points_list.push_back(Point2i(1361, 336)); + points_list.push_back(Point2i(1359, 336)); + points_list.push_back(Point2i(1359, 1016)); + points_list.push_back(Point2i(1365, 1016)); + points_list.push_back(Point2i(1366, 1017)); + + // [1366, 1019], [1430, 1019], [1430, 1017], [1431, 1016], [1440, 1016], [1440, 308] + points_list.push_back(Point2i(1366, 1019)); + points_list.push_back(Point2i(1430, 1019)); + points_list.push_back(Point2i(1430, 1017)); + points_list.push_back(Point2i(1431, 1016)); + points_list.push_back(Point2i(1440, 1016)); + points_list.push_back(Point2i(1440, 308)); + + RotatedRect fit_ellipse_params( + Point2f(1442.97900390625, 662.1879272460938), + Size2f(579.5570678710938, 730.834228515625), + 20.190902709960938 + ); + + // Point2i + { + Mat pointsi(points_list); + Mat closest_pts; + getClosestEllipsePoints(fit_ellipse_params, pointsi, closest_pts); + EXPECT_TRUE(pointsi.rows == closest_pts.rows); + EXPECT_TRUE(pointsi.cols == closest_pts.cols); + EXPECT_TRUE(pointsi.channels() == closest_pts.channels()); + + float fit_ellipse_mse = get_ellipse_fitting_error(points_list, closest_pts); + EXPECT_NEAR(fit_ellipse_mse, 1.61994, 1e-4); + } + + // Point2f + { + Mat pointsf; + Mat(points_list).convertTo(pointsf, CV_32F); + + Mat closest_pts; + getClosestEllipsePoints(fit_ellipse_params, pointsf, closest_pts); + EXPECT_TRUE(pointsf.rows == closest_pts.rows); + EXPECT_TRUE(pointsf.cols == closest_pts.cols); + EXPECT_TRUE(pointsf.channels() == closest_pts.channels()); + + float fit_ellipse_mse = get_ellipse_fitting_error(points_list, closest_pts); + EXPECT_NEAR(fit_ellipse_mse, 1.61994, 1e-4); + } +} + +static std::vector sample_ellipse_pts(const RotatedRect& ellipse_params) { + // Sample N points using the ellipse parametric form + float xc = ellipse_params.center.x; + float yc = ellipse_params.center.y; + float a = ellipse_params.size.width / 2; + float b = ellipse_params.size.height / 2; + float theta = static_cast(ellipse_params.angle * M_PI / 180); + + float cos_th = std::cos(theta); + float sin_th = std::sin(theta); + int nb_samples = 180; + std::vector ellipse_pts(nb_samples); + for (int i = 0; i < nb_samples; i++) { + float ax = a * cos_th; + float ay = a * sin_th; + float bx = -b * sin_th; + float by = b * cos_th; + + float t = static_cast(i / static_cast(nb_samples) * 2*M_PI); + float cos_t = std::cos(t); + float sin_t = std::sin(t); + + ellipse_pts[i].x = xc + ax*cos_t + bx*sin_t; + ellipse_pts[i].y = yc + ay*cos_t + by*sin_t; + } + + return ellipse_pts; +} + +TEST(Imgproc_getClosestEllipsePoints, ellipse_mse_2) { + const float tol = 1e-3f; + + // bb height > width + // Check correctness of the minor/major axes swapping and updated angle in getClosestEllipsePoints + { + RotatedRect ellipse_params( + Point2f(-142.97f, -662.1878f), + Size2f(539.557f, 730.83f), + 27.09960938f + ); + std::vector ellipse_pts = sample_ellipse_pts(ellipse_params); + + Mat pointsf, closest_pts; + Mat(ellipse_pts).convertTo(pointsf, CV_32F); + getClosestEllipsePoints(ellipse_params, pointsf, closest_pts); + + float ellipse_pts_mse = get_ellipse_fitting_error(ellipse_pts, closest_pts); + EXPECT_NEAR(ellipse_pts_mse, 0, tol); + } + + // bb height > width + negative angle + { + RotatedRect ellipse_params( + Point2f(-142.97f, 562.1878f), + Size2f(53.557f, 730.83f), + -75.09960938f + ); + std::vector ellipse_pts = sample_ellipse_pts(ellipse_params); + + Mat pointsf, closest_pts; + Mat(ellipse_pts).convertTo(pointsf, CV_32F); + getClosestEllipsePoints(ellipse_params, pointsf, closest_pts); + + float ellipse_pts_mse = get_ellipse_fitting_error(ellipse_pts, closest_pts); + EXPECT_NEAR(ellipse_pts_mse, 0, tol); + } + + // Negative angle + { + RotatedRect ellipse_params( + Point2f(742.97f, -462.1878f), + Size2f(535.57f, 130.83f), + -75.09960938f + ); + std::vector ellipse_pts = sample_ellipse_pts(ellipse_params); + + Mat pointsf, closest_pts; + Mat(ellipse_pts).convertTo(pointsf, CV_32F); + getClosestEllipsePoints(ellipse_params, pointsf, closest_pts); + + float ellipse_pts_mse = get_ellipse_fitting_error(ellipse_pts, closest_pts); + EXPECT_NEAR(ellipse_pts_mse, 0, tol); + } +} + }} // namespace diff --git a/modules/imgproc/test/test_imgwarp.cpp b/modules/imgproc/test/test_imgwarp.cpp index 01b033cbef..e54b0302ec 100644 --- a/modules/imgproc/test/test_imgwarp.cpp +++ b/modules/imgproc/test/test_imgwarp.cpp @@ -772,10 +772,10 @@ TEST_P(Imgproc_RemapRelative, validity) data64FC1.reshape(nChannels, size.height).convertTo(src, srcType); cv::Mat mapRelativeX32F(size, CV_32FC1); - mapRelativeX32F.setTo(cv::Scalar::all(-0.33)); + mapRelativeX32F.setTo(cv::Scalar::all(-0.25)); cv::Mat mapRelativeY32F(size, CV_32FC1); - mapRelativeY32F.setTo(cv::Scalar::all(-0.33)); + mapRelativeY32F.setTo(cv::Scalar::all(-0.25)); cv::Mat mapAbsoluteX32F = mapRelativeX32F.clone(); mapAbsoluteX32F.forEach([&](float& pixel, const int* position) { @@ -811,7 +811,7 @@ TEST_P(Imgproc_RemapRelative, validity) cv::remap(src, dstRelative, mapRelativeX32F, mapRelativeY32F, interpolation | WARP_RELATIVE_MAP, borderType); } - EXPECT_EQ(cvtest::norm(dstAbsolute, dstRelative, NORM_INF), 0); + EXPECT_LE(cvtest::norm(dstAbsolute, dstRelative, NORM_INF), 1); }; INSTANTIATE_TEST_CASE_P(ImgProc, Imgproc_RemapRelative, testing::Combine( diff --git a/modules/imgproc/test/test_subdivision2d.cpp b/modules/imgproc/test/test_subdivision2d.cpp index 17549b6b15..a940adfc55 100644 --- a/modules/imgproc/test/test_subdivision2d.cpp +++ b/modules/imgproc/test/test_subdivision2d.cpp @@ -50,4 +50,18 @@ TEST(Imgproc_Subdiv2D_getTriangleList, regression_5788) EXPECT_EQ(trig_cnt, 105); } +TEST(Imgproc_Subdiv2D, issue_25696) { + std::vector points{ + {0, 0}, {40, 40}, {84, 104}, {86, 108} + }; + + cv::Rect subdivRect{cv::Point{-10, -10}, cv::Point{96, 118}}; + cv::Subdiv2D subdiv{subdivRect}; + subdiv.insert(points); + + std::vector triangles; + subdiv.getTriangleList(triangles); + + ASSERT_EQ(static_cast(2), triangles.size()); +} }} diff --git a/modules/java/generator/android-21/java/org/opencv/android/JavaCamera2View.java b/modules/java/generator/android-21/java/org/opencv/android/JavaCamera2View.java index 6447f07b82..849ef734a2 100644 --- a/modules/java/generator/android-21/java/org/opencv/android/JavaCamera2View.java +++ b/modules/java/generator/android-21/java/org/opencv/android/JavaCamera2View.java @@ -96,8 +96,10 @@ public class JavaCamera2View extends CameraBridgeViewBase { Log.e(LOGTAG, "Error: camera isn't detected."); return false; } + boolean chosen = false; // remember whether the camera ID is set. if (mCameraIndex == CameraBridgeViewBase.CAMERA_ID_ANY) { mCameraID = camList[0]; + chosen = true; } else { for (String cameraID : camList) { CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraID); @@ -107,11 +109,12 @@ public class JavaCamera2View extends CameraBridgeViewBase { characteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT) ) { mCameraID = cameraID; + chosen = true; break; } } } - if (mCameraID == null) { // make JavaCamera2View behaves in the same way as JavaCameraView + if (mCameraID == null || !chosen) { // make JavaCamera2View behaves in the same way as JavaCameraView Log.i(LOGTAG, "Selecting camera by index (" + mCameraIndex + ")"); if (mCameraIndex < camList.length) { mCameraID = camList[mCameraIndex]; diff --git a/modules/photo/src/hdr_common.cpp b/modules/photo/src/hdr_common.cpp index 5e4bea9d85..70b24322b1 100644 --- a/modules/photo/src/hdr_common.cpp +++ b/modules/photo/src/hdr_common.cpp @@ -65,10 +65,14 @@ Mat triangleWeights() Mat w(LDR_SIZE, 1, CV_32F); int half = LDR_SIZE / 2; int maxVal = LDR_SIZE - 1; - for (int i = 0; i < LDR_SIZE; i++) + float epsilon = 1e-6f; + w.at(0) = epsilon; + w.at(LDR_SIZE-1) = epsilon; + for (int i = 1; i < LDR_SIZE-1; i++){ w.at(i) = (i < half) ? static_cast(i) : static_cast(maxVal - i); + } return w; } diff --git a/modules/photo/src/tonemap.cpp b/modules/photo/src/tonemap.cpp index 0c9275040b..6f90c76b20 100644 --- a/modules/photo/src/tonemap.cpp +++ b/modules/photo/src/tonemap.cpp @@ -72,8 +72,10 @@ public: double min, max; minMaxLoc(src, &min, &max); + float fmin = static_cast(min); + float fmax = static_cast(max); if(max - min > DBL_EPSILON) { - dst = (src - min) / (max - min); + dst = (src - fmin) / (fmax - fmin); } else { src.copyTo(dst); } @@ -139,8 +141,9 @@ public: gray_img /= mean; log_img.release(); - double max; - minMaxLoc(gray_img, NULL, &max); + double dmax; + minMaxLoc(gray_img, NULL, &dmax); + float max = static_cast(dmax); CV_Assert(max > 0); Mat map; @@ -150,7 +153,6 @@ public: log(2.0f + 8.0f * div, div); map = map.mul(1.0f / div); div.release(); - mapLuminance(img, img, gray_img, map, saturation); linear->setGamma(gamma); @@ -223,12 +225,14 @@ public: log_(gray_img, log_img); float log_mean = static_cast(sum(log_img)[0] / log_img.total()); - double log_min, log_max; - minMaxLoc(log_img, &log_min, &log_max); + double dlog_min, dlog_max; + minMaxLoc(log_img, &dlog_min, &dlog_max); + float log_max = static_cast(dlog_max); + float log_min = static_cast(dlog_min); log_img.release(); - double key = static_cast((log_max - log_mean) / (log_max - log_min)); - float map_key = 0.3f + 0.7f * pow(static_cast(key), 1.4f); + float key = (log_max - log_mean) / (log_max - log_min); + float map_key = 0.3f + 0.7f * pow(key, 1.4f); intensity = exp(-intensity); Scalar chan_mean = mean(img); float gray_mean = static_cast(mean(gray_img)[0]); @@ -287,9 +291,9 @@ protected: float gamma, intensity, light_adapt, color_adapt; }; -Ptr createTonemapReinhard(float gamma, float contrast, float sigma_color, float sigma_space) +Ptr createTonemapReinhard(float gamma, float intensity, float light_adapt, float color_adapt) { - return makePtr(gamma, contrast, sigma_color, sigma_space); + return makePtr(gamma, intensity, light_adapt, color_adapt); } class TonemapMantiukImpl CV_FINAL : public TonemapMantiuk diff --git a/modules/python/test/test_houghcircles.py b/modules/python/test/test_houghcircles.py index 2cfc6c9d68..942e2c8e34 100644 --- a/modules/python/test/test_houghcircles.py +++ b/modules/python/test/test_houghcircles.py @@ -79,6 +79,20 @@ class houghcircles_test(NewOpenCVTests): self.assertGreater(float(matches_counter) / len(testCircles), .5) self.assertLess(float(len(circles) - matches_counter) / len(circles), .75) + circles_acc = cv.HoughCirclesWithAccumulator( + image=img, + method=cv.HOUGH_GRADIENT, + dp=1, + minDist=10, + circles=np.array([]), + param1=150, + param2=45, + minRadius=1, + maxRadius=30) + + self.assertEqual(circles_acc.shape, (1, 2, 4)) + self.assertEqual(circles_acc[0, 0, 3], 66.) + self.assertEqual(circles_acc[0, 1, 3], 62.) def test_houghcircles_alt(self): @@ -127,5 +141,21 @@ class houghcircles_test(NewOpenCVTests): self.assertGreater(float(matches_counter) / len(testCircles), .5) self.assertLess(float(len(circles) - matches_counter) / len(circles), .75) + circles_acc = cv.HoughCirclesWithAccumulator( + image=img, + method=cv.HOUGH_GRADIENT_ALT, + dp=1, + minDist=10, + circles=np.array([]), + param1=300, + param2=0.9, + minRadius=13, + maxRadius=15) + + self.assertEqual(circles_acc.shape, (1, 3, 4)) + self.assertEqual(circles_acc[0, 0, 3], 62.) + self.assertEqual(circles_acc[0, 1, 3], 59.) + self.assertEqual(circles_acc[0, 2, 3], 47.) + if __name__ == '__main__': NewOpenCVTests.bootstrap() diff --git a/modules/videoio/src/cap_ffmpeg.cpp b/modules/videoio/src/cap_ffmpeg.cpp index a35f305ea9..9dd97efbaa 100644 --- a/modules/videoio/src/cap_ffmpeg.cpp +++ b/modules/videoio/src/cap_ffmpeg.cpp @@ -194,7 +194,8 @@ public: } } - icvWriteFrame_FFMPEG_p(ffmpegWriter, (const uchar*)image.getMat().ptr(), (int)image.step(), image.cols(), image.rows(), image.channels(), 0); + if (!icvWriteFrame_FFMPEG_p(ffmpegWriter, (const uchar*)image.getMat().ptr(), (int)image.step(), image.cols(), image.rows(), image.channels(), 0)) + CV_LOG_WARNING(NULL, "FFmpeg: Failed to write frame"); } virtual bool open( const cv::String& filename, int fourcc, double fps, cv::Size frameSize, const VideoWriterParameters& params ) { diff --git a/modules/videoio/src/cap_ffmpeg_impl.hpp b/modules/videoio/src/cap_ffmpeg_impl.hpp index 4ec2137a39..9cd20fbf77 100644 --- a/modules/videoio/src/cap_ffmpeg_impl.hpp +++ b/modules/videoio/src/cap_ffmpeg_impl.hpp @@ -2525,11 +2525,13 @@ bool CvVideoWriter_FFMPEG::writeFrame( const unsigned char* data, int step, int // check parameters if (input_pix_fmt == AV_PIX_FMT_BGR24) { if (cn != 3) { + CV_LOG_WARNING(NULL, "write frame skipped - expected 3 channels but got " << cn); return false; } } else if (input_pix_fmt == AV_PIX_FMT_GRAY8 || input_pix_fmt == AV_PIX_FMT_GRAY16LE) { if (cn != 1) { + CV_LOG_WARNING(NULL, "write frame skipped - expected 1 channel but got " << cn); return false; } } @@ -2640,14 +2642,16 @@ bool CvVideoWriter_FFMPEG::writeFrame( const unsigned char* data, int step, int } hw_frame->pts = frame_idx; int ret_write = icv_av_write_frame_FFMPEG(oc, video_st, context, outbuf, outbuf_size, hw_frame, frame_idx); - ret = ret_write >= 0 ? true : false; + // AVERROR(EAGAIN): continue sending input, not an error + ret = (ret_write >= 0 || ret_write == AVERROR(EAGAIN)); av_frame_free(&hw_frame); } else #endif { picture->pts = frame_idx; int ret_write = icv_av_write_frame_FFMPEG(oc, video_st, context, outbuf, outbuf_size, picture, frame_idx); - ret = ret_write >= 0 ? true : false; + // AVERROR(EAGAIN): continue sending input, not an error + ret = (ret_write >= 0 || ret_write == AVERROR(EAGAIN)); } frame_idx++; diff --git a/modules/videoio/src/cap_gstreamer.cpp b/modules/videoio/src/cap_gstreamer.cpp index a158119d3f..ff1ff2b028 100644 --- a/modules/videoio/src/cap_gstreamer.cpp +++ b/modules/videoio/src/cap_gstreamer.cpp @@ -2699,7 +2699,7 @@ void CvVideoWriter_GStreamer::write(InputArray image) } else if (input_pix_fmt == GST_VIDEO_FORMAT_GRAY16_LE) { if (image.type() != CV_16UC1) { - CV_WARN("write frame skipped - expected CV_16UC3"); + CV_WARN("write frame skipped - expected CV_16UC1"); return; } } diff --git a/modules/videoio/test/test_ffmpeg.cpp b/modules/videoio/test/test_ffmpeg.cpp index 03e41bd8f0..eeb0835078 100644 --- a/modules/videoio/test/test_ffmpeg.cpp +++ b/modules/videoio/test/test_ffmpeg.cpp @@ -953,4 +953,81 @@ inline static std::string videoio_ffmpeg_16bit_name_printer(const testing::TestP INSTANTIATE_TEST_CASE_P(/**/, videoio_ffmpeg_16bit, testing::ValuesIn(sixteen_bit_modes), videoio_ffmpeg_16bit_name_printer); +typedef tuple ChannelMismatchTestParams; +typedef testing::TestWithParam< ChannelMismatchTestParams > videoio_ffmpeg_channel_mismatch; + +TEST_P(videoio_ffmpeg_channel_mismatch, basic) +{ + if (!videoio_registry::hasBackend(CAP_FFMPEG)) + throw SkipTestException("FFmpeg backend was not found"); + + const string filename = "mismatch_video.mp4"; + int input_type = get<0>(GetParam()); + int depth = get<1>(GetParam()); + bool is_Color = get<2>(GetParam()); + bool is_valid = get<3>(GetParam()); + const string description = get<4>(GetParam()); + + const double fps = 15.0; + const int fourcc = VideoWriter::fourcc('m', 'p', '4', 'v'); + const Mat frame(480, 640, input_type, Scalar::all(0)); + + VideoWriter writer(filename, fourcc, fps, frame.size(), + { + cv::VIDEOWRITER_PROP_DEPTH, depth, + VIDEOWRITER_PROP_IS_COLOR, is_Color + }); + + if (!writer.isOpened()) + throw SkipTestException("Failed to open video writer"); + + for (int i = 1; i <= 15; i++) + { + // In case of mismatch between input frame channels and + // expected depth/isColor configuration a warning should be printed communicating it + writer.write(frame); + } + + writer.release(); + + VideoCapture cap(filename, CAP_FFMPEG); + + if (is_valid) { + ASSERT_TRUE(cap.isOpened()) << "Can't open video for " << description; + EXPECT_EQ(cap.get(CAP_PROP_FRAME_COUNT), 15) << "All frames should be written for: " << description; + } else { + ASSERT_FALSE(cap.isOpened()) << "Video capture should fail to open for: " << description; + } + + std::remove(filename.c_str()); +} + +const ChannelMismatchTestParams mismatch_cases[] = +{ + // Testing input frame channels and expected depth/isColor combinations + + // Open VideoWriter depth/isColor combination: CV_8U/true, everything with 3 channels should be valid + make_tuple(CV_16UC1, CV_8U, true, false, "input_CV_16UC1_expected_CV_8U_isColor_true"), + make_tuple(CV_8UC1, CV_8U, true, false, "input_CV_8UC1_expected_CV_8U_isColor_true"), + make_tuple(CV_8UC3, CV_8U, true, true, "input_CV_8UC3_expected_CV_8U_isColor_true_valid"), + make_tuple(CV_16UC3, CV_8U, true, true, "input_CV_16UC3_expected_CV_8U_isColor_true_valid"), + + // Open VideoWriter depth/isColor combination: 16U,8U/false, everything with 1 channel should be valid + make_tuple(CV_8UC3, CV_8U, false, false, "input_CV_8UC3_expected_CV_8U_isColor_false"), + make_tuple(CV_16UC3, CV_8U, false, false, "input_CV_16UC3_expected_CV_8U_isColor_false"), + make_tuple(CV_8UC3, CV_16U, false, false, "input_CV_8UC3_expected_CV_16U_isColor_false"), + make_tuple(CV_16UC3, CV_16U, false, false, "input_CV_16UC3_expected_CV_16U_isColor_false"), + make_tuple(CV_8UC1, CV_16U, false, true, "input_CV_8UC1_expected_CV_16U_isColor_false_valid"), + make_tuple(CV_16UC1, CV_8U, false, true, "input_CV_16UC1_expected_CV_8U_isColor_false_valid"), +}; + +inline static std::string videoio_ffmpeg_mismatch_name_printer(const testing::TestParamInfo& info) +{ + std::ostringstream os; + os << get<4>(info.param); + return os.str(); +} + +INSTANTIATE_TEST_CASE_P(/**/, videoio_ffmpeg_channel_mismatch, testing::ValuesIn(mismatch_cases), videoio_ffmpeg_mismatch_name_printer); + }} // namespace diff --git a/platforms/android/build_sdk.py b/platforms/android/build_sdk.py index 687038125e..9de579e5fc 100755 --- a/platforms/android/build_sdk.py +++ b/platforms/android/build_sdk.py @@ -138,7 +138,7 @@ class ABI: def __str__(self): return "%s (%s)" % (self.name, self.toolchain) def haveIPP(self): - return self.name == "x86_64" + return self.name == "x86" or self.name == "x86_64" def haveKleidiCV(self): return self.name == "arm64-v8a" diff --git a/platforms/android/default.config.py b/platforms/android/default.config.py index 2d0b45d15e..9c7b9ad0ef 100644 --- a/platforms/android/default.config.py +++ b/platforms/android/default.config.py @@ -2,5 +2,5 @@ ABIs = [ ABI("2", "armeabi-v7a", None, 21, cmake_vars=dict(ANDROID_ABI='armeabi-v7a with NEON')), ABI("3", "arm64-v8a", None, 21, cmake_vars=dict(ANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES='ON')), ABI("5", "x86_64", None, 21, cmake_vars=dict(ANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES='ON')), - ABI("4", "x86", None, 21, cmake_vars=dict(WITH_IPP='OFF')), + ABI("4", "x86", None, 21), ] diff --git a/platforms/android/fastcv.config.py b/platforms/android/fastcv.config.py index c96b534e68..e1a86a5ad8 100644 --- a/platforms/android/fastcv.config.py +++ b/platforms/android/fastcv.config.py @@ -2,5 +2,5 @@ ABIs = [ ABI("2", "armeabi-v7a", None, 21, cmake_vars=dict(ANDROID_ABI='armeabi-v7a with NEON', WITH_FASTCV='ON')), ABI("3", "arm64-v8a", None, 21, cmake_vars=dict(ANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES='ON', WITH_FASTCV='ON')), ABI("5", "x86_64", None, 21, cmake_vars=dict(ANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES='ON')), - ABI("4", "x86", None, 21, cmake_vars=dict(WITH_IPP='OFF')), + ABI("4", "x86", None, 21), ] diff --git a/samples/cpp/tutorial_code/photo/hdr_imaging/hdr_imaging.cpp b/samples/cpp/tutorial_code/photo/hdr_imaging/hdr_imaging.cpp index 5e8843bcf0..83711d5949 100644 --- a/samples/cpp/tutorial_code/photo/hdr_imaging/hdr_imaging.cpp +++ b/samples/cpp/tutorial_code/photo/hdr_imaging/hdr_imaging.cpp @@ -35,7 +35,7 @@ int main(int argc, char**argv) //! [Tonemap HDR image] Mat ldr; - Ptr tonemap = createTonemap(2.2f); + Ptr tonemap = createTonemapDrago(2.2f); tonemap->process(hdr, ldr); //! [Tonemap HDR image] diff --git a/samples/python/drawing.py b/samples/python/drawing.py index e5db400dc0..e6f61ba69c 100644 --- a/samples/python/drawing.py +++ b/samples/python/drawing.py @@ -58,7 +58,7 @@ def ellipse(): for i in range(NUMBER*2): center = [] center.append(np.random.randint(x1, x2)) - center.append(np.random.randint(x1, x2)) + center.append(np.random.randint(y1, y2)) axes = [] axes.append(np.random.randint(0, 200)) axes.append(np.random.randint(0, 200)) @@ -136,7 +136,7 @@ def circles(): for i in range(NUMBER): center = [] center.append(np.random.randint(x1, x2)) - center.append(np.random.randint(x1, x2)) + center.append(np.random.randint(y1, y2)) color = "%06x" % np.random.randint(0, 0xFFFFFF) color = tuple(int(color[i:i+2], 16) for i in (0, 2 ,4)) cv.circle(image, tuple(center), np.random.randint(0, 300), color, np.random.randint(-1, 9), lineType) @@ -149,7 +149,7 @@ def string(): for i in range(NUMBER): org = [] org.append(np.random.randint(x1, x2)) - org.append(np.random.randint(x1, x2)) + org.append(np.random.randint(y1, y2)) color = "%06x" % np.random.randint(0, 0xFFFFFF) color = tuple(int(color[i:i+2], 16) for i in (0, 2 ,4)) cv.putText(image, "Testing text rendering", tuple(org), np.random.randint(0, 8), np.random.randint(0, 100)*0.05+0.1, color, np.random.randint(1, 10), lineType) diff --git a/samples/python/tutorial_code/photo/hdr_imaging/hdr_imaging.py b/samples/python/tutorial_code/photo/hdr_imaging/hdr_imaging.py index a5403d3cca..6ab7cf1a0a 100644 --- a/samples/python/tutorial_code/photo/hdr_imaging/hdr_imaging.py +++ b/samples/python/tutorial_code/photo/hdr_imaging/hdr_imaging.py @@ -40,7 +40,7 @@ hdr = merge_debevec.process(images, times, response) ## [Make HDR image] ## [Tonemap HDR image] -tonemap = cv.createTonemap(2.2) +tonemap = cv.createTonemapDrago(2.2) ldr = tonemap.process(hdr) ## [Tonemap HDR image]