From ab0a818c84849252f91c7b866818785db1687a90 Mon Sep 17 00:00:00 2001 From: MaximSmolskiy Date: Thu, 2 Jan 2025 22:14:08 +0300 Subject: [PATCH 01/62] Fix matchTemplate with mask crash --- modules/imgproc/src/templmatch.cpp | 10 +++++++--- modules/imgproc/test/test_templmatchmask.cpp | 12 ++++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/modules/imgproc/src/templmatch.cpp b/modules/imgproc/src/templmatch.cpp index 5671025b0f..827af3eb47 100644 --- a/modules/imgproc/src/templmatch.cpp +++ b/modules/imgproc/src/templmatch.cpp @@ -850,7 +850,8 @@ static void matchTemplateMask( InputArray _img, InputArray _templ, OutputArray _ // CCorr(I', T') = CCorr(I, T'*M) - sum(T'*M)/sum(M)*CCorr(I, M) // It does not matter what to use Mat/MatExpr, it should be evaluated to perform assign subtraction - Mat temp_res = img_mask_corr.mul(sum(templx_mask).div(mask_sum)); + Mat temp_res; + multiply(img_mask_corr, sum(templx_mask).div(mask_sum), temp_res); if (img.channels() == 1) { result -= temp_res; @@ -881,8 +882,11 @@ static void matchTemplateMask( InputArray _img, InputArray _templ, OutputArray _ Mat img_mask2_corr(corrSize, img.type()); crossCorr(img2, mask2, norm_imgx, Point(0,0), 0, 0); crossCorr(img, mask2, img_mask2_corr, Point(0,0), 0, 0); - temp_res = img_mask_corr.mul(Scalar(1.0, 1.0, 1.0, 1.0).div(mask_sum)) - .mul(img_mask_corr.mul(mask2_sum.div(mask_sum)) - 2 * img_mask2_corr); + Mat temp_res1; + multiply(img_mask_corr, Scalar(1.0, 1.0, 1.0, 1.0).div(mask_sum), temp_res1); + Mat temp_res2; + multiply(img_mask_corr, mask2_sum.div(mask_sum), temp_res2); + temp_res = temp_res1.mul(temp_res2 - 2 * img_mask2_corr); if (img.channels() == 1) { norm_imgx += temp_res; diff --git a/modules/imgproc/test/test_templmatchmask.cpp b/modules/imgproc/test/test_templmatchmask.cpp index 3c8ab665ae..c9664cc406 100644 --- a/modules/imgproc/test/test_templmatchmask.cpp +++ b/modules/imgproc/test/test_templmatchmask.cpp @@ -275,4 +275,16 @@ INSTANTIATE_TEST_CASE_P(MultiChannelMask, Imgproc_MatchTemplateWithMask2, Values(cv::TM_SQDIFF, cv::TM_SQDIFF_NORMED, cv::TM_CCORR, cv::TM_CCORR_NORMED, cv::TM_CCOEFF, cv::TM_CCOEFF_NORMED))); +TEST(Imgproc_MatchTemplateWithMask, bug_26389) { + const Mat image = Mat::ones(Size(10, 10), CV_8UC1); + const Mat templ = Mat::ones(Size(10, 7), CV_8UC1); + const Mat mask = Mat::ones(Size(10, 7), CV_8UC1); + + for (const int method : {TM_CCOEFF, TM_CCOEFF_NORMED}) + { + Mat result; + matchTemplate(image, templ, result, method, mask); + } +} + }} // namespace From ce1398882d41c8bc02b001a16c913fcbfb721633 Mon Sep 17 00:00:00 2001 From: Onuralp SEZER Date: Tue, 22 Oct 2024 21:08:39 +0300 Subject: [PATCH 02/62] fix(android): Kotlin 2.0 internal error for unsafe coercions Signed-off-by: Onuralp SEZER --- modules/core/misc/java/src/java/core+MatAt.kt | 50 ++++++++++--------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/modules/core/misc/java/src/java/core+MatAt.kt b/modules/core/misc/java/src/java/core+MatAt.kt index c81e21057f..d50c3fbb0f 100644 --- a/modules/core/misc/java/src/java/core+MatAt.kt +++ b/modules/core/misc/java/src/java/core+MatAt.kt @@ -47,49 +47,50 @@ inline fun Mat.at(idx: IntArray) : Atable = class AtableUByte(val mat: Mat, val indices: IntArray): Atable { + constructor(mat: Mat, row: Int, col: Int) : this(mat, intArrayOf(row, col)) override fun getV(): UByte { - val data = UByteArray(1) + val data = ByteArray(1) mat.get(indices, data) - return data[0] + return data[0].toUByte() } override fun setV(v: UByte) { - val data = ubyteArrayOf(v) + val data = byteArrayOf(v.toByte()) mat.put(indices, data) } override fun getV2c(): Tuple2 { - val data = UByteArray(2) + val data = ByteArray(2) mat.get(indices, data) - return Tuple2(data[0], data[1]) + return Tuple2(data[0].toUByte(), data[1].toUByte()) } override fun setV2c(v: Tuple2) { - val data = ubyteArrayOf(v._0, v._1) + val data = byteArrayOf(v._0.toByte(), v._1.toByte()) mat.put(indices, data) } override fun getV3c(): Tuple3 { - val data = UByteArray(3) + val data = ByteArray(3) mat.get(indices, data) - return Tuple3(data[0], data[1], data[2]) + return Tuple3(data[0].toUByte(), data[1].toUByte(), data[2].toUByte()) } override fun setV3c(v: Tuple3) { - val data = ubyteArrayOf(v._0, v._1, v._2) + val data = byteArrayOf(v._0.toByte(), v._1.toByte(), v._2.toByte()) mat.put(indices, data) } override fun getV4c(): Tuple4 { - val data = UByteArray(4) + val data = ByteArray(4) mat.get(indices, data) - return Tuple4(data[0], data[1], data[2], data[3]) + return Tuple4(data[0].toUByte(), data[1].toUByte(), data[2].toUByte(), data[3].toUByte()) } override fun setV4c(v: Tuple4) { - val data = ubyteArrayOf(v._0, v._1, v._2, v._3) + val data = byteArrayOf(v._0.toByte(), v._1.toByte(), v._2.toByte(), v._3.toByte()) mat.put(indices, data) } } @@ -99,46 +100,47 @@ class AtableUShort(val mat: Mat, val indices: IntArray): Atable { constructor(mat: Mat, row: Int, col: Int) : this(mat, intArrayOf(row, col)) override fun getV(): UShort { - val data = UShortArray(1) + val data = ShortArray(1) mat.get(indices, data) - return data[0] + return data[0].toUShort() } override fun setV(v: UShort) { - val data = ushortArrayOf(v) + val data = shortArrayOf(v.toShort()) mat.put(indices, data) } override fun getV2c(): Tuple2 { - val data = UShortArray(2) + val data = ShortArray(2) mat.get(indices, data) - return Tuple2(data[0], data[1]) + return Tuple2(data[0].toUShort(), data[1].toUShort()) } + override fun setV2c(v: Tuple2) { - val data = ushortArrayOf(v._0, v._1) + val data = shortArrayOf(v._0.toShort(), v._1.toShort()) mat.put(indices, data) } override fun getV3c(): Tuple3 { - val data = UShortArray(3) + val data = ShortArray(3) mat.get(indices, data) - return Tuple3(data[0], data[1], data[2]) + return Tuple3(data[0].toUShort(), data[1].toUShort(), data[2].toUShort()) } override fun setV3c(v: Tuple3) { - val data = ushortArrayOf(v._0, v._1, v._2) + val data = shortArrayOf(v._0.toShort(), v._1.toShort(), v._2.toShort()) mat.put(indices, data) } override fun getV4c(): Tuple4 { - val data = UShortArray(4) + val data = ShortArray(4) mat.get(indices, data) - return Tuple4(data[0], data[1], data[2], data[3]) + return Tuple4(data[0].toUShort(), data[1].toUShort(), data[2].toUShort(), data[3].toUShort()) } override fun setV4c(v: Tuple4) { - val data = ushortArrayOf(v._0, v._1, v._2, v._3) + val data = shortArrayOf(v._0.toShort(), v._1.toShort(), v._2.toShort(), v._3.toShort()) mat.put(indices, data) } } From 452882c0076f9df6925b6de3ff844ee26b8eef8d Mon Sep 17 00:00:00 2001 From: Kavyansh Tyagi <142140238+KAVYANSHTYAGI@users.noreply.github.com> Date: Wed, 11 Jun 2025 15:54:29 +0530 Subject: [PATCH 03/62] Add diamond structuring element --- modules/imgproc/include/opencv2/imgproc.hpp | 3 ++- .../imgproc/include/opencv2/imgproc/types_c.h | 1 + modules/imgproc/src/morph.dispatch.cpp | 17 ++++++++++++++++- .../imgproc/test/test_structuring_element.cpp | 17 +++++++++++++++++ 4 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 modules/imgproc/test/test_structuring_element.cpp diff --git a/modules/imgproc/include/opencv2/imgproc.hpp b/modules/imgproc/include/opencv2/imgproc.hpp index 059d26a41c..d43123972f 100644 --- a/modules/imgproc/include/opencv2/imgproc.hpp +++ b/modules/imgproc/include/opencv2/imgproc.hpp @@ -235,8 +235,9 @@ enum MorphShapes { MORPH_RECT = 0, //!< a rectangular structuring element: \f[E_{ij}=1\f] MORPH_CROSS = 1, //!< a cross-shaped structuring element: //!< \f[E_{ij} = \begin{cases} 1 & \texttt{if } {i=\texttt{anchor.y } {or } {j=\texttt{anchor.x}}} \\0 & \texttt{otherwise} \end{cases}\f] - MORPH_ELLIPSE = 2 //!< an elliptic structuring element, that is, a filled ellipse inscribed + MORPH_ELLIPSE = 2, //!< an elliptic structuring element, that is, a filled ellipse inscribed //!< into the rectangle Rect(0, 0, esize.width, esize.height) + MORPH_DIAMOND = 3 //!< a diamond structuring element defined by Manhattan distance }; //! @} imgproc_filter diff --git a/modules/imgproc/include/opencv2/imgproc/types_c.h b/modules/imgproc/include/opencv2/imgproc/types_c.h index df11850738..14a0ec796b 100644 --- a/modules/imgproc/include/opencv2/imgproc/types_c.h +++ b/modules/imgproc/include/opencv2/imgproc/types_c.h @@ -389,6 +389,7 @@ enum MorphShapes_c CV_SHAPE_RECT =0, CV_SHAPE_CROSS =1, CV_SHAPE_ELLIPSE =2, + CV_SHAPE_DIAMOND =3, CV_SHAPE_CUSTOM =100 //!< custom structuring element }; diff --git a/modules/imgproc/src/morph.dispatch.cpp b/modules/imgproc/src/morph.dispatch.cpp index 0cb50ec368..714050ccf1 100644 --- a/modules/imgproc/src/morph.dispatch.cpp +++ b/modules/imgproc/src/morph.dispatch.cpp @@ -138,7 +138,7 @@ Mat getStructuringElement(int shape, Size ksize, Point anchor) int r = 0, c = 0; double inv_r2 = 0; - CV_Assert( shape == MORPH_RECT || shape == MORPH_CROSS || shape == MORPH_ELLIPSE ); + CV_Assert( shape == MORPH_RECT || shape == MORPH_CROSS || shape == MORPH_ELLIPSE || shape == MORPH_DIAMOND ); anchor = normalizeAnchor(anchor, ksize); @@ -151,6 +151,11 @@ Mat getStructuringElement(int shape, Size ksize, Point anchor) c = ksize.width/2; inv_r2 = r ? 1./((double)r*r) : 0; } + else if( shape == MORPH_DIAMOND ) + { + r = ksize.height/2; + c = ksize.width/2; + } Mat elem(ksize, CV_8U); @@ -163,6 +168,16 @@ Mat getStructuringElement(int shape, Size ksize, Point anchor) j2 = ksize.width; else if( shape == MORPH_CROSS ) j1 = anchor.x, j2 = j1 + 1; + else if( shape == MORPH_DIAMOND ) + { + int dy = std::abs(i - r); + if( dy <= r ) + { + int dx = r - dy; + j1 = std::max( c - dx, 0 ); + j2 = std::min( c + dx + 1, ksize.width ); + } + } else { int dy = i - r; diff --git a/modules/imgproc/test/test_structuring_element.cpp b/modules/imgproc/test/test_structuring_element.cpp new file mode 100644 index 0000000000..7ca500e10a --- /dev/null +++ b/modules/imgproc/test/test_structuring_element.cpp @@ -0,0 +1,17 @@ +#include "test_precomp.hpp" + +namespace opencv_test { namespace { + +TEST(MorphShapes, getStructuringElementDiamond) +{ + cv::Mat element = cv::getStructuringElement(cv::MORPH_DIAMOND, cv::Size(5,5)); + cv::Mat expected = (cv::Mat_(5,5) << + 0,0,1,0,0, + 0,1,1,1,0, + 1,1,1,1,1, + 0,1,1,1,0, + 0,0,1,0,0); + EXPECT_EQ(0, cvtest::norm(element, expected, cv::NORM_INF)); +} + +}} // namespace From b395a2e307e9b44c394b6f0e46de319a93ef85d4 Mon Sep 17 00:00:00 2001 From: leopardracer <136604165+leopardracer@users.noreply.github.com> Date: Wed, 11 Jun 2025 16:19:05 +0300 Subject: [PATCH 04/62] Merge pull request #27434 from leopardracer:4.x MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix Typos in Comments and Error Messages Across Multiple Files #27434 Description: This pull request corrects several typographical errors in comments and error messages in the following files: - `samples/directx/d3d11_interop.cpp`: Fixed typo in the error message ("betweem" → "between"). - `samples/dnn/yolo_detector.cpp`: Fixed typo in a comment ("elemets" → "elements"). - `samples/winrt/ImageManipulations/MediaExtensions/OcvTransform.cpp`: Fixed typo in a comment ("peferred" → "preferred"). These changes improve code readability and maintain consistency in documentation and error reporting. No functional code was modified. --- samples/directx/d3d11_interop.cpp | 2 +- samples/dnn/yolo_detector.cpp | 2 +- .../MediaExtensions/OcvTransform/OcvTransform.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/samples/directx/d3d11_interop.cpp b/samples/directx/d3d11_interop.cpp index 6c55a0c48b..1869f9e31c 100644 --- a/samples/directx/d3d11_interop.cpp +++ b/samples/directx/d3d11_interop.cpp @@ -373,7 +373,7 @@ public: r = m_pD3D11SwapChain->Present(0, 0); if (FAILED(r)) { - throw std::runtime_error("switch betweem fronat and back buffers failed!"); + throw std::runtime_error("switch between fronat and back buffers failed!"); } } // try diff --git a/samples/dnn/yolo_detector.cpp b/samples/dnn/yolo_detector.cpp index df0cd19ed1..48bb763b52 100644 --- a/samples/dnn/yolo_detector.cpp +++ b/samples/dnn/yolo_detector.cpp @@ -125,7 +125,7 @@ void yoloPostProcessing( if (model_name == "yolonas") { - // outs contains 2 elemets of shape [1, 8400, nc] and [1, 8400, 4]. Concat them to get [1, 8400, nc+4] + // outs contains 2 elements of shape [1, 8400, nc] and [1, 8400, 4]. Concat them to get [1, 8400, nc+4] Mat concat_out; // squeeze the first dimension outs[0] = outs[0].reshape(1, outs[0].size[1]); diff --git a/samples/winrt/ImageManipulations/MediaExtensions/OcvTransform/OcvTransform.cpp b/samples/winrt/ImageManipulations/MediaExtensions/OcvTransform/OcvTransform.cpp index a2854d2382..64549112d3 100644 --- a/samples/winrt/ImageManipulations/MediaExtensions/OcvTransform/OcvTransform.cpp +++ b/samples/winrt/ImageManipulations/MediaExtensions/OcvTransform/OcvTransform.cpp @@ -1047,7 +1047,7 @@ done: // Create a partial media type from our list. // -// dwTypeIndex: Index into the list of peferred media types. +// dwTypeIndex: Index into the list of preferred media types. // ppmt: Receives a pointer to the media type. HRESULT OcvImageManipulations::OnGetPartialType(DWORD dwTypeIndex, IMFMediaType **ppmt) From d750d43aa2fd6c8e5dde5861c19afe612d57911b Mon Sep 17 00:00:00 2001 From: Dmitry Kurtaev Date: Wed, 11 Jun 2025 17:37:18 +0300 Subject: [PATCH 05/62] Merge pull request #27432 from dkurt:d.kurtaev/ipp_distTransform Correct IPP distanceTransform results with single thread #27432 ### Pull Request Readiness Checklist resolves #24082 See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [x] The PR is proposed to the proper branch - [x] There is a reference to the original bug report and related work - [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [x] The feature is well documented and sample code can be built with the project CMake --- modules/imgproc/src/distransform.cpp | 16 +++++++++ .../imgproc/test/test_distancetransform.cpp | 33 +++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/modules/imgproc/src/distransform.cpp b/modules/imgproc/src/distransform.cpp index 6a7026c8c8..66be0e8ea5 100644 --- a/modules/imgproc/src/distransform.cpp +++ b/modules/imgproc/src/distransform.cpp @@ -796,6 +796,22 @@ void cv::distanceTransform( InputArray _src, OutputArray _dst, OutputArray _labe ippFree( pBuffer ); if (status>=0) { + // https://github.com/opencv/opencv/issues/24082 + // There is probably a rounding issue that leads to non-deterministic behavior + // between runs on positions closer to zeros by x-axis in straight direction. + // As a workaround, we detect the distances that expected to be exact + // number of pixels and round manually. + static const float correctionDiff = 1.0f / (1 << 11); + for (int i = 0; i < dst.rows; ++i) + { + float* row = dst.ptr(i); + for (int j = 0; j < dst.cols; ++j) + { + float rounded = static_cast(cvRound(row[j])); + if (fabs(row[j] - rounded) <= correctionDiff) + row[j] = rounded; + } + } CV_IMPL_ADD(CV_IMPL_IPP); return; } diff --git a/modules/imgproc/test/test_distancetransform.cpp b/modules/imgproc/test/test_distancetransform.cpp index bf272cd224..9692655fab 100644 --- a/modules/imgproc/test/test_distancetransform.cpp +++ b/modules/imgproc/test/test_distancetransform.cpp @@ -416,4 +416,37 @@ TEST(Imgproc_DistanceTransform, precise_long_dist) EXPECT_EQ(cv::norm(expected, dist, NORM_INF), 0); } +TEST(Imgproc_DistanceTransform, ipp_deterministic_corner) +{ + setNumThreads(1); + + Mat src(1, 4096, CV_8U, Scalar(255)), dist; + src.at(0, 0) = 0; + distanceTransform(src, dist, DIST_L2, DIST_MASK_PRECISE); + for (int i = 0; i < src.cols; ++i) + { + float expected = static_cast(i); + ASSERT_EQ(expected, dist.at(0, i)) << cv::format("diff: %e", expected - dist.at(0, i)); + } +} + +TEST(Imgproc_DistanceTransform, ipp_deterministic) +{ + setNumThreads(1); + RNG& rng = TS::ptr()->get_rng(); + Mat src(1, 800, CV_8U, Scalar(255)), dist; + int p1 = cvtest::randInt(rng) % src.cols; + int p2 = cvtest::randInt(rng) % src.cols; + int p3 = cvtest::randInt(rng) % src.cols; + src.at(0, p1) = 0; + src.at(0, p2) = 0; + src.at(0, p3) = 0; + distanceTransform(src, dist, DIST_L2, DIST_MASK_PRECISE); + for (int i = 0; i < src.cols; ++i) + { + float expected = static_cast(min(min(abs(i - p1), abs(i - p2)), abs(i - p3))); + ASSERT_EQ(expected, dist.at(0, i)) << cv::format("diff: %e", expected - dist.at(0, i)); + } +} + }} // namespace From 287dad81025240751cdfe696386060375c0d4481 Mon Sep 17 00:00:00 2001 From: eplankin Date: Thu, 12 Jun 2025 14:23:49 +0200 Subject: [PATCH 06/62] Merge pull request #27354 from eplankin:ipp_update22.1 Update IPP integration #27354 Please merge together with https://github.com/opencv/opencv_3rdparty/pull/96 Supported IPP version was updated to IPP 2022.1.0 for Linux and Windows. Bugs in norm() function which caused failure of sanity check in performance tests were fixed, IPP calls were enabled. Previous update: https://github.com/opencv/opencv/pull/26463 --- 3rdparty/ippicv/ippicv.cmake | 10 +++++----- hal/ipp/include/ipp_hal_core.hpp | 7 +++++-- modules/core/include/opencv2/core/private.hpp | 1 - 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/3rdparty/ippicv/ippicv.cmake b/3rdparty/ippicv/ippicv.cmake index 48901a1547..5396bfe8f3 100644 --- a/3rdparty/ippicv/ippicv.cmake +++ b/3rdparty/ippicv/ippicv.cmake @@ -2,7 +2,7 @@ function(download_ippicv root_var) set(${root_var} "" PARENT_SCOPE) # Commit SHA in the opencv_3rdparty repo - set(IPPICV_COMMIT "d1cbea44d326eb0421fedcdd16de4630fd8c7ed0") + set(IPPICV_COMMIT "767426b2a40a011eb2fa7f44c677c13e60e205ad") # Define actual ICV versions if(APPLE) set(IPPICV_COMMIT "0cc4aa06bf2bef4b05d237c69a5a96b9cd0cb85a") @@ -14,8 +14,8 @@ function(download_ippicv root_var) set(OPENCV_ICV_PLATFORM "linux") set(OPENCV_ICV_PACKAGE_SUBDIR "ippicv_lnx") if(X86_64) - set(OPENCV_ICV_NAME "ippicv_2022.0.0_lnx_intel64_20240904_general.tgz") - set(OPENCV_ICV_HASH "63717ee0f918ad72fb5a737992a206d1") + set(OPENCV_ICV_NAME "ippicv_2022.1.0_lnx_intel64_20250130_general.tgz") + set(OPENCV_ICV_HASH "98ff71fc242d52db9cc538388e502f57") else() if(ANDROID) set(IPPICV_COMMIT "c7c6d527dde5fee7cb914ee9e4e20f7436aab3a1") @@ -31,8 +31,8 @@ function(download_ippicv root_var) set(OPENCV_ICV_PLATFORM "windows") set(OPENCV_ICV_PACKAGE_SUBDIR "ippicv_win") if(X86_64) - set(OPENCV_ICV_NAME "ippicv_2022.0.0_win_intel64_20240904_general.zip") - set(OPENCV_ICV_HASH "3a6eca7cc3bce7159eb1443c6fca4e31") + set(OPENCV_ICV_NAME "ippicv_2022.1.0_win_intel64_20250130_general.zip") + set(OPENCV_ICV_HASH "67a611ab22410f392239bddff6f91df7") else() set(IPPICV_COMMIT "7f55c0c26be418d494615afca15218566775c725") set(OPENCV_ICV_NAME "ippicv_2021.12.0_win_ia32_20240425_general.zip") diff --git a/hal/ipp/include/ipp_hal_core.hpp b/hal/ipp/include/ipp_hal_core.hpp index caa8c765b2..65f388b859 100644 --- a/hal/ipp/include/ipp_hal_core.hpp +++ b/hal/ipp/include/ipp_hal_core.hpp @@ -21,7 +21,11 @@ int ipp_hal_minMaxIdxMaskStep(const uchar* src_data, size_t src_step, int width, #undef cv_hal_minMaxIdxMaskStep #define cv_hal_minMaxIdxMaskStep ipp_hal_minMaxIdxMaskStep -#define IPP_DISABLE_NORM_8U 1 // accuracy difference in perf test sanity check +#if (IPP_VERSION_X100 == 202200) +# define IPP_DISABLE_NORM_8U 1 // accuracy difference in perf test sanity check +# else +# define IPP_DISABLE_NORM_8U 0 +#endif int ipp_hal_norm(const uchar* src, size_t src_step, const uchar* mask, size_t mask_step, int width, int height, int type, int norm_type, double* result); @@ -29,7 +33,6 @@ int ipp_hal_norm(const uchar* src, size_t src_step, const uchar* mask, size_t ma #undef cv_hal_norm #define cv_hal_norm ipp_hal_norm - int ipp_hal_normDiff(const uchar* src1, size_t src1_step, const uchar* src2, size_t src2_step, const uchar* mask, size_t mask_step, int width, int height, int type, int norm_type, double* result); diff --git a/modules/core/include/opencv2/core/private.hpp b/modules/core/include/opencv2/core/private.hpp index 140264086f..4f23abf6de 100644 --- a/modules/core/include/opencv2/core/private.hpp +++ b/modules/core/include/opencv2/core/private.hpp @@ -209,7 +209,6 @@ T* allocSingletonNew() { return new(allocSingletonNewBuffer(sizeof(T))) T(); } #define IPP_DISABLE_XYZ_RGB 1 // big accuracy difference #define IPP_DISABLE_HOUGH 1 // improper integration/results #define IPP_DISABLE_FILTER2D_BIG_MASK 1 // different results on masks > 7x7 -#define IPP_DISABLE_NORM_8U 1 // accuracy difference in perf test sanity check // Temporary disabled named IPP region. Performance #define IPP_DISABLE_PERF_COPYMAKE 1 // performance variations From 1f674dcdb4ab57aac6883af3a37d6f45307b73af Mon Sep 17 00:00:00 2001 From: Kumataro Date: Thu, 12 Jun 2025 21:32:28 +0900 Subject: [PATCH 07/62] Merge pull request #27416 from Kumataro:fix27413 Close https://github.com/opencv/opencv/issues/27413 ### Pull Request Readiness Checklist See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [x] The PR is proposed to the proper branch - [x] There is a reference to the original bug report and related work - [ ] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [ ] The feature is well documented and sample code can be built with the project CMake --- cmake/checks/cpu_neon.cpp | 2 +- cmake/checks/cpu_neon_bf16.cpp | 2 +- cmake/checks/cpu_neon_dotprod.cpp | 2 +- cmake/checks/cpu_neon_fp16.cpp | 2 +- modules/core/include/opencv2/core/cv_cpu_dispatch.h | 6 +++--- modules/core/include/opencv2/core/cvdef.h | 2 +- modules/core/include/opencv2/core/fast_math.hpp | 2 +- modules/core/include/opencv2/core/hal/intrin_neon.hpp | 8 ++++---- modules/core/src/matmul.simd.hpp | 2 +- modules/core/src/parallel.cpp | 2 +- modules/core/src/system.cpp | 2 +- modules/dnn/src/op_inf_engine.cpp | 2 +- modules/flann/include/opencv2/flann/dist.h | 2 +- modules/highgui/src/window_w32.cpp | 2 +- modules/ts/include/opencv2/ts/ts_ext.hpp | 2 +- 15 files changed, 20 insertions(+), 20 deletions(-) diff --git a/cmake/checks/cpu_neon.cpp b/cmake/checks/cpu_neon.cpp index 7af16f5ffc..30c144cdf8 100644 --- a/cmake/checks/cpu_neon.cpp +++ b/cmake/checks/cpu_neon.cpp @@ -1,6 +1,6 @@ #include -#if defined _WIN32 && (defined(_M_ARM) || defined(_M_ARM64)) +#if defined _WIN32 && (defined(_M_ARM) || defined(_M_ARM64) || defined(_M_ARM64EC)) # define _ARM64_DISTINCT_NEON_TYPES # include # include diff --git a/cmake/checks/cpu_neon_bf16.cpp b/cmake/checks/cpu_neon_bf16.cpp index a9045d7117..5aaf8f051f 100644 --- a/cmake/checks/cpu_neon_bf16.cpp +++ b/cmake/checks/cpu_neon_bf16.cpp @@ -1,4 +1,4 @@ -#if (defined __GNUC__ && (defined __arm__ || defined __aarch64__)) || (defined _MSC_VER && defined _M_ARM64) +#if (defined __GNUC__ && (defined __arm__ || defined __aarch64__)) || (defined _MSC_VER && (defined _M_ARM64 || defined _M_ARM64EC)) #include #include "arm_neon.h" diff --git a/cmake/checks/cpu_neon_dotprod.cpp b/cmake/checks/cpu_neon_dotprod.cpp index 74f44a1832..2db6c4b1cc 100644 --- a/cmake/checks/cpu_neon_dotprod.cpp +++ b/cmake/checks/cpu_neon_dotprod.cpp @@ -1,6 +1,6 @@ #include -#if (defined __GNUC__ && (defined __arm__ || defined __aarch64__)) || (defined _MSC_VER && defined _M_ARM64) +#if (defined __GNUC__ && (defined __arm__ || defined __aarch64__)) || (defined _MSC_VER && (defined _M_ARM64 || defined _M_ARM64EC)) #include "arm_neon.h" int test() { diff --git a/cmake/checks/cpu_neon_fp16.cpp b/cmake/checks/cpu_neon_fp16.cpp index bba5b97026..45f5b05906 100644 --- a/cmake/checks/cpu_neon_fp16.cpp +++ b/cmake/checks/cpu_neon_fp16.cpp @@ -1,6 +1,6 @@ #include -#if (defined __GNUC__ && (defined __arm__ || defined __aarch64__)) || (defined _MSC_VER && defined _M_ARM64) +#if (defined __GNUC__ && (defined __arm__ || defined __aarch64__)) || (defined _MSC_VER && (defined _M_ARM64 || defined _M_ARM64EC)) #include "arm_neon.h" float16x8_t vld1q_as_f16(const float* src) diff --git a/modules/core/include/opencv2/core/cv_cpu_dispatch.h b/modules/core/include/opencv2/core/cv_cpu_dispatch.h index 607f286615..b920ba349e 100644 --- a/modules/core/include/opencv2/core/cv_cpu_dispatch.h +++ b/modules/core/include/opencv2/core/cv_cpu_dispatch.h @@ -72,7 +72,7 @@ # define CV_AVX 1 #endif #ifdef CV_CPU_COMPILE_FP16 -# if defined(__arm__) || defined(__aarch64__) || defined(_M_ARM) || defined(_M_ARM64) +# if defined(__arm__) || defined(__aarch64__) || defined(_M_ARM) || defined(_M_ARM64) || defined(_M_ARM64EC) # include # else # include @@ -137,7 +137,7 @@ # define CV_FMA3 1 #endif -#if defined _WIN32 && (defined(_M_ARM) || defined(_M_ARM64)) && (defined(CV_CPU_COMPILE_NEON) || !defined(_MSC_VER)) +#if defined _WIN32 && (defined(_M_ARM) || defined(_M_ARM64) || defined(_M_ARM64EC)) && (defined(CV_CPU_COMPILE_NEON) || !defined(_MSC_VER)) # include # include # define CV_NEON 1 @@ -230,7 +230,7 @@ struct VZeroUpperGuard { # define CV_MMX 1 # define CV_SSE 1 # define CV_SSE2 1 -#elif defined _WIN32 && (defined(_M_ARM) || defined(_M_ARM64)) && (defined(CV_CPU_COMPILE_NEON) || !defined(_MSC_VER)) +#elif defined _WIN32 && (defined(_M_ARM) || defined(_M_ARM64) || defined(_M_ARM64EC)) && (defined(CV_CPU_COMPILE_NEON) || !defined(_MSC_VER)) # include # include # define CV_NEON 1 diff --git a/modules/core/include/opencv2/core/cvdef.h b/modules/core/include/opencv2/core/cvdef.h index 0e6d6ff49b..96445a3abb 100644 --- a/modules/core/include/opencv2/core/cvdef.h +++ b/modules/core/include/opencv2/core/cvdef.h @@ -368,7 +368,7 @@ enum CpuFeatures { #include "cv_cpu_dispatch.h" -#if !defined(CV_STRONG_ALIGNMENT) && defined(__arm__) && !(defined(__aarch64__) || defined(_M_ARM64)) +#if !defined(CV_STRONG_ALIGNMENT) && defined(__arm__) && !(defined(__aarch64__) || defined(_M_ARM64) || defined(_M_ARM64EC)) // int*, int64* should be propertly aligned pointers on ARMv7 #define CV_STRONG_ALIGNMENT 1 #endif diff --git a/modules/core/include/opencv2/core/fast_math.hpp b/modules/core/include/opencv2/core/fast_math.hpp index a28c3fbedf..6f0ad67bc4 100644 --- a/modules/core/include/opencv2/core/fast_math.hpp +++ b/modules/core/include/opencv2/core/fast_math.hpp @@ -303,7 +303,7 @@ CV_INLINE int cvIsInf( double value ) { #if defined CV_INLINE_ISINF_DBL CV_INLINE_ISINF_DBL(value); -#elif defined(__x86_64__) || defined(_M_X64) || defined(__aarch64__) || defined(_M_ARM64) || defined(__PPC64__) || defined(__loongarch64) +#elif defined(__x86_64__) || defined(_M_X64) || defined(__aarch64__) || defined(_M_ARM64) || defined(_M_ARM64EC) || defined(__PPC64__) || defined(__loongarch64) Cv64suf ieee754; ieee754.f = value; return (ieee754.u & 0x7fffffffffffffff) == diff --git a/modules/core/include/opencv2/core/hal/intrin_neon.hpp b/modules/core/include/opencv2/core/hal/intrin_neon.hpp index 64fb7d73bc..3c1630148b 100644 --- a/modules/core/include/opencv2/core/hal/intrin_neon.hpp +++ b/modules/core/include/opencv2/core/hal/intrin_neon.hpp @@ -56,7 +56,7 @@ namespace cv CV_CPU_OPTIMIZATION_HAL_NAMESPACE_BEGIN #define CV_SIMD128 1 -#if defined(__aarch64__) || defined(_M_ARM64) +#if defined(__aarch64__) || defined(_M_ARM64) || defined(_M_ARM64EC) #define CV_SIMD128_64F 1 #else #define CV_SIMD128_64F 0 @@ -72,7 +72,7 @@ CV_CPU_OPTIMIZATION_HAL_NAMESPACE_BEGIN // // [1] https://developer.arm.com/documentation/101028/0012/13--Advanced-SIMD--Neon--intrinsics // [2] https://docs.microsoft.com/en-us/cpp/preprocessor/predefined-macros -#if defined(__ARM_64BIT_STATE) || defined(_M_ARM64) +#if defined(__ARM_64BIT_STATE) || defined(_M_ARM64) || defined(_M_ARM64EC) #define CV_NEON_AARCH64 1 #else #define CV_NEON_AARCH64 0 @@ -1080,7 +1080,7 @@ OPENCV_HAL_IMPL_NEON_INT_CMP_OP(v_int16x8, vreinterpretq_s16_u16, s16, u16) OPENCV_HAL_IMPL_NEON_INT_CMP_OP(v_uint32x4, OPENCV_HAL_NOP, u32, u32) OPENCV_HAL_IMPL_NEON_INT_CMP_OP(v_int32x4, vreinterpretq_s32_u32, s32, u32) OPENCV_HAL_IMPL_NEON_INT_CMP_OP(v_float32x4, vreinterpretq_f32_u32, f32, u32) -#if defined(__aarch64__) || defined(_M_ARM64) +#if defined(__aarch64__) || defined(_M_ARM64) || defined(_M_ARM64EC) static inline uint64x2_t vmvnq_u64(uint64x2_t a) { uint64x2_t vx = vreinterpretq_u64_u32(vdupq_n_u32(0xFFFFFFFF)); @@ -1822,7 +1822,7 @@ inline v_int32x4 v_load_expand_q(const schar* ptr) return v_int32x4(vmovl_s16(v1)); } -#if defined(__aarch64__) || defined(_M_ARM64) +#if defined(__aarch64__) || defined(_M_ARM64) || defined(_M_ARM64EC) #define OPENCV_HAL_IMPL_NEON_UNPACKS(_Tpvec, suffix) \ inline void v_zip(const v_##_Tpvec& a0, const v_##_Tpvec& a1, v_##_Tpvec& b0, v_##_Tpvec& b1) \ { \ diff --git a/modules/core/src/matmul.simd.hpp b/modules/core/src/matmul.simd.hpp index 09847d4a4b..f089c90a01 100644 --- a/modules/core/src/matmul.simd.hpp +++ b/modules/core/src/matmul.simd.hpp @@ -1598,7 +1598,7 @@ transform_32f( const float* src, float* dst, const float* m, int len, int scn, i // Disabled for RISC-V Vector (scalable), because of: // 1. v_matmuladd for RVV is 128-bit only but not scalable, this will fail the test `Core_Transform.accuracy`. // 2. Both gcc and clang can autovectorize this, with better performance than using Universal intrinsic. -#if (CV_SIMD || CV_SIMD_SCALABLE) && !defined(__aarch64__) && !defined(_M_ARM64) && !(CV_TRY_RVV && CV_RVV) +#if (CV_SIMD || CV_SIMD_SCALABLE) && !defined(__aarch64__) && !defined(_M_ARM64) && !defined(_M_ARM64EC) && !(CV_TRY_RVV && CV_RVV) int x = 0; if( scn == 3 && dcn == 3 ) { diff --git a/modules/core/src/parallel.cpp b/modules/core/src/parallel.cpp index 5799d73599..09c86e94d2 100644 --- a/modules/core/src/parallel.cpp +++ b/modules/core/src/parallel.cpp @@ -947,7 +947,7 @@ int getNumberOfCPUs_() #if defined _WIN32 SYSTEM_INFO sysinfo = {}; -#if (defined(_M_ARM) || defined(_M_ARM64) || defined(_M_X64) || defined(WINRT)) && _WIN32_WINNT >= 0x501 +#if (defined(_M_ARM) || defined(_M_ARM64) || defined(_M_ARM64EC) || defined(_M_X64) || defined(WINRT)) && _WIN32_WINNT >= 0x501 GetNativeSystemInfo( &sysinfo ); #else GetSystemInfo( &sysinfo ); diff --git a/modules/core/src/system.cpp b/modules/core/src/system.cpp index 98f26d5118..beb552ea74 100644 --- a/modules/core/src/system.cpp +++ b/modules/core/src/system.cpp @@ -676,7 +676,7 @@ struct HWFeatures #if defined _ARM_ && (defined(_WIN32_WCE) && _WIN32_WCE >= 0x800) have[CV_CPU_NEON] = true; #endif - #if defined _M_ARM64 + #if defined _M_ARM64 || defined _M_ARM64EC have[CV_CPU_NEON] = true; #endif #ifdef __riscv_vector diff --git a/modules/dnn/src/op_inf_engine.cpp b/modules/dnn/src/op_inf_engine.cpp index b4707434c1..c95075c855 100644 --- a/modules/dnn/src/op_inf_engine.cpp +++ b/modules/dnn/src/op_inf_engine.cpp @@ -365,7 +365,7 @@ cv::String getInferenceEngineCPUType() { auto& networkBackend = dnn_backend::createPluginDNNNetworkBackend("openvino"); CV_UNUSED(networkBackend); -#if defined(__arm__) || defined(__aarch64__) || defined(_M_ARM64) +#if defined(__arm__) || defined(__aarch64__) || defined(_M_ARM64) || defined(_M_ARM64EC) return CV_DNN_INFERENCE_ENGINE_CPU_TYPE_ARM_COMPUTE; #else return CV_DNN_INFERENCE_ENGINE_CPU_TYPE_X86; diff --git a/modules/flann/include/opencv2/flann/dist.h b/modules/flann/include/opencv2/flann/dist.h index 3029ebb5ef..b0c0ec820f 100644 --- a/modules/flann/include/opencv2/flann/dist.h +++ b/modules/flann/include/opencv2/flann/dist.h @@ -45,7 +45,7 @@ typedef unsigned __int64 uint64_t; #include "defines.h" -#if defined _WIN32 && (defined(_M_ARM) || defined(_M_ARM64)) +#if defined _WIN32 && (defined(_M_ARM) || defined(_M_ARM64) || defined(_M_ARM64EC)) # include #endif diff --git a/modules/highgui/src/window_w32.cpp b/modules/highgui/src/window_w32.cpp index 4423ea5311..b1f6d0c884 100644 --- a/modules/highgui/src/window_w32.cpp +++ b/modules/highgui/src/window_w32.cpp @@ -78,7 +78,7 @@ using namespace cv; static const char* trackbar_text = " "; -#if defined _M_X64 || defined __x86_64 || defined _M_ARM64 +#if defined _M_X64 || defined __x86_64 || defined _M_ARM64 || defined _M_ARM64EC #define icvGetWindowLongPtr GetWindowLongPtr #define icvSetWindowLongPtr(hwnd, id, ptr) SetWindowLongPtr(hwnd, id, (LONG_PTR)(ptr)) diff --git a/modules/ts/include/opencv2/ts/ts_ext.hpp b/modules/ts/include/opencv2/ts/ts_ext.hpp index 66e12d77d6..f6145a0993 100644 --- a/modules/ts/include/opencv2/ts/ts_ext.hpp +++ b/modules/ts/include/opencv2/ts/ts_ext.hpp @@ -128,7 +128,7 @@ struct SkipThisTest : public ::testing::Test { } \ // Special type of tests which require / use or validate processing of huge amount of data (>= 2Gb) -#if defined(_M_X64) || defined(_M_ARM64) || defined(__x86_64__) || defined(__aarch64__) +#if defined(_M_X64) || defined(_M_ARM64) || defined(_M_ARM64EC) || defined(__x86_64__) || defined(__aarch64__) #define BIGDATA_TEST(test_case_name, test_name) TEST_(BigData_ ## test_case_name, test_name, ::testing::Test, Body,, CV__TEST_BIGDATA_BODY_IMPL) #else #define BIGDATA_TEST(test_case_name, test_name) TEST_(BigData_ ## test_case_name, DISABLED_ ## test_name, ::testing::Test, Body,, CV__TEST_BIGDATA_BODY_IMPL) From c5ef3948cfcc1ec5c83f2062585040cb155cd0c9 Mon Sep 17 00:00:00 2001 From: kilavvy <140459108+kilavvy@users.noreply.github.com> Date: Thu, 12 Jun 2025 17:03:07 +0200 Subject: [PATCH 08/62] Update CMakeLists.txt --- samples/sycl/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/sycl/CMakeLists.txt b/samples/sycl/CMakeLists.txt index 3a3483bd21..7b19f81f0e 100644 --- a/samples/sycl/CMakeLists.txt +++ b/samples/sycl/CMakeLists.txt @@ -30,7 +30,7 @@ if(NOT SYCL_FOUND AND NOT OPENCV_SKIP_SAMPLES_SYCL_ONEDNN) if(NOT DEFINED DNNLROOT AND DEFINED ENV{DNNLROOT}) set(DNNLROOT "$ENV{DNNLROOT}") endif() - # Some verions of called script violate CMake policy and may emit unrecoverable CMake errors + # Some versions of called script violate CMake policy and may emit unrecoverable CMake errors # Use OPENCV_SKIP_SAMPLES_SYCL=1 / OPENCV_SKIP_SAMPLES_SYCL_ONEDNN to bypass this find_package(dnnl CONFIG QUIET HINTS "${DNNLROOT}") endif() From 664230091e065d0b5db6b05230d02da74adc9d1c Mon Sep 17 00:00:00 2001 From: "fuder.eth" <139509124+vtjl10@users.noreply.github.com> Date: Mon, 16 Jun 2025 09:28:07 +0300 Subject: [PATCH 09/62] Merge pull request #27444 from vtjl10:4.x Fix Typos and Improve Clarity in Documentation #27444 Description: This pull request addresses minor typographical errors and improves the clarity of documentation in several markdown files. Specifically: - Corrected spelling mistakes such as "traslation" to "translation". - Improved phrasing for better readability and understanding. - Added references to specific setter methods in the StereoBM documentation for more detailed guidance. These changes are limited to documentation and do not affect any code functionality. --- doc/py_tutorials/py_calib3d/py_depthmap/py_depthmap.markdown | 2 +- .../gpu/gpu-thrust-interop/gpu_thrust_interop.markdown | 2 +- doc/tutorials/objdetect/aruco_faq/aruco_faq.markdown | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/py_tutorials/py_calib3d/py_depthmap/py_depthmap.markdown b/doc/py_tutorials/py_calib3d/py_depthmap/py_depthmap.markdown index d904c640c3..bcbdbf40bf 100644 --- a/doc/py_tutorials/py_calib3d/py_depthmap/py_depthmap.markdown +++ b/doc/py_tutorials/py_calib3d/py_depthmap/py_depthmap.markdown @@ -63,7 +63,7 @@ There are some parameters when you get familiar with StereoBM, and you may need - uniqueness_ratio: Another post-filtering step. If the best matching disparity is not sufficiently better than every other disparity in the search range, the pixel is filtered out. You can try tweaking this if texture_threshold and the speckle filtering are still letting through spurious matches. - prefilter_size and prefilter_cap: The pre-filtering phase, which normalizes image brightness and enhances texture in preparation for block matching. Normally you should not need to adjust these. -These parameters are set with dedicated setters and getters after the algoritm +These parameters are set with dedicated setters and getters after the algorithm initialization, such as `setTextureThreshold`, `setSpeckleRange`, `setUniquenessRatio`, and more. See cv::StereoBM documentation for details. diff --git a/doc/tutorials/gpu/gpu-thrust-interop/gpu_thrust_interop.markdown b/doc/tutorials/gpu/gpu-thrust-interop/gpu_thrust_interop.markdown index b5d79ab0bc..3517723035 100644 --- a/doc/tutorials/gpu/gpu-thrust-interop/gpu_thrust_interop.markdown +++ b/doc/tutorials/gpu/gpu-thrust-interop/gpu_thrust_interop.markdown @@ -10,7 +10,7 @@ Goal ---- Thrust is an extremely powerful library for various cuda accelerated algorithms. However thrust is designed -to work with vectors and not pitched matricies. The following tutorial will discuss wrapping cv::cuda::GpuMat's +to work with vectors and not pitched matrices. The following tutorial will discuss wrapping cv::cuda::GpuMat's into thrust iterators that can be used with thrust algorithms. This tutorial should show you how to: diff --git a/doc/tutorials/objdetect/aruco_faq/aruco_faq.markdown b/doc/tutorials/objdetect/aruco_faq/aruco_faq.markdown index 0db99be835..11338369c8 100644 --- a/doc/tutorials/objdetect/aruco_faq/aruco_faq.markdown +++ b/doc/tutorials/objdetect/aruco_faq/aruco_faq.markdown @@ -165,7 +165,7 @@ for `cv::aruco::Dictionary`. The data member of board classes are public and can - Alright, but how can I render a 3d model to create an augmented reality application? To do so, you will need to use an external rendering engine library, such as OpenGL. The aruco module -only provides the functionality to obtain the camera pose, i.e. the rotation and traslation vectors, +only provides the functionality to obtain the camera pose, i.e. the rotation and translation vectors, which is necessary to create the augmented reality effect. However, you will need to adapt the rotation and traslation vectors from the OpenCV format to the format accepted by your 3d rendering library. The original ArUco library contains examples of how to do it for OpenGL and Ogre3D. From c0fdb1145eccc5662847ef6059ca3451ac55b312 Mon Sep 17 00:00:00 2001 From: Alexander Smorkalov Date: Mon, 16 Jun 2025 09:55:08 +0300 Subject: [PATCH 10/62] KleidiCV update to version 0.5 --- hal/kleidicv/kleidicv.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hal/kleidicv/kleidicv.cmake b/hal/kleidicv/kleidicv.cmake index 2ea071dade..ebd6ea3c61 100644 --- a/hal/kleidicv/kleidicv.cmake +++ b/hal/kleidicv/kleidicv.cmake @@ -1,8 +1,8 @@ function(download_kleidicv root_var) set(${root_var} "" PARENT_SCOPE) - ocv_update(KLEIDICV_SRC_COMMIT "0.3.0") - ocv_update(KLEIDICV_SRC_HASH "51a77b0185c2bac2a968a2163869b1ed") + ocv_update(KLEIDICV_SRC_COMMIT "0.5.0") + ocv_update(KLEIDICV_SRC_HASH "ba5648f8df678548f337d19d8ac607d6") set(THE_ROOT "${OpenCV_BINARY_DIR}/3rdparty/kleidicv") ocv_download(FILENAME "kleidicv-${KLEIDICV_SRC_COMMIT}.tar.gz" From f09de671be7aa1496ba3566e814f0d82aeed416a Mon Sep 17 00:00:00 2001 From: Maxim Evtush <154841002+maximevtush@users.noreply.github.com> Date: Mon, 16 Jun 2025 12:09:29 +0300 Subject: [PATCH 11/62] Update person_reid.py --- samples/dnn/person_reid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/dnn/person_reid.py b/samples/dnn/person_reid.py index 08f04faa52..8c08d69d33 100644 --- a/samples/dnn/person_reid.py +++ b/samples/dnn/person_reid.py @@ -86,7 +86,7 @@ def extract_feature(img_dir, model_path, batch_size = 32, resize_h = 384, resize def run_net(inputs, model_path, backend=cv.dnn.DNN_BACKEND_OPENCV, target=cv.dnn.DNN_TARGET_CPU): """ - Forword propagation for a batch of images. + Forward propagation for a batch of images. :param inputs: input batch of images :param model_path: path to ReID model :param backend: name of computation backend From 37a3eddd53d3c66056c007e50eb09c0ca4e4cd26 Mon Sep 17 00:00:00 2001 From: sight Date: Tue, 17 Jun 2025 09:59:44 +0000 Subject: [PATCH 12/62] Fix float-conversion warnings in FLANN by using float literals --- modules/flann/include/opencv2/flann/autotuned_index.h | 2 +- modules/flann/include/opencv2/flann/composite_index.h | 2 +- modules/flann/include/opencv2/flann/kmeans_index.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/flann/include/opencv2/flann/autotuned_index.h b/modules/flann/include/opencv2/flann/autotuned_index.h index d90f739aff..0b02f5670a 100644 --- a/modules/flann/include/opencv2/flann/autotuned_index.h +++ b/modules/flann/include/opencv2/flann/autotuned_index.h @@ -54,7 +54,7 @@ NNIndex* create_index_by_type(const Matrix Date: Tue, 17 Jun 2025 17:11:19 +0300 Subject: [PATCH 13/62] Merge pull request #27437 from mlourakis:4.x Fixed bugs in orthogonalization; simplified column vectors copying #27437 This PR mirrors to OpenCV a bug fix addressed by commit [a03d34b](https://github.com/terzakig/sqpnp/commit/a03d34b641ebba2986cf457cd910218cc8d3cc8c) in SQPnP It also fixes bugs in the orthogonalization introduced during the porting to OpenCV and simplifies column vectors copying, eliminating double loops. ### Pull Request Readiness Checklist See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [x] The PR is proposed to the proper branch - [ ] There is a reference to the original bug report and related work - [ ] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [x] The feature is well documented and sample code can be built with the project CMake --- modules/calib3d/src/sqpnp.cpp | 63 +++++++++++++++-------------------- 1 file changed, 26 insertions(+), 37 deletions(-) diff --git a/modules/calib3d/src/sqpnp.cpp b/modules/calib3d/src/sqpnp.cpp index ef2a047d80..f03da22f1e 100644 --- a/modules/calib3d/src/sqpnp.cpp +++ b/modules/calib3d/src/sqpnp.cpp @@ -65,21 +65,6 @@ const double PoseSolver::POINT_VARIANCE_THRESHOLD = 1e-5; const double PoseSolver::SQRT3 = std::sqrt(3); const int PoseSolver::SQP_MAX_ITERATION = 15; -// No checking done here for overflow, since this is not public all call instances -// are assumed to be valid -template - void set(int row, int col, cv::Matx& dest, - const cv::Matx& source) -{ - for (int y = 0; y < snrows; y++) - { - for (int x = 0; x < sncols; x++) - { - dest(row + y, col + x) = source(y, x); - } - } -} PoseSolver::PoseSolver() : num_null_vectors_(-1), @@ -280,7 +265,7 @@ void PoseSolver::computeOmega(InputArray objectPoints, InputArray imagePoints) // EVD equivalent of the SVD; less accurate cv::eigen(omega_, s_, u_); u_ = u_.t(); // eigenvectors were returned as rows -#endif +#endif // EVD #endif // HAVE_EIGEN @@ -601,9 +586,10 @@ void PoseSolver::computeRowAndNullspace(const cv::Matx& r, H(5, 4) = r(8) - dot_j5q2 * H(5, 1) - dot_j5q4 * H(5, 3); H(6, 4) = r(3) - dot_j5q3 * H(6, 2); H(7, 4) = r(4) - dot_j5q3 * H(7, 2); H(8, 4) = r(5) - dot_j5q3 * H(8, 2); - Matx q4 = H.col(4); - q4 *= (1.0 / cv::norm(q4)); - set(0, 4, H, q4); + cv::Matx q4 = H.col(4); + const double inv_norm_q4 = 1.0 / cv::norm(q4); + for (int i = 0; i < 9; ++i) + H(i, 4) = q4(i) * inv_norm_q4; K(4, 0) = 0; K(4, 1) = r(6) * H(3, 1) + r(7) * H(4, 1) + r(8) * H(5, 1); @@ -630,9 +616,10 @@ void PoseSolver::computeRowAndNullspace(const cv::Matx& r, H(7, 5) = r(1) - dot_j6q3 * H(7, 2) - dot_j6q5 * H(7, 4); H(8, 5) = r(2) - dot_j6q3 * H(8, 2) - dot_j6q5 * H(8, 4); - Matx q5 = H.col(5); - q5 *= (1.0 / cv::norm(q5)); - set(0, 5, H, q5); + cv::Matx q5 = H.col(5); + const double inv_norm_q5 = 1.0 / cv::norm(q5); + for (int i = 0; i < 9; ++i) + H(i, 5) = q5(i) * inv_norm_q5; K(5, 0) = r(6) * H(0, 0) + r(7) * H(1, 0) + r(8) * H(2, 0); K(5, 1) = 0; K(5, 2) = r(0) * H(6, 2) + r(1) * H(7, 2) + r(2) * H(8, 2); @@ -670,9 +657,10 @@ void PoseSolver::computeRowAndNullspace(const cv::Matx& r, } } - Matx v1 = Pn.col(index1); - v1 /= max_norm1; - set(0, 0, N, v1); + const cv::Matx& v1 = Pn.col(index1); + const double inv_max_norm1 = 1.0 / max_norm1; + for (int i = 0; i < 9; ++i) + N(i, 0) = v1(i) * inv_max_norm1; col_norms[index1] = -1.0; // mark to avoid use in subsequent loops for (int i = 0; i < 9; i++) @@ -690,11 +678,12 @@ void PoseSolver::computeRowAndNullspace(const cv::Matx& r, } } - Matx v2 = Pn.col(index2); - Matx n0 = N.col(0); - v2 -= v2.dot(n0) * n0; - v2 *= (1.0 / cv::norm(v2)); - set(0, 1, N, v2); + const cv::Matx& v2 = Pn.col(index2); + const cv::Matx& n0 = N.col(0); + cv::Matx u2 = v2 - v2.dot(n0) * n0; + const double inv_norm_u2 = 1.0 / cv::norm(u2); + for (int i = 0; i < 9; ++i) + N(i, 1) = u2(i) * inv_norm_u2; col_norms[index2] = -1.0; // mark to avoid use in subsequent loops for (int i = 0; i < 9; i++) @@ -709,17 +698,17 @@ void PoseSolver::computeRowAndNullspace(const cv::Matx& r, if (cos_v1_x_col + cos_v2_x_col <= min_dot1323) { index3 = i; - min_dot1323 = cos_v2_x_col + cos_v2_x_col; + min_dot1323 = cos_v1_x_col + cos_v2_x_col; } } } - Matx v3 = Pn.col(index3); - Matx n1 = N.col(1); - v3 -= (v3.dot(n1)) * n1 - (v3.dot(n0)) * n0; - v3 *= (1.0 / cv::norm(v3)); - set(0, 2, N, v3); - + const cv::Matx& v3 = Pn.col(index3); + const cv::Matx& n1 = N.col(1); + cv::Matx u3 = v3 - (v3.dot(n1) * n1) - (v3.dot(n0) * n0); + const double inv_norm_u3 = 1.0 / cv::norm(u3); + for (int i = 0; i < 9; ++i) + N(i, 2) = u3(i) * inv_norm_u3; } // if e = u*w*vt then r=u*diag([1, 1, det(u)*det(v)])*vt From bbaed6f377e7ed118b977ff178029403a6e30eea Mon Sep 17 00:00:00 2001 From: leopardracer <136604165+leopardracer@users.noreply.github.com> Date: Wed, 18 Jun 2025 15:28:25 +0300 Subject: [PATCH 14/62] Merge pull request #27455 from leopardracer:4.x Fix Typos in Comments and Documentation #27455 Description: This pull request corrects minor typos in comments and documentation within the codebase: - Replaces "representitive" with "representative" in kmeans.cpp. - Replaces "indices" with the correct spelling in a comment in main.cu. --- samples/cpp/kmeans.cpp | 2 +- samples/cpp/tutorial_code/gpu/gpu-thrust-interop/main.cu | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/cpp/kmeans.cpp b/samples/cpp/kmeans.cpp index 0a2663ac7c..6c4a33b2e9 100644 --- a/samples/cpp/kmeans.cpp +++ b/samples/cpp/kmeans.cpp @@ -10,7 +10,7 @@ using namespace std; // { // cout << "\nThis program demonstrates kmeans clustering.\n" // "It generates an image with random points, then assigns a random number of cluster\n" -// "centers and uses kmeans to move those cluster centers to their representitive location\n" +// "centers and uses kmeans to move those cluster centers to their representative location\n" // "Call\n" // "./kmeans\n" << endl; // } diff --git a/samples/cpp/tutorial_code/gpu/gpu-thrust-interop/main.cu b/samples/cpp/tutorial_code/gpu/gpu-thrust-interop/main.cu index 51f246a37d..e9803994f8 100644 --- a/samples/cpp/tutorial_code/gpu/gpu-thrust-interop/main.cu +++ b/samples/cpp/tutorial_code/gpu/gpu-thrust-interop/main.cu @@ -55,7 +55,7 @@ int main(void) thrust::sequence(idxBegin, idxEnd); // Fill the key channel with random numbers between 0 and 10. A counting iterator is used here to give an integer value for each location as an input to prg::operator() thrust::transform(thrust::make_counting_iterator(0), thrust::make_counting_iterator(d_data.cols), keyBegin, prg(0, 10)); - // Sort the key channel and index channel such that the keys and indecies stay together + // Sort the key channel and index channel such that the keys and indices stay together thrust::sort_by_key(keyBegin, keyEnd, idxBegin); cv::Mat h_idx(d_data); From 210203090e4f42af96b74616cb023839be9cf363 Mon Sep 17 00:00:00 2001 From: eplankin Date: Thu, 19 Jun 2025 07:39:28 +0200 Subject: [PATCH 15/62] Merge pull request #27454 from eplankin:norm Added define disabling ippiNorm_Inf_16u_C1MR #27454 Workaround for #27380. Fix will be available in the next ICV package update based on IPP 2022.2.0. --- hal/ipp/include/ipp_hal_core.hpp | 6 ++++++ hal/ipp/src/norm_ipp.cpp | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/hal/ipp/include/ipp_hal_core.hpp b/hal/ipp/include/ipp_hal_core.hpp index 65f388b859..7b71c8d28a 100644 --- a/hal/ipp/include/ipp_hal_core.hpp +++ b/hal/ipp/include/ipp_hal_core.hpp @@ -27,6 +27,12 @@ int ipp_hal_minMaxIdxMaskStep(const uchar* src_data, size_t src_step, int width, # define IPP_DISABLE_NORM_8U 0 #endif +#if (IPP_VERSION_X100 >= 202200 && IPP_VERSION_X100 < 202220) +# define IPP_DISABLE_NORM_INF_16U_C1MR 1 // segmentation fault in accuracy test +# else +# define IPP_DISABLE_NORM_INF_16U_C1MR 0 +#endif + int ipp_hal_norm(const uchar* src, size_t src_step, const uchar* mask, size_t mask_step, int width, int height, int type, int norm_type, double* result); diff --git a/hal/ipp/src/norm_ipp.cpp b/hal/ipp/src/norm_ipp.cpp index 95c428ac8a..313b98ca4d 100644 --- a/hal/ipp/src/norm_ipp.cpp +++ b/hal/ipp/src/norm_ipp.cpp @@ -20,7 +20,9 @@ int ipp_hal_norm(const uchar* src, size_t src_step, const uchar* mask, size_t ma ippiMaskNormFuncC1 ippiNorm_C1MR = norm_type == cv::NORM_INF ? (type == CV_8UC1 ? (ippiMaskNormFuncC1)ippiNorm_Inf_8u_C1MR : + #if (!IPP_DISABLE_NORM_INF_16U_C1MR) type == CV_16UC1 ? (ippiMaskNormFuncC1)ippiNorm_Inf_16u_C1MR : + #endif type == CV_32FC1 ? (ippiMaskNormFuncC1)ippiNorm_Inf_32f_C1MR : 0) : norm_type == cv::NORM_L1 ? @@ -141,7 +143,9 @@ int ipp_hal_normDiff(const uchar* src1, size_t src1_step, const uchar* src2, siz ippiMaskNormDiffFuncC1 ippiNormRel_C1MR = norm_type == cv::NORM_INF ? (type == CV_8UC1 ? (ippiMaskNormDiffFuncC1)ippiNormRel_Inf_8u_C1MR : + #if (!IPP_DISABLE_NORM_INF_16U_C1MR) type == CV_16UC1 ? (ippiMaskNormDiffFuncC1)ippiNormRel_Inf_16u_C1MR : + #endif type == CV_32FC1 ? (ippiMaskNormDiffFuncC1)ippiNormRel_Inf_32f_C1MR : 0) : norm_type == cv::NORM_L1 ? @@ -230,7 +234,9 @@ int ipp_hal_normDiff(const uchar* src1, size_t src1_step, const uchar* src2, siz ippiMaskNormDiffFuncC1 ippiNormDiff_C1MR = norm_type == cv::NORM_INF ? (type == CV_8UC1 ? (ippiMaskNormDiffFuncC1)ippiNormDiff_Inf_8u_C1MR : + #if (!IPP_DISABLE_NORM_INF_16U_C1MR) type == CV_16UC1 ? (ippiMaskNormDiffFuncC1)ippiNormDiff_Inf_16u_C1MR : + #endif type == CV_32FC1 ? (ippiMaskNormDiffFuncC1)ippiNormDiff_Inf_32f_C1MR : 0) : norm_type == cv::NORM_L1 ? From 1c53fd3777d7ded4bc64b097927a632f2cd1d018 Mon Sep 17 00:00:00 2001 From: Dmitry Kurtaev Date: Thu, 19 Jun 2025 10:12:15 +0300 Subject: [PATCH 16/62] Merge pull request #24426 from dkurt:qrcode_eci_encoding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Consider QRCode ECI encoding #24426 ### Pull Request Readiness Checklist related: https://github.com/opencv/opencv/pull/24350#pullrequestreview-1661658421 1. Add `getEncoding` method to obtain ECI number 2. Add `detectAndDecodeBytes`, `decodeBytes`, `decodeBytesMulti`, `detectAndDecodeBytesMulti` methods in Python (return `bytes`) and Java (return `byte[]`) 3. Allow Python bytes to std::string conversion in general and add `encode(byte[] encoded_info, Mat qrcode)` in Java Python example with Kanji encoding: ```python img = cv.imread("test.png") detect = cv.QRCodeDetector() data, points, straight_qrcode = detect.detectAndDecodeBytes(img) print(data) print(detect.getEncoding(), cv.QRCodeEncoder_ECI_SHIFT_JIS) print(data.decode("shift-jis")) ``` ``` b'\x82\xb1\x82\xf1\x82\xc9\x82\xbf\x82\xcd\x90\xa2\x8aE' 20 20 こんにちは世界 ``` source: https://github.com/opencv/opencv/blob/ba4d6c859d21536f84e0328c16f4cc3e96bf3065/modules/objdetect/test/test_qrcode_encode.cpp#L332 ![test](https://github.com/opencv/opencv/assets/25801568/0b5eefa8-918a-4c42-9acb-830f23c0ea9f) See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [x] The PR is proposed to the proper branch - [x] There is a reference to the original bug report and related work - [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [x] The feature is well documented and sample code can be built with the project CMake --- modules/java/generator/gen_java.py | 3 + .../objdetect/include/opencv2/objdetect.hpp | 10 ++- .../objdetect/graphical_code_detector.hpp | 11 +++ modules/objdetect/misc/java/filelist_common | 1 + modules/objdetect/misc/java/gen_dict.json | 68 +++++++++++++++++++ .../java/src/cpp/objdetect_converters.cpp | 20 ++++++ .../java/src/cpp/objdetect_converters.hpp | 14 ++++ .../misc/java/test/QRCodeDetectorTest.java | 28 ++++++++ .../misc/python/pyopencv_objdetect.hpp | 27 ++++++++ .../misc/python/test/test_qrcode_detect.py | 36 +++++++++- modules/objdetect/src/qrcode.cpp | 27 ++++++-- modules/objdetect/test/test_qrcode_encode.cpp | 4 +- modules/python/src2/pycompat.hpp | 9 +++ .../predefined_types.py | 1 + 14 files changed, 250 insertions(+), 9 deletions(-) create mode 100644 modules/objdetect/misc/java/filelist_common create mode 100644 modules/objdetect/misc/java/gen_dict.json create mode 100644 modules/objdetect/misc/java/src/cpp/objdetect_converters.cpp create mode 100644 modules/objdetect/misc/java/src/cpp/objdetect_converters.hpp diff --git a/modules/java/generator/gen_java.py b/modules/java/generator/gen_java.py index 0ffa5bd6ae..797863249d 100755 --- a/modules/java/generator/gen_java.py +++ b/modules/java/generator/gen_java.py @@ -1002,6 +1002,9 @@ class JavaWrapperGenerator(object): ret = "return (jlong) _retval_;" elif type_dict[fi.ctype]["jni_type"] == "jdoubleArray": ret = "return _da_retval_;" + elif "jni_var" in type_dict[ret_type]: + c_epilogue.append(type_dict[ret_type]["jni_var"] % {"n" : '_retval_'}) + ret = f"return {type_dict[ret_type]['jni_name'] % {'n' : '_retval_'}};" # hack: replacing func call with property set/get name = fi.name diff --git a/modules/objdetect/include/opencv2/objdetect.hpp b/modules/objdetect/include/opencv2/objdetect.hpp index ed0d6f76ac..4a8544b910 100644 --- a/modules/objdetect/include/opencv2/objdetect.hpp +++ b/modules/objdetect/include/opencv2/objdetect.hpp @@ -729,7 +729,8 @@ public: }; enum ECIEncodings { - ECI_UTF8 = 26 + ECI_SHIFT_JIS = 20, + ECI_UTF8 = 26, }; /** @brief QR code encoder parameters. */ @@ -808,6 +809,13 @@ public: */ CV_WRAP std::string detectAndDecodeCurved(InputArray img, OutputArray points=noArray(), OutputArray straight_qrcode = noArray()); + + /** @brief Returns a kind of encoding for the decoded info from the latest @ref decode or @ref detectAndDecode call + @param codeIdx an index of the previously decoded QR code. + When @ref decode or @ref detectAndDecode is used, valid value is zero. + For @ref decodeMulti or @ref detectAndDecodeMulti use indices corresponding to the output order. + */ + CV_WRAP QRCodeEncoder::ECIEncodings getEncoding(int codeIdx = 0); }; class CV_EXPORTS_W_SIMPLE QRCodeDetectorAruco : public GraphicalCodeDetector { diff --git a/modules/objdetect/include/opencv2/objdetect/graphical_code_detector.hpp b/modules/objdetect/include/opencv2/objdetect/graphical_code_detector.hpp index ed697c50c0..adc52379b9 100644 --- a/modules/objdetect/include/opencv2/objdetect/graphical_code_detector.hpp +++ b/modules/objdetect/include/opencv2/objdetect/graphical_code_detector.hpp @@ -73,6 +73,17 @@ public: */ CV_WRAP bool detectAndDecodeMulti(InputArray img, CV_OUT std::vector& decoded_info, OutputArray points = noArray(), OutputArrayOfArrays straight_code = noArray()) const; + +#ifdef OPENCV_BINDINGS_PARSER + CV_WRAP_AS(detectAndDecodeBytes) NativeByteArray detectAndDecode(InputArray img, OutputArray points = noArray(), + OutputArray straight_code = noArray()) const; + CV_WRAP_AS(decodeBytes) NativeByteArray decode(InputArray img, InputArray points, OutputArray straight_code = noArray()) const; + CV_WRAP_AS(decodeBytesMulti) bool decodeMulti(InputArray img, InputArray points, CV_OUT std::vector& decoded_info, + OutputArrayOfArrays straight_code = noArray()) const; + CV_WRAP_AS(detectAndDecodeBytesMulti) bool detectAndDecodeMulti(InputArray img, CV_OUT std::vector& decoded_info, OutputArray points = noArray(), + OutputArrayOfArrays straight_code = noArray()) const; +#endif + struct Impl; protected: Ptr p; diff --git a/modules/objdetect/misc/java/filelist_common b/modules/objdetect/misc/java/filelist_common new file mode 100644 index 0000000000..56da1c5df7 --- /dev/null +++ b/modules/objdetect/misc/java/filelist_common @@ -0,0 +1 @@ +misc/java/src/cpp/objdetect_converters.hpp diff --git a/modules/objdetect/misc/java/gen_dict.json b/modules/objdetect/misc/java/gen_dict.json new file mode 100644 index 0000000000..2f453a6a91 --- /dev/null +++ b/modules/objdetect/misc/java/gen_dict.json @@ -0,0 +1,68 @@ +{ + "ManualFuncs" : { + "QRCodeEncoder" : { + "QRCodeEncoder" : { + "j_code" : [ + "\n", + "/** Generates QR code from input string.", + "@param encoded_info Input bytes to encode.", + "@param qrcode Generated QR code.", + "*/", + "public void encode(byte[] encoded_info, Mat qrcode) {", + " encode_1(nativeObj, encoded_info, qrcode.nativeObj);", + "}", + "\n" + ], + "jn_code": [ + "\n", + "private static native void encode_1(long nativeObj, byte[] encoded_info, long qrcode_nativeObj);", + "\n" + ], + "cpp_code": [ + "//", + "// void cv::QRCodeEncoder::encode(String encoded_info, Mat& qrcode)", + "//", + "\n", + "JNIEXPORT void JNICALL Java_org_opencv_objdetect_QRCodeEncoder_encode_11 (JNIEnv*, jclass, jlong, jbyteArray, jlong);", + "\n", + "JNIEXPORT void JNICALL Java_org_opencv_objdetect_QRCodeEncoder_encode_11", + "(JNIEnv* env, jclass , jlong self, jbyteArray encoded_info, jlong qrcode_nativeObj)", + "{", + "", + " static const char method_name[] = \"objdetect::encode_11()\";", + " try {", + " LOGD(\"%s\", method_name);", + " Ptr* me = (Ptr*) self; //TODO: check for NULL", + " const char* n_encoded_info = reinterpret_cast(env->GetByteArrayElements(encoded_info, NULL));", + " Mat& qrcode = *((Mat*)qrcode_nativeObj);", + " (*me)->encode( n_encoded_info, qrcode );", + " } catch(const std::exception &e) {", + " throwJavaException(env, &e, method_name);", + " } catch (...) {", + " throwJavaException(env, 0, method_name);", + " }", + "}", + "\n" + ] + } + } + }, + "type_dict": { + "NativeByteArray": { + "j_type" : "byte[]", + "jn_type": "byte[]", + "jni_type": "jbyteArray", + "jni_name": "n_%(n)s", + "jni_var": "jbyteArray n_%(n)s = env->NewByteArray(static_cast(%(n)s.size())); env->SetByteArrayRegion(n_%(n)s, 0, static_cast(%(n)s.size()), reinterpret_cast(%(n)s.c_str()));", + "cast_from": "std::string" + }, + "vector_NativeByteArray": { + "j_type": "List", + "jn_type": "List", + "jni_type": "jobject", + "jni_var": "std::vector< std::string > %(n)s", + "suffix": "Ljava_util_List", + "v_type": "vector_NativeByteArray" + } + } +} diff --git a/modules/objdetect/misc/java/src/cpp/objdetect_converters.cpp b/modules/objdetect/misc/java/src/cpp/objdetect_converters.cpp new file mode 100644 index 0000000000..3f9f533769 --- /dev/null +++ b/modules/objdetect/misc/java/src/cpp/objdetect_converters.cpp @@ -0,0 +1,20 @@ +#include "objdetect_converters.hpp" + +#define LOG_TAG "org.opencv.objdetect" + +void Copy_vector_NativeByteArray_to_List(JNIEnv* env, std::vector& vs, jobject list) +{ + static jclass juArrayList = ARRAYLIST(env); + jmethodID m_clear = LIST_CLEAR(env, juArrayList); + jmethodID m_add = LIST_ADD(env, juArrayList); + + env->CallVoidMethod(list, m_clear); + for (std::vector::iterator it = vs.begin(); it != vs.end(); ++it) + { + jsize sz = static_cast((*it).size()); + jbyteArray element = env->NewByteArray(sz); + env->SetByteArrayRegion(element, 0, sz, reinterpret_cast((*it).c_str())); + env->CallBooleanMethod(list, m_add, element); + env->DeleteLocalRef(element); + } +} diff --git a/modules/objdetect/misc/java/src/cpp/objdetect_converters.hpp b/modules/objdetect/misc/java/src/cpp/objdetect_converters.hpp new file mode 100644 index 0000000000..82bb881fad --- /dev/null +++ b/modules/objdetect/misc/java/src/cpp/objdetect_converters.hpp @@ -0,0 +1,14 @@ +// 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 + +#ifndef OBJDETECT_CONVERTERS_HPP +#define OBJDETECT_CONVERTERS_HPP + +#include +#include "opencv_java.hpp" +#include "opencv2/core.hpp" + +void Copy_vector_NativeByteArray_to_List(JNIEnv* env, std::vector& vs, jobject list); + +#endif /* OBJDETECT_CONVERTERS_HPP */ diff --git a/modules/objdetect/misc/java/test/QRCodeDetectorTest.java b/modules/objdetect/misc/java/test/QRCodeDetectorTest.java index af567cbc04..225c8c6610 100644 --- a/modules/objdetect/misc/java/test/QRCodeDetectorTest.java +++ b/modules/objdetect/misc/java/test/QRCodeDetectorTest.java @@ -2,13 +2,19 @@ package org.opencv.test.objdetect; import java.util.List; import org.opencv.core.Mat; +import org.opencv.core.Size; import org.opencv.objdetect.QRCodeDetector; +import org.opencv.objdetect.QRCodeEncoder; +import org.opencv.objdetect.QRCodeEncoder_Params; import org.opencv.imgcodecs.Imgcodecs; +import org.opencv.imgproc.Imgproc; import org.opencv.test.OpenCVTestCase; import java.util.Arrays; import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; public class QRCodeDetectorTest extends OpenCVTestCase { @@ -50,4 +56,26 @@ public class QRCodeDetectorTest extends OpenCVTestCase { List < String > expectedResults = Arrays.asList("SKIP", "EXTRA", "TWO STEPS FORWARD", "STEP BACK", "QUESTION", "STEP FORWARD"); assertEquals(new HashSet(output), new HashSet(expectedResults)); } + + public void testKanji() { + byte[] inp = new byte[]{(byte)0x82, (byte)0xb1, (byte)0x82, (byte)0xf1, (byte)0x82, (byte)0xc9, (byte)0x82, + (byte)0xbf, (byte)0x82, (byte)0xcd, (byte)0x90, (byte)0xa2, (byte)0x8a, (byte)0x45}; + QRCodeEncoder_Params params = new QRCodeEncoder_Params(); + params.set_mode(QRCodeEncoder.MODE_KANJI); + QRCodeEncoder encoder = QRCodeEncoder.create(params); + + Mat qrcode = new Mat(); + encoder.encode(inp, qrcode); + Imgproc.resize(qrcode, qrcode, new Size(0, 0), 2, 2, Imgproc.INTER_NEAREST); + + QRCodeDetector detector = new QRCodeDetector(); + byte[] output = detector.detectAndDecodeBytes(qrcode); + assertEquals(detector.getEncoding(), QRCodeEncoder.ECI_SHIFT_JIS); + assertArrayEquals(inp, output); + + List < byte[] > outputs = new ArrayList< byte[] >(); + assertTrue(detector.detectAndDecodeBytesMulti(qrcode, outputs)); + assertEquals(detector.getEncoding(0), QRCodeEncoder.ECI_SHIFT_JIS); + assertArrayEquals(inp, outputs.get(0)); + } } diff --git a/modules/objdetect/misc/python/pyopencv_objdetect.hpp b/modules/objdetect/misc/python/pyopencv_objdetect.hpp index 95194e1c46..2a03b04d0a 100644 --- a/modules/objdetect/misc/python/pyopencv_objdetect.hpp +++ b/modules/objdetect/misc/python/pyopencv_objdetect.hpp @@ -7,4 +7,31 @@ typedef QRCodeEncoder::Params QRCodeEncoder_Params; typedef HOGDescriptor::HistogramNormType HOGDescriptor_HistogramNormType; typedef HOGDescriptor::DescriptorStorageFormat HOGDescriptor_DescriptorStorageFormat; +class NativeByteArray +{ +public: + inline NativeByteArray& operator=(const std::string& from) { + val = from; + return *this; + } + std::string val; +}; + +class vector_NativeByteArray : public std::vector {}; + +template<> +PyObject* pyopencv_from(const NativeByteArray& from) +{ + return PyBytes_FromStringAndSize(from.val.c_str(), from.val.size()); +} + +template<> +PyObject* pyopencv_from(const vector_NativeByteArray& results) +{ + PyObject* list = PyList_New(results.size()); + for(size_t i = 0; i < results.size(); ++i) + PyList_SetItem(list, i, PyBytes_FromStringAndSize(results[i].c_str(), results[i].size())); + return list; +} + #endif diff --git a/modules/objdetect/misc/python/test/test_qrcode_detect.py b/modules/objdetect/misc/python/test/test_qrcode_detect.py index 0237900572..8da95ccd00 100644 --- a/modules/objdetect/misc/python/test/test_qrcode_detect.py +++ b/modules/objdetect/misc/python/test/test_qrcode_detect.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- #!/usr/bin/env python ''' =============================================================================== @@ -8,7 +9,7 @@ import os import numpy as np import cv2 as cv -from tests_common import NewOpenCVTests +from tests_common import NewOpenCVTests, unittest class qrcode_detector_test(NewOpenCVTests): @@ -50,3 +51,36 @@ class qrcode_detector_test(NewOpenCVTests): self.assertTrue("STEP BACK" in decoded_data) self.assertTrue("QUESTION" in decoded_data) self.assertEqual(points.shape, (6, 4, 2)) + + def test_decode_non_ascii(self): + import sys + if sys.version_info[0] < 3: + raise unittest.SkipTest('Python 2.x is not supported') + + img = cv.imread(os.path.join(self.extraTestDataPath, 'cv/qrcode/umlaut.png')) + self.assertFalse(img is None) + detector = cv.QRCodeDetector() + decoded_data, _, _ = detector.detectAndDecode(img) + self.assertTrue(isinstance(decoded_data, str)) + self.assertTrue("Müllheimstrasse" in decoded_data) + + def test_kanji(self): + inp = "こんにちは世界" + inp_bytes = inp.encode("shift-jis") + + params = cv.QRCodeEncoder_Params() + params.mode = cv.QRCodeEncoder_MODE_KANJI + encoder = cv.QRCodeEncoder_create(params) + qrcode = encoder.encode(inp_bytes) + qrcode = cv.resize(qrcode, (0, 0), fx=2, fy=2, interpolation=cv.INTER_NEAREST) + + detector = cv.QRCodeDetector() + data, _, _ = detector.detectAndDecodeBytes(qrcode) + self.assertEqual(data, inp_bytes) + self.assertEqual(detector.getEncoding(), cv.QRCodeEncoder_ECI_SHIFT_JIS) + self.assertEqual(data.decode("shift-jis"), inp) + + _, data, _, _ = detector.detectAndDecodeBytesMulti(qrcode) + self.assertEqual(data[0], inp_bytes) + self.assertEqual(detector.getEncoding(0), cv.QRCodeEncoder_ECI_SHIFT_JIS) + self.assertEqual(data[0].decode("shift-jis"), inp) diff --git a/modules/objdetect/src/qrcode.cpp b/modules/objdetect/src/qrcode.cpp index ac1e7fcadc..c20385d7b6 100644 --- a/modules/objdetect/src/qrcode.cpp +++ b/modules/objdetect/src/qrcode.cpp @@ -963,6 +963,7 @@ public: double epsX, epsY; mutable vector> alignmentMarkers; mutable vector updateQrCorners; + mutable vector encodings; bool useAlignmentMarkers = true; bool detect(InputArray in, OutputArray points) const override; @@ -978,6 +979,8 @@ public: String decodeCurved(InputArray in, InputArray points, OutputArray straight_qrcode); std::string detectAndDecodeCurved(InputArray in, OutputArray points, OutputArray straight_qrcode); + + QRCodeEncoder::ECIEncodings getEncoding(int codeIdx); }; QRCodeDetector::QRCodeDetector() { @@ -994,6 +997,13 @@ QRCodeDetector& QRCodeDetector::setEpsY(double epsY) { return *this; } +QRCodeEncoder::ECIEncodings QRCodeDetector::getEncoding(int codeIdx) { + auto& encodings = std::dynamic_pointer_cast(p)->encodings; + CV_Assert(codeIdx >= 0); + CV_Assert(codeIdx < static_cast(encodings.size())); + return encodings[codeIdx]; +} + bool ImplContour::detect(InputArray in, OutputArray points) const { Mat inarr; @@ -1035,6 +1045,8 @@ public: uint8_t total_num = 1; } structure_info; + QRCodeEncoder::ECIEncodings eci; + protected: double getNumModules(); Mat getHomography() { @@ -2802,7 +2814,6 @@ static std::string encodeUTF8_bytesarray(const uint8_t* str, const size_t size) bool QRDecode::decodingProcess() { - QRCodeEncoder::ECIEncodings eci; const uint8_t* payload; size_t payload_len; #ifdef HAVE_QUIRC @@ -2895,7 +2906,7 @@ bool QRDecode::decodingProcess() return true; case QRCodeEncoder::EncodeMode::MODE_KANJI: // FIXIT BUG: we must return UTF-8 compatible string - CV_LOG_WARNING(NULL, "QR: Kanji is not supported properly"); + eci = QRCodeEncoder::ECIEncodings::ECI_SHIFT_JIS; result_info.assign((const char*)payload, payload_len); return true; case QRCodeEncoder::EncodeMode::MODE_ECI: @@ -2966,6 +2977,7 @@ std::string ImplContour::decode(InputArray in, InputArray points, OutputArray st alignmentMarkers = {qrdec.alignment_coords}; updateQrCorners = qrdec.getOriginalPoints(); } + encodings.resize(1, qrdec.eci); return ok ? decoded_info : std::string(); } @@ -2999,6 +3011,7 @@ String ImplContour::decodeCurved(InputArray in, InputArray points, OutputArray s { qrdec.getStraightBarcode().convertTo(straight_qrcode, CV_8UC1); } + encodings.resize(1, qrdec.eci); return ok ? decoded_info : std::string(); } @@ -4111,20 +4124,22 @@ bool ImplContour::decodeMulti( straight_qrcode.assign(tmp_straight_qrcodes); } - decoded_info.clear(); + decoded_info.resize(info.size()); + encodings.resize(info.size()); for (size_t i = 0; i < info.size(); i++) { auto& decoder = qrdec[i]; + encodings[i] = decoder.eci; if (!decoder.isStructured()) { - decoded_info.push_back(info[i]); + decoded_info[i] = info[i]; continue; } // Store final message corresponding to 0-th code in a sequence. if (decoder.structure_info.sequence_num != 0) { - decoded_info.push_back(""); + decoded_info[i] = ""; continue; } @@ -4145,7 +4160,7 @@ bool ImplContour::decodeMulti( break; } } - decoded_info.push_back(decoded); + decoded_info[i] = decoded; } alignmentMarkers.resize(src_points.size()); diff --git a/modules/objdetect/test/test_qrcode_encode.cpp b/modules/objdetect/test/test_qrcode_encode.cpp index f6cf1c069f..f90af1d9f9 100644 --- a/modules/objdetect/test/test_qrcode_encode.cpp +++ b/modules/objdetect/test/test_qrcode_encode.cpp @@ -343,9 +343,11 @@ TEST(Objdetect_QRCode_Encode_Kanji, regression) } Mat straight_barcode; - std::string decoded_info = QRCodeDetector().decode(resized_src, corners, straight_barcode); + QRCodeDetector detector; + std::string decoded_info = detector.decode(resized_src, corners, straight_barcode); EXPECT_FALSE(decoded_info.empty()) << "The generated QRcode cannot be decoded."; EXPECT_EQ(input_info, decoded_info); + EXPECT_EQ(detector.getEncoding(), QRCodeEncoder::ECIEncodings::ECI_SHIFT_JIS); } } diff --git a/modules/python/src2/pycompat.hpp b/modules/python/src2/pycompat.hpp index 05a3909562..c936f5e66a 100644 --- a/modules/python/src2/pycompat.hpp +++ b/modules/python/src2/pycompat.hpp @@ -84,6 +84,15 @@ static inline bool getUnicodeString(PyObject * obj, std::string &str) } Py_XDECREF(bytes); } + else if (PyBytes_Check(obj)) + { + const char * raw = PyBytes_AsString(obj); + if (raw) + { + str = std::string(raw); + res = true; + } + } #if PY_MAJOR_VERSION < 3 else if (PyString_Check(obj)) { diff --git a/modules/python/src2/typing_stubs_generation/predefined_types.py b/modules/python/src2/typing_stubs_generation/predefined_types.py index 6879d1a18d..d7ed78b231 100644 --- a/modules/python/src2/typing_stubs_generation/predefined_types.py +++ b/modules/python/src2/typing_stubs_generation/predefined_types.py @@ -265,6 +265,7 @@ _PREDEFINED_TYPES = ( export_name="ExtractMetaCallback", required_modules=("gapi",) ), + PrimitiveTypeNode("NativeByteArray", "bytes"), ) PREDEFINED_TYPES = dict( From 7cb7a6fd20b22605ac721dd3966d6b92cda5ed4d Mon Sep 17 00:00:00 2001 From: Alexander Smorkalov Date: Thu, 19 Jun 2025 11:03:59 +0300 Subject: [PATCH 17/62] pre: OpenCV 4.12.0 (version++). --- .../cross_referencing/tutorial_cross_referencing.markdown | 4 ++-- modules/core/include/opencv2/core/version.hpp | 2 +- modules/dnn/include/opencv2/dnn/version.hpp | 2 +- modules/python/package/setup.py | 3 ++- platforms/maven/opencv-it/pom.xml | 2 +- platforms/maven/opencv/pom.xml | 2 +- platforms/maven/pom.xml | 2 +- 7 files changed, 9 insertions(+), 8 deletions(-) diff --git a/doc/tutorials/introduction/cross_referencing/tutorial_cross_referencing.markdown b/doc/tutorials/introduction/cross_referencing/tutorial_cross_referencing.markdown index af809751f7..009571c895 100644 --- a/doc/tutorials/introduction/cross_referencing/tutorial_cross_referencing.markdown +++ b/doc/tutorials/introduction/cross_referencing/tutorial_cross_referencing.markdown @@ -46,14 +46,14 @@ Open your Doxyfile using your favorite text editor and search for the key `TAGFILES`. Change it as follows: @code -TAGFILES = ./docs/doxygen-tags/opencv.tag=http://docs.opencv.org/4.11.0 +TAGFILES = ./docs/doxygen-tags/opencv.tag=http://docs.opencv.org/4.12.0 @endcode If you had other definitions already, you can append the line using a `\`: @code TAGFILES = ./docs/doxygen-tags/libstdc++.tag=https://gcc.gnu.org/onlinedocs/libstdc++/latest-doxygen \ - ./docs/doxygen-tags/opencv.tag=http://docs.opencv.org/4.11.0 + ./docs/doxygen-tags/opencv.tag=http://docs.opencv.org/4.12.0 @endcode Doxygen can now use the information from the tag file to link to the OpenCV diff --git a/modules/core/include/opencv2/core/version.hpp b/modules/core/include/opencv2/core/version.hpp index f1805acab6..e4c93cbf2f 100644 --- a/modules/core/include/opencv2/core/version.hpp +++ b/modules/core/include/opencv2/core/version.hpp @@ -8,7 +8,7 @@ #define CV_VERSION_MAJOR 4 #define CV_VERSION_MINOR 12 #define CV_VERSION_REVISION 0 -#define CV_VERSION_STATUS "-dev" +#define CV_VERSION_STATUS "-pre" #define CVAUX_STR_EXP(__A) #__A #define CVAUX_STR(__A) CVAUX_STR_EXP(__A) diff --git a/modules/dnn/include/opencv2/dnn/version.hpp b/modules/dnn/include/opencv2/dnn/version.hpp index 5cf0870b44..8aa3177deb 100644 --- a/modules/dnn/include/opencv2/dnn/version.hpp +++ b/modules/dnn/include/opencv2/dnn/version.hpp @@ -6,7 +6,7 @@ #define OPENCV_DNN_VERSION_HPP /// Use with major OpenCV version only. -#define OPENCV_DNN_API_VERSION 20241223 +#define OPENCV_DNN_API_VERSION 20250619 #if !defined CV_DOXYGEN && !defined CV_STATIC_ANALYSIS && !defined CV_DNN_DONT_ADD_INLINE_NS #define CV__DNN_INLINE_NS __CV_CAT(dnn4_v, OPENCV_DNN_API_VERSION) diff --git a/modules/python/package/setup.py b/modules/python/package/setup.py index 76702ce1ca..72559bde96 100644 --- a/modules/python/package/setup.py +++ b/modules/python/package/setup.py @@ -19,7 +19,7 @@ def main(): os.chdir(SCRIPT_DIR) package_name = 'opencv' - package_version = os.environ.get('OPENCV_VERSION', '4.11.0') # TODO + package_version = os.environ.get('OPENCV_VERSION', '4.12.0') # TODO long_description = 'Open Source Computer Vision Library Python bindings' # TODO @@ -67,6 +67,7 @@ def main(): "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Programming Language :: C++", "Programming Language :: Python :: Implementation :: CPython", "Topic :: Scientific/Engineering", diff --git a/platforms/maven/opencv-it/pom.xml b/platforms/maven/opencv-it/pom.xml index 5355f0e67e..eb44e021d6 100644 --- a/platforms/maven/opencv-it/pom.xml +++ b/platforms/maven/opencv-it/pom.xml @@ -4,7 +4,7 @@ org.opencv opencv-parent - 4.11.0 + 4.12.0 org.opencv opencv-it diff --git a/platforms/maven/opencv/pom.xml b/platforms/maven/opencv/pom.xml index 4ecd738b40..5961e4d69f 100644 --- a/platforms/maven/opencv/pom.xml +++ b/platforms/maven/opencv/pom.xml @@ -4,7 +4,7 @@ org.opencv opencv-parent - 4.11.0 + 4.12.0 org.opencv opencv diff --git a/platforms/maven/pom.xml b/platforms/maven/pom.xml index af9f969c65..4853b32bf0 100644 --- a/platforms/maven/pom.xml +++ b/platforms/maven/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.opencv opencv-parent - 4.11.0 + 4.12.0 pom OpenCV Parent POM From a4a253ea2b426289f784db25d7c937e5f907ed05 Mon Sep 17 00:00:00 2001 From: Sergei Nikiforov Date: Mon, 30 Dec 2024 19:28:48 +0100 Subject: [PATCH 18/62] Charuco detector: stop-gap measure for poor detection rate on tough perspective add checkMarkers flag to charucoParameters to control board verification (post-detection) --- .../objdetect/include/opencv2/objdetect/charuco_detector.hpp | 4 ++++ modules/objdetect/src/aruco/charuco_detector.cpp | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/objdetect/include/opencv2/objdetect/charuco_detector.hpp b/modules/objdetect/include/opencv2/objdetect/charuco_detector.hpp index e10cb3f025..e9681521d3 100644 --- a/modules/objdetect/include/opencv2/objdetect/charuco_detector.hpp +++ b/modules/objdetect/include/opencv2/objdetect/charuco_detector.hpp @@ -16,6 +16,7 @@ struct CV_EXPORTS_W_SIMPLE CharucoParameters { CV_WRAP CharucoParameters() { minMarkers = 2; tryRefineMarkers = false; + checkMarkers = true; } /// cameraMatrix optional 3x3 floating-point camera matrix CV_PROP_RW Mat cameraMatrix; @@ -28,6 +29,9 @@ struct CV_EXPORTS_W_SIMPLE CharucoParameters { /// try to use refine board, default false CV_PROP_RW bool tryRefineMarkers; + + /// run check to verify that markers belong to the same board, default true + CV_PROP_RW bool checkMarkers; }; class CV_EXPORTS_W CharucoDetector : public Algorithm { diff --git a/modules/objdetect/src/aruco/charuco_detector.cpp b/modules/objdetect/src/aruco/charuco_detector.cpp index 77f614d4d0..48446c86e3 100644 --- a/modules/objdetect/src/aruco/charuco_detector.cpp +++ b/modules/objdetect/src/aruco/charuco_detector.cpp @@ -335,7 +335,7 @@ struct CharucoDetector::CharucoDetectorImpl { InputOutputArrayOfArrays _markerCorners = markerCorners.needed() ? markerCorners : tmpMarkerCorners; InputOutputArray _markerIds = markerIds.needed() ? markerIds : tmpMarkerIds; detectBoard(image, charucoCorners, charucoIds, _markerCorners, _markerIds); - if (checkBoard(_markerCorners, _markerIds, charucoCorners, charucoIds) == false) { + if (charucoParameters.checkMarkers && checkBoard(_markerCorners, _markerIds, charucoCorners, charucoIds) == false) { CV_LOG_DEBUG(NULL, "ChArUco board is built incorrectly"); charucoCorners.release(); charucoIds.release(); From 23d812187efefbaf9b62124d77d3fbb1c44853c4 Mon Sep 17 00:00:00 2001 From: Suleyman TURKMEN Date: Thu, 19 Jun 2025 21:49:21 +0300 Subject: [PATCH 19/62] Merge pull request #27457 from sturkmen72:WebP-bugfix fix for the issue #27456 #27457 closes #27456 ### Pull Request Readiness Checklist See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [x] The PR is proposed to the proper branch - [x] There is a reference to the original bug report and related work - [ ] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [ ] The feature is well documented and sample code can be built with the project CMake --- modules/imgcodecs/src/grfmt_webp.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/modules/imgcodecs/src/grfmt_webp.cpp b/modules/imgcodecs/src/grfmt_webp.cpp index 2d55995789..3e63dd7acb 100644 --- a/modules/imgcodecs/src/grfmt_webp.cpp +++ b/modules/imgcodecs/src/grfmt_webp.cpp @@ -155,14 +155,16 @@ bool WebPDecoder::readHeader() webp_data.size = data.total(); WebPAnimDecoderOptions dec_options; - WebPAnimDecoderOptionsInit(&dec_options); + if (!WebPAnimDecoderOptionsInit(&dec_options)) + CV_Error(Error::StsInternal, "Failed to initialize animated WebP decoding options"); dec_options.color_mode = m_use_rgb ? MODE_RGBA : MODE_BGRA; anim_decoder.reset(WebPAnimDecoderNew(&webp_data, &dec_options)); CV_Assert(anim_decoder.get() && "Error parsing image"); WebPAnimInfo anim_info; - WebPAnimDecoderGetInfo(anim_decoder.get(), &anim_info); + if (!WebPAnimDecoderGetInfo(anim_decoder.get(), &anim_info)) + CV_Error(Error::StsInternal, "Failed to get animated WebP information"); m_animation.loop_count = anim_info.loop_count; m_animation.bgcolor[0] = (anim_info.bgcolor >> 24) & 0xFF; @@ -216,7 +218,8 @@ bool WebPDecoder::readData(Mat &img) uint8_t* buf; int timestamp; - WebPAnimDecoderGetNext(anim_decoder.get(), &buf, ×tamp); + if (!WebPAnimDecoderGetNext(anim_decoder.get(), &buf, ×tamp)) + CV_Error(Error::StsInternal, "Failed to decode animated WebP frame"); Mat tmp(Size(m_width, m_height), CV_8UC4, buf); if (img.type() == CV_8UC1) @@ -446,7 +449,6 @@ bool WebPEncoder::writeanimation(const Animation& animation, const std::vector Date: Fri, 20 Jun 2025 15:09:21 +0800 Subject: [PATCH 20/62] fix: disable integral due to accuracy issue #27407 --- hal/riscv-rvv/include/imgproc.hpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/hal/riscv-rvv/include/imgproc.hpp b/hal/riscv-rvv/include/imgproc.hpp index 24c8a22d7f..398e952802 100644 --- a/hal/riscv-rvv/include/imgproc.hpp +++ b/hal/riscv-rvv/include/imgproc.hpp @@ -236,8 +236,10 @@ int integral(int depth, int sdepth, int sqdepth, uchar* tilted_data, [[maybe_unused]] size_t tilted_step, int width, int height, int cn); -#undef cv_hal_integral -#define cv_hal_integral cv::rvv_hal::imgproc::integral +// Diasbled due to accuracy issue. +// Details see https://github.com/opencv/opencv/issues/27407. +//#undef cv_hal_integral +//#define cv_hal_integral cv::rvv_hal::imgproc::integral #endif // CV_HAL_RVV_1P0_ENABLED From f24f6b8d4e6faf84aeff118a4bf22b9fa44b906a Mon Sep 17 00:00:00 2001 From: xaos-cz <64235805+xaos-cz@users.noreply.github.com> Date: Fri, 20 Jun 2025 09:43:53 +0200 Subject: [PATCH 21/62] Merge pull request #27458 from xaos-cz:4.x imread: GDAL multi-channel support #27458 - tested on 30-channel FITS and 186-channel ENVI files ### Pull Request Readiness Checklist See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [x] The PR is proposed to the proper branch - [ ] There is a reference to the original bug report and related work - [ ] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [ ] The feature is well documented and sample code can be built with the project CMake --- modules/imgcodecs/src/grfmt_gdal.cpp | 3 +++ modules/imgcodecs/test/test_gdal.cpp | 38 ++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100755 modules/imgcodecs/test/test_gdal.cpp diff --git a/modules/imgcodecs/src/grfmt_gdal.cpp b/modules/imgcodecs/src/grfmt_gdal.cpp index ff059338cf..2c09d3b62f 100644 --- a/modules/imgcodecs/src/grfmt_gdal.cpp +++ b/modules/imgcodecs/src/grfmt_gdal.cpp @@ -408,6 +408,9 @@ bool GdalDecoder::readData( Mat& img ){ case GCI_AlphaBand: color = 3; break; + case GCI_Undefined: + color = c; + break; default: CV_Error(cv::Error::StsError, "Invalid/unsupported mode"); } diff --git a/modules/imgcodecs/test/test_gdal.cpp b/modules/imgcodecs/test/test_gdal.cpp new file mode 100755 index 0000000000..b61d2ed786 --- /dev/null +++ b/modules/imgcodecs/test/test_gdal.cpp @@ -0,0 +1,38 @@ +// 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 +#include "test_precomp.hpp" +#include "test_common.hpp" + +namespace opencv_test { namespace { + +#ifdef HAVE_GDAL + +static void test_gdal_read(const string filename) { + const string path = cvtest::findDataFile(filename); + Mat img; + ASSERT_NO_THROW(img = imread(path, cv::IMREAD_LOAD_GDAL | cv::IMREAD_ANYDEPTH | cv::IMREAD_ANYCOLOR)); + ASSERT_FALSE(img.empty()); + EXPECT_EQ(3, img.cols); + EXPECT_EQ(5, img.rows); + EXPECT_EQ(CV_MAKETYPE(CV_32F, 7), img.type()); + EXPECT_EQ(101.125, (img.at>(0, 0)[0])); + EXPECT_EQ(203.500, (img.at>(2, 1)[3])); + EXPECT_EQ(305.875, (img.at>(4, 2)[6])); +} + +TEST(Imgcodecs_gdal, read_envi) +{ + test_gdal_read("../cv/gdal/envi_test.raw"); +} + + +TEST(Imgcodecs_gdal, read_fits) +{ + test_gdal_read("../cv/gdal/fits_test.fit"); +} + + +#endif // HAVE_GDAL + +}} // namespace From 32598639244ada2b5095baf08118a2ec80c6bb37 Mon Sep 17 00:00:00 2001 From: Yuantao Feng Date: Fri, 20 Jun 2025 17:00:37 +0800 Subject: [PATCH 22/62] Merge pull request #27368 from fengyuentau:4x/imgproc/CreateHanningWindow-simd imgproc: vectorize cv::createHanningWindow #27368 ### Pull Request Readiness Checklist See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [x] The PR is proposed to the proper branch - [ ] There is a reference to the original bug report and related work - [ ] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [ ] The feature is well documented and sample code can be built with the project CMake --- modules/imgproc/src/phasecorr.cpp | 47 ++++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/modules/imgproc/src/phasecorr.cpp b/modules/imgproc/src/phasecorr.cpp index d2f88420be..3fbe62883d 100644 --- a/modules/imgproc/src/phasecorr.cpp +++ b/modules/imgproc/src/phasecorr.cpp @@ -38,6 +38,7 @@ #include "precomp.hpp" #include +#include "opencv2/core/hal/intrin.hpp" namespace cv { @@ -614,8 +615,27 @@ void cv::createHanningWindow(OutputArray _dst, cv::Size winSize, int type) double* const wc = _wc.data(); double coeff0 = 2.0 * CV_PI / (double)(cols - 1), coeff1 = 2.0 * CV_PI / (double)(rows - 1); - for(int j = 0; j < cols; j++) - wc[j] = 0.5 * (1.0 - cos(coeff0 * j)); + int c = 0; +#if CV_SIMD_64F || CV_SIMD_SCALABLE_64F + const int nlanes32 = VTraits::vlanes(); + const int nlanes64 = VTraits::vlanes(); + const int max_nlanes = VTraits::max_nlanes; + std::array index; + std::iota(index.data(), index.data()+max_nlanes, 0.f); + v_float64 vindex = vx_load(index.data()); + v_float64 delta = vx_setall_f64(VTraits::vlanes()); + v_float64 vcoeff0 = vx_setall_f64(coeff0); + v_float64 one = vx_setall_f64(1.f); + v_float64 half = vx_setall_f64(0.5f); + for (; c <= cols - nlanes64; c += nlanes64) + { + v_float64 v = v_mul(half, v_sub(one, v_cos(v_mul(vcoeff0, vindex)))); + vx_store(wc + c, v); + vindex = v_add(vindex, delta); + } +#endif + for(; c < cols; c++) + wc[c] = 0.5 * (1.0 - cos(coeff0 * c)); if(dst.depth() == CV_32F) { @@ -623,7 +643,17 @@ void cv::createHanningWindow(OutputArray _dst, cv::Size winSize, int type) { float* dstData = dst.ptr(i); double wr = 0.5 * (1.0 - cos(coeff1 * i)); - for(int j = 0; j < cols; j++) + int j = 0; +#if CV_SIMD_64F || CV_SIMD_SCALABLE_64F + v_float64 vwr = vx_setall_f64(wr); + for (; j <= cols - nlanes32; j += nlanes32) + { + v_float64 v0 = v_mul(vwr, vx_load(wc + j)); + v_float64 v1 = v_mul(vwr, vx_load(wc + j + nlanes64)); + vx_store(dstData + j, v_cvt_f32(v0, v1)); + } +#endif + for(; j < cols; j++) dstData[j] = (float)(wr * wc[j]); } } @@ -633,7 +663,16 @@ void cv::createHanningWindow(OutputArray _dst, cv::Size winSize, int type) { double* dstData = dst.ptr(i); double wr = 0.5 * (1.0 - cos(coeff1 * i)); - for(int j = 0; j < cols; j++) + int j = 0; +#if CV_SIMD_64F || CV_SIMD_SCALABLE_64F + v_float64 vwr = vx_setall_f64(wr); + for (; j <= cols - nlanes64; j += nlanes64) + { + v_float64 v = v_mul(vwr, vx_load(wc + j)); + vx_store(dstData + j, v); + } +#endif + for(; j < cols; j++) dstData[j] = wr * wc[j]; } } From 89289ecaa5b16cc68b0babcbebe532e3d508332e Mon Sep 17 00:00:00 2001 From: Alexander Smorkalov Date: Fri, 20 Jun 2025 12:38:11 +0300 Subject: [PATCH 23/62] Switch to standard C11 atomics on android and with nvcc. --- modules/core/include/opencv2/core/cvdef.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/core/include/opencv2/core/cvdef.h b/modules/core/include/opencv2/core/cvdef.h index 96445a3abb..7fea9c0bd6 100644 --- a/modules/core/include/opencv2/core/cvdef.h +++ b/modules/core/include/opencv2/core/cvdef.h @@ -697,7 +697,7 @@ __CV_ENUM_FLAGS_BITWISE_XOR_EQ (EnumType, EnumType) #ifdef CV_XADD // allow to use user-defined macro #elif defined __GNUC__ || defined __clang__ -# if defined __clang__ && __clang_major__ >= 3 && !defined __ANDROID__ && !defined __EMSCRIPTEN__ && !defined(__CUDACC__) && !defined __INTEL_COMPILER +# if defined __clang__ && __clang_major__ >= 3 && !defined __EMSCRIPTEN__ && !defined __INTEL_COMPILER # ifdef __ATOMIC_ACQ_REL # define CV_XADD(addr, delta) __c11_atomic_fetch_add((_Atomic(int)*)(addr), delta, __ATOMIC_ACQ_REL) # else From 850b686f8a9a1bbadd975910aee815acaf0d6e50 Mon Sep 17 00:00:00 2001 From: Suleyman TURKMEN Date: Sat, 21 Jun 2025 10:22:10 +0300 Subject: [PATCH 24/62] Merge pull request #27127 from sturkmen72:apng_has_hidden_frame Changes about when APNG has a hidden frame #27127 closes : #27074 ### Pull Request Readiness Checklist See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [x] The PR is proposed to the proper branch - [x] There is a reference to the original bug report and related work - [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [ ] The feature is well documented and sample code can be built with the project CMake --- .../imgcodecs/include/opencv2/imgcodecs.hpp | 2 + modules/imgcodecs/src/grfmt_base.hpp | 5 + modules/imgcodecs/src/grfmt_png.cpp | 227 ++++++++++-------- modules/imgcodecs/src/loadsave.cpp | 5 +- modules/imgcodecs/test/test_animation.cpp | 47 +++- 5 files changed, 180 insertions(+), 106 deletions(-) diff --git a/modules/imgcodecs/include/opencv2/imgcodecs.hpp b/modules/imgcodecs/include/opencv2/imgcodecs.hpp index b78b641121..45a776f4d5 100644 --- a/modules/imgcodecs/include/opencv2/imgcodecs.hpp +++ b/modules/imgcodecs/include/opencv2/imgcodecs.hpp @@ -277,6 +277,8 @@ struct CV_EXPORTS_W_SIMPLE Animation CV_PROP_RW std::vector durations; //! Vector of frames, where each Mat represents a single frame. CV_PROP_RW std::vector frames; + //! image that can be used for the format in addition to the animation or if animation is not supported in the reader (like in PNG). + CV_PROP_RW Mat still_image; /** @brief Constructs an Animation object with optional loop count and background color. diff --git a/modules/imgcodecs/src/grfmt_base.hpp b/modules/imgcodecs/src/grfmt_base.hpp index ae5622528c..6d98bd3735 100644 --- a/modules/imgcodecs/src/grfmt_base.hpp +++ b/modules/imgcodecs/src/grfmt_base.hpp @@ -58,6 +58,11 @@ public: */ size_t getFrameCount() const { return m_frame_count; } + /** + * @brief Set the internal m_frame_count variable to 1. + */ + void resetFrameCount() { m_frame_count = 1; } + /** * @brief Get the type of the image (e.g., color format, depth). * @return The type of the image. diff --git a/modules/imgcodecs/src/grfmt_png.cpp b/modules/imgcodecs/src/grfmt_png.cpp index c4b5a2c3a6..3941961b56 100644 --- a/modules/imgcodecs/src/grfmt_png.cpp +++ b/modules/imgcodecs/src/grfmt_png.cpp @@ -156,7 +156,7 @@ bool APNGFrame::setMat(const cv::Mat& src, unsigned delayNum, unsigned delayDen) if (!src.empty()) { - png_uint_32 rowbytes = src.depth() == CV_16U ? src.cols * src.channels() * 2 : src.cols * src.channels(); + png_uint_32 rowbytes = src.cols * (uint32_t)src.elemSize(); _width = src.cols; _height = src.rows; _colorType = src.channels() == 1 ? PNG_COLOR_TYPE_GRAY : src.channels() == 3 ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA; @@ -416,14 +416,17 @@ bool PngDecoder::readData( Mat& img ) if (m_frame_no == 0) { + if (m_mat_raw.empty()) + { + if (m_f) + fseek(m_f, -8, SEEK_CUR); + else + m_buf_pos -= 8; + } m_mat_raw = Mat(img.rows, img.cols, m_type); m_mat_next = Mat(img.rows, img.cols, m_type); frameRaw.setMat(m_mat_raw); frameNext.setMat(m_mat_next); - if (m_f) - fseek(m_f, -8, SEEK_CUR); - else - m_buf_pos -= 8; } else m_mat_next.copyTo(mat_cur); @@ -433,9 +436,6 @@ bool PngDecoder::readData( Mat& img ) if (!processing_start((void*)&frameRaw, mat_cur)) return false; - if(setjmp(png_jmpbuf(m_png_ptr))) - return false; - while (true) { id = read_chunk(chunk); @@ -446,53 +446,53 @@ bool PngDecoder::readData( Mat& img ) { if (!m_is_fcTL_loaded) { - m_is_fcTL_loaded = true; - w0 = m_width; - h0 = m_height; - } - - if (processing_finish()) - { - if (dop == 2) - memcpy(frameNext.getPixels(), frameCur.getPixels(), imagesize); - - compose_frame(frameCur.getRows(), frameRaw.getRows(), bop, x0, y0, w0, h0, mat_cur); - if (!delay_den) - delay_den = 100; - m_animation.durations.push_back(cvRound(1000.*delay_num/delay_den)); - - if (mat_cur.channels() == img.channels()) - { - if (mat_cur.depth() == CV_16U && img.depth() == CV_8U) - mat_cur.convertTo(img, CV_8U, 1. / 255); - else - mat_cur.copyTo(img); - } - else - { - Mat mat_cur_scaled; - if (mat_cur.depth() == CV_16U && img.depth() == CV_8U) - mat_cur.convertTo(mat_cur_scaled, CV_8U, 1. / 255); - else - mat_cur_scaled = mat_cur; - - if (img.channels() == 1) - cvtColor(mat_cur_scaled, img, COLOR_BGRA2GRAY); - else if (img.channels() == 3) - cvtColor(mat_cur_scaled, img, COLOR_BGRA2BGR); - } - - if (dop != 2) - { - memcpy(frameNext.getPixels(), frameCur.getPixels(), imagesize); - if (dop == 1) - for (j = 0; j < h0; j++) - memset(frameNext.getRows()[y0 + j] + x0 * img.channels(), 0, w0 * img.channels()); - } + m_mat_raw.copyTo(m_animation.still_image); } else { - return false; + if (processing_finish()) + { + if (dop == 2) + memcpy(frameNext.getPixels(), frameCur.getPixels(), imagesize); + + compose_frame(frameCur.getRows(), frameRaw.getRows(), bop, x0, y0, w0, h0, mat_cur); + if (!delay_den) + delay_den = 100; + m_animation.durations.push_back(cvRound(1000. * delay_num / delay_den)); + + if (mat_cur.channels() == img.channels()) + { + if (mat_cur.depth() == CV_16U && img.depth() == CV_8U) + mat_cur.convertTo(img, CV_8U, 1. / 255); + else + mat_cur.copyTo(img); + } + else + { + Mat mat_cur_scaled; + if (mat_cur.depth() == CV_16U && img.depth() == CV_8U) + mat_cur.convertTo(mat_cur_scaled, CV_8U, 1. / 255); + else + mat_cur_scaled = mat_cur; + + if (img.channels() == 1) + cvtColor(mat_cur_scaled, img, COLOR_BGRA2GRAY); + else if (img.channels() == 3) + cvtColor(mat_cur_scaled, img, COLOR_BGRA2BGR); + } + + if (dop != 2) + { + memcpy(frameNext.getPixels(), frameCur.getPixels(), imagesize); + if (dop == 1) + for (j = 0; j < h0; j++) + memset(frameNext.getRows()[y0 + j] + x0 * img.channels(), 0, w0 * img.channels()); + } + } + else + { + return false; + } } w0 = png_get_uint_32(&chunk.p[12]); @@ -515,7 +515,16 @@ bool PngDecoder::readData( Mat& img ) } memcpy(&m_chunkIHDR.p[8], &chunk.p[12], 8); - return true; + + if (m_is_fcTL_loaded) + return true; + else + { + m_is_fcTL_loaded = true; + ClearPngPtr(); + if (!processing_start((void*)&frameRaw, mat_cur)) + return false; + } } else if (id == id_IDAT) { @@ -650,8 +659,8 @@ void PngDecoder::compose_frame(std::vector& rows_dst, const std::vect const size_t elem_size = img.elemSize(); if (_bop == 0) { // Overwrite mode: copy source row directly to destination - for(uint32_t j = 0; j < h; ++j) { - std::memcpy(rows_dst[j + y] + x * elem_size,rows_src[j], w * elem_size); + for (uint32_t j = 0; j < h; ++j) { + std::memcpy(rows_dst[j + y] + x * elem_size, rows_src[j], w * elem_size); } return; } @@ -665,23 +674,24 @@ void PngDecoder::compose_frame(std::vector& rows_dst, const std::vect // Blending mode for (unsigned int i = 0; i < w; i++, sp += channels, dp += channels) { - if (channels < 4 || sp[3] == 65535) { // Fully opaque in 16-bit (max value) + uint16_t alpha = sp[3]; + + if (channels < 4 || alpha == 65535 || dp[3] == 0) { + // Fully opaque OR destination fully transparent: direct copy memcpy(dp, sp, elem_size); + continue; } - else if (sp[3] != 0) { // Partially transparent - if (dp[3] != 0) { // Both source and destination have alpha - uint32_t u = sp[3] * 65535; // 16-bit max - uint32_t v = (65535 - sp[3]) * dp[3]; - uint32_t al = u + v; - dp[0] = static_cast((sp[0] * u + dp[0] * v) / al); // Red - dp[1] = static_cast((sp[1] * u + dp[1] * v) / al); // Green - dp[2] = static_cast((sp[2] * u + dp[2] * v) / al); // Blue - dp[3] = static_cast(al / 65535); // Alpha - } - else { - // If destination alpha is 0, copy source pixel - memcpy(dp, sp, elem_size); - } + + if (alpha != 0) { + // Alpha blending + uint64_t u = static_cast(alpha) * 65535; + uint64_t v = static_cast(65535 - alpha) * dp[3]; + uint64_t al = u + v; + + dp[0] = static_cast((sp[0] * u + dp[0] * v) / al); // Red + dp[1] = static_cast((sp[1] * u + dp[1] * v) / al); // Green + dp[2] = static_cast((sp[2] * u + dp[2] * v) / al); // Blue + dp[3] = static_cast(al / 65535); // Alpha } } } @@ -694,25 +704,24 @@ void PngDecoder::compose_frame(std::vector& rows_dst, const std::vect // Blending mode for (unsigned int i = 0; i < w; i++, sp += channels, dp += channels) { - if (channels < 4 || sp[3] == 255) { - // Fully opaque: copy source pixel directly + uint8_t alpha = sp[3]; + + if (channels < 4 || alpha == 255 || dp[3] == 0) { + // Fully opaque OR destination fully transparent: direct copy memcpy(dp, sp, elem_size); + continue; } - else if (sp[3] != 0) { + + if (alpha != 0) { // Alpha blending - if (dp[3] != 0) { - int u = sp[3] * 255; - int v = (255 - sp[3]) * dp[3]; - int al = u + v; - dp[0] = (sp[0] * u + dp[0] * v) / al; // Red - dp[1] = (sp[1] * u + dp[1] * v) / al; // Green - dp[2] = (sp[2] * u + dp[2] * v) / al; // Blue - dp[3] = al / 255; // Alpha - } - else { - // If destination alpha is 0, copy source pixel - memcpy(dp, sp, elem_size); - } + uint32_t u = alpha * 255; + uint32_t v = (255 - alpha) * dp[3]; + uint32_t al = u + v; + + dp[0] = static_cast((sp[0] * u + dp[0] * v) / al); // Red + dp[1] = static_cast((sp[1] * u + dp[1] * v) / al); // Green + dp[2] = static_cast((sp[2] * u + dp[2] * v) / al); // Blue + dp[3] = static_cast(al / 255); // Alpha } } } @@ -1483,7 +1492,7 @@ bool PngEncoder::writeanimation(const Animation& animation, const std::vector 1) writeChunk(m_f, "acTL", buf_acTL, 8); - else - first = 0; if (palsize > 0) writeChunk(m_f, "PLTE", (unsigned char*)(&palette), palsize * 3); @@ -1634,19 +1641,31 @@ bool PngEncoder::writeanimation(const Animation& animation, const std::vector 1) + { + CV_Assert(animation.still_image.type() == animation.frames[0].type() && animation.still_image.size() == animation.frames[0].size()); + APNGFrame apngFrame; + Mat tmp; + if (animation.still_image.depth() == CV_16U) + { + animation.still_image.convertTo(tmp, CV_8U, 1.0 / 255); + } + else + tmp = animation.still_image; + + cvtColor(tmp, tmp, COLOR_BGRA2RGBA); + apngFrame.setMat(tmp); + + deflateRectOp(apngFrame.getPixels(), x0, y0, w0, h0, bpp, rowbytes, zbuf_size, 0); + deflateRectFin(zbuf.data(), &zsize, bpp, rowbytes, rows.data(), zbuf_size, 0); + writeIDATs(m_f, 0, zbuf.data(), zsize, idat_size); + } + deflateRectOp(frames[0].getPixels(), x0, y0, w0, h0, bpp, rowbytes, zbuf_size, 0); deflateRectFin(zbuf.data(), &zsize, bpp, rowbytes, rows.data(), zbuf_size, 0); - if (first) - { - writeIDATs(m_f, 0, zbuf.data(), zsize, idat_size); - for (j = 0; j < 6; j++) - op[j].valid = 0; - deflateRectOp(frames[1].getPixels(), x0, y0, w0, h0, bpp, rowbytes, zbuf_size, 0); - deflateRectFin(zbuf.data(), &zsize, bpp, rowbytes, rows.data(), zbuf_size, 0); - } - - for (i = first; i < num_frames - 1; i++) + for (i = 0; i < num_frames - 1; i++) { uint32_t op_min; int op_best; @@ -1673,7 +1692,7 @@ bool PngEncoder::writeanimation(const Animation& animation, const std::vector first) + if (i > 0) getRect(width, height, rest.data(), frames[i + 1].getPixels(), over3.data(), bpp, rowbytes, zbuf_size, has_tcolor, tcolor, 2); op_min = op[0].size; @@ -1699,9 +1718,9 @@ bool PngEncoder::writeanimation(const Animation& animation, const std::vector 1) + if (num_frames > 1 /* don't write fcTL chunk if animation has only one frame */) { png_save_uint_32(buf_fcTL, next_seq_num++); png_save_uint_32(buf_fcTL + 4, w0); diff --git a/modules/imgcodecs/src/loadsave.cpp b/modules/imgcodecs/src/loadsave.cpp index fd547a378a..dfbf118fb9 100644 --- a/modules/imgcodecs/src/loadsave.cpp +++ b/modules/imgcodecs/src/loadsave.cpp @@ -501,11 +501,12 @@ imread_( const String& filename, int flags, OutputArray mat ) Mat real_mat = mat.getMat(); const void * original_ptr = real_mat.data; bool success = false; + decoder->resetFrameCount(); // this is needed for PngDecoder. it should be called before decoder->readData() try { if (decoder->readData(real_mat)) { - CV_CheckTrue((decoder->getFrameCount() > 1) || original_ptr == real_mat.data, "Internal imread issue"); + CV_CheckTrue(original_ptr == real_mat.data, "Internal imread issue"); success = true; } } @@ -800,6 +801,7 @@ imreadanimation_(const String& filename, int flags, int start, int count, Animat } animation.bgcolor = decoder->animation().bgcolor; animation.loop_count = decoder->animation().loop_count; + animation.still_image = decoder->animation().still_image; return success; } @@ -910,6 +912,7 @@ static bool imdecodeanimation_(InputArray buf, int flags, int start, int count, } animation.bgcolor = decoder->animation().bgcolor; animation.loop_count = decoder->animation().loop_count; + animation.still_image = decoder->animation().still_image; return success; } diff --git a/modules/imgcodecs/test/test_animation.cpp b/modules/imgcodecs/test/test_animation.cpp index ece0d19d29..5fead70135 100644 --- a/modules/imgcodecs/test/test_animation.cpp +++ b/modules/imgcodecs/test/test_animation.cpp @@ -636,6 +636,51 @@ TEST(Imgcodecs_APNG, imencode_animation) } } +TEST(Imgcodecs_APNG, animation_has_hidden_frame) +{ + // Set the path to the test image directory and filename for loading. + const string root = cvtest::TS::ptr()->get_data_path(); + const string filename = root + "readwrite/033.png"; + Animation animation1, animation2, animation3; + + imreadanimation(filename, animation1); + + EXPECT_FALSE(animation1.still_image.empty()); + EXPECT_EQ((size_t)2, animation1.frames.size()); + + std::vector buf; + EXPECT_TRUE(imencodeanimation(".png", animation1, buf)); + EXPECT_TRUE(imdecodeanimation(buf, animation2)); + + EXPECT_FALSE(animation2.still_image.empty()); + EXPECT_EQ(animation1.frames.size(), animation2.frames.size()); + + animation1.frames.erase(animation1.frames.begin()); + animation1.durations.erase(animation1.durations.begin()); + EXPECT_TRUE(imencodeanimation(".png", animation1, buf)); + EXPECT_TRUE(imdecodeanimation(buf, animation3)); + + EXPECT_FALSE(animation1.still_image.empty()); + EXPECT_TRUE(animation3.still_image.empty()); + EXPECT_EQ((size_t)1, animation3.frames.size()); +} + +TEST(Imgcodecs_APNG, animation_imread_preview) +{ + // Set the path to the test image directory and filename for loading. + const string root = cvtest::TS::ptr()->get_data_path(); + const string filename = root + "readwrite/033.png"; + cv::Mat imread_result; + cv::imread(filename, imread_result, cv::IMREAD_UNCHANGED); + EXPECT_FALSE(imread_result.empty()); + + Animation animation; + imreadanimation(filename, animation); + EXPECT_FALSE(animation.still_image.empty()); + + EXPECT_EQ(0, cv::norm(animation.still_image, imread_result, cv::NORM_INF)); +} + #endif // HAVE_PNG #if defined(HAVE_PNG) || defined(HAVE_SPNG) @@ -676,7 +721,7 @@ TEST(Imgcodecs_APNG, imread_animation_16u) img = imread(filename, IMREAD_ANYDEPTH); ASSERT_FALSE(img.empty()); EXPECT_TRUE(img.type() == CV_16UC1); - EXPECT_EQ(19519, img.at(0, 0)); + EXPECT_EQ(19517, img.at(0, 0)); img = imread(filename, IMREAD_COLOR | IMREAD_ANYDEPTH); ASSERT_FALSE(img.empty()); From 1950c4dbb993c60f11ddc8adf3c4eeab998fc175 Mon Sep 17 00:00:00 2001 From: cudawarped <12133430+cudawarped@users.noreply.github.com> Date: Sat, 21 Jun 2025 16:32:31 +0300 Subject: [PATCH 25/62] Merge pull request #27379 from cudawarped:fix_cuda_convertTo cuda: Fix GpuMat::convertTo issues described in 27373 #27379 Fix https://github.com/opencv/opencv/issues/27373. 1. `GpuMat::convertTo` uses `convertToScale` due to incorrect overload. 2. There are no runtime checks to prevent the use of `CV_16U` data types in Release builds. ### Pull Request Readiness Checklist See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [x] The PR is proposed to the proper branch - [x] There is a reference to the original bug report and related work - [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [x] The feature is well documented and sample code can be built with the project CMake --- modules/core/include/opencv2/core/cuda.hpp | 9 ++++++++- modules/core/src/cuda/gpu_mat.cu | 4 +++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/modules/core/include/opencv2/core/cuda.hpp b/modules/core/include/opencv2/core/cuda.hpp index 8191c00783..76b8e6bff1 100644 --- a/modules/core/include/opencv2/core/cuda.hpp +++ b/modules/core/include/opencv2/core/cuda.hpp @@ -240,6 +240,10 @@ public: //! converts GpuMat to another datatype (Blocking call) void convertTo(OutputArray dst, int rtype) const; + //! bindings overload which converts GpuMat to another datatype (Blocking call) + CV_WRAP void convertTo(CV_OUT GpuMat& dst, int rtype) const { + convertTo(static_cast(dst), rtype); + } //! converts GpuMat to another datatype (Non-Blocking call) void convertTo(OutputArray dst, int rtype, Stream& stream) const; @@ -250,10 +254,13 @@ public: //! converts GpuMat to another datatype with scaling (Blocking call) void convertTo(OutputArray dst, int rtype, double alpha, double beta = 0.0) const; + //! bindings overload which converts GpuMat to another datatype with scaling(Blocking call) - CV_WRAP void convertTo(CV_OUT GpuMat& dst, int rtype, double alpha = 1.0, double beta = 0.0) const { +#ifdef OPENCV_BINDINGS_PARSER + CV_WRAP void convertTo(CV_OUT GpuMat& dst, int rtype, double alpha=1.0, double beta = 0.0) const { convertTo(static_cast(dst), rtype, alpha, beta); } +#endif //! converts GpuMat to another datatype with scaling (Non-Blocking call) void convertTo(OutputArray dst, int rtype, double alpha, Stream& stream) const; diff --git a/modules/core/src/cuda/gpu_mat.cu b/modules/core/src/cuda/gpu_mat.cu index a86888cac3..b6f95445db 100644 --- a/modules/core/src/cuda/gpu_mat.cu +++ b/modules/core/src/cuda/gpu_mat.cu @@ -546,7 +546,7 @@ void cv::cuda::GpuMat::convertTo(OutputArray _dst, int rtype, Stream& stream) co return; } - CV_DbgAssert( sdepth <= CV_64F && ddepth <= CV_64F ); + CV_Assert( sdepth <= CV_64F && ddepth <= CV_64F ); GpuMat src = *this; @@ -578,6 +578,8 @@ void cv::cuda::GpuMat::convertTo(OutputArray _dst, int rtype, double alpha, doub const int sdepth = depth(); const int ddepth = CV_MAT_DEPTH(rtype); + CV_Assert(sdepth <= CV_64F && ddepth <= CV_64F); + GpuMat src = *this; _dst.create(size(), rtype); From afe5b226b405741eb1cdb3979d498deb50d169a0 Mon Sep 17 00:00:00 2001 From: Suleyman TURKMEN Date: Sun, 22 Jun 2025 15:43:09 +0300 Subject: [PATCH 26/62] Merge pull request #27113 from sturkmen72:spng-CV_16U Fixing imread() function 16 bit reading png problem with libspng #27113 The purpose of the PR was to load bit-exact compatible results with libspng and libpng. To test this, `Imgcodecs_Png_PngSuite `was improved. Files containing gamma correction were moved to a separate test called `Imgcodecs_Png_PngSuite_Gamma `because the logic created for the other files did not apply to those with gamma correction. As a result, libspng now works in bit-exact compatibility with libpng. The code can be refactored later. ### Pull Request Readiness Checklist See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [x] The PR is proposed to the proper branch - [ ] There is a reference to the original bug report and related work - [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [ ] The feature is well documented and sample code can be built with the project CMake --- .../misc/java/test/ImgcodecsTest.java | 4 + modules/imgcodecs/src/grfmt_png.cpp | 2 +- modules/imgcodecs/src/grfmt_spng.cpp | 394 ++++++++---------- modules/imgcodecs/test/test_png.cpp | 145 ++++++- 4 files changed, 314 insertions(+), 231 deletions(-) diff --git a/modules/imgcodecs/misc/java/test/ImgcodecsTest.java b/modules/imgcodecs/misc/java/test/ImgcodecsTest.java index 1f5de6a2ef..91066eb93d 100644 --- a/modules/imgcodecs/misc/java/test/ImgcodecsTest.java +++ b/modules/imgcodecs/misc/java/test/ImgcodecsTest.java @@ -15,6 +15,10 @@ import java.util.List; public class ImgcodecsTest extends OpenCVTestCase { public void testAnimation() { + if (!Imgcodecs.haveImageWriter("*.apng")) { + return; + } + Mat src = Imgcodecs.imread(OpenCVTestRunner.LENA_PATH, Imgcodecs.IMREAD_REDUCED_COLOR_4); assertFalse(src.empty()); diff --git a/modules/imgcodecs/src/grfmt_png.cpp b/modules/imgcodecs/src/grfmt_png.cpp index 3941961b56..20653cf3c1 100644 --- a/modules/imgcodecs/src/grfmt_png.cpp +++ b/modules/imgcodecs/src/grfmt_png.cpp @@ -861,7 +861,7 @@ void PngDecoder::row_fn(png_structp png_ptr, png_bytep new_row, png_uint_32 row_ PngEncoder::PngEncoder() { - m_description = "Portable Network Graphics files (*.png)"; + m_description = "Portable Network Graphics files (*.png;*.apng)"; m_buf_supported = true; op_zstream1.zalloc = NULL; op_zstream2.zalloc = NULL; diff --git a/modules/imgcodecs/src/grfmt_spng.cpp b/modules/imgcodecs/src/grfmt_spng.cpp index 9804c7a8ae..acf2f0d55d 100644 --- a/modules/imgcodecs/src/grfmt_spng.cpp +++ b/modules/imgcodecs/src/grfmt_spng.cpp @@ -31,18 +31,18 @@ * with these values. (png_set_rgb_to_gray( png_ptr, 1, 0.299, 0.587 );) For this codec implementation, * slightly modified versions are implemented in the below of this page. */ -void spngCvt_BGR2Gray_8u_C3C1R(const uchar *bgr, int bgr_step, - uchar *gray, int gray_step, - cv::Size size, int _swap_rb); - -void spngCvt_BGRA2Gray_8u_C4C1R(const uchar *bgra, int rgba_step, +void spngCvt_BGRA2Gray_8u_CnC1R(const uchar *bgr, int bgr_step, uchar *gray, int gray_step, - cv::Size size, int _swap_rb); + cv::Size size, int ncn, int _swap_rb); void spngCvt_BGRA2Gray_16u_CnC1R(const ushort *bgr, int bgr_step, ushort *gray, int gray_step, cv::Size size, int ncn, int _swap_rb); +void spngCvt_BGRA2Gray_16u28u_CnC1R(const ushort *bgr, int bgr_step, + uchar *gray, int gray_step, + cv::Size size, int ncn, int _swap_rb); + namespace cv { @@ -109,7 +109,7 @@ int SPngDecoder::readDataFromBuf(void *sp_ctx, void *user, void *dst, size_t siz bool SPngDecoder::readHeader() { - volatile bool result = false; + bool result = false; close(); spng_ctx *ctx = spng_ctx_new(SPNG_CTX_IGNORE_ADLER32); @@ -136,40 +136,36 @@ bool SPngDecoder::readHeader() if (!m_buf.empty() || m_f) { struct spng_ihdr ihdr; - int ret = spng_get_ihdr(ctx, &ihdr); - if (ret == SPNG_OK) + if (spng_get_ihdr(ctx, &ihdr) == SPNG_OK) { m_width = static_cast(ihdr.width); m_height = static_cast(ihdr.height); m_color_type = ihdr.color_type; m_bit_depth = ihdr.bit_depth; - if (ihdr.bit_depth <= 8 || ihdr.bit_depth == 16) + int num_trans; + switch (ihdr.color_type) { - int num_trans; - switch (ihdr.color_type) - { - case SPNG_COLOR_TYPE_TRUECOLOR: - case SPNG_COLOR_TYPE_INDEXED: - struct spng_trns trns; - num_trans = !spng_get_trns(ctx, &trns); - if (num_trans > 0) - m_type = CV_8UC4; - else - m_type = CV_8UC3; - break; - case SPNG_COLOR_TYPE_GRAYSCALE_ALPHA: - case SPNG_COLOR_TYPE_TRUECOLOR_ALPHA: + case SPNG_COLOR_TYPE_TRUECOLOR: + case SPNG_COLOR_TYPE_INDEXED: + struct spng_trns trns; + num_trans = !spng_get_trns(ctx, &trns); + if (num_trans > 0) m_type = CV_8UC4; - break; - default: - m_type = CV_8UC1; - } - if (ihdr.bit_depth == 16) - m_type = CV_MAKETYPE(CV_16U, CV_MAT_CN(m_type)); - result = true; + else + m_type = CV_8UC3; + break; + case SPNG_COLOR_TYPE_GRAYSCALE_ALPHA: + case SPNG_COLOR_TYPE_TRUECOLOR_ALPHA: + m_type = CV_8UC4; + break; + default: + m_type = CV_8UC1; } + if (ihdr.bit_depth == 16) + m_type = CV_MAKETYPE(CV_16U, CV_MAT_CN(m_type)); + result = true; } } @@ -178,98 +174,86 @@ bool SPngDecoder::readHeader() bool SPngDecoder::readData(Mat &img) { - volatile bool result = false; - bool color = img.channels() > 1; - - struct spng_ctx *png_ptr = (struct spng_ctx *)m_ctx; + bool result = false; if (m_ctx && m_width && m_height) { - int fmt = SPNG_FMT_PNG; + struct spng_ctx* png_ptr = (struct spng_ctx*)m_ctx; + bool color = img.channels() > 1; + int fmt = img.channels() == 4 ? m_bit_depth == 16 ? SPNG_FMT_RGBA16 : SPNG_FMT_RGBA8 : SPNG_FMT_PNG; + int decode_flags = img.channels() == 4 ? SPNG_DECODE_TRNS : 0; - struct spng_trns trns; - int have_trns = spng_get_trns((struct spng_ctx *)m_ctx, &trns); - - int decode_flags = 0; - if (have_trns == SPNG_OK) - { - decode_flags = SPNG_DECODE_TRNS; - } - if (img.channels() == 4) - { - if (m_color_type == SPNG_COLOR_TYPE_TRUECOLOR || - m_color_type == SPNG_COLOR_TYPE_INDEXED || - m_color_type == SPNG_COLOR_TYPE_TRUECOLOR_ALPHA) - fmt = m_bit_depth == 16 ? SPNG_FMT_RGBA16 : SPNG_FMT_RGBA8; - else if (m_color_type == SPNG_COLOR_TYPE_GRAYSCALE) - fmt = m_bit_depth == 16 ? SPNG_FMT_GA16 : SPNG_FMT_GA8; - else if (m_color_type == SPNG_COLOR_TYPE_GRAYSCALE_ALPHA) - { - fmt = m_bit_depth == 16 ? SPNG_FMT_RGBA16 : SPNG_FMT_RGBA8; - } - else - fmt = SPNG_FMT_RGBA8; - } - if (img.channels() == 3) + if (img.type() == CV_8UC3) { fmt = SPNG_FMT_RGB8; - if ((m_color_type == SPNG_COLOR_TYPE_GRAYSCALE || m_color_type == SPNG_COLOR_TYPE_GRAYSCALE_ALPHA) && - m_bit_depth == 16) - fmt = SPNG_FMT_RGB8; - else if (m_bit_depth == 16) - fmt = SPNG_FMT_PNG; } else if (img.channels() == 1) { if (m_color_type == SPNG_COLOR_TYPE_GRAYSCALE && m_bit_depth <= 8) fmt = SPNG_FMT_G8; - else if (m_color_type == SPNG_COLOR_TYPE_GRAYSCALE && m_bit_depth == 16) - { - if (img.depth() == CV_8U || img.depth() == CV_8S) - { - fmt = SPNG_FMT_RGB8; - } - else - { - fmt = SPNG_FMT_PNG; - } - } - else if (m_color_type == SPNG_COLOR_TYPE_INDEXED || - m_color_type == SPNG_COLOR_TYPE_TRUECOLOR) - { - if (img.depth() == CV_8U || img.depth() == CV_8S) - { - fmt = SPNG_FMT_RGB8; - } - else - { - fmt = m_bit_depth == 16 ? SPNG_FMT_RGBA16 : SPNG_FMT_RGB8; - } - } - else if (m_color_type == SPNG_COLOR_TYPE_GRAYSCALE_ALPHA || fmt == SPNG_COLOR_TYPE_TRUECOLOR_ALPHA) - { - if (img.depth() == CV_8U || img.depth() == CV_8S) - { - fmt = SPNG_FMT_RGB8; - } - else - { - fmt = m_bit_depth == 16 ? SPNG_FMT_RGBA16 : SPNG_FMT_RGBA8; - } - } else - fmt = SPNG_FMT_RGB8; + fmt = img.depth() == CV_16U ? SPNG_FMT_RGBA16 : SPNG_FMT_RGB8; } + if (fmt == SPNG_FMT_PNG && m_bit_depth == 16 && m_color_type >= SPNG_COLOR_TYPE_GRAYSCALE_ALPHA) + { + Mat tmp(m_height, m_width, CV_16UC4); + if (SPNG_OK != spng_decode_image(png_ptr, tmp.data, tmp.total() * tmp.elemSize(), SPNG_FMT_RGBA16, 0)) + return false; + cvtColor(tmp, img, m_use_rgb ? COLOR_RGBA2RGB : COLOR_RGBA2BGR); + return true; + } + + struct spng_ihdr ihdr; + spng_get_ihdr(png_ptr, &ihdr); + size_t image_width, image_size = 0; int ret = spng_decoded_image_size(png_ptr, fmt, &image_size); - struct spng_ihdr ihdr; - spng_get_ihdr(png_ptr, &ihdr); if (ret == SPNG_OK) { image_width = image_size / m_height; + if (!color && fmt == SPNG_FMT_RGB8 && m_bit_depth == 16 && (m_color_type == SPNG_COLOR_TYPE_TRUECOLOR || m_color_type == SPNG_COLOR_TYPE_TRUECOLOR_ALPHA)) + { + Mat tmp(m_height, m_width, CV_16UC4); + if (SPNG_OK != spng_decode_image(png_ptr, tmp.data, tmp.total() * tmp.elemSize(), SPNG_FMT_RGBA16, 0)) + return false; + spngCvt_BGRA2Gray_16u28u_CnC1R(reinterpret_cast(tmp.data), (int)tmp.step1(), + img.data, (int)img.step1(), Size(m_width, m_height), 4, 2); + return true; + } + + if (!color && ihdr.interlace_method && (fmt == SPNG_FMT_RGB8 || fmt == SPNG_FMT_RGBA16)) + { + if (fmt == SPNG_FMT_RGBA16) + { + Mat tmp(m_height, m_width, CV_16UC4); + if (SPNG_OK != spng_decode_image(png_ptr, tmp.data, tmp.total() * tmp.elemSize(), fmt, 0)) + return false; + spngCvt_BGRA2Gray_16u_CnC1R(reinterpret_cast(tmp.data), (int)tmp.step1(), + reinterpret_cast(img.data), (int)img.step1(), Size(m_width, m_height), 4, 2); + return true; + } + else + { + Mat tmp(m_height, m_width, CV_8UC3); + if (SPNG_OK != spng_decode_image(png_ptr, tmp.data, image_size, fmt, 0)) + return false; + spngCvt_BGRA2Gray_8u_CnC1R(tmp.data, (int)tmp.step1(), img.data, (int)img.step1(), Size(m_width, m_height), 3, 2); + return true; + } + } + + if (fmt == SPNG_FMT_PNG && img.elemSize() * m_width / 3 == image_width) + { + Mat tmp(m_height, m_width, CV_16U); + if (SPNG_OK != spng_decode_image(png_ptr, tmp.data, image_size, SPNG_FMT_PNG, 0)) + return false; + cvtColor(tmp, img, COLOR_GRAY2BGR); + return true; + } + ret = spng_decode_image(png_ptr, nullptr, 0, fmt, SPNG_DECODE_PROGRESSIVE | decode_flags); if (ret == SPNG_OK) { @@ -279,88 +263,46 @@ bool SPngDecoder::readData(Mat &img) // decode image then convert to grayscale if (!color && (fmt == SPNG_FMT_RGB8 || fmt == SPNG_FMT_RGBA8 || fmt == SPNG_FMT_RGBA16)) { - if (ihdr.interlace_method == 0) + AutoBuffer buffer; + buffer.allocate(image_width); + if (fmt == SPNG_FMT_RGB8) { - AutoBuffer buffer; - buffer.allocate(image_width); - if (fmt == SPNG_FMT_RGB8) + do { - do - { - ret = spng_get_row_info(png_ptr, &row_info); - if (ret) - break; + ret = spng_get_row_info(png_ptr, &row_info); + if (ret) + break; - ret = spng_decode_row(png_ptr, buffer.data(), image_width); - spngCvt_BGR2Gray_8u_C3C1R( - buffer.data(), - 0, - img.data + row_info.row_num * img.step, - 0, Size(m_width, 1), 2); - } while (ret == SPNG_OK); - } - else if (fmt == SPNG_FMT_RGBA8) - { - do - { - ret = spng_get_row_info(png_ptr, &row_info); - if (ret) - break; - - ret = spng_decode_row(png_ptr, buffer.data(), image_width); - spngCvt_BGRA2Gray_8u_C4C1R( - buffer.data(), - 0, - img.data + row_info.row_num * img.step, - 0, Size(m_width, 1), 2); - } while (ret == SPNG_OK); - } - else if (fmt == SPNG_FMT_RGBA16) - { - do - { - ret = spng_get_row_info(png_ptr, &row_info); - if (ret) - break; - - ret = spng_decode_row(png_ptr, buffer.data(), image_width); - spngCvt_BGRA2Gray_16u_CnC1R( - reinterpret_cast(buffer.data()), 0, - reinterpret_cast(img.data + row_info.row_num * img.step), - 0, Size(m_width, 1), - 4, 2); - } while (ret == SPNG_OK); - } + ret = spng_decode_row(png_ptr, buffer.data(), image_width); + spngCvt_BGRA2Gray_8u_CnC1R(buffer.data(), 0, img.data + row_info.row_num * img.step, 0, Size(m_width, 1), 3, 2); + } while (ret == SPNG_OK); } - else + else if (fmt == SPNG_FMT_RGBA8) { - AutoBuffer imageBuffer(image_size); - ret = spng_decode_image(png_ptr, imageBuffer.data(), image_size, fmt, 0); - int step = m_width * img.channels(); - if (fmt == SPNG_FMT_RGB8) + do { - spngCvt_BGR2Gray_8u_C3C1R( - imageBuffer.data(), - step, - img.data, - step, Size(m_width, m_height), 2); - } - else if (fmt == SPNG_FMT_RGBA8) - { - spngCvt_BGRA2Gray_8u_C4C1R( - imageBuffer.data(), - step, - img.data, - step, Size(m_width, m_height), 2); - } - else if (fmt == SPNG_FMT_RGBA16) + ret = spng_get_row_info(png_ptr, &row_info); + if (ret) + break; + + ret = spng_decode_row(png_ptr, buffer.data(), image_width); + spngCvt_BGRA2Gray_8u_CnC1R(buffer.data(), 0, img.data + row_info.row_num * img.step, 0, Size(m_width, 1), 4, 2); + } while (ret == SPNG_OK); + } + else if (fmt == SPNG_FMT_RGBA16) + { + do { + ret = spng_get_row_info(png_ptr, &row_info); + if (ret) + break; + + ret = spng_decode_row(png_ptr, buffer.data(), image_width); spngCvt_BGRA2Gray_16u_CnC1R( - reinterpret_cast(imageBuffer.data()), step / 3, - reinterpret_cast(img.data), - step / 3, Size(m_width, m_height), - 4, 2); - } + reinterpret_cast(buffer.data()), 0, + reinterpret_cast(img.data + row_info.row_num * img.step), + 0, Size(m_width, 1), 4, 2); + } while (ret == SPNG_OK); } } else if (color) @@ -383,9 +325,8 @@ bool SPngDecoder::readData(Mat &img) ret = spng_decode_row(png_ptr, buffer[row_info.row_num], image_width); 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)); + 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 && !m_use_rgb) @@ -414,6 +355,8 @@ bool SPngDecoder::readData(Mat &img) } else if (fmt == SPNG_FMT_PNG) { + AutoBuffer bufcn4; + bufcn4.allocate(image_width); do { ret = spng_get_row_info(png_ptr, &row_info); @@ -421,16 +364,17 @@ bool SPngDecoder::readData(Mat &img) break; ret = spng_decode_row(png_ptr, buffer[row_info.row_num], image_width); + 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)); + 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 && !m_use_rgb) { - icvCvt_RGB2BGR_16u_C3R(reinterpret_cast(img.data), step, - reinterpret_cast(img.data), step, Size(m_width, m_height)); + icvCvt_RGB2BGR_16u_C3R(reinterpret_cast(img.data), step, + reinterpret_cast(img.data), step, Size(m_width, m_height)); } } else @@ -454,7 +398,6 @@ bool SPngDecoder::readData(Mat &img) } } else - { do { ret = spng_get_row_info(png_ptr, &row_info); @@ -462,8 +405,8 @@ bool SPngDecoder::readData(Mat &img) break; ret = spng_decode_row(png_ptr, img.data + row_info.row_num * image_width, image_width); + } while (ret == SPNG_OK); - } } if (ret == SPNG_EOI) @@ -687,45 +630,32 @@ bool SPngEncoder::write(const Mat &img, const std::vector ¶ms) } -void spngCvt_BGR2Gray_8u_C3C1R(const uchar *bgr, int bgr_step, - uchar *gray, int gray_step, - cv::Size size, int _swap_rb) +void spngCvt_BGRA2Gray_8u_CnC1R(const uchar *bgr, int bgr_step, + uchar *gray, int gray_step, + cv::Size size, int ncn, int _swap_rb) { int i; for (; size.height--; gray += gray_step) { - double cBGR0 = 0.1140441895; - double cBGR2 = 0.2989807129; - if (_swap_rb) - std::swap(cBGR0, cBGR2); - for (i = 0; i < size.width; i++, bgr += 3) - { - int t = static_cast(cBGR0 * bgr[0] + 0.5869750977 * bgr[1] + cBGR2 * bgr[2]); - gray[i] = (uchar)t; - } - - bgr += bgr_step - size.width * 3; - } -} - -void spngCvt_BGRA2Gray_8u_C4C1R(const uchar *bgra, int rgba_step, - uchar *gray, int gray_step, - cv::Size size, int _swap_rb) -{ - for (; size.height--; gray += gray_step) - { - double cBGR0 = 0.1140441895; - double cBGR1 = 0.5869750977; - double cBGR2 = 0.2989807129; + int cBGR0 = 3737; + int cBGR1 = 19234; + int cBGR2 = 9797; if (_swap_rb) std::swap(cBGR0, cBGR2); - for (int i = 0; i < size.width; i++, bgra += 4) + for (i = 0; i < size.width; i++, bgr += ncn) { - gray[i] = cv::saturate_cast(cBGR0 * bgra[0] + cBGR1 * bgra[1] + cBGR2 * bgra[2]); + if (bgr[0] != bgr[1] || bgr[0] != bgr[2]) + { + gray[i] = (uchar)((cBGR0 * bgr[0] + cBGR1 * bgr[1] + cBGR2 * bgr[2]) >> 15); + } + else + { + gray[i] = bgr[0]; + } } - bgra += rgba_step - size.width * 4; + bgr += bgr_step - size.width * ncn; } } @@ -735,15 +665,43 @@ void spngCvt_BGRA2Gray_16u_CnC1R(const ushort *bgr, int bgr_step, { for (; size.height--; gray += gray_step) { - double cBGR0 = 0.1140441895; - double cBGR1 = 0.5869750977; - double cBGR2 = 0.2989807129; + int cBGR0 = 3737; + int cBGR1 = 19234; + int cBGR2 = 9797; if (_swap_rb) std::swap(cBGR0, cBGR2); for (int i = 0; i < size.width; i++, bgr += ncn) { - gray[i] = (ushort)(cBGR0 * bgr[0] + cBGR1 * bgr[1] + cBGR2 * bgr[2]); + if (bgr[0] != bgr[1] || bgr[0] != bgr[2]) + { + gray[i] = (ushort)((cBGR0 * bgr[0] + cBGR1 * bgr[1] + cBGR2 * bgr[2] + 16384) >> 15); + } + else + { + gray[i] = bgr[0]; + } + } + + bgr += bgr_step - size.width * ncn; + } +} + +void spngCvt_BGRA2Gray_16u28u_CnC1R(const ushort *bgr, int bgr_step, + uchar *gray, int gray_step, + cv::Size size, int ncn, int _swap_rb) +{ + int cBGR0 = 3737; + int cBGR1 = 19234; + int cBGR2 = 9797; + if (_swap_rb) + std::swap(cBGR0, cBGR2); + + for (; size.height--; gray += gray_step) + { + for (int i = 0; i < size.width; i++, bgr += ncn) + { + gray[i] = static_cast(((cBGR0 * bgr[0] + cBGR1 * bgr[1] + cBGR2 * bgr[2] + 16384) >> 15) >> 8); } bgr += bgr_step - size.width * ncn; diff --git a/modules/imgcodecs/test/test_png.cpp b/modules/imgcodecs/test/test_png.cpp index f271950a5b..583039202a 100644 --- a/modules/imgcodecs/test/test_png.cpp +++ b/modules/imgcodecs/test/test_png.cpp @@ -150,19 +150,107 @@ TEST(Imgcodecs_Png, decode_regression27295) typedef testing::TestWithParam Imgcodecs_Png_PngSuite; +// Parameterized test for decoding PNG files from the PNGSuite test set TEST_P(Imgcodecs_Png_PngSuite, decode) { + // Construct full paths for the PNG image and corresponding ground truth XML file const string root = cvtest::TS::ptr()->get_data_path(); const string filename = root + "pngsuite/" + GetParam() + ".png"; const string xml_filename = root + "pngsuite/" + GetParam() + ".xml"; - FileStorage fs(xml_filename, FileStorage::READ); - EXPECT_TRUE(fs.isOpened()); + // Load the XML file containing the ground truth data + FileStorage fs(xml_filename, FileStorage::READ); + ASSERT_TRUE(fs.isOpened()); // Ensure the file was opened successfully + + // Load the image using IMREAD_UNCHANGED to preserve original format Mat src = imread(filename, IMREAD_UNCHANGED); + ASSERT_FALSE(src.empty()); // Ensure the image was loaded successfully + + // Load the ground truth matrix from XML Mat gt; fs.getFirstTopLevelNode() >> gt; + // Compare the image loaded with IMREAD_UNCHANGED to the ground truth EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), src, gt); + + // Declare matrices for ground truth in different imread flag combinations + Mat gt_0, gt_1, gt_2, gt_3, gt_256, gt_258; + + // Handle grayscale 8-bit and 16-bit images + if (gt.channels() == 1) + { + gt.copyTo(gt_2); // For IMREAD_ANYDEPTH + if (gt.depth() == CV_16U) + gt_2.convertTo(gt_0, CV_8U, 1. / 256); + else + gt_0 = gt_2; // For IMREAD_GRAYSCALE + + cvtColor(gt_2, gt_3, COLOR_GRAY2BGR); // For IMREAD_COLOR | IMREAD_ANYDEPTH + + if (gt.depth() == CV_16U) + gt_3.convertTo(gt_1, CV_8U, 1. / 256); + else + gt_1 = gt_3; // For IMREAD_COLOR + + gt_256 = gt_1; // For IMREAD_COLOR_RGB + gt_258 = gt_3; // For IMREAD_COLOR_RGB | IMREAD_ANYDEPTH + } + + // Handle color images (3 or 4 channels) with 8-bit and 16-bit depth + if (gt.channels() > 1) + { + // Convert to grayscale + cvtColor(gt, gt_2, COLOR_BGRA2GRAY); + if (gt.depth() == CV_16U) + gt_2.convertTo(gt_0, CV_8U, 1. / 256); + else + gt_0 = gt_2; + + // Convert to 3-channel BGR + if (gt.channels() == 3) + gt.copyTo(gt_3); + else + cvtColor(gt, gt_3, COLOR_BGRA2BGR); + + if (gt.depth() == CV_16U) + gt_3.convertTo(gt_1, CV_8U, 1. / 256); + else + gt_1 = gt_3; + + // Convert to RGB for IMREAD_COLOR_RGB variants + cvtColor(gt_1, gt_256, COLOR_BGR2RGB); + cvtColor(gt_3, gt_258, COLOR_BGR2RGB); + } + + // Perform comparisons with different imread flags + EXPECT_PRED_FORMAT2(cvtest::MatComparator(1, 0), imread(filename, IMREAD_GRAYSCALE), gt_0); + EXPECT_PRED_FORMAT2(cvtest::MatComparator(1, 0), imread(filename, IMREAD_COLOR), gt_1); + EXPECT_PRED_FORMAT2(cvtest::MatComparator(4, 0), imread(filename, IMREAD_ANYDEPTH), gt_2); + EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), imread(filename, IMREAD_COLOR | IMREAD_ANYDEPTH), gt_3); + EXPECT_PRED_FORMAT2(cvtest::MatComparator(1, 0), imread(filename, IMREAD_COLOR_RGB), gt_256); + EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), imread(filename, IMREAD_COLOR_RGB | IMREAD_ANYDEPTH), gt_258); + +// Uncomment this block to write out the decoded images for visual/manual inspection +// or for regenerating expected ground truth PNGs (for example, after changing decoder logic). +#if 0 + imwrite(filename + "_0.png", imread(filename, IMREAD_GRAYSCALE)); + imwrite(filename + "_1.png", imread(filename, IMREAD_COLOR)); + imwrite(filename + "_2.png", imread(filename, IMREAD_ANYDEPTH)); + imwrite(filename + "_3.png", imread(filename, IMREAD_COLOR | IMREAD_ANYDEPTH)); + imwrite(filename + "_256.png", imread(filename, IMREAD_COLOR_RGB)); + imwrite(filename + "_258.png", imread(filename, IMREAD_COLOR_RGB | IMREAD_ANYDEPTH)); +#endif + +// Uncomment this block to verify that saved images (from above) load identically +// when read back with IMREAD_UNCHANGED. Helps ensure write-read symmetry. +#if 0 + EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), imread(filename, IMREAD_GRAYSCALE), imread(filename + "_0.png", IMREAD_UNCHANGED)); + EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), imread(filename, IMREAD_COLOR), imread(filename + "_1.png", IMREAD_UNCHANGED)); + EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), imread(filename, IMREAD_ANYDEPTH), imread(filename + "_2.png", IMREAD_UNCHANGED)); + EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), imread(filename, IMREAD_COLOR | IMREAD_ANYDEPTH), imread(filename + "_3.png", IMREAD_UNCHANGED)); + EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), imread(filename, IMREAD_COLOR_RGB), imread(filename + "_256.png", IMREAD_UNCHANGED)); + EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), imread(filename, IMREAD_COLOR_RGB | IMREAD_ANYDEPTH), imread(filename + "_258.png", IMREAD_UNCHANGED)); +#endif } const string pngsuite_files[] = @@ -243,23 +331,13 @@ const string pngsuite_files[] = "f04n2c08", "f99n0g04", "g03n0g16", - "g03n2c08", - "g03n3p04", "g04n0g16", - "g04n2c08", - "g04n3p04", "g05n0g16", - "g05n2c08", - "g05n3p04", "g07n0g16", - "g07n2c08", - "g07n3p04", "g10n0g16", "g10n2c08", "g10n3p04", "g25n0g16", - "g25n2c08", - "g25n3p04", "oi1n0g16", "oi1n2c16", "oi2n0g16", @@ -333,6 +411,49 @@ const string pngsuite_files[] = INSTANTIATE_TEST_CASE_P(/*nothing*/, Imgcodecs_Png_PngSuite, testing::ValuesIn(pngsuite_files)); +typedef testing::TestWithParam Imgcodecs_Png_PngSuite_Gamma; + +// Parameterized test for decoding PNG files from the PNGSuite test set +TEST_P(Imgcodecs_Png_PngSuite_Gamma, decode) +{ + // Construct full paths for the PNG image and corresponding ground truth XML file + const string root = cvtest::TS::ptr()->get_data_path(); + const string filename = root + "pngsuite/" + GetParam() + ".png"; + const string xml_filename = root + "pngsuite/" + GetParam() + ".xml"; + + // Load the XML file containing the ground truth data + FileStorage fs(xml_filename, FileStorage::READ); + ASSERT_TRUE(fs.isOpened()); // Ensure the file was opened successfully + + // Load the image using IMREAD_UNCHANGED to preserve original format + Mat src = imread(filename, IMREAD_UNCHANGED); + ASSERT_FALSE(src.empty()); // Ensure the image was loaded successfully + + // Load the ground truth matrix from XML + Mat gt; + fs.getFirstTopLevelNode() >> gt; + + // Compare the image loaded with IMREAD_UNCHANGED to the ground truth + EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), src, gt); +} + +const string pngsuite_files_gamma[] = +{ + "g03n2c08", + "g03n3p04", + "g04n2c08", + "g04n3p04", + "g05n2c08", + "g05n3p04", + "g07n2c08", + "g07n3p04", + "g25n2c08", + "g25n3p04" +}; + +INSTANTIATE_TEST_CASE_P(/*nothing*/, Imgcodecs_Png_PngSuite_Gamma, + testing::ValuesIn(pngsuite_files_gamma)); + typedef testing::TestWithParam Imgcodecs_Png_PngSuite_Corrupted; TEST_P(Imgcodecs_Png_PngSuite_Corrupted, decode) From 54fe519ae0b2cc6c73934b3d6fe9c96cf5ad5a34 Mon Sep 17 00:00:00 2001 From: Alexander Smorkalov Date: Mon, 23 Jun 2025 09:21:25 +0300 Subject: [PATCH 27/62] phaseCorrelate documentation imrpovement. --- modules/imgproc/include/opencv2/imgproc.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/imgproc/include/opencv2/imgproc.hpp b/modules/imgproc/include/opencv2/imgproc.hpp index 059d26a41c..27b8fca24f 100644 --- a/modules/imgproc/include/opencv2/imgproc.hpp +++ b/modules/imgproc/include/opencv2/imgproc.hpp @@ -2992,9 +2992,9 @@ Calculates the cross-power spectrum of two supplied source arrays. The arrays ar with getOptimalDFTSize. The function performs the following equations: -- 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. +- First it applies a Hanning window to each image to remove possible edge effects, if it's provided +by user. See @ref createHanningWindow and . This window may +be cached until the array size changes to speed up processing time. - Next it computes the forward DFTs of each source array: \f[\mathbf{G}_a = \mathcal{F}\{src_1\}, \; \mathbf{G}_b = \mathcal{F}\{src_2\}\f] where \f$\mathcal{F}\f$ is the forward DFT. From 462929916304c131a4cbecf3a92e113eeca0075d Mon Sep 17 00:00:00 2001 From: Suleyman TURKMEN Date: Mon, 23 Jun 2025 12:47:21 +0300 Subject: [PATCH 28/62] Merge pull request #27469 from sturkmen72:png-fixes Fix for 2 channel PNGs #27469 closes #26825 ### Pull Request Readiness Checklist See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [x] The PR is proposed to the proper branch - [x] There is a reference to the original bug report and related work - [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [ ] The feature is well documented and sample code can be built with the project CMake --- modules/imgcodecs/src/grfmt_png.cpp | 8 ++------ modules/imgcodecs/test/test_animation.cpp | 5 +++-- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/modules/imgcodecs/src/grfmt_png.cpp b/modules/imgcodecs/src/grfmt_png.cpp index 20653cf3c1..a47db5aa2a 100644 --- a/modules/imgcodecs/src/grfmt_png.cpp +++ b/modules/imgcodecs/src/grfmt_png.cpp @@ -508,11 +508,6 @@ bool PngDecoder::readData( Mat& img ) { return false; } - // Asking for blend over with no alpha is invalid. - if (bop == 1 && mat_cur.channels() != 4) - { - return false; - } memcpy(&m_chunkIHDR.p[8], &chunk.p[12], 8); @@ -1654,7 +1649,8 @@ bool PngEncoder::writeanimation(const Animation& animation, const std::vector 2) + cvtColor(tmp, tmp, COLOR_BGRA2RGBA); apngFrame.setMat(tmp); deflateRectOp(apngFrame.getPixels(), x0, y0, w0, h0, bpp, rowbytes, zbuf_size, 0); diff --git a/modules/imgcodecs/test/test_animation.cpp b/modules/imgcodecs/test/test_animation.cpp index 5fead70135..2d45132ffd 100644 --- a/modules/imgcodecs/test/test_animation.cpp +++ b/modules/imgcodecs/test/test_animation.cpp @@ -669,14 +669,15 @@ TEST(Imgcodecs_APNG, animation_imread_preview) { // Set the path to the test image directory and filename for loading. const string root = cvtest::TS::ptr()->get_data_path(); - const string filename = root + "readwrite/033.png"; + const string filename = root + "readwrite/034.png"; cv::Mat imread_result; cv::imread(filename, imread_result, cv::IMREAD_UNCHANGED); EXPECT_FALSE(imread_result.empty()); Animation animation; - imreadanimation(filename, animation); + ASSERT_TRUE(imreadanimation(filename, animation)); EXPECT_FALSE(animation.still_image.empty()); + EXPECT_EQ((size_t)2, animation.frames.size()); EXPECT_EQ(0, cv::norm(animation.still_image, imread_result, cv::NORM_INF)); } From c3400603d020f7ab134977f82723d3bbe3dc6f20 Mon Sep 17 00:00:00 2001 From: Alexander Smorkalov Date: Mon, 23 Jun 2025 15:42:23 +0300 Subject: [PATCH 29/62] Fixed out-of-bound access to function table in cv::norm for HORM_HAMING. --- modules/core/src/norm.dispatch.cpp | 2 +- modules/core/src/norm.simd.hpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/core/src/norm.dispatch.cpp b/modules/core/src/norm.dispatch.cpp index 6999e55cdf..a20eaf0824 100644 --- a/modules/core/src/norm.dispatch.cpp +++ b/modules/core/src/norm.dispatch.cpp @@ -316,7 +316,7 @@ double norm( InputArray _src, int normType, InputArray _mask ) } NormFunc func = getNormFunc(normType >> 1, depth == CV_16F ? CV_32F : depth); - CV_Assert( func != 0 ); + CV_Assert( (normType >> 1) >= 3 || func != 0 ); if( src.isContinuous() && mask.empty() ) { diff --git a/modules/core/src/norm.simd.hpp b/modules/core/src/norm.simd.hpp index 68bd21258e..1e94fff719 100644 --- a/modules/core/src/norm.simd.hpp +++ b/modules/core/src/norm.simd.hpp @@ -1324,6 +1324,8 @@ NormFunc getNormFunc(int normType, int depth) } }; + if (normType >= 3 || normType < 0) return nullptr; + return normTab[normType][depth]; } From 7dfd1226ce15d6d2e5e85e118ac3ea57c1cd2a02 Mon Sep 17 00:00:00 2001 From: Souriya Trinh Date: Tue, 24 Jun 2025 03:55:14 +0200 Subject: [PATCH 30/62] Reduce the size of the checkerboard_radon.png image in the doc. Add references to the perspective camera model figure for solvePnP and related functions for better explanation. --- .../calib3d/real_time_pose/images/pnp.jpg | Bin 31839 -> 76405 bytes modules/calib3d/doc/solvePnP.markdown | 4 +- modules/calib3d/include/opencv2/calib3d.hpp | 38 ++++++++++++------ modules/calib3d/src/solvepnp.cpp | 2 +- 4 files changed, 29 insertions(+), 15 deletions(-) diff --git a/doc/tutorials/calib3d/real_time_pose/images/pnp.jpg b/doc/tutorials/calib3d/real_time_pose/images/pnp.jpg index f7d01f5f224f9a102150cabd4a8b393f32c353fb..cc34b217d9f12ac579fedd27df567f52fe593b7a 100644 GIT binary patch literal 76405 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%0DR_wTy7>k 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

B-Bu;rNs~)GPJxXnUzkiN^ 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 eS7r*i?@kw~{$DQ Additional references, notes
@note - Many functions in this module take a camera intrinsic matrix as an input parameter. Although all @@ -999,7 +1002,9 @@ An example program about homography from the camera displacement Check @ref tutorial_homography "the corresponding tutorial" for more details */ -/** @brief Finds an object pose from 3D-2D point correspondences. +/** @brief Finds an object pose \f$ {}^{c}\mathbf{T}_o \f$ from 3D-2D point correspondences: + +![Perspective projection, from object to camera frame](pics/pinhole_homogeneous_transformation.png){ width=50% } @see @ref calib3d_solvePnP @@ -1062,14 +1067,16 @@ More information about Perspective-n-Points is described in @ref calib3d_solvePn - point 1: [ squareLength / 2, squareLength / 2, 0] - point 2: [ squareLength / 2, -squareLength / 2, 0] - point 3: [-squareLength / 2, -squareLength / 2, 0] - - With @ref SOLVEPNP_SQPNP input points must be >= 3 + - With @ref SOLVEPNP_SQPNP input points must be >= 3 */ CV_EXPORTS_W bool solvePnP( InputArray objectPoints, InputArray imagePoints, InputArray cameraMatrix, InputArray distCoeffs, OutputArray rvec, OutputArray tvec, bool useExtrinsicGuess = false, int flags = SOLVEPNP_ITERATIVE ); -/** @brief Finds an object pose from 3D-2D point correspondences using the RANSAC scheme. +/** @brief Finds an object pose \f$ {}^{c}\mathbf{T}_o \f$ from 3D-2D point correspondences using the RANSAC scheme to deal with bad matches. + +![Perspective projection, from object to camera frame](pics/pinhole_homogeneous_transformation.png){ width=50% } @see @ref calib3d_solvePnP @@ -1102,8 +1109,8 @@ projections imagePoints and the projected (using @ref projectPoints ) objectPoin makes the function resistant to outliers. @note - - An example of how to use solvePNPRansac for object detection can be found at - opencv_source_code/samples/cpp/tutorial_code/calib3d/real_time_pose_estimation/ + - An example of how to use solvePnPRansac for object detection can be found at + @ref tutorial_real_time_pose - The default method used to estimate the camera pose for the Minimal Sample Sets step is #SOLVEPNP_EPNP. Exceptions are: - if you choose #SOLVEPNP_P3P or #SOLVEPNP_AP3P, these methods will be used. @@ -1129,7 +1136,9 @@ CV_EXPORTS_W bool solvePnPRansac( InputArray objectPoints, InputArray imagePoint OutputArray rvec, OutputArray tvec, OutputArray inliers, const UsacParams ¶ms=UsacParams()); -/** @brief Finds an object pose from 3 3D-2D point correspondences. +/** @brief Finds an object pose \f$ {}^{c}\mathbf{T}_o \f$ from **3** 3D-2D point correspondences. + +![Perspective projection, from object to camera frame](pics/pinhole_homogeneous_transformation.png){ width=50% } @see @ref calib3d_solvePnP @@ -1222,7 +1231,9 @@ CV_EXPORTS_W void solvePnPRefineVVS( InputArray objectPoints, InputArray imagePo TermCriteria criteria = TermCriteria(TermCriteria::EPS + TermCriteria::COUNT, 20, FLT_EPSILON), double VVSlambda = 1); -/** @brief Finds an object pose from 3D-2D point correspondences. +/** @brief Finds an object pose \f$ {}^{c}\mathbf{T}_o \f$ from 3D-2D point correspondences. + +![Perspective projection, from object to camera frame](pics/pinhole_homogeneous_transformation.png){ width=50% } @see @ref calib3d_solvePnP @@ -1293,6 +1304,7 @@ More information is described in @ref calib3d_solvePnP - point 1: [ squareLength / 2, squareLength / 2, 0] - point 2: [ squareLength / 2, -squareLength / 2, 0] - point 3: [-squareLength / 2, -squareLength / 2, 0] + - With @ref SOLVEPNP_SQPNP input points must be >= 3 */ CV_EXPORTS_W int solvePnPGeneric( InputArray objectPoints, InputArray imagePoints, InputArray cameraMatrix, InputArray distCoeffs, @@ -1373,7 +1385,8 @@ the board to make the detection more robust in various environments. Otherwise, border and the background is dark, the outer black squares cannot be segmented properly and so the square grouping and ordering algorithm fails. -Use gen_pattern.py (@ref tutorial_camera_calibration_pattern) to create checkerboard. +Use the `gen_pattern.py` Python script (@ref tutorial_camera_calibration_pattern) +to create the desired checkerboard pattern. */ CV_EXPORTS_W bool findChessboardCorners( InputArray image, Size patternSize, OutputArray corners, int flags = CALIB_CB_ADAPTIVE_THRESH + CALIB_CB_NORMALIZE_IMAGE ); @@ -1431,8 +1444,9 @@ which are located on the outside of the board. The following figure illustrates a sample checkerboard optimized for the detection. However, any other checkerboard can be used as well. -Use gen_pattern.py (@ref tutorial_camera_calibration_pattern) to create checkerboard. -![Checkerboard](pics/checkerboard_radon.png) +Use the `gen_pattern.py` Python script (@ref tutorial_camera_calibration_pattern) +to create the corresponding checkerboard pattern: +\image html pics/checkerboard_radon.png width=60% */ CV_EXPORTS_AS(findChessboardCornersSBWithMeta) bool findChessboardCornersSB(InputArray image,Size patternSize, OutputArray corners, @@ -4248,9 +4262,9 @@ optimization. It is the \f$max(width,height)/\pi\f$ or the provided \f$f_x\f$, \ @brief Finds an object pose from 3D-2D point correspondences for fisheye camera moodel. @param objectPoints Array of object points in the object coordinate space, Nx3 1-channel or - 1xN/Nx1 3-channel, where N is the number of points. vector\ can be also passed here. + 1xN/Nx1 3-channel, where N is the number of points. vector\ can also be passed here. @param imagePoints Array of corresponding image points, Nx2 1-channel or 1xN/Nx1 2-channel, - where N is the number of points. vector\ can be also passed here. + where N is the number of points. vector\ can also be passed here. @param cameraMatrix Input camera intrinsic matrix \f$\cameramatrix{A}\f$ . @param distCoeffs Input vector of distortion coefficients (4x1/1x4). @param rvec Output rotation vector (see @ref Rodrigues ) that, together with tvec, brings points from diff --git a/modules/calib3d/src/solvepnp.cpp b/modules/calib3d/src/solvepnp.cpp index c9428427e8..eefdc4116f 100644 --- a/modules/calib3d/src/solvepnp.cpp +++ b/modules/calib3d/src/solvepnp.cpp @@ -120,7 +120,7 @@ void drawFrameAxes(InputOutputArray image, InputArray cameraMatrix, InputArray d if (!allIn) { - CV_LOG_WARNING(NULL, "Some of projected axes endpoints are out of frame. The drawn axes may be not relaible."); + CV_LOG_WARNING(NULL, "Some of projected axes endpoints are out of frame. The drawn axes may be not reliable."); } // draw axes lines From ba70d1104f348f2800ef2820611ccb115191f73a Mon Sep 17 00:00:00 2001 From: Souriya Trinh Date: Tue, 24 Jun 2025 04:58:54 +0200 Subject: [PATCH 31/62] Check for empty vector to avoid throwing an exception in LineSegmentDetectorImpl::drawSegments() function. --- modules/imgproc/src/lsd.cpp | 4 ++++ modules/imgproc/test/test_lsd.cpp | 14 ++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/modules/imgproc/src/lsd.cpp b/modules/imgproc/src/lsd.cpp index 3b5b412e25..3e424a42b9 100644 --- a/modules/imgproc/src/lsd.cpp +++ b/modules/imgproc/src/lsd.cpp @@ -1076,6 +1076,10 @@ void LineSegmentDetectorImpl::drawSegments(InputOutputArray _image, InputArray l } Mat _lines = lines.getMat(); + if (_lines.empty()) + { + return; + } const int N = _lines.checkVector(4); CV_Assert(_lines.depth() == CV_32F || _lines.depth() == CV_32S); diff --git a/modules/imgproc/test/test_lsd.cpp b/modules/imgproc/test/test_lsd.cpp index 43d00b4928..5f31a15c21 100644 --- a/modules/imgproc/test/test_lsd.cpp +++ b/modules/imgproc/test/test_lsd.cpp @@ -402,4 +402,18 @@ TEST_F(Imgproc_LSD_Common, compareSegmentsVec4i) ASSERT_EQ(result2, 11); } +TEST_F(Imgproc_LSD_Common, drawSegmentsEmpty) +{ + Ptr detector = createLineSegmentDetector(LSD_REFINE_STD); + Mat1b img = Mat1b::zeros(240, 320); + + std::vector lines_4i; + detector->detect(img, lines_4i); + + Mat3b img_color = Mat3b::zeros(240, 320); + ASSERT_NO_THROW( + detector->drawSegments(img_color, lines_4i); + ); +} + }} // namespace From 0fac0e760fed15a3273f22849171615827980d48 Mon Sep 17 00:00:00 2001 From: Martin Valgur Date: Tue, 24 Jun 2025 23:33:30 +0300 Subject: [PATCH 32/62] imgcodecs: fix a minor bug in CMake for JPEG-XL Remove a likely copy-paste error. --- modules/imgcodecs/CMakeLists.txt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/modules/imgcodecs/CMakeLists.txt b/modules/imgcodecs/CMakeLists.txt index 7f97dde391..c322645b20 100644 --- a/modules/imgcodecs/CMakeLists.txt +++ b/modules/imgcodecs/CMakeLists.txt @@ -23,11 +23,6 @@ if(HAVE_JPEG) list(APPEND GRFMT_LIBS ${JPEG_LIBRARIES}) endif() -if(HAVE_JPEGXL) - ocv_include_directories(${OPENJPEG_INCLUDE_DIRS}) - list(APPEND GRFMT_LIBS ${OPENJPEG_LIBRARIES}) -endif() - if(HAVE_WEBP) add_definitions(-DHAVE_WEBP) ocv_include_directories(${WEBP_INCLUDE_DIR}) From 77d2a5868aeca512e4c422229594e26eeeb6a329 Mon Sep 17 00:00:00 2001 From: fengyuentau Date: Sat, 23 Dec 2023 17:48:13 +0800 Subject: [PATCH 33/62] feat: support more cann operators --- modules/dnn/src/layers/elementwise_layers.cpp | 48 +++++++++++- .../dnn/src/layers/nary_eltwise_layers.cpp | 3 +- modules/dnn/src/layers/reduce_layer.cpp | 56 +++++++++++++ modules/dnn/src/layers/reshape_layer.cpp | 78 +++++++++++++------ modules/dnn/src/layers/slice_layer.cpp | 2 +- modules/dnn/src/onnx/onnx_importer.cpp | 2 + modules/dnn/src/op_cann.cpp | 4 +- 7 files changed, 163 insertions(+), 30 deletions(-) diff --git a/modules/dnn/src/layers/elementwise_layers.cpp b/modules/dnn/src/layers/elementwise_layers.cpp index 92d0b221c8..4b57450072 100644 --- a/modules/dnn/src/layers/elementwise_layers.cpp +++ b/modules/dnn/src/layers/elementwise_layers.cpp @@ -845,8 +845,12 @@ struct GeluFunctor : public BaseFunctor { #endif } - bool supportBackend(int backendId, int) { - return backendId == DNN_BACKEND_OPENCV || backendId == DNN_BACKEND_CUDA || backendId == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH; + bool supportBackend(int backendId, int) + { + return backendId == DNN_BACKEND_OPENCV || + backendId == DNN_BACKEND_CUDA || + backendId == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH || + backendId == DNN_BACKEND_CANN; } void apply(const float* srcptr, float* dstptr, int stripeStart, int len, size_t planeSize, int cn0, int cn1) const { @@ -943,7 +947,19 @@ struct GeluFunctor : public BaseFunctor { const std::vector > &inputs, const std::vector >& nodes) { - CV_Error(Error::StsNotImplemented, ""); + auto input_wrapper = inputs[0].dynamicCast(); + + auto op = std::make_shared(name); + + auto input_node = nodes[0].dynamicCast()->getOp(); + op->set_input_x_by_name(*input_node, input_wrapper->name.c_str()); + auto input_desc = input_wrapper->getTensorDesc(); + op->update_input_desc_x(*input_desc); + + auto output_desc = std::make_shared(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT); + op->update_output_desc_y(*output_desc); + + return Ptr(new CannBackendNode(op)); } #endif // HAVE_CANN @@ -1781,7 +1797,10 @@ struct SqrtFunctor : public BaseDefaultFunctor bool supportBackend(int backendId, int) { - return backendId == DNN_BACKEND_OPENCV || backendId == DNN_BACKEND_CUDA || backendId == DNN_BACKEND_HALIDE; + return backendId == DNN_BACKEND_OPENCV || + backendId == DNN_BACKEND_CUDA || + backendId == DNN_BACKEND_CANN || + backendId == DNN_BACKEND_HALIDE; } inline float calculate(float x) const @@ -1811,6 +1830,27 @@ struct SqrtFunctor : public BaseDefaultFunctor } #endif // HAVE_DNN_NGRAPH +#ifdef HAVE_CANN + Ptr initCannOp(const std::string& name, + const std::vector > &inputs, + const std::vector >& nodes) + { + auto input_wrapper = inputs[0].dynamicCast(); + + auto op = std::make_shared(name); + + auto input_node = nodes[0].dynamicCast()->getOp(); + op->set_input_x_by_name(*input_node, input_wrapper->name.c_str()); + auto input_desc = input_wrapper->getTensorDesc(); + op->update_input_desc_x(*input_desc); + + auto output_desc = std::make_shared(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT); + op->update_output_desc_y(*output_desc); + + return Ptr(new CannBackendNode(op)); + } +#endif // HAVE_CANN + int64 getFLOPSPerElement() const { return 1; } }; diff --git a/modules/dnn/src/layers/nary_eltwise_layers.cpp b/modules/dnn/src/layers/nary_eltwise_layers.cpp index 305070f9b8..348252d6fd 100644 --- a/modules/dnn/src/layers/nary_eltwise_layers.cpp +++ b/modules/dnn/src/layers/nary_eltwise_layers.cpp @@ -271,7 +271,7 @@ public: if (backendId == DNN_BACKEND_CANN) return op == OPERATION::ADD || op == OPERATION::PROD || op == OPERATION::SUB || op == OPERATION::DIV || op == OPERATION::MAX || op == OPERATION::MIN || - op == OPERATION::MOD || op == OPERATION::FMOD; + op == OPERATION::MOD || op == OPERATION::FMOD || op == OPERATION::POW; #endif if (backendId == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH) return (op == OPERATION::ADD || @@ -979,6 +979,7 @@ public: BUILD_CANN_ELTWISE_OP(OPERATION::MIN, Minimum, name); BUILD_CANN_ELTWISE_OP(OPERATION::MOD, Mod, name); BUILD_CANN_ELTWISE_OP(OPERATION::FMOD, Mod, name); + BUILD_CANN_ELTWISE_OP(OPERATION::POW, Pow, name); #undef BUILD_CANN_ELTWISE_OP default: CV_Error(Error::StsNotImplemented, "Unsupported eltwise operation"); } diff --git a/modules/dnn/src/layers/reduce_layer.cpp b/modules/dnn/src/layers/reduce_layer.cpp index d9d8b111fd..19e8df995d 100644 --- a/modules/dnn/src/layers/reduce_layer.cpp +++ b/modules/dnn/src/layers/reduce_layer.cpp @@ -5,6 +5,8 @@ #include "../precomp.hpp" #include +#include "../op_cann.hpp" + namespace cv { namespace dnn { @@ -54,6 +56,13 @@ public: } virtual bool supportBackend(int backendId) CV_OVERRIDE { +#ifdef HAVE_CANN + if (backendId == DNN_BACKEND_CANN) + return reduce_type == ReduceType::MAX || reduce_type == ReduceType::MIN || + reduce_type == ReduceType::MEAN || reduce_type == ReduceType::SUM || + reduce_type == ReduceType::PROD || reduce_type == ReduceType::LOG_SUM || + reduce_type == ReduceType::LOG_SUM_EXP; +#endif return backendId == DNN_BACKEND_OPENCV; } @@ -497,6 +506,53 @@ public: } } +#ifdef HAVE_CANN + virtual Ptr initCann(const std::vector > &inputs, + const std::vector > &outputs, + const std::vector >& nodes) CV_OVERRIDE + { + CV_CheckFalse(axes.empty(), "DNN/CANN: Reduce layers need axes to build CANN operators"); + + auto input_node = nodes[0].dynamicCast()->getOp(); + auto input_wrapper = inputs[0].dynamicCast(); + auto input_desc = input_wrapper->getTensorDesc(); + + std::vector axes_shape{(int)axes.size()}; + Mat axes_mat(axes_shape, CV_32S, &axes[0]); + auto axes_node = std::make_shared(axes_mat.data, axes_mat.type(), axes_shape, cv::format("%s_axes", name.c_str())); + auto axes_desc = axes_node->getTensorDesc(); + + auto output_desc = std::make_shared(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT); + + std::shared_ptr reduce_op = nullptr; + switch (reduce_type) + { +#define BUILD_CANN_REDUCE_OP(op_type, class_name, op_name) \ + case op_type: { \ + auto op = std::make_shared(op_name); \ + op->set_input_x_by_name(*input_node, input_wrapper->name.c_str()); \ + op->set_input_axes(*(axes_node)->getOp()); \ + op->set_attr_keep_dims(keepdims); \ + op->update_input_desc_x(*input_desc); \ + op->update_input_desc_axes(*axes_desc); \ + op->update_output_desc_y(*output_desc); \ + reduce_op = op; \ + } break; + BUILD_CANN_REDUCE_OP(ReduceType::MAX, ReduceMax, name); + BUILD_CANN_REDUCE_OP(ReduceType::MIN, ReduceMin, name); + BUILD_CANN_REDUCE_OP(ReduceType::MEAN, ReduceMean, name); + BUILD_CANN_REDUCE_OP(ReduceType::SUM, ReduceSum, name); + BUILD_CANN_REDUCE_OP(ReduceType::PROD, ReduceProd, name); + BUILD_CANN_REDUCE_OP(ReduceType::LOG_SUM, ReduceLogSum, name); + BUILD_CANN_REDUCE_OP(ReduceType::LOG_SUM_EXP, ReduceLogSumExp, name); +#undef BUILD_CANN_REDUCE_OP + default: CV_Error(Error::StsNotImplemented, "Unsupported reduce operation"); + } + + return Ptr(new CannBackendNode(reduce_op)); + } +#endif // HAVE_CANN + private: enum ReduceType { diff --git a/modules/dnn/src/layers/reshape_layer.cpp b/modules/dnn/src/layers/reshape_layer.cpp index f259629e96..c82f009b75 100644 --- a/modules/dnn/src/layers/reshape_layer.cpp +++ b/modules/dnn/src/layers/reshape_layer.cpp @@ -184,6 +184,16 @@ public: for (i = 0; i < dims; i++) newShapeDesc[i] = paramShape.get(i); } + if (params.has("unsqueeze_axes")) + { + const DictValue& param_unsqueeze_axes = params.get("unsqueeze_axes"); + int len_axes = param_unsqueeze_axes.size(); + unsqueeze_axes.resize(len_axes); + for (int i = 0; i < len_axes; ++i) + { + unsqueeze_axes[i] = (int64_t)param_unsqueeze_axes.get(i); + } + } if (hasDynamicShapes) { dynamicShapes.clear(); @@ -331,33 +341,56 @@ public: const std::vector > &outputs, const std::vector >& nodes) CV_OVERRIDE { - auto x = inputs[0].dynamicCast(); + auto input_wrapper = inputs[0].dynamicCast(); - // create operator - auto op = std::make_shared(name); + if (!unsqueeze_axes.empty()) + { + auto op = std::make_shared(name); - // set attributes - op->set_attr_axis(axis); - op->set_attr_num_axes(numAxes); + // set attributes + op->set_attr_axes(unsqueeze_axes); - // set inputs - // set inputs : x - auto op_x = nodes[0].dynamicCast()->getOp(); - op->set_input_x_by_name(*op_x, x->name.c_str()); - auto x_desc = x->getTensorDesc(); - op->update_input_desc_x(*x_desc); - // set inputs : shape - std::vector shape_of_shape{(int)newShapeDesc.size()}; - Mat shape_mat(shape_of_shape, CV_32S, newShapeDesc.data()); - auto op_const_shape = std::make_shared(shape_mat.data, shape_mat.type(), shape_of_shape, cv::format("%s_shape", name.c_str())); - op->set_input_shape(*(op_const_shape->getOp())); - op->update_input_desc_shape(*(op_const_shape->getTensorDesc())); + // set inputs + // set inputs : x + auto input_node = nodes[0].dynamicCast()->getOp(); + op->set_input_x_by_name(*input_node, input_wrapper->name.c_str()); + auto input_desc = input_wrapper->getTensorDesc(); + op->update_input_desc_x(*input_desc); - // set outputs - auto output_y_desc = std::make_shared(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT); - op->update_output_desc_y(*output_y_desc); + // set outputs + auto desc_y = std::make_shared(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT); + op->update_output_desc_y(*desc_y); - return Ptr(new CannBackendNode(op)); + return Ptr(new CannBackendNode(op)); + } + else + { + // create operator + auto op = std::make_shared(name); + + // set attributes + op->set_attr_axis(axis); + op->set_attr_num_axes(numAxes); + + // set inputs + // set inputs : x + auto input_node = nodes[0].dynamicCast()->getOp(); + op->set_input_x_by_name(*input_node, input_wrapper->name.c_str()); + auto input_desc = input_wrapper->getTensorDesc(); + op->update_input_desc_x(*input_desc); + // set inputs : shape + std::vector shape_of_shape{(int)newShapeDesc.size()}; + Mat shape_mat(shape_of_shape, CV_32S, newShapeDesc.data()); + auto op_const_shape = std::make_shared(shape_mat.data, shape_mat.type(), shape_of_shape, cv::format("%s_shape", name.c_str())); + op->set_input_shape(*(op_const_shape->getOp())); + op->update_input_desc_shape(*(op_const_shape->getTensorDesc())); + + // set outputs + auto desc_y = std::make_shared(ge::Shape(), ge::FORMAT_NCHW, ge::DT_FLOAT); + op->update_output_desc_y(*desc_y); + + return Ptr(new CannBackendNode(op)); + } } #endif // HAVE_CANN @@ -509,6 +542,7 @@ private: bool shapesInitialized; float scale; int zeropoint; + std::vector unsqueeze_axes; }; Ptr ReshapeLayer::create(const LayerParams& params) diff --git a/modules/dnn/src/layers/slice_layer.cpp b/modules/dnn/src/layers/slice_layer.cpp index bd74ae96f8..9c5e0554d4 100644 --- a/modules/dnn/src/layers/slice_layer.cpp +++ b/modules/dnn/src/layers/slice_layer.cpp @@ -651,7 +651,7 @@ public: auto op = std::make_shared(name); // set attr - int n_split = static_cast(sliceRanges[0].size()); + int n_split = static_cast(outputs.size()); op->set_attr_num_split(n_split); // set inputs diff --git a/modules/dnn/src/onnx/onnx_importer.cpp b/modules/dnn/src/onnx/onnx_importer.cpp index 5b53442850..93198d66d1 100644 --- a/modules/dnn/src/onnx/onnx_importer.cpp +++ b/modules/dnn/src/onnx/onnx_importer.cpp @@ -2281,6 +2281,8 @@ void ONNXImporter::parseUnsqueeze(LayerParams& layerParams, const opencv_onnx::N if (axes.size() != 1) CV_Error(Error::StsNotImplemented, "Multidimensional unsqueeze"); + layerParams.set("unsqueeze_axes", axes); + int depth = layerParams.get("depth", CV_32F); MatShape inpShape = outShapes[node_proto.input(0)]; diff --git a/modules/dnn/src/op_cann.cpp b/modules/dnn/src/op_cann.cpp index 5894aef337..c36633dc15 100644 --- a/modules/dnn/src/op_cann.cpp +++ b/modules/dnn/src/op_cann.cpp @@ -61,14 +61,14 @@ CannConstOp::CannConstOp(const uint8_t* data, const int dtype, const std::vector { case CV_32F: break; case CV_32S: ge_dtype = ge::DT_INT32; break; - default: CV_Error(Error::StsNotImplemented, "Unsupported data type"); + default: CV_Error(Error::StsNotImplemented, cv::format("Unsupported data type %d of node %s", dtype, name.c_str())); } auto size_of_type = sizeof(float); switch (dtype) { case CV_32F: break; case CV_32S: size_of_type = sizeof(int); break; - default: CV_Error(Error::StsNotImplemented, "Unsupported data type"); + default: CV_Error(Error::StsNotImplemented, cv::format("Unsupported data type %d of node %s", dtype, name.c_str())); } desc_ = std::make_shared(ge_shape, ge::FORMAT_NCHW, ge_dtype); auto ge_tensor = std::make_shared(); From 288471f559ff4ef28e270ccd1351e0e0cbc9d85b Mon Sep 17 00:00:00 2001 From: Alexander Smorkalov Date: Wed, 25 Jun 2025 10:35:53 +0300 Subject: [PATCH 34/62] Fixed ifdef logic operators evaluation with some old python versions. --- modules/python/src2/hdr_parser.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/modules/python/src2/hdr_parser.py b/modules/python/src2/hdr_parser.py index 3d11b0ef2a..22532f14f5 100755 --- a/modules/python/src2/hdr_parser.py +++ b/modules/python/src2/hdr_parser.py @@ -97,7 +97,8 @@ def evaluate_conditional_inclusion_directive(directive, preprocessor_definitions ... ValueError: Failed to evaluate '#if strangedefinedvar' directive, stripped down to 'strangedefinedvar' """ - OPERATORS = { "!": "not ", "&&": "and", "&": "and", "||": "or", "|": "or" } + OPERATORS1 = {"&&": "and", "||": "or"} + OPERATORS2 = { "!": "not ", "&": "and", "|": "or" } input_directive = directive @@ -134,7 +135,10 @@ def evaluate_conditional_inclusion_directive(directive, preprocessor_definitions directive ) - for src_op, dst_op in OPERATORS.items(): + for src_op, dst_op in OPERATORS1.items(): + directive = directive.replace(src_op, dst_op) + + for src_op, dst_op in OPERATORS2.items(): directive = directive.replace(src_op, dst_op) try: From 42ec439d5a8094e04accc635d39e1cc2741dd78b Mon Sep 17 00:00:00 2001 From: Alexander Smorkalov Date: Wed, 25 Jun 2025 13:34:49 +0300 Subject: [PATCH 35/62] Documentaion update for haveImageReader function. --- modules/imgcodecs/include/opencv2/imgcodecs.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/imgcodecs/include/opencv2/imgcodecs.hpp b/modules/imgcodecs/include/opencv2/imgcodecs.hpp index 45a776f4d5..c610802b10 100644 --- a/modules/imgcodecs/include/opencv2/imgcodecs.hpp +++ b/modules/imgcodecs/include/opencv2/imgcodecs.hpp @@ -590,7 +590,7 @@ This can be useful for verifying support for a given image format before attempt @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. +It does not load the image codec implementation and decode data, but uses signature check. If the file cannot be opened or the format is unsupported, the function will return false. @sa cv::haveImageWriter, cv::imread, cv::imdecode From 83309580f4d01a6a6c58179fca4c66d2f464913c Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Wed, 25 Jun 2025 17:29:07 +0000 Subject: [PATCH 36/62] ffmpeg/4.x: update FFmpeg wrapper 2025.06 --- 3rdparty/ffmpeg/ffmpeg.cmake | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/3rdparty/ffmpeg/ffmpeg.cmake b/3rdparty/ffmpeg/ffmpeg.cmake index 718461ed6c..091197a010 100644 --- a/3rdparty/ffmpeg/ffmpeg.cmake +++ b/3rdparty/ffmpeg/ffmpeg.cmake @@ -1,8 +1,8 @@ -# Binaries branch name: ffmpeg/4.x_20241226 -# Binaries were created for OpenCV: 09892c9d1706f40342bda0bc404580f63492d9f8 -ocv_update(FFMPEG_BINARIES_COMMIT "d63d7c154c57242bf2283be61166be2bd30ec47e") -ocv_update(FFMPEG_FILE_HASH_BIN32 "642b94d032a8292b07550126934173f6") -ocv_update(FFMPEG_FILE_HASH_BIN64 "a8c3560c8f20e1ae465bef81580fa92c") +# Binaries branch name: ffmpeg/4.x_20250625 +# Binaries were created for OpenCV: e9f1da7e8e977a65b8bf8fe7ea8b92eef9171f19 +ocv_update(FFMPEG_BINARIES_COMMIT "ea9240e39bc0d6a69d2b1f0ba4513bdc7612a41e") +ocv_update(FFMPEG_FILE_HASH_BIN32 "2821ea672a11147a70974d760a54e9bc") +ocv_update(FFMPEG_FILE_HASH_BIN64 "e5c6936240201064b15bcecf1816e8f4") ocv_update(FFMPEG_FILE_HASH_CMAKE "8862c87496e2e8c375965e1277dee1c7") function(download_win_ffmpeg script_var) From 2e54a1f14f49ec411dc8a40eb025656fb426aec5 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Wed, 25 Jun 2025 17:37:46 +0000 Subject: [PATCH 37/62] videoio(test): re-enable FFmpeg tests on WIN32 - PR26800 --- modules/videoio/test/test_orientation.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/modules/videoio/test/test_orientation.cpp b/modules/videoio/test/test_orientation.cpp index 7ebabc02b2..7bc64b0ebf 100644 --- a/modules/videoio/test/test_orientation.cpp +++ b/modules/videoio/test/test_orientation.cpp @@ -8,10 +8,6 @@ using namespace std; namespace opencv_test { namespace { -// PR: https://github.com/opencv/opencv/pull/26800 -// TODO: Enable the tests back on Windows after FFmpeg plugin rebuild -#ifndef _WIN32 - struct VideoCaptureAPITests: TestWithParam { void SetUp() @@ -89,6 +85,4 @@ inline static std::string VideoCaptureAPITests_name_printer(const testing::TestP INSTANTIATE_TEST_CASE_P(videoio, VideoCaptureAPITests, testing::ValuesIn(supported_backends), VideoCaptureAPITests_name_printer); -#endif // WIN32 - }} // namespace From 029030095cab1510c16deb603157325d025e1d7a Mon Sep 17 00:00:00 2001 From: Maksim Shabunin Date: Wed, 25 Jun 2025 20:48:14 +0300 Subject: [PATCH 38/62] doc: added note to normalize function --- modules/core/include/opencv2/core.hpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/core/include/opencv2/core.hpp b/modules/core/include/opencv2/core.hpp index 12951d2a98..153bd17320 100644 --- a/modules/core/include/opencv2/core.hpp +++ b/modules/core/include/opencv2/core.hpp @@ -805,6 +805,11 @@ Possible usage with some positive example data: normalize(positiveData, normalizedData_minmax, 1.0, 0.0, NORM_MINMAX); @endcode +@note Due to rounding issues, min-max normalization can result in values outside provided boundaries. +If exact range conformity is needed, following workarounds can be used: +- use double floating point precision (dtype = CV_64F) +- manually clip values (`cv::max(res, left_bound, res)`, `cv::min(res, right_bound, res)` or `np.clip`) + @param src input array. @param dst output array of the same size as src . @param alpha norm value to normalize to or the lower range boundary in case of the range From 1d45a63598e825a140625efd4fed7391e041322d Mon Sep 17 00:00:00 2001 From: Martin Valgur Date: Thu, 26 Jun 2025 07:51:04 +0300 Subject: [PATCH 39/62] system.cpp: add missing #include --- modules/core/src/system.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/core/src/system.cpp b/modules/core/src/system.cpp index beb552ea74..3531678f92 100644 --- a/modules/core/src/system.cpp +++ b/modules/core/src/system.cpp @@ -43,6 +43,7 @@ #include "precomp.hpp" #include +#include #include #include @@ -125,7 +126,6 @@ void* allocSingletonNewBuffer(size_t size) { return malloc(size); } #endif #ifdef CV_ERROR_SET_TERMINATE_HANDLER -#include // std::set_terminate #include // std::abort #endif From 611d69aae115b72945334f5564eea456b2349c8d Mon Sep 17 00:00:00 2001 From: xaos-cz <64235805+xaos-cz@users.noreply.github.com> Date: Thu, 26 Jun 2025 14:45:19 +0200 Subject: [PATCH 40/62] filesystem support under Cygwin add support for enabling filesystem under Cygwin environment --- .../include/opencv2/core/utils/filesystem.private.hpp | 2 +- .../include/opencv2/core/utils/plugin_loader.private.hpp | 8 ++++---- modules/core/src/utils/filesystem.cpp | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/modules/core/include/opencv2/core/utils/filesystem.private.hpp b/modules/core/include/opencv2/core/utils/filesystem.private.hpp index 80c9a5282c..bf5dea416d 100644 --- a/modules/core/include/opencv2/core/utils/filesystem.private.hpp +++ b/modules/core/include/opencv2/core/utils/filesystem.private.hpp @@ -11,7 +11,7 @@ /* no support */ # elif defined WINRT || defined _WIN32_WCE /* not supported */ -# elif defined __ANDROID__ || defined __linux__ || defined _WIN32 || \ +# elif defined __ANDROID__ || defined __linux__ || defined _WIN32 || defined __CYGWIN__ || \ defined __FreeBSD__ || defined __bsdi__ || defined __HAIKU__ || \ defined __GNU__ || defined __QNX__ # define OPENCV_HAVE_FILESYSTEM_SUPPORT 1 diff --git a/modules/core/include/opencv2/core/utils/plugin_loader.private.hpp b/modules/core/include/opencv2/core/utils/plugin_loader.private.hpp index 23e48ee0eb..7f92cb5fa6 100644 --- a/modules/core/include/opencv2/core/utils/plugin_loader.private.hpp +++ b/modules/core/include/opencv2/core/utils/plugin_loader.private.hpp @@ -12,7 +12,7 @@ #if defined(_WIN32) #include -#elif defined(__linux__) || defined(__APPLE__) || defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__HAIKU__) || defined(__GLIBC__) || defined(__EMSCRIPTEN__) +#elif defined(__linux__) || defined(__APPLE__) || defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__HAIKU__) || defined(__GLIBC__) || defined(__EMSCRIPTEN__) || defined(__CYGWIN__) #include #endif @@ -65,7 +65,7 @@ void* getSymbol_(LibHandle_t h, const char* symbolName) { #if defined(_WIN32) return (void*)GetProcAddress(h, symbolName); -#elif defined(__linux__) || defined(__APPLE__) || defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__HAIKU__) || defined(__GLIBC__) || defined(__EMSCRIPTEN__) +#elif defined(__linux__) || defined(__APPLE__) || defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__HAIKU__) || defined(__GLIBC__) || defined(__EMSCRIPTEN__) || defined(__CYGWIN__) return dlsym(h, symbolName); #endif } @@ -79,7 +79,7 @@ LibHandle_t libraryLoad_(const FileSystemPath_t& filename) # else return LoadLibraryW(filename.c_str()); #endif -#elif defined(__linux__) || defined(__APPLE__) || defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__HAIKU__) || defined(__GLIBC__) || defined(__EMSCRIPTEN__) +#elif defined(__linux__) || defined(__APPLE__) || defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__HAIKU__) || defined(__GLIBC__) || defined(__EMSCRIPTEN__) || defined(__CYGWIN__) void* handle = dlopen(filename.c_str(), RTLD_NOW); CV_LOG_IF_DEBUG(NULL, !handle, "dlopen() error: " << dlerror()); return handle; @@ -91,7 +91,7 @@ void libraryRelease_(LibHandle_t h) { #if defined(_WIN32) FreeLibrary(h); -#elif defined(__linux__) || defined(__APPLE__) || defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__HAIKU__) || defined(__GLIBC__) || defined(__EMSCRIPTEN__) +#elif defined(__linux__) || defined(__APPLE__) || defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__HAIKU__) || defined(__GLIBC__) || defined(__EMSCRIPTEN__) || defined(__CYGWIN__) dlclose(h); #endif } diff --git a/modules/core/src/utils/filesystem.cpp b/modules/core/src/utils/filesystem.cpp index 0a44d48d40..da07782d73 100644 --- a/modules/core/src/utils/filesystem.cpp +++ b/modules/core/src/utils/filesystem.cpp @@ -34,7 +34,7 @@ #include #include #include -#elif defined __linux__ || defined __APPLE__ || defined __HAIKU__ || defined __FreeBSD__ || defined __GNU__ || defined __EMSCRIPTEN__ || defined __QNX__ +#elif defined __linux__ || defined __APPLE__ || defined __HAIKU__ || defined __FreeBSD__ || defined __GNU__ || defined __EMSCRIPTEN__ || defined __QNX__ || defined __CYGWIN__ #include #include #include @@ -194,7 +194,7 @@ cv::String getcwd() sz = GetCurrentDirectoryA((DWORD)buf.size(), buf.data()); return cv::String(buf.data(), (size_t)sz); #endif -#elif defined __linux__ || defined __APPLE__ || defined __HAIKU__ || defined __FreeBSD__ || defined __EMSCRIPTEN__ || defined __QNX__ +#elif defined __linux__ || defined __APPLE__ || defined __HAIKU__ || defined __FreeBSD__ || defined __EMSCRIPTEN__ || defined __QNX__ || defined __CYGWIN__ for(;;) { char* p = ::getcwd(buf.data(), buf.size()); @@ -228,7 +228,7 @@ bool createDirectory(const cv::String& path) #else int result = _mkdir(path.c_str()); #endif -#elif defined __linux__ || defined __APPLE__ || defined __HAIKU__ || defined __FreeBSD__ || defined __EMSCRIPTEN__ || defined __QNX__ +#elif defined __linux__ || defined __APPLE__ || defined __HAIKU__ || defined __FreeBSD__ || defined __EMSCRIPTEN__ || defined __QNX__ || defined __CYGWIN__ int result = mkdir(path.c_str(), 0777); #else int result = -1; @@ -343,7 +343,7 @@ private: Impl& operator=(const Impl&); // disabled }; -#elif defined __linux__ || defined __APPLE__ || defined __HAIKU__ || defined __FreeBSD__ || defined __GNU__ || defined __EMSCRIPTEN__ || defined __QNX__ +#elif defined __linux__ || defined __APPLE__ || defined __HAIKU__ || defined __FreeBSD__ || defined __GNU__ || defined __EMSCRIPTEN__ || defined __QNX__ || defined __CYGWIN__ struct FileLock::Impl { From d9e2b4c650936420f818a1bd1c1289c17e3376a9 Mon Sep 17 00:00:00 2001 From: Alexander Smorkalov Date: Thu, 26 Jun 2025 17:19:10 +0300 Subject: [PATCH 41/62] Fixed out-of-bound access issue in QR Encoder Java warappers. --- modules/objdetect/misc/java/gen_dict.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/objdetect/misc/java/gen_dict.json b/modules/objdetect/misc/java/gen_dict.json index 2f453a6a91..376c7ad920 100644 --- a/modules/objdetect/misc/java/gen_dict.json +++ b/modules/objdetect/misc/java/gen_dict.json @@ -34,8 +34,9 @@ " LOGD(\"%s\", method_name);", " Ptr* me = (Ptr*) self; //TODO: check for NULL", " const char* n_encoded_info = reinterpret_cast(env->GetByteArrayElements(encoded_info, NULL));", + " const jsize n_encoded_info_size = env->GetArrayLength(encoded_info);", " Mat& qrcode = *((Mat*)qrcode_nativeObj);", - " (*me)->encode( n_encoded_info, qrcode );", + " (*me)->encode( std::string(n_encoded_info, n_encoded_info_size), qrcode );", " } catch(const std::exception &e) {", " throwJavaException(env, &e, method_name);", " } catch (...) {", From 27867cc72c94637f240ebc7f39a448295f7d2b9f Mon Sep 17 00:00:00 2001 From: krikera Date: Fri, 27 Jun 2025 21:09:53 +0530 Subject: [PATCH 42/62] Add MORPH_DIAMOND support to samples and tutorials --- .../js_morphological_ops/js_morphological_ops.markdown | 2 +- .../py_morphological_ops/py_morphological_ops.markdown | 8 ++++++++ .../erosion_dilatation/erosion_dilatation.markdown | 2 ++ samples/cpp/morphology2.cpp | 7 +++++-- samples/cpp/tutorial_code/ImgProc/Morphology_1.cpp | 8 +++++--- samples/cpp/tutorial_code/ImgProc/Morphology_2.cpp | 4 ++-- samples/gpu/morphology.cpp | 9 +++++++-- .../ImgProc/erosion_dilatation/MorphologyDemo1.java | 4 +++- samples/python/morphology.py | 2 +- .../imgProc/erosion_dilatation/morphology_1.py | 6 ++++-- .../imgProc/opening_closing_hats/morphology_2.py | 6 ++++-- 11 files changed, 42 insertions(+), 16 deletions(-) diff --git a/doc/js_tutorials/js_imgproc/js_morphological_ops/js_morphological_ops.markdown b/doc/js_tutorials/js_imgproc/js_morphological_ops/js_morphological_ops.markdown index 9ffa218bb2..09cad1003c 100644 --- a/doc/js_tutorials/js_imgproc/js_morphological_ops/js_morphological_ops.markdown +++ b/doc/js_tutorials/js_imgproc/js_morphological_ops/js_morphological_ops.markdown @@ -158,7 +158,7 @@ Structuring Element ------------------- We manually created a structuring elements in the previous examples with help of cv.Mat.ones. It is -rectangular shape. But in some cases, you may need elliptical/circular shaped kernels. So for this +rectangular shape. But in some cases, you may need elliptical/circular shaped kernels or diamond-shaped kernels. So for this purpose, OpenCV has a function, **cv.getStructuringElement()**. You just pass the shape and size of the kernel, you get the desired kernel. diff --git a/doc/py_tutorials/py_imgproc/py_morphological_ops/py_morphological_ops.markdown b/doc/py_tutorials/py_imgproc/py_morphological_ops/py_morphological_ops.markdown index 24b504914f..6d56d26095 100644 --- a/doc/py_tutorials/py_imgproc/py_morphological_ops/py_morphological_ops.markdown +++ b/doc/py_tutorials/py_imgproc/py_morphological_ops/py_morphological_ops.markdown @@ -147,6 +147,14 @@ array([[0, 0, 1, 0, 0], [1, 1, 1, 1, 1], [0, 0, 1, 0, 0], [0, 0, 1, 0, 0]], dtype=uint8) + +# Diamond-shaped Kernel +>>> cv.getStructuringElement(cv.MORPH_DIAMOND,(5,5)) +array([[0, 0, 1, 0, 0], + [0, 1, 1, 1, 0], + [1, 1, 1, 1, 1], + [0, 1, 1, 1, 0], + [0, 0, 1, 0, 0]], dtype=uint8) @endcode Additional Resources -------------------- diff --git a/doc/tutorials/imgproc/erosion_dilatation/erosion_dilatation.markdown b/doc/tutorials/imgproc/erosion_dilatation/erosion_dilatation.markdown index 36829683d5..2ba3061bab 100644 --- a/doc/tutorials/imgproc/erosion_dilatation/erosion_dilatation.markdown +++ b/doc/tutorials/imgproc/erosion_dilatation/erosion_dilatation.markdown @@ -129,6 +129,7 @@ receives three arguments: - Rectangular box: MORPH_RECT - Cross: MORPH_CROSS - Ellipse: MORPH_ELLIPSE + - Diamond: MORPH_DIAMOND Then, we just have to specify the size of our kernel and the *anchor point*. If not specified, it is assumed to be in the center. @@ -256,6 +257,7 @@ receives two arguments and returns the processed image: - Rectangular box: MORPH_RECT - Cross: MORPH_CROSS - Ellipse: MORPH_ELLIPSE + - Diamond: MORPH_DIAMOND Then, we just have to specify the size of our kernel and the *anchor point*. If the anchor point not specified, it is assumed to be in the center. diff --git a/samples/cpp/morphology2.cpp b/samples/cpp/morphology2.cpp index f1d8d15b17..e6bb659cac 100644 --- a/samples/cpp/morphology2.cpp +++ b/samples/cpp/morphology2.cpp @@ -12,12 +12,13 @@ static void help(char** argv) printf("\nShow off image morphology: erosion, dialation, open and close\n" "Call:\n %s [image]\n" - "This program also shows use of rect, ellipse and cross kernels\n\n", argv[0]); + "This program also shows use of rect, ellipse, cross and diamond kernels\n\n", argv[0]); printf( "Hot keys: \n" "\tESC - quit the program\n" "\tr - use rectangle structuring element\n" "\te - use elliptic structuring element\n" "\tc - use cross-shaped structuring element\n" + "\td - use diamond-shaped structuring element\n" "\tSPACE - loop through all the options\n" ); } @@ -101,8 +102,10 @@ int main( int argc, char** argv ) element_shape = MORPH_RECT; else if( c == 'c' ) element_shape = MORPH_CROSS; + else if( c == 'd' ) + element_shape = MORPH_DIAMOND; else if( c == ' ' ) - element_shape = (element_shape + 1) % 3; + element_shape = (element_shape + 1) % 4; } return 0; diff --git a/samples/cpp/tutorial_code/ImgProc/Morphology_1.cpp b/samples/cpp/tutorial_code/ImgProc/Morphology_1.cpp index 33e006269d..6c30ff34ac 100644 --- a/samples/cpp/tutorial_code/ImgProc/Morphology_1.cpp +++ b/samples/cpp/tutorial_code/ImgProc/Morphology_1.cpp @@ -18,7 +18,7 @@ int erosion_elem = 0; int erosion_size = 0; int dilation_elem = 0; int dilation_size = 0; -int const max_elem = 2; +int const max_elem = 3; int const max_kernel_size = 21; /** Function Headers */ @@ -47,7 +47,7 @@ int main( int argc, char** argv ) moveWindow( "Dilation Demo", src.cols, 0 ); /// Create Erosion Trackbar - createTrackbar( "Element:\n 0: Rect \n 1: Cross \n 2: Ellipse", "Erosion Demo", + createTrackbar( "Element:\n 0: Rect \n 1: Cross \n 2: Ellipse \n 3: Diamond", "Erosion Demo", &erosion_elem, max_elem, Erosion ); @@ -56,7 +56,7 @@ int main( int argc, char** argv ) Erosion ); /// Create Dilation Trackbar - createTrackbar( "Element:\n 0: Rect \n 1: Cross \n 2: Ellipse", "Dilation Demo", + createTrackbar( "Element:\n 0: Rect \n 1: Cross \n 2: Ellipse \n 3: Diamond", "Dilation Demo", &dilation_elem, max_elem, Dilation ); @@ -83,6 +83,7 @@ void Erosion( int, void* ) if( erosion_elem == 0 ){ erosion_type = MORPH_RECT; } else if( erosion_elem == 1 ){ erosion_type = MORPH_CROSS; } else if( erosion_elem == 2) { erosion_type = MORPH_ELLIPSE; } + else if( erosion_elem == 3) { erosion_type = MORPH_DIAMOND; } //![kernel] Mat element = getStructuringElement( erosion_type, @@ -106,6 +107,7 @@ void Dilation( int, void* ) if( dilation_elem == 0 ){ dilation_type = MORPH_RECT; } else if( dilation_elem == 1 ){ dilation_type = MORPH_CROSS; } else if( dilation_elem == 2) { dilation_type = MORPH_ELLIPSE; } + else if( dilation_elem == 3) { dilation_type = MORPH_DIAMOND; } Mat element = getStructuringElement( dilation_type, Size( 2*dilation_size + 1, 2*dilation_size+1 ), diff --git a/samples/cpp/tutorial_code/ImgProc/Morphology_2.cpp b/samples/cpp/tutorial_code/ImgProc/Morphology_2.cpp index 3bc40d3b0d..bab4489b7e 100644 --- a/samples/cpp/tutorial_code/ImgProc/Morphology_2.cpp +++ b/samples/cpp/tutorial_code/ImgProc/Morphology_2.cpp @@ -18,7 +18,7 @@ int morph_elem = 0; int morph_size = 0; int morph_operator = 0; int const max_operator = 4; -int const max_elem = 2; +int const max_elem = 3; int const max_kernel_size = 21; const char* window_name = "Morphology Transformations Demo"; @@ -54,7 +54,7 @@ int main( int argc, char** argv ) //![create_trackbar2] /// Create Trackbar to select kernel type - createTrackbar( "Element:\n 0: Rect - 1: Cross - 2: Ellipse", window_name, + createTrackbar( "Element:\n 0: Rect - 1: Cross - 2: Ellipse - 3: Diamond", window_name, &morph_elem, max_elem, Morphology_Operations ); //![create_trackbar2] diff --git a/samples/gpu/morphology.cpp b/samples/gpu/morphology.cpp index b5e50caa3c..0ace6ddced 100644 --- a/samples/gpu/morphology.cpp +++ b/samples/gpu/morphology.cpp @@ -101,8 +101,12 @@ int App::run() element_shape = MORPH_CROSS; break; + case 'd': + element_shape = MORPH_DIAMOND; + break; + case ' ': - element_shape = (element_shape + 1) % 3; + element_shape = (element_shape + 1) % 4; break; } } @@ -113,13 +117,14 @@ void App::help() cout << "Show off image morphology: erosion, dialation, open and close \n"; cout << "Call: \n"; cout << " gpu-example-morphology [image] \n"; - cout << "This program also shows use of rect, ellipse and cross kernels \n" << endl; + cout << "This program also shows use of rect, ellipse, cross and diamond kernels \n" << endl; cout << "Hot keys: \n"; cout << "\tESC - quit the program \n"; cout << "\tr - use rectangle structuring element \n"; cout << "\te - use elliptic structuring element \n"; cout << "\tc - use cross-shaped structuring element \n"; + cout << "\td - use diamond-shaped structuring element \n"; cout << "\tSPACE - loop through all the options \n" << endl; } diff --git a/samples/java/tutorial_code/ImgProc/erosion_dilatation/MorphologyDemo1.java b/samples/java/tutorial_code/ImgProc/erosion_dilatation/MorphologyDemo1.java index 084fc9544d..8f11965995 100644 --- a/samples/java/tutorial_code/ImgProc/erosion_dilatation/MorphologyDemo1.java +++ b/samples/java/tutorial_code/ImgProc/erosion_dilatation/MorphologyDemo1.java @@ -23,7 +23,7 @@ import org.opencv.imgcodecs.Imgcodecs; import org.opencv.imgproc.Imgproc; public class MorphologyDemo1 { - private static final String[] ELEMENT_TYPE = { "Rectangle", "Cross", "Ellipse" }; + private static final String[] ELEMENT_TYPE = { "Rectangle", "Cross", "Ellipse", "Diamond" }; private static final String[] MORPH_OP = { "Erosion", "Dilatation" }; private static final int MAX_KERNEL_SIZE = 21; private Mat matImgSrc; @@ -79,6 +79,8 @@ public class MorphologyDemo1 { elementType = Imgproc.MORPH_CROSS; } else if (cb.getSelectedIndex() == 2) { elementType = Imgproc.MORPH_ELLIPSE; + } else if (cb.getSelectedIndex() == 3) { + elementType = Imgproc.MORPH_DIAMOND; } update(); } diff --git a/samples/python/morphology.py b/samples/python/morphology.py index 183f5e8288..f816e4916d 100755 --- a/samples/python/morphology.py +++ b/samples/python/morphology.py @@ -40,7 +40,7 @@ def main(): cv.imshow('original', img) modes = cycle(['erode/dilate', 'open/close', 'blackhat/tophat', 'gradient']) - str_modes = cycle(['ellipse', 'rect', 'cross']) + str_modes = cycle(['ellipse', 'rect', 'cross', 'diamond']) if PY3: cur_mode = next(modes) diff --git a/samples/python/tutorial_code/imgProc/erosion_dilatation/morphology_1.py b/samples/python/tutorial_code/imgProc/erosion_dilatation/morphology_1.py index 3645eab3d1..21b404ef46 100644 --- a/samples/python/tutorial_code/imgProc/erosion_dilatation/morphology_1.py +++ b/samples/python/tutorial_code/imgProc/erosion_dilatation/morphology_1.py @@ -5,9 +5,9 @@ import argparse src = None erosion_size = 0 -max_elem = 2 +max_elem = 3 max_kernel_size = 21 -title_trackbar_element_shape = 'Element:\n 0: Rect \n 1: Cross \n 2: Ellipse' +title_trackbar_element_shape = 'Element:\n 0: Rect \n 1: Cross \n 2: Ellipse \n 3: Diamond' title_trackbar_kernel_size = 'Kernel size:\n 2n +1' title_erosion_window = 'Erosion Demo' title_dilation_window = 'Dilation Demo' @@ -42,6 +42,8 @@ def morph_shape(val): return cv.MORPH_CROSS elif val == 2: return cv.MORPH_ELLIPSE + elif val == 3: + return cv.MORPH_DIAMOND ## [erosion] diff --git a/samples/python/tutorial_code/imgProc/opening_closing_hats/morphology_2.py b/samples/python/tutorial_code/imgProc/opening_closing_hats/morphology_2.py index e0fc758467..667fa50a30 100644 --- a/samples/python/tutorial_code/imgProc/opening_closing_hats/morphology_2.py +++ b/samples/python/tutorial_code/imgProc/opening_closing_hats/morphology_2.py @@ -5,10 +5,10 @@ import argparse morph_size = 0 max_operator = 4 -max_elem = 2 +max_elem = 3 max_kernel_size = 21 title_trackbar_operator_type = 'Operator:\n 0: Opening - 1: Closing \n 2: Gradient - 3: Top Hat \n 4: Black Hat' -title_trackbar_element_type = 'Element:\n 0: Rect - 1: Cross - 2: Ellipse' +title_trackbar_element_type = 'Element:\n 0: Rect - 1: Cross - 2: Ellipse - 3: Diamond' title_trackbar_kernel_size = 'Kernel size:\n 2n + 1' title_window = 'Morphology Transformations Demo' morph_op_dic = {0: cv.MORPH_OPEN, 1: cv.MORPH_CLOSE, 2: cv.MORPH_GRADIENT, 3: cv.MORPH_TOPHAT, 4: cv.MORPH_BLACKHAT} @@ -24,6 +24,8 @@ def morphology_operations(val): morph_elem = cv.MORPH_CROSS elif val_type == 2: morph_elem = cv.MORPH_ELLIPSE + elif val_type == 3: + morph_elem = cv.MORPH_DIAMOND element = cv.getStructuringElement(morph_elem, (2*morph_size + 1, 2*morph_size+1), (morph_size, morph_size)) operation = morph_op_dic[morph_operator] From 43112409ef0b711b18c2dc12433ad5e2403aea71 Mon Sep 17 00:00:00 2001 From: Martin Date: Sat, 28 Jun 2025 21:06:42 +0200 Subject: [PATCH 43/62] Merge pull request #26974 from klosteraner:Fix-IntersectConvexConvex Issue 26972: Proper treatment of float values in intersectConvexConvex #26974 As outlined in https://github.com/opencv/opencv/issues/26972 the function `intersectConvexConvex()` may not work as expected in the corner case, where two polygons intersect at a corner. A concrete example is given that I added as unit test. The unit test would fail without the proposed bug fix. I recommend porting the fix to all versions. Now concerning the fix: When digging into the implementation I found, that when the line intersections are computed, openCV currently does not apply floating point comparison syntax, but pretends that line end points are exact. Instead I replaced the formulation using the eps that is already used in another component of the function in line.277: `epx=1e-5`. IMO that is solid enough, definitely better than assuming an exact floating point comparison is possible. As a follow up I would suggest to use a scalable eps, s.t. also cases with high floating point numbers would be less error prone. However that would need to be done in all relevant sub steps, not just the line intersection code. So for me outside the scope of this fix. ### Pull Request Readiness Checklist See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [x] The PR is proposed to the proper branch - [ ] There is a reference to the original bug report and related work - [ ] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [ ] The feature is well documented and sample code can be built with the project CMake --- modules/imgproc/src/geometry.cpp | 10 ++++----- .../test/test_intersectconvexconvex.cpp | 22 +++++++++++++++++++ 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/modules/imgproc/src/geometry.cpp b/modules/imgproc/src/geometry.cpp index eed9ff3c5c..c7a52b2f46 100644 --- a/modules/imgproc/src/geometry.cpp +++ b/modules/imgproc/src/geometry.cpp @@ -330,20 +330,20 @@ static LineSegmentIntersection parallelInt( Point2f a, Point2f b, Point2f c, Poi static LineSegmentIntersection intersectLineSegments( Point2f a, Point2f b, Point2f c, Point2f d, Point2f& p, Point2f& q ) { - double denom = (a.x - b.x) * (double)(d.y - c.y) - (a.y - b.y) * (double)(d.x - c.x); + double denom = ((double)a.x - b.x) * ((double)d.y - c.y) - ((double)a.y - b.y) * ((double)d.x - c.x); // If denom is zero, then segments are parallel: handle separately. if( denom == 0. ) return parallelInt(a, b, c, d, p, q); - double num = (d.y - a.y) * (double)(a.x - c.x) + (a.x - d.x) * (double)(a.y - c.y); + double num = ((double)d.y - a.y) * ((double)a.x - c.x) + ((double)a.x - d.x) * ((double)a.y - c.y); double s = num / denom; - num = (b.y - a.y) * (double)(a.x - c.x) + (c.y - a.y) * (double)(b.x - a.x); + num = ((double)b.y - a.y) * ((double)a.x - c.x) + ((double)c.y - a.y) * ((double)b.x - a.x); double t = num / denom; - p.x = (float)(a.x + s*(b.x - a.x)); - p.y = (float)(a.y + s*(b.y - a.y)); + p.x = (float)(a.x + s*((double)b.x - a.x)); + p.y = (float)(a.y + s*((double)b.y - a.y)); q = p; return s < 0. || s > 1. || t < 0. || t > 1. ? LS_NO_INTERSECTION : diff --git a/modules/imgproc/test/test_intersectconvexconvex.cpp b/modules/imgproc/test/test_intersectconvexconvex.cpp index 00e3674f48..146c891b84 100644 --- a/modules/imgproc/test/test_intersectconvexconvex.cpp +++ b/modules/imgproc/test/test_intersectconvexconvex.cpp @@ -292,5 +292,27 @@ TEST(Imgproc_IntersectConvexConvex, not_convex) EXPECT_LE(area, 0.f); } +// The intersection was not properly detected when one line sneaked its way in through an edge point +TEST(Imgproc_IntersectConvexConvex, intersection_at_line_transition) +{ + std::vector convex1 = { + { -1.7604526f, -0.00028443217f }, + {1276.5778f , 0.2091252f}, + {1276.4617f , 719.27f}, + { -1.8754264f, 719.06866f} + + }; + std::vector convex2 = { + { 0.f , 0.f }, + {1280.f , 0.f }, + {1280.f , 720.f}, + { 0.f , 720.f } + }; + std::vector intersection; + + float area = cv::intersectConvexConvex(convex1, convex2, intersection, false); + EXPECT_GE(cv::contourArea(convex1), area); + EXPECT_GE(cv::contourArea(convex2), area); +} } // namespace } // opencv_test From 66e5fce9282fb2a9daaec9a79e0e7ed8bb01db06 Mon Sep 17 00:00:00 2001 From: Vadim Pisarevsky Date: Tue, 1 Jul 2025 18:38:22 +0300 Subject: [PATCH 44/62] Merge pull request #27499 from vpisarev:image_io_with_metadata Extend image I/O API with metadata support #27499 Covered with the PR: * AVIF encoder can write exif, xmp, icc * AVIF decoder can read exif * JPEG encoder can write exif * JPEG decoder can read exif * PNG encoder can write exif * PNG decoder can read exif This PR is a sort of preamble for #27488. I suggest to merge this one first to OpenCV 4.x, then promote this change to OpenCV 5.x and then provide extra API to read and write metadata in 5.x (or maybe 4.x) in a style similar to #27488. Maybe in that PR exif packing/unpacking should be done using a separate external API. That is, metadata reading and writing can/should be done in 2 steps: * [1] pack and then [2] embed exif into image at the encoding stage. * [1] extract and then [2] unpack exif at the decoding stage. ### Pull Request Readiness Checklist See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [x] The PR is proposed to the proper branch - [x] There is a reference to the original bug report and related work - [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [ ] The feature is well documented and sample code can be built with the project CMake --- .../imgcodecs/include/opencv2/imgcodecs.hpp | 68 +++++ modules/imgcodecs/src/exif.cpp | 4 + modules/imgcodecs/src/exif.hpp | 4 + modules/imgcodecs/src/grfmt_avif.cpp | 24 +- modules/imgcodecs/src/grfmt_base.cpp | 36 +++ modules/imgcodecs/src/grfmt_base.hpp | 23 ++ modules/imgcodecs/src/grfmt_jpeg.cpp | 18 ++ modules/imgcodecs/src/grfmt_png.cpp | 12 + modules/imgcodecs/src/loadsave.cpp | 161 +++++++++++- modules/imgcodecs/test/test_exif.cpp | 241 +++++++++++++++++- modules/python/test/test_imread.py | 12 + 11 files changed, 589 insertions(+), 14 deletions(-) diff --git a/modules/imgcodecs/include/opencv2/imgcodecs.hpp b/modules/imgcodecs/include/opencv2/imgcodecs.hpp index c610802b10..d0f6ee61d6 100644 --- a/modules/imgcodecs/include/opencv2/imgcodecs.hpp +++ b/modules/imgcodecs/include/opencv2/imgcodecs.hpp @@ -251,6 +251,15 @@ enum ImwriteGIFCompressionFlags { IMWRITE_GIF_COLORTABLE_SIZE_256 = 8 }; +enum ImageMetadataType +{ + IMAGE_METADATA_UNKNOWN = -1, + IMAGE_METADATA_EXIF = 0, + IMAGE_METADATA_XMP = 1, + IMAGE_METADATA_ICCP = 2, + IMAGE_METADATA_MAX = 2 +}; + //! @} imgcodecs_flags /** @brief Represents an animation with multiple frames. @@ -360,6 +369,17 @@ The image passing through the img parameter can be pre-allocated. The memory is */ CV_EXPORTS_W void imread( const String& filename, OutputArray dst, int flags = IMREAD_COLOR_BGR ); +/** @brief Reads an image from a file together with associated metadata. + +The function imreadWithMetadata reads image from the specified file. It does the same thing as imread, but additionally reads metadata if the corresponding file contains any. +@param filename Name of the file to be loaded. +@param metadataTypes Output vector with types of metadata chucks returned in metadata, see ImageMetadataType. +@param metadata Output vector of vectors or vector of matrices to store the retrieved metadata +@param flags Flag that can take values of cv::ImreadModes +*/ +CV_EXPORTS_W Mat imreadWithMetadata( const String& filename, CV_OUT std::vector& metadataTypes, + OutputArrayOfArrays metadata, int flags = IMREAD_ANYCOLOR); + /** @brief Loads a multi-page image from a file. The function imreadmulti loads a multi-page image from the specified file into a vector of Mat objects. @@ -508,6 +528,20 @@ It also demonstrates how to save multiple images in a TIFF file: CV_EXPORTS_W bool imwrite( const String& filename, InputArray img, const std::vector& params = std::vector()); +/** @brief Saves an image to a specified file with metadata + +The function imwriteWithMetadata saves the image to the specified file. It does the same thing as imwrite, but additionally writes metadata if the corresponding format supports it. +@param filename Name of the file. As with imwrite, image format is determined by the file extension. +@param img (Mat or vector of Mat) Image or Images to be saved. +@param metadataTypes Vector with types of metadata chucks stored in metadata to write, see ImageMetadataType. +@param metadata Vector of vectors or vector of matrices with chunks of metadata to store into the file +@param params Format-specific parameters encoded as pairs (paramId_1, paramValue_1, paramId_2, paramValue_2, ... .) see cv::ImwriteFlags +*/ +CV_EXPORTS_W bool imwriteWithMetadata( const String& filename, InputArray img, + const std::vector& metadataTypes, + InputArrayOfArrays& metadata, + const std::vector& params = std::vector()); + //! @brief multi-image overload for bindings CV_WRAP static inline bool imwritemulti(const String& filename, InputArrayOfArrays img, @@ -529,6 +563,22 @@ See cv::imread for the list of supported formats and flags description. */ CV_EXPORTS_W Mat imdecode( InputArray buf, int flags ); +/** @brief Reads an image from a buffer in memory together with associated metadata. + +The function imdecode reads an image from the specified buffer in the memory. If the buffer is too short or +contains invalid data, the function returns an empty matrix ( Mat::data==NULL ). + +See cv::imread for the list of supported formats and flags description. + +@note In the case of color images, the decoded images will have the channels stored in **B G R** order. +@param buf Input array or vector of bytes. +@param metadataTypes Output vector with types of metadata chucks returned in metadata, see ImageMetadataType. +@param metadata Output vector of vectors or vector of matrices to store the retrieved metadata +@param flags The same flags as in cv::imread, see cv::ImreadModes. +*/ +CV_EXPORTS_W Mat imdecodeWithMetadata( InputArray buf, CV_OUT std::vector& metadataTypes, + OutputArrayOfArrays metadata, int flags = IMREAD_ANYCOLOR ); + /** @overload @param buf Input array or vector of bytes. @param flags The same flags as in cv::imread, see cv::ImreadModes. @@ -567,6 +617,24 @@ CV_EXPORTS_W bool imencode( const String& ext, InputArray img, CV_OUT std::vector& buf, const std::vector& params = std::vector()); +/** @brief Encodes an image into a memory buffer. + +The function imencode compresses the image and stores it in the memory buffer that is resized to fit the +result. See cv::imwrite for the list of supported formats and flags description. + +@param ext File extension that defines the output format. Must include a leading period. +@param img Image to be compressed. +@param metadataTypes Vector with types of metadata chucks stored in metadata to write, see ImageMetadataType. +@param metadata Vector of vectors or vector of matrices with chunks of metadata to store into the file +@param buf Output buffer resized to fit the compressed image. +@param params Format-specific parameters. See cv::imwrite and cv::ImwriteFlags. +*/ +CV_EXPORTS_W bool imencodeWithMetadata( const String& ext, InputArray img, + const std::vector& metadataTypes, + InputArrayOfArrays metadata, + CV_OUT std::vector& buf, + const std::vector& params = std::vector()); + /** @brief Encodes array of images into a memory buffer. The function is analog to cv::imencode for in-memory multi-page image compression. diff --git a/modules/imgcodecs/src/exif.cpp b/modules/imgcodecs/src/exif.cpp index 8ed9760556..3f1bbdbe18 100644 --- a/modules/imgcodecs/src/exif.cpp +++ b/modules/imgcodecs/src/exif.cpp @@ -94,6 +94,10 @@ ExifEntry_t ExifReader::getTag(const ExifTagName tag) const return entry; } +const std::vector& ExifReader::getData() const +{ + return m_data; +} /** * @brief Parsing the exif data buffer and prepare (internal) exif directory diff --git a/modules/imgcodecs/src/exif.hpp b/modules/imgcodecs/src/exif.hpp index a8914bec03..3c5fbc7fe8 100644 --- a/modules/imgcodecs/src/exif.hpp +++ b/modules/imgcodecs/src/exif.hpp @@ -175,6 +175,10 @@ public: */ ExifEntry_t getTag( const ExifTagName tag ) const; + /** + * @brief Get the whole exif buffer + */ + const std::vector& getData() const; private: std::vector m_data; diff --git a/modules/imgcodecs/src/grfmt_avif.cpp b/modules/imgcodecs/src/grfmt_avif.cpp index c35eb50306..600f673fb4 100644 --- a/modules/imgcodecs/src/grfmt_avif.cpp +++ b/modules/imgcodecs/src/grfmt_avif.cpp @@ -68,8 +68,8 @@ avifResult CopyToMat(const avifImage *image, int channels, bool useRGB , Mat *ma return avifImageYUVToRGB(image, &rgba); } -AvifImageUniquePtr ConvertToAvif(const cv::Mat &img, bool lossless, - int bit_depth) { +AvifImageUniquePtr ConvertToAvif(const cv::Mat &img, bool lossless, int bit_depth, + const std::vector >& metadata) { CV_Assert(img.depth() == CV_8U || img.depth() == CV_16U); const int width = img.cols; @@ -112,6 +112,18 @@ AvifImageUniquePtr ConvertToAvif(const cv::Mat &img, bool lossless, result->yuvRange = AVIF_RANGE_FULL; } + if (!metadata.empty()) { + const std::vector& metadata_exif = metadata[IMAGE_METADATA_EXIF]; + const std::vector& metadata_xmp = metadata[IMAGE_METADATA_XMP]; + const std::vector& metadata_iccp = metadata[IMAGE_METADATA_ICCP]; + if (!metadata_exif.empty()) + avifImageSetMetadataExif(result, (const uint8_t*)metadata_exif.data(), metadata_exif.size()); + if (!metadata_exif.empty()) + avifImageSetMetadataXMP(result, (const uint8_t*)metadata_xmp.data(), metadata_xmp.size()); + if (!metadata_iccp.empty()) + avifImageSetProfileICC(result, (const uint8_t*)metadata_iccp.data(), metadata_iccp.size()); + } + avifRGBImage rgba; avifRGBImageSetDefaults(&rgba, result); if (img.channels() == 3) { @@ -120,7 +132,7 @@ AvifImageUniquePtr ConvertToAvif(const cv::Mat &img, bool lossless, CV_Assert(img.channels() == 4); rgba.format = AVIF_RGB_FORMAT_BGRA; } - rgba.rowBytes = img.step[0]; + rgba.rowBytes = (uint32_t)img.step[0]; rgba.depth = bit_depth; rgba.pixels = const_cast(reinterpret_cast(img.data)); @@ -287,6 +299,10 @@ bool AvifDecoder::nextPage() { AvifEncoder::AvifEncoder() { m_description = "AVIF files (*.avif)"; m_buf_supported = true; + m_support_metadata.assign((size_t)IMAGE_METADATA_MAX + 1, false); + m_support_metadata[(size_t)IMAGE_METADATA_EXIF] = true; + m_support_metadata[(size_t)IMAGE_METADATA_XMP] = true; + m_support_metadata[(size_t)IMAGE_METADATA_ICCP] = true; encoder_ = avifEncoderCreate(); } @@ -349,7 +365,7 @@ bool AvifEncoder::writeanimation(const Animation& animation, img.channels() == 1 || img.channels() == 3 || img.channels() == 4, "AVIF only supports 1, 3, 4 channels"); - images.emplace_back(ConvertToAvif(img, do_lossless, bit_depth)); + images.emplace_back(ConvertToAvif(img, do_lossless, bit_depth, m_metadata)); } for (size_t i = 0; i < images.size(); i++) diff --git a/modules/imgcodecs/src/grfmt_base.cpp b/modules/imgcodecs/src/grfmt_base.cpp index dc3d07ab78..1241edb077 100644 --- a/modules/imgcodecs/src/grfmt_base.cpp +++ b/modules/imgcodecs/src/grfmt_base.cpp @@ -58,11 +58,30 @@ BaseImageDecoder::BaseImageDecoder() m_frame_count = 1; } +bool BaseImageDecoder::haveMetadata(ImageMetadataType type) const +{ + if (type == IMAGE_METADATA_EXIF) + return !m_exif.getData().empty(); + return false; +} + +Mat BaseImageDecoder::getMetadata(ImageMetadataType type) const +{ + if (type == IMAGE_METADATA_EXIF) { + const std::vector& exif = m_exif.getData(); + if (!exif.empty()) { + Mat exifmat(1, (int)exif.size(), CV_8U, (void*)exif.data()); + return exifmat; + } + } + return Mat(); +} ExifEntry_t BaseImageDecoder::getExifTag(const ExifTagName tag) const { return m_exif.getTag(tag); } + bool BaseImageDecoder::setSource( const String& filename ) { m_filename = filename; @@ -140,6 +159,23 @@ bool BaseImageEncoder::setDestination( std::vector& buf ) return true; } +bool BaseImageEncoder::addMetadata(ImageMetadataType type, const Mat& metadata) +{ + CV_Assert_N(type >= IMAGE_METADATA_EXIF, type <= IMAGE_METADATA_MAX); + if (metadata.empty()) + return true; + size_t itype = (size_t)type; + if (itype >= m_support_metadata.size() || !m_support_metadata[itype]) + return false; + if (m_metadata.empty()) + m_metadata.resize((size_t)IMAGE_METADATA_MAX+1); + CV_Assert(metadata.elemSize() == 1); + CV_Assert(metadata.isContinuous()); + const unsigned char* data = metadata.ptr(); + m_metadata[itype].assign(data, data + metadata.total()); + return true; +} + bool BaseImageEncoder::write(const Mat &img, const std::vector ¶ms) { std::vector img_vec(1, img); return writemulti(img_vec, params); diff --git a/modules/imgcodecs/src/grfmt_base.hpp b/modules/imgcodecs/src/grfmt_base.hpp index 6d98bd3735..2eeb2fc130 100644 --- a/modules/imgcodecs/src/grfmt_base.hpp +++ b/modules/imgcodecs/src/grfmt_base.hpp @@ -69,6 +69,20 @@ public: */ virtual int type() const { return m_type; } + /** + * @brief Checks whether file contains metadata of the certain type. + * @param type The type of metadata to look for + */ + virtual bool haveMetadata(ImageMetadataType type) const; + + /** + * @brief Retrieves metadata (if any) of the certain kind. + * If there is no such metadata, the method returns empty array. + * + * @param type The type of metadata to look for + */ + virtual Mat getMetadata(ImageMetadataType type) const; + /** * @brief Fetch a specific EXIF tag from the image's metadata. * @param tag The EXIF tag to retrieve. @@ -205,6 +219,13 @@ public: */ virtual bool setDestination(std::vector& buf); + /** + * @brief Sets the metadata to write together with the image data + * @param type The type of metadata to add + * @param metadata The packed metadata (Exif, XMP, ...) + */ + virtual bool addMetadata(ImageMetadataType type, const Mat& metadata); + /** * @brief Encode and write the image data. * @param img The Mat object containing the image data to be encoded. @@ -243,6 +264,8 @@ public: virtual void throwOnError() const; protected: + std::vector > m_metadata; // see IMAGE_METADATA_... + std::vector m_support_metadata; String m_description; ///< Description of the encoder (e.g., format name, capabilities). String m_filename; ///< Destination file name for encoded data. std::vector* m_buf; ///< Pointer to the buffer for encoded data if using memory-based destination. diff --git a/modules/imgcodecs/src/grfmt_jpeg.cpp b/modules/imgcodecs/src/grfmt_jpeg.cpp index a3a7f70c3c..9b2ab59b2b 100644 --- a/modules/imgcodecs/src/grfmt_jpeg.cpp +++ b/modules/imgcodecs/src/grfmt_jpeg.cpp @@ -600,6 +600,8 @@ JpegEncoder::JpegEncoder() { m_description = "JPEG files (*.jpeg;*.jpg;*.jpe)"; m_buf_supported = true; + m_support_metadata.assign((size_t)IMAGE_METADATA_MAX + 1, false); + m_support_metadata[(size_t)IMAGE_METADATA_EXIF] = true; } @@ -815,6 +817,22 @@ bool JpegEncoder::write( const Mat& img, const std::vector& params ) jpeg_start_compress( &cinfo, TRUE ); + if (!m_metadata.empty()) { + const std::vector& metadata_exif = m_metadata[IMAGE_METADATA_EXIF]; + size_t exif_size = metadata_exif.size(); + if (exif_size > 0u) { + const char app1_exif_prefix[] = {'E', 'x', 'i', 'f', '\0', '\0'}; + size_t app1_exif_prefix_size = sizeof(app1_exif_prefix); + size_t data_size = exif_size + app1_exif_prefix_size; + + std::vector metadata_app1(data_size); + uchar* data = metadata_app1.data(); + memcpy(data, app1_exif_prefix, app1_exif_prefix_size); + memcpy(data + app1_exif_prefix_size, metadata_exif.data(), exif_size); + jpeg_write_marker(&cinfo, JPEG_APP0 + 1, data, (unsigned)data_size); + } + } + if( doDirectWrite ) { for( int y = 0; y < height; y++ ) diff --git a/modules/imgcodecs/src/grfmt_png.cpp b/modules/imgcodecs/src/grfmt_png.cpp index a47db5aa2a..f0f656bd25 100644 --- a/modules/imgcodecs/src/grfmt_png.cpp +++ b/modules/imgcodecs/src/grfmt_png.cpp @@ -858,6 +858,8 @@ PngEncoder::PngEncoder() { m_description = "Portable Network Graphics files (*.png;*.apng)"; m_buf_supported = true; + m_support_metadata.assign((size_t)IMAGE_METADATA_MAX+1, false); + m_support_metadata[IMAGE_METADATA_EXIF] = true; op_zstream1.zalloc = NULL; op_zstream2.zalloc = NULL; next_seq_num = 0; @@ -989,6 +991,16 @@ bool PngEncoder::write( const Mat& img, const std::vector& params ) for( y = 0; y < height; y++ ) buffer[y] = img.data + y*img.step; + if (!m_metadata.empty()) { + std::vector& exif = m_metadata[IMAGE_METADATA_EXIF]; + if (!exif.empty()) { + writeChunk(f, "eXIf", exif.data(), (uint32_t)exif.size()); + } + // [TODO] add xmp and icc. They need special handling, + // see https://dev.exiv2.org/projects/exiv2/wiki/The_Metadata_in_PNG_files and + // https://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html. + } + png_write_image( png_ptr, buffer.data() ); png_write_end( png_ptr, info_ptr ); diff --git a/modules/imgcodecs/src/loadsave.cpp b/modules/imgcodecs/src/loadsave.cpp index dfbf118fb9..8f811f9085 100644 --- a/modules/imgcodecs/src/loadsave.cpp +++ b/modules/imgcodecs/src/loadsave.cpp @@ -410,6 +410,76 @@ static void ApplyExifOrientation(ExifEntry_t orientationTag, OutputArray img) } } +static void readMetadata(ImageDecoder& decoder, + std::vector* metadata_types, + OutputArrayOfArrays metadata) +{ + if (!metadata_types) + return; + int kind = metadata.kind(); + void* obj = metadata.getObj(); + std::vector* matvector = nullptr; + std::vector >* vecvector = nullptr; + if (kind == _InputArray::STD_VECTOR_MAT) { + matvector = (std::vector*)obj; + } else if (kind == _InputArray::STD_VECTOR_VECTOR) { + int elemtype = metadata.type(0); + CV_Assert(elemtype == CV_8UC1 || elemtype == CV_8SC1); + vecvector = (std::vector >*)obj; + } else { + CV_Error(Error::StsBadArg, + "unsupported metadata type, should be a vector of matrices or vector of byte vectors"); + } + std::vector src_metadata; + for (int m = (int)IMAGE_METADATA_EXIF; m <= (int)IMAGE_METADATA_MAX; m++) { + Mat mm = decoder->getMetadata((ImageMetadataType)m); + if (!mm.empty()) { + CV_Assert(mm.isContinuous()); + CV_Assert(mm.elemSize() == 1u); + metadata_types->push_back(m); + src_metadata.push_back(mm); + } + } + size_t nmetadata = metadata_types->size(); + if (matvector) { + matvector->resize(nmetadata); + for (size_t m = 0; m < nmetadata; m++) + src_metadata[m].copyTo(matvector->at(m)); + } else { + vecvector->resize(nmetadata); + for (size_t m = 0; m < nmetadata; m++) { + const Mat& mm = src_metadata[m]; + const uchar* data = (uchar*)mm.data; + vecvector->at(m).assign(data, data + mm.total()); + } + } +} + +static const char* metadataTypeToString(ImageMetadataType type) +{ + return type == IMAGE_METADATA_EXIF ? "Exif" : + type == IMAGE_METADATA_XMP ? "XMP" : + type == IMAGE_METADATA_ICCP ? "ICC Profile" : "???"; +} + +static void addMetadata(ImageEncoder& encoder, + const std::vector& metadata_types, + InputArrayOfArrays metadata) +{ + size_t nmetadata_chunks = metadata_types.size(); + for (size_t i = 0; i < nmetadata_chunks; i++) { + ImageMetadataType metadata_type = (ImageMetadataType)metadata_types[i]; + bool ok = encoder->addMetadata(metadata_type, metadata.getMat((int)i)); + if (!ok) { + std::string desc = encoder->getDescription(); + CV_LOG_WARNING(NULL, "Imgcodecs: metadata of type '" + << metadataTypeToString(metadata_type) + << "' is not supported when encoding '" + << desc << "'"); + } + } +} + /** * Read an image into memory and return the information * @@ -419,11 +489,15 @@ static void ApplyExifOrientation(ExifEntry_t orientationTag, OutputArray img) * */ static bool -imread_( const String& filename, int flags, OutputArray mat ) +imread_( const String& filename, int flags, OutputArray mat, + std::vector* metadata_types, OutputArrayOfArrays metadata) { /// Search for the relevant decoder to handle the imagery ImageDecoder decoder; + if (metadata_types) + metadata_types->clear(); + #ifdef HAVE_GDAL if(flags != IMREAD_UNCHANGED && (flags & IMREAD_LOAD_GDAL) == IMREAD_LOAD_GDAL ){ decoder = GdalDecoder().newDecoder(); @@ -509,6 +583,8 @@ imread_( const String& filename, int flags, OutputArray mat ) CV_CheckTrue(original_ptr == real_mat.data, "Internal imread issue"); success = true; } + + readMetadata(decoder, metadata_types, metadata); } catch (const cv::Exception& e) { @@ -662,7 +738,24 @@ Mat imread( const String& filename, int flags ) Mat img; /// load the data - imread_( filename, flags, img ); + imread_( filename, flags, img, nullptr, noArray() ); + + /// return a reference to the data + return img; +} + +Mat imreadWithMetadata( const String& filename, + std::vector& metadata_types, + OutputArrayOfArrays metadata, + int flags ) +{ + CV_TRACE_FUNCTION(); + + /// create the basic container + Mat img; + + /// load the data + imread_( filename, flags, img, &metadata_types, metadata ); /// return a reference to the data return img; @@ -673,7 +766,7 @@ void imread( const String& filename, OutputArray dst, int flags ) CV_TRACE_FUNCTION(); /// load the data - imread_(filename, flags, dst); + imread_(filename, flags, dst, nullptr, noArray()); } /** @@ -946,6 +1039,8 @@ size_t imcount(const String& filename, int flags) static bool imwrite_( const String& filename, const std::vector& img_vec, + const std::vector& metadata_types, + InputArrayOfArrays metadata, const std::vector& params_, bool flipv ) { bool isMultiImg = img_vec.size() > 1; @@ -981,6 +1076,8 @@ static bool imwrite_( const String& filename, const std::vector& img_vec, } encoder->setDestination( filename ); + addMetadata(encoder, metadata_types, metadata); + #if CV_VERSION_MAJOR < 5 && defined(HAVE_IMGCODEC_HDR) bool fixed = false; std::vector params_pair(2); @@ -1055,7 +1152,26 @@ bool imwrite( const String& filename, InputArray _img, img_vec.push_back(_img.getMat()); CV_Assert(!img_vec.empty()); - return imwrite_(filename, img_vec, params, false); + return imwrite_(filename, img_vec, {}, noArray(), params, false); +} + +bool imwriteWithMetadata( const String& filename, InputArray _img, + const std::vector& metadata_types, + InputArrayOfArrays metadata, + const std::vector& params ) +{ + CV_TRACE_FUNCTION(); + + CV_Assert(!_img.empty()); + + std::vector img_vec; + if (_img.isMatVector() || _img.isUMatVector()) + _img.getMatVector(img_vec); + else + img_vec.push_back(_img.getMat()); + + CV_Assert(!img_vec.empty()); + return imwrite_(filename, img_vec, metadata_types, metadata, params, false); } static bool imwriteanimation_(const String& filename, const Animation& animation, const std::vector& params) @@ -1140,8 +1256,13 @@ bool imencodeanimation(const String& ext, const Animation& animation, std::vecto } static bool -imdecode_( const Mat& buf, int flags, Mat& mat ) +imdecode_( const Mat& buf, int flags, Mat& mat, + std::vector* metadata_types, + OutputArrayOfArrays metadata ) { + if (metadata_types) + metadata_types->clear(); + CV_Assert(!buf.empty()); CV_Assert(buf.isContinuous()); CV_Assert(buf.checkVector(1, CV_8U) > 0); @@ -1231,6 +1352,7 @@ imdecode_( const Mat& buf, int flags, Mat& mat ) { if (decoder->readData(mat)) success = true; + readMetadata(decoder, metadata_types, metadata); } catch (const cv::Exception& e) { @@ -1274,7 +1396,7 @@ Mat imdecode( InputArray _buf, int flags ) CV_TRACE_FUNCTION(); Mat buf = _buf.getMat(), img; - if (!imdecode_(buf, flags, img)) + if (!imdecode_(buf, flags, img, nullptr, noArray())) img.release(); return img; @@ -1286,12 +1408,24 @@ Mat imdecode( InputArray _buf, int flags, Mat* dst ) Mat buf = _buf.getMat(), img; dst = dst ? dst : &img; - if (imdecode_(buf, flags, *dst)) + if (imdecode_(buf, flags, *dst, nullptr, noArray())) return *dst; else return cv::Mat(); } +Mat imdecodeWithMetadata( InputArray _buf, std::vector& metadata_types, + OutputArrayOfArrays metadata, int flags ) +{ + CV_TRACE_FUNCTION(); + + Mat buf = _buf.getMat(), img; + if (!imdecode_(buf, flags, img, &metadata_types, metadata)) + img.release(); + + return img; +} + static bool imdecodemulti_(const Mat& buf, int flags, std::vector& mats, int start, int count) { @@ -1447,8 +1581,10 @@ bool imdecodemulti(InputArray _buf, int flags, CV_OUT std::vector& mats, co } } -bool imencode( const String& ext, InputArray _img, - std::vector& buf, const std::vector& params_ ) +bool imencodeWithMetadata( const String& ext, InputArray _img, + const std::vector& metadata_types, + InputArrayOfArrays metadata, + std::vector& buf, const std::vector& params_ ) { CV_TRACE_FUNCTION(); @@ -1517,6 +1653,7 @@ bool imencode( const String& ext, InputArray _img, code = encoder->setDestination(filename); CV_Assert( code ); } + addMetadata(encoder, metadata_types, metadata); try { if (!isMultiImg) @@ -1553,6 +1690,12 @@ bool imencode( const String& ext, InputArray _img, return code; } +bool imencode( const String& ext, InputArray img, + std::vector& buf, const std::vector& params_ ) +{ + return imencodeWithMetadata(ext, img, {}, noArray(), buf, params_); +} + bool imencodemulti( const String& ext, InputArrayOfArrays imgs, std::vector& buf, const std::vector& params) { diff --git a/modules/imgcodecs/test/test_exif.cpp b/modules/imgcodecs/test/test_exif.cpp index d1a9e720a9..792c38514f 100644 --- a/modules/imgcodecs/test/test_exif.cpp +++ b/modules/imgcodecs/test/test_exif.cpp @@ -148,7 +148,246 @@ const std::vector exif_files }; INSTANTIATE_TEST_CASE_P(Imgcodecs, Exif, - testing::ValuesIn(exif_files)); + testing::ValuesIn(exif_files)); +static Mat makeCirclesImage(Size size, int type, int nbits) +{ + Mat img(size, type); + img.setTo(Scalar::all(0)); + RNG& rng = theRNG(); + int maxval = (int)(1 << nbits); + for (int i = 0; i < 100; i++) { + int x = rng.uniform(0, img.cols); + int y = rng.uniform(0, img.rows); + int radius = rng.uniform(5, std::min(img.cols, img.rows)/5); + int b = rng.uniform(0, maxval); + int g = rng.uniform(0, maxval); + int r = rng.uniform(0, maxval); + circle(img, Point(x, y), radius, Scalar(b, g, r), -1, LINE_AA); + } + return img; } + +#ifdef HAVE_AVIF +TEST(Imgcodecs_Avif, ReadWriteWithExif) +{ + static const uchar exif_data[] = { + 'M', 'M', 0, '*', 0, 0, 0, 8, 0, 10, 1, 0, 0, 4, 0, 0, 0, 1, 0, 0, 5, + 0, 1, 1, 0, 4, 0, 0, 0, 1, 0, 0, 2, 208, 1, 2, 0, 3, 0, 0, 0, 1, + 0, 10, 0, 0, 1, 18, 0, 3, 0, 0, 0, 1, 0, 1, 0, 0, 1, 14, 0, 2, 0, 0, + 0, '"', 0, 0, 0, 176, 1, '1', 0, 2, 0, 0, 0, 7, 0, 0, 0, 210, 1, 26, + 0, 5, 0, 0, 0, 1, 0, 0, 0, 218, 1, 27, 0, 5, 0, 0, 0, 1, 0, 0, 0, + 226, 1, '(', 0, 3, 0, 0, 0, 1, 0, 2, 0, 0, 135, 'i', 0, 4, 0, 0, 0, + 1, 0, 0, 0, 134, 0, 0, 0, 0, 0, 3, 144, 0, 0, 7, 0, 0, 0, 4, '0', '2', + '2', '1', 160, 2, 0, 4, 0, 0, 0, 1, 0, 0, 5, 0, 160, 3, 0, 4, 0, 0, + 0, 1, 0, 0, 2, 208, 0, 0, 0, 0, 'S', 'a', 'm', 'p', 'l', 'e', ' ', '1', '0', + '-', 'b', 'i', 't', ' ', 'i', 'm', 'a', 'g', 'e', ' ', 'w', 'i', 't', 'h', ' ', + 'm', 'e', 't', 'a', 'd', 'a', 't', 'a', 0, 'O', 'p', 'e', 'n', 'C', 'V', 0, 0, + 0, 0, 0, 'H', 0, 0, 0, 1, 0, 0, 0, 'H', 0, 0, 0, 1 + }; + + int avif_nbits = 10; + int avif_speed = 10; + int avif_quality = 85; + int imgdepth = avif_nbits > 8 ? CV_16U : CV_8U; + int imgtype = CV_MAKETYPE(imgdepth, 3); + const string outputname = cv::tempfile(".avif"); + Mat img = makeCirclesImage(Size(1280, 720), imgtype, avif_nbits); + + std::vector metadata_types = {IMAGE_METADATA_EXIF}; + std::vector > metadata(1); + metadata[0].assign(exif_data, exif_data + sizeof(exif_data)); + + std::vector write_params = { + IMWRITE_AVIF_DEPTH, avif_nbits, + IMWRITE_AVIF_SPEED, avif_speed, + IMWRITE_AVIF_QUALITY, avif_quality + }; + + imwriteWithMetadata(outputname, img, metadata_types, metadata, write_params); + std::vector compressed; + imencodeWithMetadata(outputname, img, metadata_types, metadata, compressed, write_params); + + std::vector read_metadata_types, read_metadata_types2; + std::vector > read_metadata, read_metadata2; + Mat img2 = imreadWithMetadata(outputname, read_metadata_types, read_metadata, IMREAD_UNCHANGED); + Mat img3 = imdecodeWithMetadata(compressed, read_metadata_types2, read_metadata2, IMREAD_UNCHANGED); + EXPECT_EQ(img2.cols, img.cols); + EXPECT_EQ(img2.rows, img.rows); + EXPECT_EQ(img2.type(), imgtype); + EXPECT_EQ(read_metadata_types, read_metadata_types2); + EXPECT_GE(read_metadata_types.size(), 1u); + EXPECT_EQ(read_metadata, read_metadata2); + EXPECT_EQ(read_metadata_types[0], IMAGE_METADATA_EXIF); + EXPECT_EQ(read_metadata_types.size(), read_metadata.size()); + EXPECT_EQ(read_metadata[0], metadata[0]); + EXPECT_EQ(cv::norm(img2, img3, NORM_INF), 0.); + double mse = cv::norm(img, img2, NORM_L2SQR)/(img.rows*img.cols); + EXPECT_LT(mse, 1500); + remove(outputname.c_str()); } +#endif // HAVE_AVIF + +TEST(Imgcodecs_Jpeg, ReadWriteWithExif) +{ + static const uchar exif_data[] = { + 'M', 'M', 0, '*', 0, 0, 0, 8, 0, 10, 1, 0, 0, 4, 0, 0, 0, 1, 0, 0, 5, + 0, 1, 1, 0, 4, 0, 0, 0, 1, 0, 0, 2, 208, 1, 2, 0, 3, 0, 0, 0, 1, + 0, 8, 0, 0, 1, 18, 0, 3, 0, 0, 0, 1, 0, 1, 0, 0, 1, 14, 0, 2, 0, 0, + 0, '!', 0, 0, 0, 176, 1, '1', 0, 2, 0, 0, 0, 7, 0, 0, 0, 210, 1, 26, + 0, 5, 0, 0, 0, 1, 0, 0, 0, 218, 1, 27, 0, 5, 0, 0, 0, 1, 0, 0, 0, + 226, 1, '(', 0, 3, 0, 0, 0, 1, 0, 2, 0, 0, 135, 'i', 0, 4, 0, 0, 0, + 1, 0, 0, 0, 134, 0, 0, 0, 0, 0, 3, 144, 0, 0, 7, 0, 0, 0, 4, '0', '2', + '2', '1', 160, 2, 0, 4, 0, 0, 0, 1, 0, 0, 5, 0, 160, 3, 0, 4, 0, 0, + 0, 1, 0, 0, 2, 208, 0, 0, 0, 0, 'S', 'a', 'm', 'p', 'l', 'e', ' ', '8', '-', + 'b', 'i', 't', ' ', 'i', 'm', 'a', 'g', 'e', ' ', 'w', 'i', 't', 'h', ' ', 'm', + 'e', 't', 'a', 'd', 'a', 't', 'a', 0, 0, 'O', 'p', 'e', 'n', 'C', 'V', 0, 0, + 0, 0, 0, 'H', 0, 0, 0, 1, 0, 0, 0, 'H', 0, 0, 0, 1 + }; + + int jpeg_quality = 95; + int imgtype = CV_MAKETYPE(CV_8U, 3); + const string outputname = cv::tempfile(".jpeg"); + Mat img = makeCirclesImage(Size(1280, 720), imgtype, 8); + + std::vector metadata_types = {IMAGE_METADATA_EXIF}; + std::vector > metadata(1); + metadata[0].assign(exif_data, exif_data + sizeof(exif_data)); + + std::vector write_params = { + IMWRITE_JPEG_QUALITY, jpeg_quality + }; + + imwriteWithMetadata(outputname, img, metadata_types, metadata, write_params); + std::vector compressed; + imencodeWithMetadata(outputname, img, metadata_types, metadata, compressed, write_params); + + std::vector read_metadata_types, read_metadata_types2; + std::vector > read_metadata, read_metadata2; + Mat img2 = imreadWithMetadata(outputname, read_metadata_types, read_metadata, IMREAD_UNCHANGED); + Mat img3 = imdecodeWithMetadata(compressed, read_metadata_types2, read_metadata2, IMREAD_UNCHANGED); + EXPECT_EQ(img2.cols, img.cols); + EXPECT_EQ(img2.rows, img.rows); + EXPECT_EQ(img2.type(), imgtype); + EXPECT_EQ(read_metadata_types, read_metadata_types2); + EXPECT_GE(read_metadata_types.size(), 1u); + EXPECT_EQ(read_metadata, read_metadata2); + EXPECT_EQ(read_metadata_types[0], IMAGE_METADATA_EXIF); + EXPECT_EQ(read_metadata_types.size(), read_metadata.size()); + EXPECT_EQ(read_metadata[0], metadata[0]); + EXPECT_EQ(cv::norm(img2, img3, NORM_INF), 0.); + double mse = cv::norm(img, img2, NORM_L2SQR)/(img.rows*img.cols); + EXPECT_LT(mse, 80); + remove(outputname.c_str()); +} + +TEST(Imgcodecs_Png, ReadWriteWithExif) +{ + static const uchar exif_data[] = { + 'M', 'M', 0, '*', 0, 0, 0, 8, 0, 10, 1, 0, 0, 4, 0, 0, 0, 1, 0, 0, 5, + 0, 1, 1, 0, 4, 0, 0, 0, 1, 0, 0, 2, 208, 1, 2, 0, 3, 0, 0, 0, 1, + 0, 8, 0, 0, 1, 18, 0, 3, 0, 0, 0, 1, 0, 1, 0, 0, 1, 14, 0, 2, 0, 0, + 0, '!', 0, 0, 0, 176, 1, '1', 0, 2, 0, 0, 0, 7, 0, 0, 0, 210, 1, 26, + 0, 5, 0, 0, 0, 1, 0, 0, 0, 218, 1, 27, 0, 5, 0, 0, 0, 1, 0, 0, 0, + 226, 1, '(', 0, 3, 0, 0, 0, 1, 0, 2, 0, 0, 135, 'i', 0, 4, 0, 0, 0, + 1, 0, 0, 0, 134, 0, 0, 0, 0, 0, 3, 144, 0, 0, 7, 0, 0, 0, 4, '0', '2', + '2', '1', 160, 2, 0, 4, 0, 0, 0, 1, 0, 0, 5, 0, 160, 3, 0, 4, 0, 0, + 0, 1, 0, 0, 2, 208, 0, 0, 0, 0, 'S', 'a', 'm', 'p', 'l', 'e', ' ', '8', '-', + 'b', 'i', 't', ' ', 'i', 'm', 'a', 'g', 'e', ' ', 'w', 'i', 't', 'h', ' ', 'm', + 'e', 't', 'a', 'd', 'a', 't', 'a', 0, 0, 'O', 'p', 'e', 'n', 'C', 'V', 0, 0, + 0, 0, 0, 'H', 0, 0, 0, 1, 0, 0, 0, 'H', 0, 0, 0, 1 + }; + + int png_compression = 3; + int imgtype = CV_MAKETYPE(CV_8U, 3); + const string outputname = cv::tempfile(".png"); + Mat img = makeCirclesImage(Size(1280, 720), imgtype, 8); + + std::vector metadata_types = {IMAGE_METADATA_EXIF}; + std::vector > metadata(1); + metadata[0].assign(exif_data, exif_data + sizeof(exif_data)); + + std::vector write_params = { + IMWRITE_PNG_COMPRESSION, png_compression + }; + + imwriteWithMetadata(outputname, img, metadata_types, metadata, write_params); + std::vector compressed; + imencodeWithMetadata(outputname, img, metadata_types, metadata, compressed, write_params); + + std::vector read_metadata_types, read_metadata_types2; + std::vector > read_metadata, read_metadata2; + Mat img2 = imreadWithMetadata(outputname, read_metadata_types, read_metadata, IMREAD_UNCHANGED); + Mat img3 = imdecodeWithMetadata(compressed, read_metadata_types2, read_metadata2, IMREAD_UNCHANGED); + EXPECT_EQ(img2.cols, img.cols); + EXPECT_EQ(img2.rows, img.rows); + EXPECT_EQ(img2.type(), imgtype); + EXPECT_EQ(read_metadata_types, read_metadata_types2); + EXPECT_GE(read_metadata_types.size(), 1u); + EXPECT_EQ(read_metadata, read_metadata2); + EXPECT_EQ(read_metadata_types[0], IMAGE_METADATA_EXIF); + EXPECT_EQ(read_metadata_types.size(), read_metadata.size()); + EXPECT_EQ(read_metadata[0], metadata[0]); + EXPECT_EQ(cv::norm(img2, img3, NORM_INF), 0.); + double mse = cv::norm(img, img2, NORM_L2SQR)/(img.rows*img.cols); + EXPECT_EQ(mse, 0); // png is lossless + remove(outputname.c_str()); +} + +static size_t locateString(const uchar* exif, size_t exif_size, const std::string& pattern) +{ + size_t plen = pattern.size(); + for (size_t i = 0; i + plen <= exif_size; i++) { + if (exif[i] == pattern[0] && memcmp(&exif[i], pattern.c_str(), plen) == 0) + return i; + } + return 0xFFFFFFFFu; +} + +typedef std::tuple ReadExif_Sanity_Params; +typedef testing::TestWithParam ReadExif_Sanity; + +TEST_P(ReadExif_Sanity, Check) +{ + std::string filename = get<0>(GetParam()); + size_t exif_size = get<1>(GetParam()); + std::string pattern = get<2>(GetParam()); + size_t ploc = get<3>(GetParam()); + + const string root = cvtest::TS::ptr()->get_data_path(); + filename = root + filename; + + std::vector metadata_types; + std::vector metadata; + Mat img = imreadWithMetadata(filename, metadata_types, metadata, 1); + + EXPECT_EQ(img.type(), CV_8UC3); + ASSERT_GE(metadata_types.size(), 1u); + EXPECT_EQ(metadata_types.size(), metadata.size()); + const Mat& exif = metadata[IMAGE_METADATA_EXIF]; + EXPECT_EQ(exif.type(), CV_8U); + EXPECT_EQ(exif.total(), exif_size); + ASSERT_GE(exif_size, 26u); // minimal exif should take at least 26 bytes + // (the header + IDF0 with at least 1 entry). + EXPECT_TRUE(exif.data[0] == 'I' || exif.data[0] == 'M'); + EXPECT_EQ(exif.data[0], exif.data[1]); + EXPECT_EQ(locateString(exif.data, exif_size, pattern), ploc); +} + +static const std::vector exif_sanity_params +{ +#ifdef HAVE_JPEG + {"readwrite/testExifOrientation_3.jpg", 916, "Photoshop", 120}, +#endif +#ifdef OPENCV_IMGCODECS_PNG_WITH_EXIF + {"readwrite/testExifOrientation_5.png", 112, "ExifTool", 102}, +#endif +#ifdef HAVE_AVIF + {"readwrite/testExifOrientation_7.avif", 913, "Photoshop", 120}, +#endif +}; + +INSTANTIATE_TEST_CASE_P(Imgcodecs, ReadExif_Sanity, + testing::ValuesIn(exif_sanity_params)); + +}} diff --git a/modules/python/test/test_imread.py b/modules/python/test/test_imread.py index b5f286d426..471c786acc 100644 --- a/modules/python/test/test_imread.py +++ b/modules/python/test/test_imread.py @@ -22,6 +22,18 @@ class imread_test(NewOpenCVTests): cv.imread(path, img) self.assertEqual(cv.norm(ref, img, cv.NORM_INF), 0.0) + def test_imread_with_meta(self): + path = self.extraTestDataPath + '/highgui/readwrite/testExifOrientation_1.jpg' + img, meta_types, meta_data = cv.imreadWithMetadata(path) + self.assertTrue(img is not None) + self.assertTrue(meta_types is not None) + self.assertTrue(meta_data is not None) + + path = self.extraTestDataPath + '/highgui/readwrite/testExifOrientation_1.png' + img, meta_types, meta_data = cv.imreadWithMetadata(path) + self.assertTrue(img is not None) + self.assertTrue(meta_types is not None) + self.assertTrue(meta_data is not None) if __name__ == '__main__': NewOpenCVTests.bootstrap() From 7a0d6559c3ca60e858c31162ece142c85d9e5d70 Mon Sep 17 00:00:00 2001 From: Alexander Smorkalov Date: Wed, 2 Jul 2025 10:07:13 +0300 Subject: [PATCH 45/62] Made some GDAL specific tests optional. --- modules/imgcodecs/test/test_gdal.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/modules/imgcodecs/test/test_gdal.cpp b/modules/imgcodecs/test/test_gdal.cpp index b61d2ed786..0ede7098f7 100755 --- a/modules/imgcodecs/test/test_gdal.cpp +++ b/modules/imgcodecs/test/test_gdal.cpp @@ -8,10 +8,14 @@ namespace opencv_test { namespace { #ifdef HAVE_GDAL -static void test_gdal_read(const string filename) { +static void test_gdal_read(const string filename, bool required = true) { const string path = cvtest::findDataFile(filename); Mat img; ASSERT_NO_THROW(img = imread(path, cv::IMREAD_LOAD_GDAL | cv::IMREAD_ANYDEPTH | cv::IMREAD_ANYCOLOR)); + if(!required && img.empty()) + { + throw SkipTestException("GDAL is built wihout required back-end support"); + } ASSERT_FALSE(img.empty()); EXPECT_EQ(3, img.cols); EXPECT_EQ(5, img.rows); @@ -26,13 +30,12 @@ TEST(Imgcodecs_gdal, read_envi) test_gdal_read("../cv/gdal/envi_test.raw"); } - TEST(Imgcodecs_gdal, read_fits) { - test_gdal_read("../cv/gdal/fits_test.fit"); + // .fit test is optional because GDAL may be built wihtout CFITSIO library support + test_gdal_read("../cv/gdal/fits_test.fit", false); } - #endif // HAVE_GDAL }} // namespace From 49486f61fb25722cbcf586b7f4320921d46fb38e Mon Sep 17 00:00:00 2001 From: Alexander Smorkalov Date: Tue, 1 Jul 2025 10:02:50 +0300 Subject: [PATCH 46/62] release: OpenCV 4.12.0 --- modules/core/include/opencv2/core/version.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/core/include/opencv2/core/version.hpp b/modules/core/include/opencv2/core/version.hpp index e4c93cbf2f..78a9b9e724 100644 --- a/modules/core/include/opencv2/core/version.hpp +++ b/modules/core/include/opencv2/core/version.hpp @@ -8,7 +8,7 @@ #define CV_VERSION_MAJOR 4 #define CV_VERSION_MINOR 12 #define CV_VERSION_REVISION 0 -#define CV_VERSION_STATUS "-pre" +#define CV_VERSION_STATUS "" #define CVAUX_STR_EXP(__A) #__A #define CVAUX_STR(__A) CVAUX_STR_EXP(__A) From 1cdb76c8e330a7e042edccc0dc90a9adf5d85ba5 Mon Sep 17 00:00:00 2001 From: Vincent Rabaud Date: Thu, 3 Jul 2025 13:14:48 +0200 Subject: [PATCH 47/62] Include opencv2/videoio.hpp In my configuration with bazel, when building the Java bindings, it is not like building C++ and including videio/videoio.hpp triggers: error this is a compatibility header which should not be used inside the OpenCV library --- modules/videoio/misc/java/src/cpp/videoio_converters.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/videoio/misc/java/src/cpp/videoio_converters.hpp b/modules/videoio/misc/java/src/cpp/videoio_converters.hpp index d1ec43e2be..d1bfe6e0f4 100644 --- a/modules/videoio/misc/java/src/cpp/videoio_converters.hpp +++ b/modules/videoio/misc/java/src/cpp/videoio_converters.hpp @@ -4,7 +4,7 @@ #include #include "opencv_java.hpp" #include "opencv2/core.hpp" -#include "opencv2/videoio/videoio.hpp" +#include "opencv2/videoio.hpp" class JavaStreamReader : public cv::IStreamReader { From 2d60f3c63b1a08e2fe57eff5e7fad241fd23fb13 Mon Sep 17 00:00:00 2001 From: Vincent Rabaud Date: Wed, 2 Jul 2025 22:42:07 +0200 Subject: [PATCH 48/62] Fix XMP write and discarded return value. --- modules/imgcodecs/src/grfmt_avif.cpp | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/modules/imgcodecs/src/grfmt_avif.cpp b/modules/imgcodecs/src/grfmt_avif.cpp index 600f673fb4..c1b86362e0 100644 --- a/modules/imgcodecs/src/grfmt_avif.cpp +++ b/modules/imgcodecs/src/grfmt_avif.cpp @@ -116,12 +116,27 @@ AvifImageUniquePtr ConvertToAvif(const cv::Mat &img, bool lossless, int bit_dept const std::vector& metadata_exif = metadata[IMAGE_METADATA_EXIF]; const std::vector& metadata_xmp = metadata[IMAGE_METADATA_XMP]; const std::vector& metadata_iccp = metadata[IMAGE_METADATA_ICCP]; +#if AVIF_VERSION_MAJOR >= 1 + if ((!metadata_exif.empty() && + avifImageSetMetadataExif(result, (const uint8_t *)metadata_exif.data(), + metadata_exif.size()) != AVIF_RESULT_OK) || + (!metadata_xmp.empty() && + avifImageSetMetadataXMP(result, (const uint8_t *)metadata_xmp.data(), + metadata_xmp.size()) != AVIF_RESULT_OK) || + (!metadata_iccp.empty() && + avifImageSetProfileICC(result, (const uint8_t *)metadata_iccp.data(), + metadata_iccp.size()) != AVIF_RESULT_OK)) { + avifImageDestroy(result); + return nullptr; + } +#else if (!metadata_exif.empty()) avifImageSetMetadataExif(result, (const uint8_t*)metadata_exif.data(), metadata_exif.size()); - if (!metadata_exif.empty()) + if (!metadata_xmp.empty()) avifImageSetMetadataXMP(result, (const uint8_t*)metadata_xmp.data(), metadata_xmp.size()); if (!metadata_iccp.empty()) avifImageSetProfileICC(result, (const uint8_t*)metadata_iccp.data(), metadata_iccp.size()); +#endif } avifRGBImage rgba; From c48dad1d9d92d1bd1b5ba12730feb4a637670371 Mon Sep 17 00:00:00 2001 From: eplankin Date: Fri, 4 Jul 2025 11:04:31 +0200 Subject: [PATCH 49/62] Merge pull request #27324 from eplankin:warp_hal_4x * Moved IPP impl of warpAffine to HAL Co-authored-by: victorget --- hal/ipp/CMakeLists.txt | 3 + hal/ipp/include/ipp_hal_imgproc.hpp | 27 ++ hal/ipp/src/precomp_ipp.hpp | 71 ++++ hal/ipp/src/transforms_ipp.cpp | 14 +- hal/ipp/src/warp_ipp.cpp | 367 ++++++++++++++++++ modules/core/include/opencv2/core/private.hpp | 2 - modules/imgproc/src/imgwarp.cpp | 363 +---------------- 7 files changed, 470 insertions(+), 377 deletions(-) create mode 100644 hal/ipp/include/ipp_hal_imgproc.hpp create mode 100644 hal/ipp/src/precomp_ipp.hpp create mode 100644 hal/ipp/src/warp_ipp.cpp diff --git a/hal/ipp/CMakeLists.txt b/hal/ipp/CMakeLists.txt index bf57db6f8e..203e38c6d6 100644 --- a/hal/ipp/CMakeLists.txt +++ b/hal/ipp/CMakeLists.txt @@ -5,6 +5,7 @@ set(IPP_HAL_LIBRARIES "ipphal" CACHE INTERNAL "") set(IPP_HAL_INCLUDE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/include" CACHE INTERNAL "") set(IPP_HAL_HEADERS "${CMAKE_CURRENT_SOURCE_DIR}/include/ipp_hal_core.hpp" + "${CMAKE_CURRENT_SOURCE_DIR}/include/ipp_hal_imgproc.hpp" CACHE INTERNAL "") add_library(ipphal STATIC @@ -13,6 +14,7 @@ add_library(ipphal STATIC "${CMAKE_CURRENT_SOURCE_DIR}/src/norm_ipp.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/cart_polar_ipp.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/transforms_ipp.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/warp_ipp.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/sum_ipp.cpp" ) @@ -34,6 +36,7 @@ ocv_warnings_disable(CMAKE_CXX_FLAGS -Wno-suggest-override) target_include_directories(ipphal PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src" ${CMAKE_SOURCE_DIR}/modules/core/include + ${CMAKE_SOURCE_DIR}/modules/imgproc/include ${IPP_INCLUDE_DIRS} ) diff --git a/hal/ipp/include/ipp_hal_imgproc.hpp b/hal/ipp/include/ipp_hal_imgproc.hpp new file mode 100644 index 0000000000..bafbf3faaf --- /dev/null +++ b/hal/ipp/include/ipp_hal_imgproc.hpp @@ -0,0 +1,27 @@ +// 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 + +#ifndef __IPP_HAL_IMGPROC_HPP__ +#define __IPP_HAL_IMGPROC_HPP__ + +#include +#include "ipp_utils.hpp" + +#ifdef HAVE_IPP_IW + +int ipp_hal_warpAffine(int src_type, const uchar *src_data, size_t src_step, int src_width, int src_height, uchar *dst_data, size_t dst_step, int dst_width, + int dst_height, const double M[6], int interpolation, int borderType, const double borderValue[4]); + +#undef cv_hal_warpAffine +#define cv_hal_warpAffine ipp_hal_warpAffine +#endif + +#if IPP_VERSION_X100 >= 810 +int ipp_hal_warpPerspective(int src_type, const uchar *src_data, size_t src_step, int src_width, int src_height, uchar *dst_data, size_t dst_step, int dst_width, + int dst_height, const double M[9], int interpolation, int borderType, const double borderValue[4]); +#undef cv_hal_warpPerspective +#define cv_hal_warpPerspective ipp_hal_warpPerspective +#endif + +#endif //__IPP_HAL_IMGPROC_HPP__ diff --git a/hal/ipp/src/precomp_ipp.hpp b/hal/ipp/src/precomp_ipp.hpp new file mode 100644 index 0000000000..b63e660a94 --- /dev/null +++ b/hal/ipp/src/precomp_ipp.hpp @@ -0,0 +1,71 @@ +// 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 + +#ifndef __PRECOMP_IPP_HPP__ +#define __PRECOMP_IPP_HPP__ + +#include + +#ifdef HAVE_IPP_IW +#include "iw++/iw.hpp" +#endif + +static inline IppDataType ippiGetDataType(int depth) +{ + depth = CV_MAT_DEPTH(depth); + return depth == CV_8U ? ipp8u : + depth == CV_8S ? ipp8s : + depth == CV_16U ? ipp16u : + depth == CV_16S ? ipp16s : + depth == CV_32S ? ipp32s : + depth == CV_32F ? ipp32f : + depth == CV_64F ? ipp64f : + (IppDataType)-1; +} + +static inline IppiInterpolationType ippiGetInterpolation(int inter) +{ + inter &= cv::InterpolationFlags::INTER_MAX; + return inter == cv::InterpolationFlags::INTER_NEAREST ? ippNearest : + inter == cv::InterpolationFlags::INTER_LINEAR ? ippLinear : + inter == cv::InterpolationFlags::INTER_CUBIC ? ippCubic : + inter == cv::InterpolationFlags::INTER_LANCZOS4 ? ippLanczos : + inter == cv::InterpolationFlags::INTER_AREA ? ippSuper : + (IppiInterpolationType)-1; +} + +static inline IppiBorderType ippiGetBorderType(int borderTypeNI) +{ + return borderTypeNI == cv::BorderTypes::BORDER_CONSTANT ? ippBorderConst : + borderTypeNI == cv::BorderTypes::BORDER_TRANSPARENT ? ippBorderTransp : + borderTypeNI == cv::BorderTypes::BORDER_REPLICATE ? ippBorderRepl : + (IppiBorderType)-1; +} + +static inline int ippiSuggestThreadsNum(size_t width, size_t height, size_t elemSize, double multiplier) +{ + int threads = cv::getNumThreads(); + if(threads > 1 && height >= 64) + { + size_t opMemory = (int)(width*height*elemSize*multiplier); + int l2cache = 0; +#if IPP_VERSION_X100 >= 201700 + ippGetL2CacheSize(&l2cache); +#endif + if(!l2cache) + l2cache = 1 << 18; + + return IPP_MAX(1, (IPP_MIN((int)(opMemory/l2cache), threads))); + } + return 1; +} + +#ifdef HAVE_IPP_IW +static inline int ippiSuggestThreadsNum(const ::ipp::IwiImage &image, double multiplier) +{ + return ippiSuggestThreadsNum(image.m_size.width, image.m_size.height, image.m_typeSize*image.m_channels, multiplier); +} +#endif + +#endif //__PRECOMP_IPP_HPP__ diff --git a/hal/ipp/src/transforms_ipp.cpp b/hal/ipp/src/transforms_ipp.cpp index 83e66d52a5..0d2e05f291 100644 --- a/hal/ipp/src/transforms_ipp.cpp +++ b/hal/ipp/src/transforms_ipp.cpp @@ -3,6 +3,7 @@ // of this distribution and at http://opencv.org/license.html #include "ipp_hal_core.hpp" +#include "precomp_ipp.hpp" #include #include @@ -72,19 +73,6 @@ int ipp_hal_transpose2d(const uchar* src_data, size_t src_step, uchar* dst_data, #ifdef HAVE_IPP_IW -static inline IppDataType ippiGetDataType(int depth) -{ - depth = CV_MAT_DEPTH(depth); - return depth == CV_8U ? ipp8u : - depth == CV_8S ? ipp8s : - depth == CV_16U ? ipp16u : - depth == CV_16S ? ipp16s : - depth == CV_32S ? ipp32s : - depth == CV_32F ? ipp32f : - depth == CV_64F ? ipp64f : - (IppDataType)-1; -} - static inline ::ipp::IwiImage ippiGetImage(int src_type, const uchar* src_data, size_t src_step, int src_width, int src_height) { ::ipp::IwiImage dst; diff --git a/hal/ipp/src/warp_ipp.cpp b/hal/ipp/src/warp_ipp.cpp new file mode 100644 index 0000000000..a41d51460a --- /dev/null +++ b/hal/ipp/src/warp_ipp.cpp @@ -0,0 +1,367 @@ +// 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 + +#include "ipp_hal_imgproc.hpp" + +#include +#include +#include "precomp_ipp.hpp" + +#ifdef HAVE_IPP_IW +#include "iw++/iw.hpp" +#endif + +#define IPP_WARPAFFINE_PARALLEL 1 +#define CV_TYPE(src_type) (src_type & (CV_DEPTH_MAX - 1)) +#ifdef HAVE_IPP_IW + +class ipp_warpAffineParallel: public cv::ParallelLoopBody +{ +public: + ipp_warpAffineParallel(::ipp::IwiImage &src, ::ipp::IwiImage &dst, IppiInterpolationType _inter, double (&_coeffs)[2][3], ::ipp::IwiBorderType _borderType, IwTransDirection _iwTransDirection, bool *_ok):m_src(src), m_dst(dst) + { + pOk = _ok; + + inter = _inter; + borderType = _borderType; + iwTransDirection = _iwTransDirection; + + for( int i = 0; i < 2; i++ ) + for( int j = 0; j < 3; j++ ) + coeffs[i][j] = _coeffs[i][j]; + + *pOk = true; + } + ~ipp_warpAffineParallel() {} + + virtual void operator() (const cv::Range& range) const CV_OVERRIDE + { + //CV_INSTRUMENT_REGION_IPP(); + + if(*pOk == false) + return; + + try + { + ::ipp::IwiTile tile = ::ipp::IwiRoi(0, range.start, m_dst.m_size.width, range.end - range.start); + CV_INSTRUMENT_FUN_IPP(::ipp::iwiWarpAffine, m_src, m_dst, coeffs, iwTransDirection, inter, ::ipp::IwiWarpAffineParams(), borderType, tile); + } + catch(const ::ipp::IwException &) + { + *pOk = false; + return; + } + } +private: + ::ipp::IwiImage &m_src; + ::ipp::IwiImage &m_dst; + + IppiInterpolationType inter; + double coeffs[2][3]; + ::ipp::IwiBorderType borderType; + IwTransDirection iwTransDirection; + + bool *pOk; + const ipp_warpAffineParallel& operator= (const ipp_warpAffineParallel&); +}; + +#if (IPP_VERSION_X100 >= 700) +int ipp_hal_warpAffine(int src_type, const uchar *src_data, size_t src_step, int src_width, int src_height, uchar *dst_data, size_t dst_step, int dst_width, + int dst_height, const double M[6], int interpolation, int borderType, const double borderValue[4]) +{ + //CV_INSTRUMENT_REGION_IPP(); + + IppiInterpolationType ippInter = ippiGetInterpolation(interpolation); + if((int)ippInter < 0 || interpolation > 2) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + + /* C1 C2 C3 C4 */ + char impl[CV_DEPTH_MAX][4][3]={{{0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, //8U + {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, //8S + {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {1, 0, 0}}, //16U + {{1, 0, 0}, {0, 0, 0}, {1, 0, 0}, {1, 0, 0}}, //16S + {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, //32S + {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, //32F + {{1, 0, 0}, {0, 0, 0}, {1, 0, 0}, {1, 0, 0}}}; //64F + + if(impl[CV_TYPE(src_type)][CV_MAT_CN(src_type)-1][interpolation] == 0) + { + return CV_HAL_ERROR_NOT_IMPLEMENTED; + } + + // Acquire data and begin processing + try + { + ::ipp::IwiImage iwSrc; + iwSrc.Init({src_width, src_height}, ippiGetDataType(src_type), CV_MAT_CN(src_type), NULL, src_data, IwSize(src_step)); + ::ipp::IwiImage iwDst({dst_width, dst_height}, ippiGetDataType(src_type), CV_MAT_CN(src_type), NULL, dst_data, dst_step); + ::ipp::IwiBorderType ippBorder(ippiGetBorderType(borderType), {borderValue[0], borderValue[1], borderValue[2], borderValue[3]}); + IwTransDirection iwTransDirection = iwTransForward; + + if((int)ippBorder == -1) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + + double coeffs[2][3]; + for( int i = 0; i < 2; i++ ) + for( int j = 0; j < 3; j++ ) + coeffs[i][j] = M[i*3 + j]; + + const int threads = ippiSuggestThreadsNum(iwDst, 2); + + if(IPP_WARPAFFINE_PARALLEL && threads > 1) + { + bool ok = true; + cv::Range range(0, (int)iwDst.m_size.height); + ipp_warpAffineParallel invoker(iwSrc, iwDst, ippInter, coeffs, ippBorder, iwTransDirection, &ok); + if(!ok) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + + parallel_for_(range, invoker, threads*4); + + if(!ok) + return CV_HAL_ERROR_NOT_IMPLEMENTED; + } else { + CV_INSTRUMENT_FUN_IPP(::ipp::iwiWarpAffine, iwSrc, iwDst, coeffs, iwTransDirection, ippInter, ::ipp::IwiWarpAffineParams(), ippBorder); + } + } + catch (const ::ipp::IwException &) + { + return CV_HAL_ERROR_NOT_IMPLEMENTED; + } + + return CV_HAL_ERROR_OK; +} +#endif + +typedef IppStatus (CV_STDCALL* ippiSetFunc)(const void*, void *, int, IppiSize); + +template +bool IPPSetSimple(cv::Scalar value, void *dataPointer, int step, IppiSize &size, ippiSetFunc func) +{ + //CV_INSTRUMENT_REGION_IPP(); + + Type values[channels]; + for( int i = 0; i < channels; i++ ) + values[i] = cv::saturate_cast(value[i]); + return func(values, dataPointer, step, size) >= 0; +} + +static bool IPPSet(const cv::Scalar &value, void *dataPointer, int step, IppiSize &size, int channels, int depth) +{ + //CV_INSTRUMENT_REGION_IPP(); + + if( channels == 1 ) + { + switch( depth ) + { + case CV_8U: + return CV_INSTRUMENT_FUN_IPP(ippiSet_8u_C1R, cv::saturate_cast(value[0]), (Ipp8u *)dataPointer, step, size) >= 0; + case CV_16U: + return CV_INSTRUMENT_FUN_IPP(ippiSet_16u_C1R, cv::saturate_cast(value[0]), (Ipp16u *)dataPointer, step, size) >= 0; + case CV_32F: + return CV_INSTRUMENT_FUN_IPP(ippiSet_32f_C1R, cv::saturate_cast(value[0]), (Ipp32f *)dataPointer, step, size) >= 0; + } + } + else + { + if( channels == 3 ) + { + switch( depth ) + { + case CV_8U: + return IPPSetSimple<3, Ipp8u>(value, dataPointer, step, size, (ippiSetFunc)ippiSet_8u_C3R); + case CV_16U: + return IPPSetSimple<3, Ipp16u>(value, dataPointer, step, size, (ippiSetFunc)ippiSet_16u_C3R); + case CV_32F: + return IPPSetSimple<3, Ipp32f>(value, dataPointer, step, size, (ippiSetFunc)ippiSet_32f_C3R); + } + } + else if( channels == 4 ) + { + switch( depth ) + { + case CV_8U: + return IPPSetSimple<4, Ipp8u>(value, dataPointer, step, size, (ippiSetFunc)ippiSet_8u_C4R); + case CV_16U: + return IPPSetSimple<4, Ipp16u>(value, dataPointer, step, size, (ippiSetFunc)ippiSet_16u_C4R); + case CV_32F: + return IPPSetSimple<4, Ipp32f>(value, dataPointer, step, size, (ippiSetFunc)ippiSet_32f_C4R); + } + } + } + return false; +} + +#if (IPP_VERSION_X100 >= 810) +typedef IppStatus (CV_STDCALL* ippiWarpPerspectiveFunc)(const Ipp8u*, int, Ipp8u*, int,IppiPoint, IppiSize, const IppiWarpSpec*,Ipp8u*); + +class IPPWarpPerspectiveInvoker : + public cv::ParallelLoopBody +{ +public: + IPPWarpPerspectiveInvoker(int _src_type, cv::Mat &_src, size_t _src_step, cv::Mat &_dst, size_t _dst_step, IppiInterpolationType _interpolation, + double (&_coeffs)[3][3], int &_borderType, const double _borderValue[4], ippiWarpPerspectiveFunc _func, + bool *_ok) : + ParallelLoopBody(), src_type(_src_type), src(_src), src_step(_src_step), dst(_dst), dst_step(_dst_step), inter(_interpolation), coeffs(_coeffs), + borderType(_borderType), func(_func), ok(_ok) + { + memcpy(this->borderValue, _borderValue, sizeof(this->borderValue)); + *ok = true; + } + + virtual void operator() (const cv::Range& range) const CV_OVERRIDE + { + //CV_INSTRUMENT_REGION_IPP(); + IppiWarpSpec* pSpec = 0; + int specSize = 0, initSize = 0, bufSize = 0; Ipp8u* pBuffer = 0; + IppiPoint dstRoiOffset = {0, 0}; + IppiWarpDirection direction = ippWarpBackward; //fixed for IPP + const Ipp32u numChannels = CV_MAT_CN(src_type); + + IppiSize srcsize = {src.cols, src.rows}; + IppiSize dstsize = {dst.cols, dst.rows}; + IppiRect srcroi = {0, 0, src.cols, src.rows}; + + /* Spec and init buffer sizes */ + IppStatus status = ippiWarpPerspectiveGetSize(srcsize, srcroi, dstsize, ippiGetDataType(src_type), coeffs, inter, ippWarpBackward, ippiGetBorderType(borderType), &specSize, &initSize); + + pSpec = (IppiWarpSpec*)ippMalloc_L(specSize); + + if (inter == ippLinear) + { + status = ippiWarpPerspectiveLinearInit(srcsize, srcroi, dstsize, ippiGetDataType(src_type), coeffs, direction, numChannels, ippiGetBorderType(borderType), + borderValue, 0, pSpec); + } else + { + status = ippiWarpPerspectiveNearestInit(srcsize, srcroi, dstsize, ippiGetDataType(src_type), coeffs, direction, numChannels, ippiGetBorderType(borderType), + borderValue, 0, pSpec); + } + + status = ippiWarpGetBufferSize(pSpec, dstsize, &bufSize); + pBuffer = (Ipp8u*)ippMalloc_L(bufSize); + IppiSize dstRoiSize = dstsize; + + int cnn = src.channels(); + + if( borderType == cv::BorderTypes::BORDER_CONSTANT ) + { + IppiSize setSize = {dst.cols, range.end - range.start}; + void *dataPointer = dst.ptr(range.start); + if( !IPPSet( cv::Scalar(borderValue[0], borderValue[1], borderValue[2], borderValue[3]), dataPointer, (int)dst.step[0], setSize, cnn, src.depth() ) ) + { + *ok = false; + return; + } + } + + status = CV_INSTRUMENT_FUN_IPP(func, src.ptr(), (int)src_step, dst.ptr(), (int)dst_step, dstRoiOffset, dstRoiSize, pSpec, pBuffer); + if (status != ippStsNoErr) + *ok = false; + else + { + CV_IMPL_ADD(CV_IMPL_IPP|CV_IMPL_MT); + } + } +private: + int src_type; + cv::Mat &src; + size_t src_step; + cv::Mat &dst; + size_t dst_step; + IppiInterpolationType inter; + double (&coeffs)[3][3]; + int borderType; + double borderValue[4]; + ippiWarpPerspectiveFunc func; + bool *ok; + + const IPPWarpPerspectiveInvoker& operator= (const IPPWarpPerspectiveInvoker&); +}; + +int ipp_hal_warpPerspective(int src_type, const uchar *src_data, size_t src_step, int src_width, int src_height, uchar * dst_data, size_t dst_step, + int dst_width, int dst_height, const double M[9], int interpolation, int borderType, const double borderValue[4]) +{ + //CV_INSTRUMENT_REGION_IPP(); + ippiWarpPerspectiveFunc ippFunc = 0; + if (interpolation == cv::InterpolationFlags::INTER_NEAREST) + { + ippFunc = src_type == CV_8UC1 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveNearest_8u_C1R : + src_type == CV_8UC3 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveNearest_8u_C3R : + src_type == CV_8UC4 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveNearest_8u_C4R : + src_type == CV_16UC1 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveNearest_16u_C1R : + src_type == CV_16UC3 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveNearest_16u_C3R : + src_type == CV_16UC4 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveNearest_16u_C4R : + src_type == CV_16SC1 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveNearest_16s_C1R : + src_type == CV_16SC3 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveNearest_16s_C3R : + src_type == CV_16SC4 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveNearest_16s_C4R : + src_type == CV_32FC1 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveNearest_32f_C1R : + src_type == CV_32FC3 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveNearest_32f_C3R : + src_type == CV_32FC4 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveNearest_32f_C4R : 0; + } + else if (interpolation == cv::InterpolationFlags::INTER_LINEAR) + { + ippFunc = src_type == CV_8UC1 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveLinear_8u_C1R : + src_type == CV_8UC3 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveLinear_8u_C3R : + src_type == CV_8UC4 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveLinear_8u_C4R : + src_type == CV_16UC1 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveLinear_16u_C1R : + src_type == CV_16UC3 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveLinear_16u_C3R : + src_type == CV_16UC4 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveLinear_16u_C4R : + src_type == CV_16SC1 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveLinear_16s_C1R : + src_type == CV_16SC3 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveLinear_16s_C3R : + src_type == CV_16SC4 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveLinear_16s_C4R : + src_type == CV_32FC1 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveLinear_32f_C1R : + src_type == CV_32FC3 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveLinear_32f_C3R : + src_type == CV_32FC4 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveLinear_32f_C4R : 0; + } + else + { + return CV_HAL_ERROR_NOT_IMPLEMENTED; + } + + if(src_height == 1 || src_width == 1) return CV_HAL_ERROR_NOT_IMPLEMENTED; + + int mode = + interpolation == cv::InterpolationFlags::INTER_NEAREST ? IPPI_INTER_NN : + interpolation == cv::InterpolationFlags::INTER_LINEAR ? IPPI_INTER_LINEAR : 0; + + if (mode == 0 || ippFunc == 0) + { + return CV_HAL_ERROR_NOT_IMPLEMENTED; + } + + /* C1 C2 C3 C4 */ + char impl[CV_DEPTH_MAX][4][2]={{{0, 0}, {1, 1}, {0, 0}, {0, 0}}, //8U + {{1, 1}, {1, 1}, {1, 1}, {1, 1}}, //8S + {{0, 0}, {1, 1}, {0, 1}, {0, 1}}, //16U + {{1, 1}, {1, 1}, {1, 1}, {1, 1}}, //16S + {{1, 1}, {1, 1}, {1, 0}, {1, 1}}, //32S + {{1, 0}, {1, 0}, {0, 0}, {1, 0}}, //32F + {{1, 1}, {1, 1}, {1, 1}, {1, 1}}}; //64F + + if(impl[CV_TYPE(src_type)][CV_MAT_CN(src_type)-1][interpolation] == 0) + { + return CV_HAL_ERROR_NOT_IMPLEMENTED; + } + + double coeffs[3][3]; + for( int i = 0; i < 3; i++ ) + for( int j = 0; j < 3; j++ ) + coeffs[i][j] = M[i*3 + j]; + + bool ok; + cv::Range range(0, dst_height); + cv::Mat src(cv::Size(src_width, src_height), src_type, const_cast(src_data), src_step); + cv::Mat dst(cv::Size(dst_width, dst_height), src_type, dst_data, dst_step); + IppiInterpolationType ippInter = ippiGetInterpolation(interpolation); + IPPWarpPerspectiveInvoker invoker(src_type, src, src_step, dst, dst_step, ippInter, coeffs, borderType, borderValue, ippFunc, &ok); + parallel_for_(range, invoker, dst.total()/(double)(1<<16)); + + if( ok ) + { + CV_IMPL_ADD(CV_IMPL_IPP|CV_IMPL_MT); + } + return CV_HAL_ERROR_OK; +} +#endif +#endif diff --git a/modules/core/include/opencv2/core/private.hpp b/modules/core/include/opencv2/core/private.hpp index 4f23abf6de..d6f4425be2 100644 --- a/modules/core/include/opencv2/core/private.hpp +++ b/modules/core/include/opencv2/core/private.hpp @@ -197,8 +197,6 @@ T* allocSingletonNew() { return new(allocSingletonNewBuffer(sizeof(T))) T(); } #define IPP_DISABLE_PYRAMIDS_UP 1 // Different results #define IPP_DISABLE_PYRAMIDS_DOWN 1 // Different results #define IPP_DISABLE_PYRAMIDS_BUILD 1 // Different results -#define IPP_DISABLE_WARPAFFINE 1 // Different results -#define IPP_DISABLE_WARPPERSPECTIVE 1 // Different results #define IPP_DISABLE_REMAP 1 // Different results #define IPP_DISABLE_YUV_RGB 1 // accuracy difference #define IPP_DISABLE_RGB_YUV 1 // breaks OCL accuracy tests diff --git a/modules/imgproc/src/imgwarp.cpp b/modules/imgproc/src/imgwarp.cpp index f348860549..4690265420 100644 --- a/modules/imgproc/src/imgwarp.cpp +++ b/modules/imgproc/src/imgwarp.cpp @@ -60,7 +60,7 @@ using namespace cv; namespace cv { -#if defined (HAVE_IPP) && (!IPP_DISABLE_WARPAFFINE || !IPP_DISABLE_WARPPERSPECTIVE || !IPP_DISABLE_REMAP) +#if defined (HAVE_IPP) && (!IPP_DISABLE_REMAP) typedef IppStatus (CV_STDCALL* ippiSetFunc)(const void*, void *, int, IppiSize); template @@ -2207,62 +2207,6 @@ private: const double *M; }; - -#if defined (HAVE_IPP) && IPP_VERSION_X100 >= 810 && !IPP_DISABLE_WARPAFFINE -typedef IppStatus (CV_STDCALL* ippiWarpAffineBackFunc)(const void*, IppiSize, int, IppiRect, void *, int, IppiRect, double [2][3], int); - -class IPPWarpAffineInvoker : - public ParallelLoopBody -{ -public: - IPPWarpAffineInvoker(Mat &_src, Mat &_dst, double (&_coeffs)[2][3], int &_interpolation, int _borderType, - const Scalar &_borderValue, ippiWarpAffineBackFunc _func, bool *_ok) : - ParallelLoopBody(), src(_src), dst(_dst), mode(_interpolation), coeffs(_coeffs), - borderType(_borderType), borderValue(_borderValue), func(_func), ok(_ok) - { - *ok = true; - } - - virtual void operator() (const Range& range) const CV_OVERRIDE - { - IppiSize srcsize = { src.cols, src.rows }; - IppiRect srcroi = { 0, 0, src.cols, src.rows }; - IppiRect dstroi = { 0, range.start, dst.cols, range.end - range.start }; - int cnn = src.channels(); - if( borderType == BORDER_CONSTANT ) - { - IppiSize setSize = { dst.cols, range.end - range.start }; - void *dataPointer = dst.ptr(range.start); - if( !IPPSet( borderValue, dataPointer, (int)dst.step[0], setSize, cnn, src.depth() ) ) - { - *ok = false; - return; - } - } - - // Aug 2013: problem in IPP 7.1, 8.0 : sometimes function return ippStsCoeffErr - IppStatus status = CV_INSTRUMENT_FUN_IPP(func,( src.ptr(), srcsize, (int)src.step[0], srcroi, dst.ptr(), - (int)dst.step[0], dstroi, coeffs, mode )); - if( status < 0) - *ok = false; - else - { - CV_IMPL_ADD(CV_IMPL_IPP|CV_IMPL_MT); - } - } -private: - Mat &src; - Mat &dst; - int mode; - double (&coeffs)[2][3]; - int borderType; - Scalar borderValue; - ippiWarpAffineBackFunc func; - bool *ok; - const IPPWarpAffineInvoker& operator= (const IPPWarpAffineInvoker&); -}; -#endif - #ifdef HAVE_OPENCL enum { OCL_OP_PERSPECTIVE = 1, OCL_OP_AFFINE = 0 }; @@ -2452,132 +2396,6 @@ static bool ocl_warpTransform(InputArray _src, OutputArray _dst, InputArray _M0, #endif -#ifdef HAVE_IPP -#define IPP_WARPAFFINE_PARALLEL 1 - -#ifdef HAVE_IPP_IW - -class ipp_warpAffineParallel: public ParallelLoopBody -{ -public: - ipp_warpAffineParallel(::ipp::IwiImage &src, ::ipp::IwiImage &dst, IppiInterpolationType _inter, double (&_coeffs)[2][3], ::ipp::IwiBorderType _borderType, IwTransDirection _iwTransDirection, bool *_ok):m_src(src), m_dst(dst) - { - pOk = _ok; - - inter = _inter; - borderType = _borderType; - iwTransDirection = _iwTransDirection; - - for( int i = 0; i < 2; i++ ) - for( int j = 0; j < 3; j++ ) - coeffs[i][j] = _coeffs[i][j]; - - *pOk = true; - } - ~ipp_warpAffineParallel() {} - - virtual void operator() (const Range& range) const CV_OVERRIDE - { - CV_INSTRUMENT_REGION_IPP(); - - if(*pOk == false) - return; - - try - { - ::ipp::IwiTile tile = ::ipp::IwiRoi(0, range.start, m_dst.m_size.width, range.end - range.start); - CV_INSTRUMENT_FUN_IPP(::ipp::iwiWarpAffine, m_src, m_dst, coeffs, iwTransDirection, inter, ::ipp::IwiWarpAffineParams(), borderType, tile); - } - catch(const ::ipp::IwException &) - { - *pOk = false; - return; - } - } -private: - ::ipp::IwiImage &m_src; - ::ipp::IwiImage &m_dst; - - IppiInterpolationType inter; - double coeffs[2][3]; - ::ipp::IwiBorderType borderType; - IwTransDirection iwTransDirection; - - bool *pOk; - const ipp_warpAffineParallel& operator= (const ipp_warpAffineParallel&); -}; - -#endif - -static bool ipp_warpAffine( InputArray _src, OutputArray _dst, int interpolation, int borderType, const Scalar & borderValue, InputArray _M, int flags ) -{ -#ifdef HAVE_IPP_IW - CV_INSTRUMENT_REGION_IPP(); - - if (!cv::ipp::useIPP_NotExact()) - return false; - - IppiInterpolationType ippInter = ippiGetInterpolation(interpolation); - if((int)ippInter < 0) - return false; - - // 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::IwiBorderType ippBorder(ippiGetBorderType(borderType), ippiGetValue(borderValue)); - IwTransDirection iwTransDirection; - if(!ippBorder) - return false; - - if( !(flags & WARP_INVERSE_MAP) ) - iwTransDirection = iwTransForward; - else - iwTransDirection = iwTransInverse; - - Mat M = _M.getMat(); - double coeffs[2][3]; - for( int i = 0; i < 2; i++ ) - for( int j = 0; j < 3; j++ ) - coeffs[i][j] = M.at(i, j); - - const int threads = ippiSuggestThreadsNum(iwDst, 2); - - if(IPP_WARPAFFINE_PARALLEL && threads > 1) - { - bool ok = true; - Range range(0, (int)iwDst.m_size.height); - ipp_warpAffineParallel invoker(iwSrc, iwDst, ippInter, coeffs, ippBorder, iwTransDirection, &ok); - if(!ok) - return false; - - parallel_for_(range, invoker, threads*4); - - if(!ok) - return false; - } else { - CV_INSTRUMENT_FUN_IPP(::ipp::iwiWarpAffine, iwSrc, iwDst, coeffs, iwTransDirection, ippInter, ::ipp::IwiWarpAffineParams(), ippBorder); - } - - } - catch (const ::ipp::IwException &) - { - return false; - } - - return true; -#else - CV_UNUSED(_src); CV_UNUSED(_dst); CV_UNUSED(interpolation); - CV_UNUSED(borderType); CV_UNUSED(borderValue); CV_UNUSED(_M); CV_UNUSED(flags); - return false; -#endif -} - -#endif - namespace hal { void warpAffine(int src_type, @@ -2729,8 +2547,6 @@ void cv::warpAffine( InputArray _src, OutputArray _dst, CV_Assert( (M0.type() == CV_32F || M0.type() == CV_64F) && M0.rows == 2 && M0.cols == 3 ); M0.convertTo(matM, matM.type()); - CV_IPP_RUN_FAST(ipp_warpAffine(src, dst, interpolation, borderType, borderValue, matM, flags)); - if( !(flags & WARP_INVERSE_MAP) ) { double D = M[0]*M[4] - M[1]*M[3]; @@ -2743,70 +2559,6 @@ void cv::warpAffine( InputArray _src, OutputArray _dst, M[2] = b1; M[5] = b2; } -#if defined (HAVE_IPP) && IPP_VERSION_X100 >= 810 && !IPP_DISABLE_WARPAFFINE - CV_IPP_CHECK() - { - int type = src.type(), depth = CV_MAT_DEPTH(type), cn = CV_MAT_CN(type); - if( ( depth == CV_8U || depth == CV_16U || depth == CV_32F ) && - ( cn == 1 || cn == 3 || cn == 4 ) && - ( interpolation == INTER_NEAREST || interpolation == INTER_LINEAR || interpolation == INTER_CUBIC) && - ( borderType == cv::BORDER_TRANSPARENT || borderType == cv::BORDER_CONSTANT) ) - { - ippiWarpAffineBackFunc ippFunc = 0; - if ((flags & WARP_INVERSE_MAP) != 0) - { - ippFunc = - type == CV_8UC1 ? (ippiWarpAffineBackFunc)ippiWarpAffineBack_8u_C1R : - type == CV_8UC3 ? (ippiWarpAffineBackFunc)ippiWarpAffineBack_8u_C3R : - type == CV_8UC4 ? (ippiWarpAffineBackFunc)ippiWarpAffineBack_8u_C4R : - type == CV_16UC1 ? (ippiWarpAffineBackFunc)ippiWarpAffineBack_16u_C1R : - type == CV_16UC3 ? (ippiWarpAffineBackFunc)ippiWarpAffineBack_16u_C3R : - type == CV_16UC4 ? (ippiWarpAffineBackFunc)ippiWarpAffineBack_16u_C4R : - type == CV_32FC1 ? (ippiWarpAffineBackFunc)ippiWarpAffineBack_32f_C1R : - type == CV_32FC3 ? (ippiWarpAffineBackFunc)ippiWarpAffineBack_32f_C3R : - type == CV_32FC4 ? (ippiWarpAffineBackFunc)ippiWarpAffineBack_32f_C4R : - 0; - } - else - { - ippFunc = - type == CV_8UC1 ? (ippiWarpAffineBackFunc)ippiWarpAffine_8u_C1R : - type == CV_8UC3 ? (ippiWarpAffineBackFunc)ippiWarpAffine_8u_C3R : - type == CV_8UC4 ? (ippiWarpAffineBackFunc)ippiWarpAffine_8u_C4R : - type == CV_16UC1 ? (ippiWarpAffineBackFunc)ippiWarpAffine_16u_C1R : - type == CV_16UC3 ? (ippiWarpAffineBackFunc)ippiWarpAffine_16u_C3R : - type == CV_16UC4 ? (ippiWarpAffineBackFunc)ippiWarpAffine_16u_C4R : - type == CV_32FC1 ? (ippiWarpAffineBackFunc)ippiWarpAffine_32f_C1R : - type == CV_32FC3 ? (ippiWarpAffineBackFunc)ippiWarpAffine_32f_C3R : - type == CV_32FC4 ? (ippiWarpAffineBackFunc)ippiWarpAffine_32f_C4R : - 0; - } - int mode = - interpolation == INTER_LINEAR ? IPPI_INTER_LINEAR : - interpolation == INTER_NEAREST ? IPPI_INTER_NN : - interpolation == INTER_CUBIC ? IPPI_INTER_CUBIC : - 0; - CV_Assert(mode && ippFunc); - - double coeffs[2][3]; - for( int i = 0; i < 2; i++ ) - for( int j = 0; j < 3; j++ ) - coeffs[i][j] = matM.at(i, j); - - bool ok; - Range range(0, dst.rows); - IPPWarpAffineInvoker invoker(src, dst, coeffs, mode, borderType, borderValue, ippFunc, &ok); - parallel_for_(range, invoker, dst.total()/(double)(1<<16)); - if( ok ) - { - CV_IMPL_ADD(CV_IMPL_IPP|CV_IMPL_MT); - return; - } - setIppErrorStatus(); - } - } -#endif - hal::warpAffine(src.type(), src.data, src.step, src.cols, src.rows, dst.data, dst.step, dst.cols, dst.rows, M, interpolation, borderType, borderValue.val); } @@ -3135,60 +2887,6 @@ private: Scalar borderValue; }; -#if defined (HAVE_IPP) && IPP_VERSION_X100 >= 810 && !IPP_DISABLE_WARPPERSPECTIVE -typedef IppStatus (CV_STDCALL* ippiWarpPerspectiveFunc)(const void*, IppiSize, int, IppiRect, void *, int, IppiRect, double [3][3], int); - -class IPPWarpPerspectiveInvoker : - public ParallelLoopBody -{ -public: - IPPWarpPerspectiveInvoker(Mat &_src, Mat &_dst, double (&_coeffs)[3][3], int &_interpolation, - int &_borderType, const Scalar &_borderValue, ippiWarpPerspectiveFunc _func, bool *_ok) : - ParallelLoopBody(), src(_src), dst(_dst), mode(_interpolation), coeffs(_coeffs), - borderType(_borderType), borderValue(_borderValue), func(_func), ok(_ok) - { - *ok = true; - } - - virtual void operator() (const Range& range) const CV_OVERRIDE - { - IppiSize srcsize = {src.cols, src.rows}; - IppiRect srcroi = {0, 0, src.cols, src.rows}; - IppiRect dstroi = {0, range.start, dst.cols, range.end - range.start}; - int cnn = src.channels(); - - if( borderType == BORDER_CONSTANT ) - { - IppiSize setSize = {dst.cols, range.end - range.start}; - void *dataPointer = dst.ptr(range.start); - if( !IPPSet( borderValue, dataPointer, (int)dst.step[0], setSize, cnn, src.depth() ) ) - { - *ok = false; - return; - } - } - - IppStatus status = CV_INSTRUMENT_FUN_IPP(func,(src.ptr();, srcsize, (int)src.step[0], srcroi, dst.ptr(), (int)dst.step[0], dstroi, coeffs, mode)); - if (status != ippStsNoErr) - *ok = false; - else - { - CV_IMPL_ADD(CV_IMPL_IPP|CV_IMPL_MT); - } - } -private: - Mat &src; - Mat &dst; - int mode; - double (&coeffs)[3][3]; - int borderType; - const Scalar borderValue; - ippiWarpPerspectiveFunc func; - bool *ok; - - const IPPWarpPerspectiveInvoker& operator= (const IPPWarpPerspectiveInvoker&); -}; -#endif namespace hal { @@ -3309,65 +3007,6 @@ void cv::warpPerspective( InputArray _src, OutputArray _dst, InputArray _M0, CV_Assert( (M0.type() == CV_32F || M0.type() == CV_64F) && M0.rows == 3 && M0.cols == 3 ); M0.convertTo(matM, matM.type()); -#if defined (HAVE_IPP) && IPP_VERSION_X100 >= 810 && !IPP_DISABLE_WARPPERSPECTIVE - CV_IPP_CHECK() - { - int type = src.type(), depth = CV_MAT_DEPTH(type), cn = CV_MAT_CN(type); - if( (depth == CV_8U || depth == CV_16U || depth == CV_32F) && - (cn == 1 || cn == 3 || cn == 4) && - ( borderType == cv::BORDER_TRANSPARENT || borderType == cv::BORDER_CONSTANT ) && - (interpolation == INTER_NEAREST || interpolation == INTER_LINEAR || interpolation == INTER_CUBIC)) - { - ippiWarpPerspectiveFunc ippFunc = 0; - if ((flags & WARP_INVERSE_MAP) != 0) - { - ippFunc = type == CV_8UC1 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveBack_8u_C1R : - type == CV_8UC3 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveBack_8u_C3R : - type == CV_8UC4 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveBack_8u_C4R : - type == CV_16UC1 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveBack_16u_C1R : - type == CV_16UC3 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveBack_16u_C3R : - type == CV_16UC4 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveBack_16u_C4R : - type == CV_32FC1 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveBack_32f_C1R : - type == CV_32FC3 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveBack_32f_C3R : - type == CV_32FC4 ? (ippiWarpPerspectiveFunc)ippiWarpPerspectiveBack_32f_C4R : 0; - } - else - { - ippFunc = type == CV_8UC1 ? (ippiWarpPerspectiveFunc)ippiWarpPerspective_8u_C1R : - type == CV_8UC3 ? (ippiWarpPerspectiveFunc)ippiWarpPerspective_8u_C3R : - type == CV_8UC4 ? (ippiWarpPerspectiveFunc)ippiWarpPerspective_8u_C4R : - type == CV_16UC1 ? (ippiWarpPerspectiveFunc)ippiWarpPerspective_16u_C1R : - type == CV_16UC3 ? (ippiWarpPerspectiveFunc)ippiWarpPerspective_16u_C3R : - type == CV_16UC4 ? (ippiWarpPerspectiveFunc)ippiWarpPerspective_16u_C4R : - type == CV_32FC1 ? (ippiWarpPerspectiveFunc)ippiWarpPerspective_32f_C1R : - type == CV_32FC3 ? (ippiWarpPerspectiveFunc)ippiWarpPerspective_32f_C3R : - type == CV_32FC4 ? (ippiWarpPerspectiveFunc)ippiWarpPerspective_32f_C4R : 0; - } - int mode = - interpolation == INTER_NEAREST ? IPPI_INTER_NN : - interpolation == INTER_LINEAR ? IPPI_INTER_LINEAR : - interpolation == INTER_CUBIC ? IPPI_INTER_CUBIC : 0; - CV_Assert(mode && ippFunc); - - double coeffs[3][3]; - for( int i = 0; i < 3; i++ ) - for( int j = 0; j < 3; j++ ) - coeffs[i][j] = matM.at(i, j); - - bool ok; - Range range(0, dst.rows); - IPPWarpPerspectiveInvoker invoker(src, dst, coeffs, mode, borderType, borderValue, ippFunc, &ok); - parallel_for_(range, invoker, dst.total()/(double)(1<<16)); - if( ok ) - { - CV_IMPL_ADD(CV_IMPL_IPP|CV_IMPL_MT); - return; - } - setIppErrorStatus(); - } - } -#endif - if( !(flags & WARP_INVERSE_MAP) ) invert(matM, matM); From 8d426718c6ae924d8f5b846f4b5a5e2a55c1cc67 Mon Sep 17 00:00:00 2001 From: Kumataro Date: Sun, 6 Jul 2025 07:53:05 +0900 Subject: [PATCH 50/62] js: remove deprecated DEMANGLE_SUPPORT option --- modules/js/CMakeLists.txt | 48 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/modules/js/CMakeLists.txt b/modules/js/CMakeLists.txt index 47cba260e1..ca27e3df60 100644 --- a/modules/js/CMakeLists.txt +++ b/modules/js/CMakeLists.txt @@ -34,6 +34,47 @@ if(NOT EMSCRIPTEN_INCLUDE_DIR OR NOT PYTHON_DEFAULT_AVAILABLE) ocv_module_disable(js) endif() +# Get Emscripten version from emscripten/version.h +unset(EMSCRIPTEN_VERSION) +unset(EMSCRIPTEN_VERSION_CONTENTS) +unset(EMSCRIPTEN_VERSION_MAJOR) +unset(EMSCRIPTEN_VERSION_MINOR) +unset(EMSCRIPTEN_VERSION_TINY) + +set(EMSCRIPTEN_VERSION_PATH "${EMSCRIPTEN_INCLUDE_DIR}/emscripten/version.h") +if(NOT EXISTS "${EMSCRIPTEN_VERSION_PATH}") + message(STATUS "${EMSCRIPTEN_INCLUDE_DIR}/emscripten/version.h is missing") +else() + file(STRINGS "${EMSCRIPTEN_VERSION_PATH}" EMSCRIPTEN_VERSION_CONTENTS REGEX "^#define[ \t]+__EMSCRIPTEN_[a-z]+__[ \t][0-9]+") + if(NOT EMSCRIPTEN_VERSION_CONTENTS) + message(STATUS "${EMSCRIPTEN_INCLUDE_DIR}/emscripten/version.h is exists, but is not readable") + else() + if(EMSCRIPTEN_VERSION_CONTENTS MATCHES "__EMSCRIPTEN_major__[ \t]+([0-9]+)") + set(EMSCRIPTEN_VERSION_MAJOR "${CMAKE_MATCH_1}") + endif() + if(EMSCRIPTEN_VERSION_CONTENTS MATCHES "__EMSCRIPTEN_minor__[ \t]+([0-9]+)") + set(EMSCRIPTEN_VERSION_MINOR "${CMAKE_MATCH_1}") + endif() + if(EMSCRIPTEN_VERSION_CONTENTS MATCHES "__EMSCRIPTEN_tiny__[ \t]+([0-9]+)") + set(EMSCRIPTEN_VERSION_TINY "${CMAKE_MATCH_1}") + endif() + + # When version(major/minor/tiny) is 0, "if(version)" is failed. + # "if(version GREATER_EQUAL 0)" can compare version as numeric. + if( (EMSCRIPTEN_VERSION_MAJOR GREATER_EQUAL "0") AND + (EMSCRIPTEN_VERSION_MINOR GREATER_EQUAL "0") AND + (EMSCRIPTEN_VERSION_TINY GREATER_EQUAL "0") + ) + set(EMSCRIPTEN_VERSION "${EMSCRIPTEN_VERSION_MAJOR}.${EMSCRIPTEN_VERSION_MINOR}.${EMSCRIPTEN_VERSION_TINY}") + message(STATUS "js: Emscripten version = ${EMSCRIPTEN_VERSION}") + else() + message(STATUS "js: Emscripten version is not able to parsed") + message(AUTHOR_WARNING "EMSCRIPTEN_VERSION_CONTENTS = ${EMSCRIPTEN_VERSION_CONTENTS}") + endif() + endif() +endif() + + ocv_add_module(js BINDINGS PRIVATE_REQUIRED opencv_js_bindings_generator) ocv_module_include_directories(${EMSCRIPTEN_INCLUDE_DIR}) @@ -71,7 +112,12 @@ endif() set(EMSCRIPTEN_LINK_FLAGS "${EMSCRIPTEN_LINK_FLAGS} -s TOTAL_MEMORY=128MB -s WASM_MEM_MAX=1GB -s ALLOW_MEMORY_GROWTH=1") set(EMSCRIPTEN_LINK_FLAGS "${EMSCRIPTEN_LINK_FLAGS} -s MODULARIZE=1") -set(EMSCRIPTEN_LINK_FLAGS "${EMSCRIPTEN_LINK_FLAGS} -s EXPORT_NAME=\"'cv'\" -s DEMANGLE_SUPPORT=1") +set(EMSCRIPTEN_LINK_FLAGS "${EMSCRIPTEN_LINK_FLAGS} -s EXPORT_NAME=\"'cv'\"") +# See https://github.com/opencv/opencv/issues/27513 +# DEMANGLE_SUPPRT is deprecated at Emscripten 3.1.54 and later. +if(NOT EMSCRIPTEN_VERSION OR EMSCRIPTEN_VERSION VERSION_LESS "3.1.54") + set(EMSCRIPTEN_LINK_FLAGS "${EMSCRIPTEN_LINK_FLAGS} -s DEMANGLE_SUPPORT=1") +endif() set(EMSCRIPTEN_LINK_FLAGS "${EMSCRIPTEN_LINK_FLAGS} -s FORCE_FILESYSTEM=1 --use-preload-plugins --bind --post-js ${JS_HELPER} ${COMPILE_FLAGS}") set_target_properties(${the_module} PROPERTIES LINK_FLAGS "${EMSCRIPTEN_LINK_FLAGS}") From 6a5884c04f4c8f450bb22d61cf1b81dfdd798567 Mon Sep 17 00:00:00 2001 From: ClaudioMartino Date: Sat, 5 Jul 2025 11:15:35 +0200 Subject: [PATCH 51/62] New normalization in histogram comparison tutorial to use KV divergence The Kullback-Leibler divergence works with histogram that have integral = 1, otherwise it can return negative values. The normalization of the histograms have been changed accordingly, and all the six comparison methods have been used in the histogram comparison tutorial. --- .../histogram_comparison.markdown | 36 +++++++++++-------- .../Histograms_Matching/compareHist_Demo.cpp | 10 +++--- .../histogram_comparison/CompareHistDemo.java | 10 +++--- .../histogram_comparison/compareHist_Demo.py | 10 +++--- 4 files changed, 37 insertions(+), 29 deletions(-) diff --git a/doc/tutorials/imgproc/histograms/histogram_comparison/histogram_comparison.markdown b/doc/tutorials/imgproc/histograms/histogram_comparison/histogram_comparison.markdown index 9fcb0a46f9..2a7517fd95 100644 --- a/doc/tutorials/imgproc/histograms/histogram_comparison/histogram_comparison.markdown +++ b/doc/tutorials/imgproc/histograms/histogram_comparison/histogram_comparison.markdown @@ -25,7 +25,7 @@ Theory - To compare two histograms ( \f$H_{1}\f$ and \f$H_{2}\f$ ), first we have to choose a *metric* (\f$d(H_{1}, H_{2})\f$) to express how well both histograms match. -- OpenCV implements the function @ref cv::compareHist to perform a comparison. It also offers 4 +- OpenCV implements the function @ref cv::compareHist to perform a comparison. It also offers 6 different metrics to compute the matching: -# **Correlation ( cv::HISTCMP_CORREL )** \f[d(H_1,H_2) = \frac{\sum_I (H_1(I) - \bar{H_1}) (H_2(I) - \bar{H_2})}{\sqrt{\sum_I(H_1(I) - \bar{H_1})^2 \sum_I(H_2(I) - \bar{H_2})^2}}\f] @@ -36,12 +36,18 @@ Theory -# **Chi-Square ( cv::HISTCMP_CHISQR )** \f[d(H_1,H_2) = \sum _I \frac{\left(H_1(I)-H_2(I)\right)^2}{H_1(I)}\f] - -# **Intersection ( method=cv::HISTCMP_INTERSECT )** + -# **Intersection ( cv::HISTCMP_INTERSECT )** \f[d(H_1,H_2) = \sum _I \min (H_1(I), H_2(I))\f] -# **Bhattacharyya distance ( cv::HISTCMP_BHATTACHARYYA )** \f[d(H_1,H_2) = \sqrt{1 - \frac{1}{\sqrt{\bar{H_1} \bar{H_2} N^2}} \sum_I \sqrt{H_1(I) \cdot H_2(I)}}\f] + -# **Alternative Chi-Square ( cv::HISTCMP_CHISQR_ALT )** + \f[d(H_1,H_2) = 2 * \sum _I \frac{\left(H_1(I)-H_2(I)\right)^2}{H_1(I)+H_2(I)}\f] + + -# **Kullback-Leibler divergence ( cv::HISTCMP_KL_DIV )** + \f[d(H_1,H_2) = \sum _I H_1(I) \log \left(\frac{H_1(I)}{H_2(I)}\right)\f] */ + Code ---- @@ -123,7 +129,7 @@ Explanation @snippet samples/python/tutorial_code/Histograms_Matching/histogram_comparison/compareHist_Demo.py Convert to HSV half @end_toggle -- Initialize the arguments to calculate the histograms (bins, ranges and channels H and S ). +- Initialize the arguments to calculate the histograms (bins, ranges and channels H and S). @add_toggle_cpp @snippet samples/cpp/tutorial_code/Histograms_Matching/compareHist_Demo.cpp Using 50 bins for hue and 60 for saturation @@ -151,7 +157,7 @@ Explanation @snippet samples/python/tutorial_code/Histograms_Matching/histogram_comparison/compareHist_Demo.py Calculate the histograms for the HSV images @end_toggle -- Apply sequentially the 4 comparison methods between the histogram of the base image (hist_base) +- Apply sequentially the 6 comparison methods between the histogram of the base image (hist_base) and the other histograms: @add_toggle_cpp @@ -182,15 +188,17 @@ Results are from the same source. For the other two test images, we can observe that they have very different lighting conditions, so the matching should not be very good: --# Here the numeric results we got with OpenCV 3.4.1: - *Method* | Base - Base | Base - Half | Base - Test 1 | Base - Test 2 - ----------------- | ------------ | ------------ | -------------- | --------------- - *Correlation* | 1.000000 | 0.880438 | 0.20457 | 0.0664547 - *Chi-square* | 0.000000 | 4.6834 | 2697.98 | 4763.8 - *Intersection* | 18.8947 | 13.022 | 5.44085 | 2.58173 - *Bhattacharyya* | 0.000000 | 0.237887 | 0.679826 | 0.874173 +-# Here the numeric results we got with OpenCV 4.12.0: + *Method* | Base - Base | Base - Half | Base - Test 1 | Base - Test 2 + ------------------- | ------------ | ------------ | -------------- | --------------- + *Correlation* | 1.000000 | 0.880438 | 0.20457 | 0.065752 + *Chi-square* | 0.000000 | 0.328307 | 181.674 | 80.1494 + *Intersection* | 1.000000 | 0.75005 | 0.315061 | 0.0908022 + *Bhattacharyya* | 0.000000 | 0.237866 | 0.679825 | 0.873709 + *Chi-Square alt.* | 0.000000 | 0.395046 | 2.31572 | 3.41024 + *KL divergence* | 0.000000 | 0.321064 | 2.6616 | 9.55412 + For the *Correlation* and *Intersection* methods, the higher the metric, the more accurate the match. As we can see, the match *base-base* is the highest of all as expected. Also we can observe - that the match *base-half* is the second best match (as we predicted). For the other two metrics, - the less the result, the better the match. We can observe that the matches between the test 1 and - test 2 with respect to the base are worse, which again, was expected. + that the match *base-half* is the second best match (as we predicted). For the other four metrics, + the less the result, the better the match. diff --git a/samples/cpp/tutorial_code/Histograms_Matching/compareHist_Demo.cpp b/samples/cpp/tutorial_code/Histograms_Matching/compareHist_Demo.cpp index dcbcf8d568..2e08385577 100644 --- a/samples/cpp/tutorial_code/Histograms_Matching/compareHist_Demo.cpp +++ b/samples/cpp/tutorial_code/Histograms_Matching/compareHist_Demo.cpp @@ -66,20 +66,20 @@ int main( int argc, char** argv ) Mat hist_base, hist_half_down, hist_test1, hist_test2; calcHist( &hsv_base, 1, channels, Mat(), hist_base, 2, histSize, ranges, true, false ); - normalize( hist_base, hist_base, 0, 1, NORM_MINMAX, -1, Mat() ); + normalize( hist_base, hist_base, 1, 0, NORM_L1, -1, Mat() ); calcHist( &hsv_half_down, 1, channels, Mat(), hist_half_down, 2, histSize, ranges, true, false ); - normalize( hist_half_down, hist_half_down, 0, 1, NORM_MINMAX, -1, Mat() ); + normalize( hist_half_down, hist_half_down, 1, 0, NORM_L1, -1, Mat() ); calcHist( &hsv_test1, 1, channels, Mat(), hist_test1, 2, histSize, ranges, true, false ); - normalize( hist_test1, hist_test1, 0, 1, NORM_MINMAX, -1, Mat() ); + normalize( hist_test1, hist_test1, 1, 0, NORM_L1, -1, Mat() ); calcHist( &hsv_test2, 1, channels, Mat(), hist_test2, 2, histSize, ranges, true, false ); - normalize( hist_test2, hist_test2, 0, 1, NORM_MINMAX, -1, Mat() ); + normalize( hist_test2, hist_test2, 1, 0, NORM_L1, -1, Mat() ); //! [Calculate the histograms for the HSV images] //! [Apply the histogram comparison methods] - for( int compare_method = 0; compare_method < 4; compare_method++ ) + for( int compare_method = 0; compare_method < 6; compare_method++ ) { double base_base = compareHist( hist_base, hist_base, compare_method ); double base_half = compareHist( hist_base, hist_half_down, compare_method ); diff --git a/samples/java/tutorial_code/Histograms_Matching/histogram_comparison/CompareHistDemo.java b/samples/java/tutorial_code/Histograms_Matching/histogram_comparison/CompareHistDemo.java index fb65dc6d23..49370fa20b 100644 --- a/samples/java/tutorial_code/Histograms_Matching/histogram_comparison/CompareHistDemo.java +++ b/samples/java/tutorial_code/Histograms_Matching/histogram_comparison/CompareHistDemo.java @@ -52,23 +52,23 @@ class CompareHist { List hsvBaseList = Arrays.asList(hsvBase); Imgproc.calcHist(hsvBaseList, new MatOfInt(channels), new Mat(), histBase, new MatOfInt(histSize), new MatOfFloat(ranges), false); - Core.normalize(histBase, histBase, 0, 1, Core.NORM_MINMAX); + Core.normalize(histBase, histBase, 1, 0, Core.NORM_L1); List hsvHalfDownList = Arrays.asList(hsvHalfDown); Imgproc.calcHist(hsvHalfDownList, new MatOfInt(channels), new Mat(), histHalfDown, new MatOfInt(histSize), new MatOfFloat(ranges), false); - Core.normalize(histHalfDown, histHalfDown, 0, 1, Core.NORM_MINMAX); + Core.normalize(histHalfDown, histHalfDown, 1, 0, Core.NORM_L1); List hsvTest1List = Arrays.asList(hsvTest1); Imgproc.calcHist(hsvTest1List, new MatOfInt(channels), new Mat(), histTest1, new MatOfInt(histSize), new MatOfFloat(ranges), false); - Core.normalize(histTest1, histTest1, 0, 1, Core.NORM_MINMAX); + Core.normalize(histTest1, histTest1, 1, 0, Core.NORM_L1); List hsvTest2List = Arrays.asList(hsvTest2); Imgproc.calcHist(hsvTest2List, new MatOfInt(channels), new Mat(), histTest2, new MatOfInt(histSize), new MatOfFloat(ranges), false); - Core.normalize(histTest2, histTest2, 0, 1, Core.NORM_MINMAX); + Core.normalize(histTest2, histTest2, 1, 0, Core.NORM_L1); //! [Calculate the histograms for the HSV images] //! [Apply the histogram comparison methods] - for( int compareMethod = 0; compareMethod < 4; compareMethod++ ) { + for( int compareMethod = 0; compareMethod < 6; compareMethod++ ) { double baseBase = Imgproc.compareHist( histBase, histBase, compareMethod ); double baseHalf = Imgproc.compareHist( histBase, histHalfDown, compareMethod ); double baseTest1 = Imgproc.compareHist( histBase, histTest1, compareMethod ); diff --git a/samples/python/tutorial_code/Histograms_Matching/histogram_comparison/compareHist_Demo.py b/samples/python/tutorial_code/Histograms_Matching/histogram_comparison/compareHist_Demo.py index 08d0dc3564..5a070e09b9 100644 --- a/samples/python/tutorial_code/Histograms_Matching/histogram_comparison/compareHist_Demo.py +++ b/samples/python/tutorial_code/Histograms_Matching/histogram_comparison/compareHist_Demo.py @@ -45,20 +45,20 @@ channels = [0, 1] ## [Calculate the histograms for the HSV images] hist_base = cv.calcHist([hsv_base], channels, None, histSize, ranges, accumulate=False) -cv.normalize(hist_base, hist_base, alpha=0, beta=1, norm_type=cv.NORM_MINMAX) +cv.normalize(hist_base, hist_base, alpha=1, beta=0, norm_type=cv.NORM_L1) hist_half_down = cv.calcHist([hsv_half_down], channels, None, histSize, ranges, accumulate=False) -cv.normalize(hist_half_down, hist_half_down, alpha=0, beta=1, norm_type=cv.NORM_MINMAX) +cv.normalize(hist_half_down, hist_half_down, alpha=1, beta=0, norm_type=cv.NORM_L1) hist_test1 = cv.calcHist([hsv_test1], channels, None, histSize, ranges, accumulate=False) -cv.normalize(hist_test1, hist_test1, alpha=0, beta=1, norm_type=cv.NORM_MINMAX) +cv.normalize(hist_test1, hist_test1, alpha=1, beta=0, norm_type=cv.NORM_L1) hist_test2 = cv.calcHist([hsv_test2], channels, None, histSize, ranges, accumulate=False) -cv.normalize(hist_test2, hist_test2, alpha=0, beta=1, norm_type=cv.NORM_MINMAX) +cv.normalize(hist_test2, hist_test2, alpha=1, beta=0, norm_type=cv.NORM_L1) ## [Calculate the histograms for the HSV images] ## [Apply the histogram comparison methods] -for compare_method in range(4): +for compare_method in range(6): base_base = cv.compareHist(hist_base, hist_base, compare_method) base_half = cv.compareHist(hist_base, hist_half_down, compare_method) base_test1 = cv.compareHist(hist_base, hist_test1, compare_method) From 3001db47767eaae32e25260461d0ccce56075c89 Mon Sep 17 00:00:00 2001 From: ClaudioMartino Date: Mon, 7 Jul 2025 15:34:33 +0200 Subject: [PATCH 52/62] Fix: Removed '*/' from equation in histogram comparison doc --- .../histogram_comparison/histogram_comparison.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/tutorials/imgproc/histograms/histogram_comparison/histogram_comparison.markdown b/doc/tutorials/imgproc/histograms/histogram_comparison/histogram_comparison.markdown index 2a7517fd95..eac3db03d2 100644 --- a/doc/tutorials/imgproc/histograms/histogram_comparison/histogram_comparison.markdown +++ b/doc/tutorials/imgproc/histograms/histogram_comparison/histogram_comparison.markdown @@ -46,7 +46,7 @@ Theory \f[d(H_1,H_2) = 2 * \sum _I \frac{\left(H_1(I)-H_2(I)\right)^2}{H_1(I)+H_2(I)}\f] -# **Kullback-Leibler divergence ( cv::HISTCMP_KL_DIV )** - \f[d(H_1,H_2) = \sum _I H_1(I) \log \left(\frac{H_1(I)}{H_2(I)}\right)\f] */ + \f[d(H_1,H_2) = \sum _I H_1(I) \log \left(\frac{H_1(I)}{H_2(I)}\right)\f] Code ---- From d19dd94deefcc7ef700a6a872231cab3efc5dfc0 Mon Sep 17 00:00:00 2001 From: ekharkov Date: Tue, 8 Jul 2025 12:31:40 +0200 Subject: [PATCH 53/62] Moved IPP remap to HAL --- hal/ipp/include/ipp_hal_imgproc.hpp | 9 ++ hal/ipp/src/precomp_ipp.hpp | 12 +++ hal/ipp/src/warp_ipp.cpp | 127 ++++++++++++++++++++++- modules/imgproc/src/imgwarp.cpp | 151 ---------------------------- 4 files changed, 143 insertions(+), 156 deletions(-) diff --git a/hal/ipp/include/ipp_hal_imgproc.hpp b/hal/ipp/include/ipp_hal_imgproc.hpp index bafbf3faaf..29ebee241d 100644 --- a/hal/ipp/include/ipp_hal_imgproc.hpp +++ b/hal/ipp/include/ipp_hal_imgproc.hpp @@ -24,4 +24,13 @@ int ipp_hal_warpPerspective(int src_type, const uchar *src_data, size_t src_step #define cv_hal_warpPerspective ipp_hal_warpPerspective #endif + +int ipp_hal_remap32f(int src_type, const uchar *src_data, size_t src_step, int src_width, int src_height, + uchar *dst_data, size_t dst_step, int dst_width, int dst_height, + float* mapx, size_t mapx_step, float* mapy, size_t mapy_step, + int interpolation, int border_type, const double border_value[4]); +#undef cv_hal_remap32f +#define cv_hal_remap32f ipp_hal_remap32f + + #endif //__IPP_HAL_IMGPROC_HPP__ diff --git a/hal/ipp/src/precomp_ipp.hpp b/hal/ipp/src/precomp_ipp.hpp index b63e660a94..bff2d499b1 100644 --- a/hal/ipp/src/precomp_ipp.hpp +++ b/hal/ipp/src/precomp_ipp.hpp @@ -11,6 +11,18 @@ #include "iw++/iw.hpp" #endif +static inline IppiSize ippiSize(size_t width, size_t height) +{ + IppiSize size = { (int)width, (int)height }; + return size; +} + +static inline IppiSize ippiSize(const cv::Size & _size) +{ + IppiSize size = { _size.width, _size.height }; + return size; +} + static inline IppDataType ippiGetDataType(int depth) { depth = CV_MAT_DEPTH(depth); diff --git a/hal/ipp/src/warp_ipp.cpp b/hal/ipp/src/warp_ipp.cpp index a41d51460a..ffad2e13e2 100644 --- a/hal/ipp/src/warp_ipp.cpp +++ b/hal/ipp/src/warp_ipp.cpp @@ -133,21 +133,22 @@ int ipp_hal_warpAffine(int src_type, const uchar *src_data, size_t src_step, int return CV_HAL_ERROR_OK; } #endif +#endif typedef IppStatus (CV_STDCALL* ippiSetFunc)(const void*, void *, int, IppiSize); template -bool IPPSetSimple(cv::Scalar value, void *dataPointer, int step, IppiSize &size, ippiSetFunc func) +bool IPPSetSimple(const double value[4], void *dataPointer, int step, IppiSize &size, ippiSetFunc func) { //CV_INSTRUMENT_REGION_IPP(); Type values[channels]; for( int i = 0; i < channels; i++ ) values[i] = cv::saturate_cast(value[i]); - return func(values, dataPointer, step, size) >= 0; + return CV_INSTRUMENT_FUN_IPP(func, values, dataPointer, step, size) >= 0; } -static bool IPPSet(const cv::Scalar &value, void *dataPointer, int step, IppiSize &size, int channels, int depth) +static bool IPPSet(const double value[4], void *dataPointer, int step, IppiSize &size, int channels, int depth) { //CV_INSTRUMENT_REGION_IPP(); @@ -248,7 +249,7 @@ public: { IppiSize setSize = {dst.cols, range.end - range.start}; void *dataPointer = dst.ptr(range.start); - if( !IPPSet( cv::Scalar(borderValue[0], borderValue[1], borderValue[2], borderValue[3]), dataPointer, (int)dst.step[0], setSize, cnn, src.depth() ) ) + if( !IPPSet( borderValue, dataPointer, (int)dst.step[0], setSize, cnn, src.depth() ) ) { *ok = false; return; @@ -364,4 +365,120 @@ int ipp_hal_warpPerspective(int src_type, const uchar *src_data, size_t src_step return CV_HAL_ERROR_OK; } #endif -#endif + +typedef IppStatus(CV_STDCALL *ippiRemap)(const void *pSrc, IppiSize srcSize, int srcStep, IppiRect srcRoi, + const Ipp32f *pxMap, int xMapStep, const Ipp32f *pyMap, int yMapStep, + void *pDst, int dstStep, IppiSize dstRoiSize, int interpolation); + +class IPPRemapInvoker : public cv::ParallelLoopBody +{ +public: + IPPRemapInvoker(int _src_type, const uchar *_src_data, size_t _src_step, int _src_width, int _src_height, + uchar *_dst_data, size_t _dst_step, int _dst_width, float *_mapx, size_t _mapx_step, float *_mapy, + size_t _mapy_step, ippiRemap _ippFunc, int _ippInterpolation, int _borderType, const double _borderValue[4], bool *_ok) : + ParallelLoopBody(), + src_type(_src_type), src(_src_data), src_step(_src_step), src_width(_src_width), src_height(_src_height), + dst(_dst_data), dst_step(_dst_step), dst_width(_dst_width), mapx(_mapx), mapx_step(_mapx_step), mapy(_mapy), + mapy_step(_mapy_step), ippFunc(_ippFunc), ippInterpolation(_ippInterpolation), borderType(_borderType), ok(_ok) + { + memcpy(this->borderValue, _borderValue, sizeof(this->borderValue)); + *ok = true; + } + + virtual void operator()(const cv::Range &range) const + { + IppiRect srcRoiRect = {0, 0, src_width, src_height}; + uchar *dst_roi_data = dst + range.start * dst_step; + IppiSize dstRoiSize = ippiSize(dst_width, range.size()); + int depth = CV_MAT_DEPTH(src_type), cn = CV_MAT_CN(src_type); + + if (borderType == cv::BORDER_CONSTANT && + !IPPSet(borderValue, dst_roi_data, (int)dst_step, dstRoiSize, cn, depth)) + { + *ok = false; + return; + } + + if (CV_INSTRUMENT_FUN_IPP(ippFunc, src, {src_width, src_height}, (int)src_step, srcRoiRect, + mapx, (int)mapx_step, mapy, (int)mapy_step, + dst_roi_data, (int)dst_step, dstRoiSize, ippInterpolation) < 0) + *ok = false; + else + { + CV_IMPL_ADD(CV_IMPL_IPP | CV_IMPL_MT); + } + } + +private: + int src_type; + const uchar *src; + size_t src_step; + int src_width, src_height; + uchar *dst; + size_t dst_step; + int dst_width; + float *mapx; + size_t mapx_step; + float *mapy; + size_t mapy_step; + ippiRemap ippFunc; + int ippInterpolation, borderType; + double borderValue[4]; + bool *ok; +}; + +int ipp_hal_remap32f(int src_type, const uchar *src_data, size_t src_step, int src_width, int src_height, + uchar *dst_data, size_t dst_step, int dst_width, int dst_height, + float *mapx, size_t mapx_step, float *mapy, size_t mapy_step, + int interpolation, int border_type, const double border_value[4]) +{ + if ((interpolation == cv::INTER_LINEAR || interpolation == cv::INTER_CUBIC || interpolation == cv::INTER_NEAREST) && + (border_type == cv::BORDER_CONSTANT || border_type == cv::BORDER_TRANSPARENT)) + { + int ippInterpolation = + interpolation == cv::INTER_NEAREST ? IPPI_INTER_NN : interpolation == cv::INTER_LINEAR ? IPPI_INTER_LINEAR + : IPPI_INTER_CUBIC; + + /* C1 C2 C3 C4 */ + char impl[CV_DEPTH_MAX][4][3]={{{0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, //8U + {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, //8S + {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, //16U + {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, //16S + {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, //32S + {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, //32F + {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}}}; //64F + + if (impl[CV_TYPE(src_type)][CV_MAT_CN(src_type) - 1][interpolation] == 0) + { + return CV_HAL_ERROR_NOT_IMPLEMENTED; + } + + ippiRemap ippFunc = + src_type == CV_8UC1 ? (ippiRemap)ippiRemap_8u_C1R : src_type == CV_8UC3 ? (ippiRemap)ippiRemap_8u_C3R + : src_type == CV_8UC4 ? (ippiRemap)ippiRemap_8u_C4R + : src_type == CV_16UC1 ? (ippiRemap)ippiRemap_16u_C1R + : src_type == CV_16UC3 ? (ippiRemap)ippiRemap_16u_C3R + : src_type == CV_16UC4 ? (ippiRemap)ippiRemap_16u_C4R + : src_type == CV_32FC1 ? (ippiRemap)ippiRemap_32f_C1R + : src_type == CV_32FC3 ? (ippiRemap)ippiRemap_32f_C3R + : src_type == CV_32FC4 ? (ippiRemap)ippiRemap_32f_C4R + : 0; + + if (ippFunc) + { + bool ok; + + IPPRemapInvoker invoker(src_type, src_data, src_step, src_width, src_height, dst_data, dst_step, dst_width, + mapx, mapx_step, mapy, mapy_step, ippFunc, ippInterpolation, border_type, border_value, &ok); + cv::Range range(0, dst_height); + cv::parallel_for_(range, invoker, dst_width * dst_height / (double)(1 << 16)); + + if (ok) + { + CV_IMPL_ADD(CV_IMPL_IPP | CV_IMPL_MT); + return CV_HAL_ERROR_OK; + } + } + } + return CV_HAL_ERROR_NOT_IMPLEMENTED; +} diff --git a/modules/imgproc/src/imgwarp.cpp b/modules/imgproc/src/imgwarp.cpp index 4690265420..602ed802a2 100644 --- a/modules/imgproc/src/imgwarp.cpp +++ b/modules/imgproc/src/imgwarp.cpp @@ -60,66 +60,6 @@ using namespace cv; namespace cv { -#if defined (HAVE_IPP) && (!IPP_DISABLE_REMAP) -typedef IppStatus (CV_STDCALL* ippiSetFunc)(const void*, void *, int, IppiSize); - -template -bool IPPSetSimple(cv::Scalar value, void *dataPointer, int step, IppiSize &size, ippiSetFunc func) -{ - CV_INSTRUMENT_REGION_IPP(); - - Type values[channels]; - for( int i = 0; i < channels; i++ ) - values[i] = saturate_cast(value[i]); - return func(values, dataPointer, step, size) >= 0; -} - -static bool IPPSet(const cv::Scalar &value, void *dataPointer, int step, IppiSize &size, int channels, int depth) -{ - CV_INSTRUMENT_REGION_IPP(); - - if( channels == 1 ) - { - switch( depth ) - { - case CV_8U: - return CV_INSTRUMENT_FUN_IPP(ippiSet_8u_C1R, saturate_cast(value[0]), (Ipp8u *)dataPointer, step, size) >= 0; - case CV_16U: - return CV_INSTRUMENT_FUN_IPP(ippiSet_16u_C1R, saturate_cast(value[0]), (Ipp16u *)dataPointer, step, size) >= 0; - case CV_32F: - return CV_INSTRUMENT_FUN_IPP(ippiSet_32f_C1R, saturate_cast(value[0]), (Ipp32f *)dataPointer, step, size) >= 0; - } - } - else - { - if( channels == 3 ) - { - switch( depth ) - { - case CV_8U: - return IPPSetSimple<3, Ipp8u>(value, dataPointer, step, size, (ippiSetFunc)ippiSet_8u_C3R); - case CV_16U: - return IPPSetSimple<3, Ipp16u>(value, dataPointer, step, size, (ippiSetFunc)ippiSet_16u_C3R); - case CV_32F: - return IPPSetSimple<3, Ipp32f>(value, dataPointer, step, size, (ippiSetFunc)ippiSet_32f_C3R); - } - } - else if( channels == 4 ) - { - switch( depth ) - { - case CV_8U: - return IPPSetSimple<4, Ipp8u>(value, dataPointer, step, size, (ippiSetFunc)ippiSet_8u_C4R); - case CV_16U: - return IPPSetSimple<4, Ipp16u>(value, dataPointer, step, size, (ippiSetFunc)ippiSet_16u_C4R); - case CV_32F: - return IPPSetSimple<4, Ipp32f>(value, dataPointer, step, size, (ippiSetFunc)ippiSet_32f_C4R); - } - } - } - return false; -} -#endif /************** interpolation formulas and tables ***************/ @@ -1572,57 +1512,7 @@ static bool ocl_logPolar(InputArray _src, OutputArray _dst, #endif -#if defined HAVE_IPP && !IPP_DISABLE_REMAP -typedef IppStatus (CV_STDCALL * ippiRemap)(const void * pSrc, IppiSize srcSize, int srcStep, IppiRect srcRoi, - const Ipp32f* pxMap, int xMapStep, const Ipp32f* pyMap, int yMapStep, - void * pDst, int dstStep, IppiSize dstRoiSize, int interpolation); - -class IPPRemapInvoker : - public ParallelLoopBody -{ -public: - IPPRemapInvoker(Mat & _src, Mat & _dst, Mat & _xmap, Mat & _ymap, ippiRemap _ippFunc, - int _ippInterpolation, int _borderType, const Scalar & _borderValue, bool * _ok) : - ParallelLoopBody(), src(_src), dst(_dst), map1(_xmap), map2(_ymap), ippFunc(_ippFunc), - ippInterpolation(_ippInterpolation), borderType(_borderType), borderValue(_borderValue), ok(_ok) - { - *ok = true; - } - - virtual void operator() (const Range & range) const - { - IppiRect srcRoiRect = { 0, 0, src.cols, src.rows }; - Mat dstRoi = dst.rowRange(range); - IppiSize dstRoiSize = ippiSize(dstRoi.size()); - int type = dst.type(), depth = CV_MAT_DEPTH(type), cn = CV_MAT_CN(type); - - if (borderType == BORDER_CONSTANT && - !IPPSet(borderValue, dstRoi.ptr(), (int)dstRoi.step, dstRoiSize, cn, depth)) - { - *ok = false; - return; - } - - if (CV_INSTRUMENT_FUN_IPP(ippFunc, src.ptr(), ippiSize(src.size()), (int)src.step, srcRoiRect, - map1.ptr(), (int)map1.step, map2.ptr(), (int)map2.step, - dstRoi.ptr(), (int)dstRoi.step, dstRoiSize, ippInterpolation) < 0) - *ok = false; - else - { - CV_IMPL_ADD(CV_IMPL_IPP|CV_IMPL_MT); - } - } - -private: - Mat & src, & dst, & map1, & map2; - ippiRemap ippFunc; - int ippInterpolation, borderType; - Scalar borderValue; - bool * ok; -}; - -#endif } @@ -1737,47 +1627,6 @@ void cv::remap( InputArray _src, OutputArray _dst, int type = src.type(), depth = CV_MAT_DEPTH(type); -#if defined HAVE_IPP && !IPP_DISABLE_REMAP - CV_IPP_CHECK() - { - if ((interpolation == INTER_LINEAR || interpolation == INTER_CUBIC || interpolation == INTER_NEAREST) && - map1.type() == CV_32FC1 && map2.type() == CV_32FC1 && - (borderType == BORDER_CONSTANT || borderType == BORDER_TRANSPARENT)) - { - int ippInterpolation = - interpolation == INTER_NEAREST ? IPPI_INTER_NN : - interpolation == INTER_LINEAR ? IPPI_INTER_LINEAR : IPPI_INTER_CUBIC; - - ippiRemap ippFunc = - type == CV_8UC1 ? (ippiRemap)ippiRemap_8u_C1R : - type == CV_8UC3 ? (ippiRemap)ippiRemap_8u_C3R : - type == CV_8UC4 ? (ippiRemap)ippiRemap_8u_C4R : - type == CV_16UC1 ? (ippiRemap)ippiRemap_16u_C1R : - type == CV_16UC3 ? (ippiRemap)ippiRemap_16u_C3R : - type == CV_16UC4 ? (ippiRemap)ippiRemap_16u_C4R : - type == CV_32FC1 ? (ippiRemap)ippiRemap_32f_C1R : - type == CV_32FC3 ? (ippiRemap)ippiRemap_32f_C3R : - type == CV_32FC4 ? (ippiRemap)ippiRemap_32f_C4R : 0; - - if (ippFunc) - { - bool ok; - IPPRemapInvoker invoker(src, dst, map1, map2, ippFunc, ippInterpolation, - borderType, borderValue, &ok); - Range range(0, dst.rows); - parallel_for_(range, invoker, dst.total() / (double)(1 << 16)); - - if (ok) - { - CV_IMPL_ADD(CV_IMPL_IPP|CV_IMPL_MT); - return; - } - setIppErrorStatus(); - } - } - } -#endif - RemapNNFunc nnfunc = 0; RemapFunc ifunc = 0; const void* ctab = 0; From deaf58689a4573b0414e88ff307d0250417851ff Mon Sep 17 00:00:00 2001 From: Vincent Rabaud Date: Thu, 10 Jul 2025 15:05:56 +0200 Subject: [PATCH 54/62] Fix potential crashes found by fuzzer. Namely 429429085, 429645379, 430091585 --- modules/imgcodecs/src/grfmt_png.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/imgcodecs/src/grfmt_png.cpp b/modules/imgcodecs/src/grfmt_png.cpp index f0f656bd25..c78c0efc55 100644 --- a/modules/imgcodecs/src/grfmt_png.cpp +++ b/modules/imgcodecs/src/grfmt_png.cpp @@ -455,6 +455,9 @@ bool PngDecoder::readData( Mat& img ) if (dop == 2) memcpy(frameNext.getPixels(), frameCur.getPixels(), imagesize); + if (x0 + w0 > frameCur.getWidth() || y0 + h0 > frameCur.getHeight()) + return false; + compose_frame(frameCur.getRows(), frameRaw.getRows(), bop, x0, y0, w0, h0, mat_cur); if (!delay_den) delay_den = 100; @@ -849,6 +852,8 @@ void PngDecoder::row_fn(png_structp png_ptr, png_bytep new_row, png_uint_32 row_ { CV_UNUSED(pass); APNGFrame* frame = (APNGFrame*)png_get_progressive_ptr(png_ptr); + if(row_num >= frame->getHeight()) + return; png_progressive_combine_row(png_ptr, frame->getRows()[row_num], new_row); } From 1ecdb39fdebec305c7fd24a827335eb6449d081a Mon Sep 17 00:00:00 2001 From: eplankin Date: Fri, 11 Jul 2025 10:55:08 +0200 Subject: [PATCH 55/62] fixed memory leak in ipp_warp function --- hal/ipp/src/warp_ipp.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/hal/ipp/src/warp_ipp.cpp b/hal/ipp/src/warp_ipp.cpp index a41d51460a..5d043099b0 100644 --- a/hal/ipp/src/warp_ipp.cpp +++ b/hal/ipp/src/warp_ipp.cpp @@ -251,6 +251,8 @@ public: if( !IPPSet( cv::Scalar(borderValue[0], borderValue[1], borderValue[2], borderValue[3]), dataPointer, (int)dst.step[0], setSize, cnn, src.depth() ) ) { *ok = false; + ippsFree(pBuffer); + ippsFree(pSpec); return; } } @@ -262,6 +264,8 @@ public: { CV_IMPL_ADD(CV_IMPL_IPP|CV_IMPL_MT); } + ippsFree(pBuffer); + ippsFree(pSpec); } private: int src_type; From 4c024c35fbc7f0610501e087a9ef20c336a75e2b Mon Sep 17 00:00:00 2001 From: Sachin Shah <39803835+inventshah@users.noreply.github.com> Date: Mon, 14 Jul 2025 04:59:44 -0400 Subject: [PATCH 56/62] Merge pull request #27523 from inventshah:fix-set-then-get-pos FIX: CvCapture_FFMPEG::setProperty(CAP_PROP_POS_*) followed by getProperty #27523 Partially fixes #23088 and #23472. This PR fixes `get(CAP_PROP_POS_MSEC)` calls after a `set(CAP_PROP_POS_*)` without calling `read` first. Since `seek` calls `grabFrame` which already sets `picture_pts`, manually setting `picture_pts` anywhere else in the call stack should not be necessary (except for the special case of seeking to frame 0). Minimal example from #23088 ```cpp for(int i = 0; i < 3; i++) cap.read(img); printf("at: %f frames, %f msec\n", cap.get(CAP_PROP_POS_FRAMES), cap.get(CAP_PROP_POS_MSEC)); cap.set(CAP_PROP_POS_FRAMES, 3); printf("at: %f frames, %f msec\n", cap.get(CAP_PROP_POS_FRAMES), cap.get(CAP_PROP_POS_MSEC)); ``` Current ```txt at: 3.000000 frames, 80.000000 msec at: 3.000000 frames, 0.234375 msec ``` PR ```txt at: 3.000000 frames, 80.000000 msec at: 3.000000 frames, 80.000000 msec ``` It similarly helps with `CAP_PROP_POS_MSEC`: Current ```txt at: 3.000000 frames, 80.000000 msec at: 2.000000 frames, 6.250000 msec ``` PR ```txt at: 3.000000 frames, 80.000000 msec at: 2.000000 frames, 40.000000 msec ``` Note the seek operation is still inconsistent between the `CAP_PROP_POS_*` options as mentioned by #23088, and VFR video seeking has issues discussed in #9053. For fixed-frame rate video, we could change 0.5 to 1 in `void CvCapture_FFMPEG::seek(double sec);` to align `CAP_PROP_POS_MSEC` with `CAP_PROP_POS_FRAMES`, but `CAP_POS_AVI_RATIO` and VFR video would still be broken. ### Pull Request Readiness Checklist See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [x] The PR is proposed to the proper branch - [x] There is a reference to the original bug report and related work - [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [x] The feature is well documented and sample code can be built with the project CMake --- modules/videoio/src/cap_ffmpeg_impl.hpp | 26 +++++++----------------- modules/videoio/test/test_ffmpeg.cpp | 27 +++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 19 deletions(-) diff --git a/modules/videoio/src/cap_ffmpeg_impl.hpp b/modules/videoio/src/cap_ffmpeg_impl.hpp index 9cd20fbf77..489dbe565d 100644 --- a/modules/videoio/src/cap_ffmpeg_impl.hpp +++ b/modules/videoio/src/cap_ffmpeg_impl.hpp @@ -2080,6 +2080,7 @@ void CvCapture_FFMPEG::seek(int64_t _frame_number) else { frame_number = 0; + picture_pts = AV_NOPTS_VALUE_; break; } } @@ -2097,27 +2098,14 @@ bool CvCapture_FFMPEG::setProperty( int property_id, double value ) switch( property_id ) { case CAP_PROP_POS_MSEC: + seek(value/1000.0); + return true; case CAP_PROP_POS_FRAMES: + seek((int64_t)value); + return true; case CAP_PROP_POS_AVI_RATIO: - { - switch( property_id ) - { - case CAP_PROP_POS_FRAMES: - seek((int64_t)value); - break; - - case CAP_PROP_POS_MSEC: - seek(value/1000.0); - break; - - case CAP_PROP_POS_AVI_RATIO: - seek((int64_t)(value*ic->duration)); - break; - } - - picture_pts=(int64_t)value; - } - break; + seek((int64_t)(value*ic->duration)); + return true; case CAP_PROP_FORMAT: if (value == -1) return setRaw(); diff --git a/modules/videoio/test/test_ffmpeg.cpp b/modules/videoio/test/test_ffmpeg.cpp index eeb0835078..3021bdfa4e 100644 --- a/modules/videoio/test/test_ffmpeg.cpp +++ b/modules/videoio/test/test_ffmpeg.cpp @@ -1030,4 +1030,31 @@ inline static std::string videoio_ffmpeg_mismatch_name_printer(const testing::Te INSTANTIATE_TEST_CASE_P(/**/, videoio_ffmpeg_channel_mismatch, testing::ValuesIn(mismatch_cases), videoio_ffmpeg_mismatch_name_printer); +// PR: https://github.com/opencv/opencv/pull/27523 +// TODO: Enable the tests back on Windows after FFmpeg plugin rebuild +#ifndef _WIN32 + +// related issue: https://github.com/opencv/opencv/issues/23088 +TEST(ffmpeg_cap_properties, set_pos_get_msec) +{ + if (!videoio_registry::hasBackend(CAP_FFMPEG)) + throw SkipTestException("FFmpeg backend was not found"); + + string video_file = findDataFile("video/big_buck_bunny.mp4"); + VideoCapture cap; + EXPECT_NO_THROW(cap.open(video_file, CAP_FFMPEG)); + ASSERT_TRUE(cap.isOpened()) << "Can't open the video"; + + cap.set(CAP_PROP_POS_FRAMES, 25); + EXPECT_EQ(cap.get(CAP_PROP_POS_MSEC), 1000.0); + + cap.set(CAP_PROP_POS_MSEC, 525); + EXPECT_EQ(cap.get(CAP_PROP_POS_MSEC), 500.0); + + cap.set(CAP_PROP_POS_AVI_RATIO, 0); + EXPECT_EQ(cap.get(CAP_PROP_POS_MSEC), 0.0); +} + +#endif // WIN32 + }} // namespace From 69b2024a3dac6f51291523d6e31a0337bc98837f Mon Sep 17 00:00:00 2001 From: xaos-cz <64235805+xaos-cz@users.noreply.github.com> Date: Mon, 14 Jul 2025 15:04:25 +0200 Subject: [PATCH 57/62] imgcodecs: OpenEXR multispectral read:write support (#27485) imgcodecs: OpenEXR multispectral read/write support #27485 OpenCV Extra: https://github.com/opencv/opencv_extra/pull/1262/ Adds capability to read and write multispectral (>4 channels) images in OpenEXR format. ### Pull Request Readiness Checklist See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [x] The PR is proposed to the proper branch - [ ] There is a reference to the original bug report and related work - [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [ ] The feature is well documented and sample code can be built with the project CMake --- .../imgcodecs/include/opencv2/imgcodecs.hpp | 2 +- modules/imgcodecs/src/grfmt_exr.cpp | 120 +++++++++++++----- modules/imgcodecs/src/grfmt_exr.hpp | 2 + modules/imgcodecs/src/loadsave.cpp | 12 ++ modules/imgcodecs/test/test_exr.impl.hpp | 30 +++++ 5 files changed, 130 insertions(+), 36 deletions(-) diff --git a/modules/imgcodecs/include/opencv2/imgcodecs.hpp b/modules/imgcodecs/include/opencv2/imgcodecs.hpp index d0f6ee61d6..14d562c823 100644 --- a/modules/imgcodecs/include/opencv2/imgcodecs.hpp +++ b/modules/imgcodecs/include/opencv2/imgcodecs.hpp @@ -484,7 +484,7 @@ filename extension (see cv::imread for the list of extensions). In general, only single-channel or 3-channel (with 'BGR' channel order) images can be saved using this function, with these exceptions: -- With OpenEXR encoder, only 32-bit float (CV_32F) images can be saved. +- With OpenEXR encoder, only 32-bit float (CV_32F) images can be saved. More than 4 channels can be saved. (imread can load it then.) - 8-bit unsigned (CV_8U) images are not supported. - With Radiance HDR encoder, non 64-bit float (CV_64F) images can be saved. - All images will be converted to 32-bit float (CV_32F). diff --git a/modules/imgcodecs/src/grfmt_exr.cpp b/modules/imgcodecs/src/grfmt_exr.cpp index 0ffd0d2618..44a0934517 100644 --- a/modules/imgcodecs/src/grfmt_exr.cpp +++ b/modules/imgcodecs/src/grfmt_exr.cpp @@ -118,7 +118,8 @@ ExrDecoder::ExrDecoder() m_ischroma = false; m_hasalpha = false; m_native_depth = false; - + m_multispectral = false; + m_channels = 0; } @@ -140,7 +141,7 @@ void ExrDecoder::close() int ExrDecoder::type() const { - return CV_MAKETYPE((m_isfloat ? CV_32F : CV_32S), ((m_iscolor && m_hasalpha) ? 4 : m_iscolor ? 3 : m_hasalpha ? 2 : 1)); + return CV_MAKETYPE((m_isfloat ? CV_32F : CV_32S), (m_multispectral ? m_channels : (m_iscolor && m_hasalpha) ? 4 : m_iscolor ? 3 : m_hasalpha ? 2 : 1)); } @@ -169,6 +170,7 @@ bool ExrDecoder::readHeader() m_green = channels.findChannel( "G" ); m_blue = channels.findChannel( "B" ); m_alpha = channels.findChannel( "A" ); + m_multispectral = channels.findChannel( "0" ) != nullptr; if( m_alpha ) // alpha channel supported in RGB, Y, and YC scenarios m_hasalpha = true; @@ -179,6 +181,23 @@ bool ExrDecoder::readHeader() m_ischroma = false; result = true; } + else if( m_multispectral ) + { + m_channels = 0; + for( auto it = channels.begin(); it != channels.end(); it++ ) + m_channels++; + + m_iscolor = true; // ??? false + m_ischroma = false; + m_hasalpha = false; + result = m_channels <= CV_CN_MAX; + + for ( int i = 1; result && i < m_channels; i++ ) // channel 0 was found previously + { + const Channel *ch = channels.findChannel( std::to_string(i) ); + result = ch && ch->xSampling == 1 && ch->ySampling == 1; // subsampling is not supported + } + } else { m_green = channels.findChannel( "Y" ); @@ -214,8 +233,9 @@ bool ExrDecoder::readHeader() bool ExrDecoder::readData( Mat& img ) { m_native_depth = CV_MAT_DEPTH(type()) == img.depth(); + bool multispectral = img.channels() > 4; bool color = img.channels() > 2; // output mat has 3+ channels; Y or YA are the 1 and 2 channel scenario - bool alphasupported = ( img.channels() % 2 == 0 ); // even number of channels indicates alpha + bool alphasupported = !multispectral && ( img.channels() % 2 == 0 ); // even number of channels indicates alpha int channels = 0; uchar* data = img.ptr(); size_t step = img.step; @@ -231,10 +251,17 @@ bool ExrDecoder::readData( Mat& img ) const size_t floatsize = sizeof(float); size_t xstep = m_native_depth ? floatsize : 1; // 4 bytes if native depth (FLOAT), otherwise converting to 1 byte U8 depth size_t ystep = 0; - const int channelstoread = ( (m_iscolor && alphasupported) ? 4 : + const int channelstoread = ( multispectral ? img.channels() : (m_iscolor && alphasupported) ? 4 : ( (m_iscolor && !m_ischroma) || color) ? 3 : alphasupported ? 2 : 1 ); // number of channels to read may exceed channels in output img size_t xStride = floatsize * channelstoread; + if ( m_multispectral ) // possible gray/RGB conversions + { + CV_CheckChannelsEQ(img.channels(), CV_MAT_CN(type()), "OpenCV EXR decoder needs more number of channels for multispectral images. Use cv::IMREAD_UNCHANGED mode for imread."); // IMREAD_ANYCOLOR needed + CV_CheckDepthEQ(img.depth(), CV_MAT_DEPTH(type()), "OpenCV EXR decoder supports CV_32F depth only for multispectral images. Use cv::IMREAD_UNCHANGED mode for imread."); // IMREAD_ANYDEPTH needed + } + CV_Assert( multispectral == m_multispectral && (!multispectral || justcopy) ); // should be true after previous checks + // See https://github.com/opencv/opencv/issues/26705 // If ALGO_HINT_ACCURATE is set, read BGR and swap to RGB. // If ALGO_HINT_APPROX is set, read RGB directly. @@ -312,6 +339,15 @@ bool ExrDecoder::readData( Mat& img ) xsample[0] = m_green->xSampling; } } + else if( m_multispectral ) + { + for ( int i = 0; i < m_channels; i++ ) + { + frame.insert( std::to_string(i), Slice( m_type, + buffer - m_datawindow.min.x * xStride - m_datawindow.min.y * ystep + (floatsize * i), + xStride, ystep, 1, 1, 0.0 )); + } + } else { if( m_blue ) @@ -382,39 +418,42 @@ bool ExrDecoder::readData( Mat& img ) { m_file->readPixels( m_datawindow.min.y, m_datawindow.max.y ); - if( m_iscolor ) + if( !m_multispectral ) { - if (doReadRGB) + if( m_iscolor ) { - 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 ); + if (doReadRGB) + { + 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_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 ); + 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 ) - { - if (doReadRGB) - ChromaToRGB( (float *)data, m_height, channelstoread, step / xstep ); - else - ChromaToBGR( (float *)data, m_height, channelstoread, step / xstep ); + if( chromatorgb ) + { + if (doReadRGB) + ChromaToRGB( (float *)data, m_height, channelstoread, step / xstep ); + else + ChromaToBGR( (float *)data, m_height, channelstoread, step / xstep ); + } } } - else + else // m_multispectral should be false { uchar *out = data; int x, y; @@ -804,13 +843,19 @@ bool ExrEncoder::write( const Mat& img, const std::vector& params ) header.channels().insert( "B", Channel( type ) ); //printf("bunt\n"); } - else + else if( channels == 1 || channels == 2 ) { header.channels().insert( "Y", Channel( type ) ); //printf("gray\n"); } + else if( channels > 4 ) + { + for ( int i = 0; i < channels; i++ ) + header.channels().insert( std::to_string(i), Channel( type ) ); + //printf("multi-channel\n"); + } - if( channels % 2 == 0 ) + if( channels % 2 == 0 && channels <= 4) { // even number of channels indicates Alpha header.channels().insert( "A", Channel( type ) ); } @@ -843,10 +888,15 @@ bool ExrEncoder::write( const Mat& img, const std::vector& params ) frame.insert( "G", Slice( type, buffer + size, size * channels, bufferstep )); frame.insert( "R", Slice( type, buffer + size * 2, size * channels, bufferstep )); } - else + else if( channels == 1 || channels == 2 ) frame.insert( "Y", Slice( type, buffer, size * channels, bufferstep )); + else if( channels > 4 ) + { + for ( int i = 0; i < channels; i++ ) + frame.insert( std::to_string(i), Slice( type, buffer + size * i, size * channels, bufferstep )); + } - if( channels % 2 == 0 ) + if( channels % 2 == 0 && channels <= 4 ) { // even channel count indicates Alpha channel frame.insert( "A", Slice( type, buffer + size * (channels - 1), size * channels, bufferstep )); } diff --git a/modules/imgcodecs/src/grfmt_exr.hpp b/modules/imgcodecs/src/grfmt_exr.hpp index 48ca09acd8..ec37649d17 100644 --- a/modules/imgcodecs/src/grfmt_exr.hpp +++ b/modules/imgcodecs/src/grfmt_exr.hpp @@ -100,6 +100,8 @@ protected: bool m_iscolor; bool m_isfloat; bool m_hasalpha; + bool m_multispectral; + int m_channels; private: ExrDecoder(const ExrDecoder &); // copy disabled diff --git a/modules/imgcodecs/src/loadsave.cpp b/modules/imgcodecs/src/loadsave.cpp index 8f811f9085..d1e3bc93cf 100644 --- a/modules/imgcodecs/src/loadsave.cpp +++ b/modules/imgcodecs/src/loadsave.cpp @@ -98,6 +98,9 @@ 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_ANYCOLOR) != 0 /*&& CV_MAT_CN(type) > 1*/ ) + // type = CV_MAKETYPE(CV_MAT_DEPTH(type), CV_MAT_CN(type)); + //else if( (flags & IMREAD_COLOR) != 0 || (flags & IMREAD_COLOR_RGB) != 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); @@ -1055,7 +1058,12 @@ static bool imwrite_( const String& filename, const std::vector& img_vec, Mat image = img_vec[page]; CV_Assert(!image.empty()); +#ifdef HAVE_OPENEXR + CV_Assert( image.channels() == 1 || image.channels() == 3 || image.channels() == 4 || encoder.dynamicCast() ); +#else CV_Assert( image.channels() == 1 || image.channels() == 3 || image.channels() == 4 ); +#endif + Mat temp; if( !encoder->isFormatSupported(image.depth()) ) @@ -1609,7 +1617,11 @@ bool imencodeWithMetadata( const String& ext, InputArray _img, CV_Assert(!image.empty()); const int channels = image.channels(); +#ifdef HAVE_OPENEXR + CV_Assert( channels == 1 || channels == 3 || channels == 4 || encoder.dynamicCast() ); +#else CV_Assert( channels == 1 || channels == 3 || channels == 4 ); +#endif Mat temp; if( !encoder->isFormatSupported(image.depth()) ) diff --git a/modules/imgcodecs/test/test_exr.impl.hpp b/modules/imgcodecs/test/test_exr.impl.hpp index 6b4ac0b8d1..d439b7da44 100644 --- a/modules/imgcodecs/test/test_exr.impl.hpp +++ b/modules/imgcodecs/test/test_exr.impl.hpp @@ -68,6 +68,36 @@ TEST(Imgcodecs_EXR, readWrite_32FC3) EXPECT_EQ(0, remove(filenameOutput.c_str())); } +TEST(Imgcodecs_EXR, readWrite_32FC7) +{ // 0-6 channels (multispectral) + const string root = cvtest::TS::ptr()->get_data_path(); + const string filenameInput = root + "readwrite/test32FC7.exr"; + const string filenameOutput = cv::tempfile(".exr"); +#ifndef GENERATE_DATA + const Mat img = cv::imread(filenameInput, IMREAD_UNCHANGED); +#else + const Size sz(3, 5); + Mat img(sz, CV_32FC7); + img.at>(0, 0)[0] = 101.125; + img.at>(2, 1)[3] = 203.500; + img.at>(4, 2)[6] = 305.875; + ASSERT_TRUE(cv::imwrite(filenameInput, img)); +#endif + ASSERT_FALSE(img.empty()); + ASSERT_EQ(CV_MAKETYPE(CV_32F, 7), img.type()); + + ASSERT_TRUE(cv::imwrite(filenameOutput, img)); + const Mat img2 = cv::imread(filenameOutput, IMREAD_UNCHANGED); + EXPECT_EQ(img2.type(), img.type()); + EXPECT_EQ(img2.size(), img.size()); + EXPECT_LE(cvtest::norm(img, img2, NORM_INF | NORM_RELATIVE), 1e-3); + EXPECT_EQ(0, remove(filenameOutput.c_str())); + const Mat img3 = cv::imread(filenameInput, IMREAD_GRAYSCALE); + ASSERT_TRUE(img3.empty()); + const Mat img4 = cv::imread(filenameInput, IMREAD_COLOR); + ASSERT_TRUE(img4.empty()); +} + TEST(Imgcodecs_EXR, readWrite_32FC1_half) { From 468de9b36740b3355f0d5cd8be2ce28b340df120 Mon Sep 17 00:00:00 2001 From: Kumataro Date: Tue, 15 Jul 2025 01:09:24 +0900 Subject: [PATCH 58/62] Merge pull request #27536 from Kumataro:fix27530 eigen: fix to get version from eigen after v3.4.0 #27536 Close https://github.com/opencv/opencv/issues/27530 ### Pull Request Readiness Checklist See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [x] The PR is proposed to the proper branch - [x] There is a reference to the original bug report and related work - [ ] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [ ] The feature is well documented and sample code can be built with the project CMake --- cmake/OpenCVFindLibsPerf.cmake | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/cmake/OpenCVFindLibsPerf.cmake b/cmake/OpenCVFindLibsPerf.cmake index dfc94597bb..861a39c47f 100644 --- a/cmake/OpenCVFindLibsPerf.cmake +++ b/cmake/OpenCVFindLibsPerf.cmake @@ -84,7 +84,13 @@ if(WITH_EIGEN AND NOT HAVE_EIGEN) set(EIGEN_WORLD_VERSION ${EIGEN3_WORLD_VERSION}) set(EIGEN_MAJOR_VERSION ${EIGEN3_MAJOR_VERSION}) set(EIGEN_MINOR_VERSION ${EIGEN3_MINOR_VERSION}) - else() # Eigen config file + elseif(DEFINED Eigen3_VERSION_MAJOR) # Recommended package config variables + # see https://github.com/opencv/opencv/issues/27530 + set(EIGEN_WORLD_VERSION ${Eigen3_VERSION_MAJOR}) + set(EIGEN_MAJOR_VERSION ${Eigen3_VERSION_MINOR}) + set(EIGEN_MINOR_VERSION ${Eigen3_VERSION_PATCH}) + else() # Deprecated package config variables + # Removed on master at https://gitlab.com/libeigen/eigen/-/commit/f2984cd0778dd0a1d7e74216d826eaff2bc6bfab set(EIGEN_WORLD_VERSION ${EIGEN3_VERSION_MAJOR}) set(EIGEN_MAJOR_VERSION ${EIGEN3_VERSION_MINOR}) set(EIGEN_MINOR_VERSION ${EIGEN3_VERSION_PATCH}) From 10ce4d406de79c67439d5c9211818e39696bfa59 Mon Sep 17 00:00:00 2001 From: inventshah <39803835+inventshah@users.noreply.github.com> Date: Mon, 14 Jul 2025 21:35:58 -0400 Subject: [PATCH 59/62] fix: mark Feature2D.detectAndCompute mask as optional in Python type stubs --- modules/python/src2/typing_stubs_generation/api_refinement.py | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/python/src2/typing_stubs_generation/api_refinement.py b/modules/python/src2/typing_stubs_generation/api_refinement.py index 11ba221021..cff54c602f 100644 --- a/modules/python/src2/typing_stubs_generation/api_refinement.py +++ b/modules/python/src2/typing_stubs_generation/api_refinement.py @@ -340,6 +340,7 @@ NODES_TO_REFINE = { SymbolName(("cv", ), (), "resize"): make_optional_arg("dsize"), SymbolName(("cv", ), (), "calcHist"): make_optional_arg("mask"), SymbolName(("cv", ), (), "floodFill"): make_optional_arg("mask"), + SymbolName(("cv", ), ("Feature2D", ), "detectAndCompute"): make_optional_arg("mask"), SymbolName(("cv", ), (), "imread"): make_optional_none_return, SymbolName(("cv", ), (), "imdecode"): make_optional_none_return, } From 61a3d7d25d2987b9c72e072ff9d38dbcbb31ab54 Mon Sep 17 00:00:00 2001 From: Vincent Rabaud Date: Tue, 15 Jul 2025 09:17:57 +0200 Subject: [PATCH 60/62] Replace deprecated proto2::FieldDescriptor::is_optional It has been marked for inlining, cf https://github.com/protocolbuffers/protobuf/blob/930036a8cf4a489521ea78ea47510a3a6fb9d7c0/src/google/protobuf/descriptor.h#L936 --- modules/dnn/src/caffe/caffe_importer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/dnn/src/caffe/caffe_importer.cpp b/modules/dnn/src/caffe/caffe_importer.cpp index fc6cbcdd28..a4afec136a 100644 --- a/modules/dnn/src/caffe/caffe_importer.cpp +++ b/modules/dnn/src/caffe/caffe_importer.cpp @@ -217,7 +217,7 @@ public: const google::protobuf::UnknownFieldSet& unknownFields = msgRefl->GetUnknownFields(msg); bool hasData = fd->is_required() || - (fd->is_optional() && msgRefl->HasField(msg, fd)) || + (!fd->is_repeated() && !fd->is_required() && msgRefl->HasField(msg, fd)) || (fd->is_repeated() && msgRefl->FieldSize(msg, fd) > 0) || !unknownFields.empty(); if (!hasData) From 8366a2e506ffa8d625085c240584a10c061f0d12 Mon Sep 17 00:00:00 2001 From: Aakash Preetam Date: Wed, 16 Jul 2025 12:31:00 +0530 Subject: [PATCH 61/62] Merge pull request #27525 from CodeLinaro:apreetam_7thPost Update FastCV lib hash for Linux and Android Updated libs PR [opencv/opencv_3rdparty#101](https://github.com/opencv/opencv_3rdparty/pull/101) ### Pull Request Readiness Checklist See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [ ] The PR is proposed to the proper branch - [ ] There is a reference to the original bug report and related work - [ ] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [ ] The feature is well documented and sample code can be built with the project CMake --- 3rdparty/fastcv/fastcv.cmake | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/3rdparty/fastcv/fastcv.cmake b/3rdparty/fastcv/fastcv.cmake index 215c73af16..bd63ddd85a 100644 --- a/3rdparty/fastcv/fastcv.cmake +++ b/3rdparty/fastcv/fastcv.cmake @@ -1,23 +1,23 @@ function(download_fastcv root_dir) # Commit SHA in the opencv_3rdparty repo - set(FASTCV_COMMIT "2265e79b3b9a8512a9c615b8c4d0244e88f45a9d") + set(FASTCV_COMMIT "9e8d42b6d7e769548d70b2e5674e263b056de8b4") # Define actual FastCV versions if(ANDROID) if(AARCH64) message(STATUS "Download FastCV for Android aarch64") - set(FCV_PACKAGE_NAME "fastcv_android_aarch64_2025_04_29.tgz") - set(FCV_PACKAGE_HASH "d9172a9a3e5d92d080a4192cc5691001") + set(FCV_PACKAGE_NAME "fastcv_android_aarch64_2025_07_09.tgz") + set(FCV_PACKAGE_HASH "8b9497858cf3c3502a0be4369d06ebf8") else() message(STATUS "Download FastCV for Android armv7") - set(FCV_PACKAGE_NAME "fastcv_android_arm32_2025_04_29.tgz") - set(FCV_PACKAGE_HASH "246b5253233391cd2c74d01d49aee9c3") + set(FCV_PACKAGE_NAME "fastcv_android_arm32_2025_07_09.tgz") + set(FCV_PACKAGE_HASH "e0e6009c9f2f2b96140cd6a639c7383f") endif() elseif(UNIX AND NOT APPLE AND NOT IOS AND NOT XROS) if(AARCH64) - set(FCV_PACKAGE_NAME "fastcv_linux_aarch64_2025_05_29.tgz") - set(FCV_PACKAGE_HASH "decd490524f786e103125b8b948151f3") + set(FCV_PACKAGE_NAME "fastcv_linux_aarch64_2025_07_09.tgz") + set(FCV_PACKAGE_HASH "05e254e0eb3c13fa23eb7213f0fe6d82") else() message("FastCV: fastcv lib for 32-bit Linux is not supported for now!") endif() From f59a955bea1df94218d698a4a8b1d3507fd9153c Mon Sep 17 00:00:00 2001 From: Alexander Smorkalov Date: Tue, 15 Jul 2025 13:25:40 +0300 Subject: [PATCH 62/62] Added command line option to select VideoCapture backend. --- apps/interactive-calibration/calibCommon.hpp | 1 + .../interactive-calibration/calibPipeline.cpp | 4 +-- apps/interactive-calibration/main.cpp | 26 ++++++++++++++++--- .../parametersController.cpp | 23 +++++++++++++++- 4 files changed, 47 insertions(+), 7 deletions(-) diff --git a/apps/interactive-calibration/calibCommon.hpp b/apps/interactive-calibration/calibCommon.hpp index 73cb4ab9fc..ed6c3613d1 100644 --- a/apps/interactive-calibration/calibCommon.hpp +++ b/apps/interactive-calibration/calibCommon.hpp @@ -92,6 +92,7 @@ namespace calib std::string videoFileName; bool flipVertical; int camID; + int camBackend; int fps; cv::Size cameraResolution; int maxFramesNum; diff --git a/apps/interactive-calibration/calibPipeline.cpp b/apps/interactive-calibration/calibPipeline.cpp index ac54764e41..4eae5469d6 100644 --- a/apps/interactive-calibration/calibPipeline.cpp +++ b/apps/interactive-calibration/calibPipeline.cpp @@ -34,7 +34,7 @@ PipelineExitStatus CalibPipeline::start(std::vector > pr auto open_camera = [this] () { if(mCaptureParams.source == Camera) { - mCapture.open(mCaptureParams.camID); + mCapture.open(mCaptureParams.camID, mCaptureParams.camBackend); cv::Size maxRes = getCameraResolution(); cv::Size neededRes = mCaptureParams.cameraResolution; @@ -55,7 +55,7 @@ PipelineExitStatus CalibPipeline::start(std::vector > pr mCapture.set(cv::CAP_PROP_AUTOFOCUS, 0); } else if (mCaptureParams.source == File) - mCapture.open(mCaptureParams.videoFileName); + mCapture.open(mCaptureParams.videoFileName, mCaptureParams.camBackend); }; if(!mCapture.isOpened()) { diff --git a/apps/interactive-calibration/main.cpp b/apps/interactive-calibration/main.cpp index 92ea2a93ae..b9ec7e8805 100644 --- a/apps/interactive-calibration/main.cpp +++ b/apps/interactive-calibration/main.cpp @@ -6,7 +6,7 @@ #include #include #include - +#include #include #include @@ -23,9 +23,25 @@ using namespace calib; -const std::string keys = +static std::string getVideoIoBackendsString() +{ + std::string result; + auto backs = cv::videoio_registry::getBackends(); + for (const auto& b: backs) + { + if (!result.empty()) + result += ", "; + + result += cv::videoio_registry::getBackendName(b); + } + + return result; +} + +const char* keys = "{v | | Input from video file }" - "{ci | 0 | Default camera id }" + "{ci | 0 | Camera id }" + "{vb | | Video I/O back-end. One of: %s }" "{flip | false | Vertical flip of input frames }" "{t | circles | Template for calibration (circles, chessboard, dualCircles, charuco, symcircles) }" "{sz | 16.3 | Distance between two nearest centers of circles or squares on calibration board}" @@ -95,11 +111,13 @@ static void undistortButton(int state, void* data) int main(int argc, char** argv) { - cv::CommandLineParser parser(argc, argv, keys); + cv::CommandLineParser parser(argc, argv, cv::format(keys, getVideoIoBackendsString().c_str())); + if(parser.has("help")) { parser.printMessage(); return 0; } + std::cout << consoleHelp << std::endl; parametersController paramsController; diff --git a/apps/interactive-calibration/parametersController.cpp b/apps/interactive-calibration/parametersController.cpp index 4ce81f352a..90d24b04c9 100644 --- a/apps/interactive-calibration/parametersController.cpp +++ b/apps/interactive-calibration/parametersController.cpp @@ -4,7 +4,7 @@ #include "parametersController.hpp" #include - +#include #include template @@ -106,6 +106,27 @@ bool calib::parametersController::loadFromParser(cv::CommandLineParser &parser) mCapParams.camID = parser.get("ci"); } + mCapParams.camBackend = cv::CAP_ANY; + if (parser.has("vb")) + { + std::string backendName = parser.get("vb"); + auto backs = cv::videoio_registry::getBackends(); + bool backendSet = false; + for (const auto& b: backs) + { + if (backendName == cv::videoio_registry::getBackendName(b)) + { + mCapParams.camBackend = b; + backendSet = true; + } + } + if (!backendSet) + { + std::cout << "Unknown or unsupported backend " << backendName << std::endl; + return false; + } + } + std::string templateType = parser.get("t"); if(templateType.find("symcircles", 0) == 0) {