From fab419a484853f418721a39a501f4ed4dc6421e8 Mon Sep 17 00:00:00 2001 From: CSBVision Date: Fri, 20 Sep 2024 10:44:21 +0200 Subject: [PATCH 001/102] Update op_cuda.hpp --- modules/dnn/src/op_cuda.cpp | 15 +++++++++++++++ modules/dnn/src/op_cuda.hpp | 8 +------- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/modules/dnn/src/op_cuda.cpp b/modules/dnn/src/op_cuda.cpp index 46e68f7689..09efa52c87 100644 --- a/modules/dnn/src/op_cuda.cpp +++ b/modules/dnn/src/op_cuda.cpp @@ -107,3 +107,18 @@ void Net::Impl::initCUDABackend(const std::vector& blobsToKeep_) CV__DNN_INLINE_NS_END }} // namespace cv::dnn #endif // HAVE_CUDA + +namespace cv { namespace dnn { + +bool haveCUDA() +{ +#ifdef HAVE_CUDA + int dev = 0; + static bool ret = (cudaGetDevice(&dev) == cudaSuccess); + return ret; +#else + return false; +#endif +} + +}} // namespace cv::dnn diff --git a/modules/dnn/src/op_cuda.hpp b/modules/dnn/src/op_cuda.hpp index 0ce4d469fc..781ebaeddf 100644 --- a/modules/dnn/src/op_cuda.hpp +++ b/modules/dnn/src/op_cuda.hpp @@ -29,13 +29,7 @@ namespace cv { namespace dnn { return id == DNN_TARGET_CUDA_FP16 || id == DNN_TARGET_CUDA; } - constexpr bool haveCUDA() { -#ifdef HAVE_CUDA - return true; -#else - return false; -#endif - } + bool haveCUDA(); #ifdef HAVE_CUDA namespace cuda4dnn { namespace csl { From d6fe289a79597fbc64f193ff69d2a2b6b2114007 Mon Sep 17 00:00:00 2001 From: Maksim Shabunin Date: Wed, 21 Aug 2024 18:42:23 +0300 Subject: [PATCH 002/102] imgproc: restore multiplanar conversion functions in cv::hal namespace --- .../include/opencv2/imgproc/hal/hal.hpp | 28 ++++++++++----- modules/imgproc/src/color_yuv.dispatch.cpp | 35 ++++++++++++++----- 2 files changed, 45 insertions(+), 18 deletions(-) diff --git a/modules/imgproc/include/opencv2/imgproc/hal/hal.hpp b/modules/imgproc/include/opencv2/imgproc/hal/hal.hpp index d4b0f3fbb9..1d1db5e3a0 100644 --- a/modules/imgproc/include/opencv2/imgproc/hal/hal.hpp +++ b/modules/imgproc/include/opencv2/imgproc/hal/hal.hpp @@ -3,6 +3,7 @@ #include "opencv2/core/cvdef.h" #include "opencv2/core/cvstd.hpp" +#include "opencv2/core/utility.hpp" #include "opencv2/core/hal/interface.h" namespace cv { namespace hal { @@ -158,12 +159,14 @@ CV_EXPORTS void cvtGraytoBGR5x5(const uchar * src_data, size_t src_step, CV_EXPORTS void cvtBGRtoYUV(const uchar * src_data, size_t src_step, uchar * dst_data, size_t dst_step, int width, int height, - int depth, int scn, bool swapBlue, bool isCbCr); + int depth, int scn, bool swapBlue, bool isCbCr, + AlgorithmHint hint = ALGO_HINT_DEFAULT); CV_EXPORTS void cvtYUVtoBGR(const uchar * src_data, size_t src_step, uchar * dst_data, size_t dst_step, int width, int height, - int depth, int dcn, bool swapBlue, bool isCbCr); + int depth, int dcn, bool swapBlue, bool isCbCr, + AlgorithmHint hint = ALGO_HINT_DEFAULT); CV_EXPORTS void cvtBGRtoXYZ(const uchar * src_data, size_t src_step, uchar * dst_data, size_t dst_step, @@ -198,28 +201,33 @@ CV_EXPORTS void cvtLabtoBGR(const uchar * src_data, size_t src_step, CV_EXPORTS void cvtTwoPlaneYUVtoBGR(const uchar * src_data, size_t src_step, uchar * dst_data, size_t dst_step, int dst_width, int dst_height, - int dcn, bool swapBlue, int uIdx); + int dcn, bool swapBlue, int uIdx, + AlgorithmHint hint = ALGO_HINT_DEFAULT); //! Separate Y and UV planes CV_EXPORTS void cvtTwoPlaneYUVtoBGR(const uchar * y_data, const uchar * uv_data, size_t src_step, uchar * dst_data, size_t dst_step, int dst_width, int dst_height, - int dcn, bool swapBlue, int uIdx); + int dcn, bool swapBlue, int uIdx, + AlgorithmHint hint = ALGO_HINT_DEFAULT); CV_EXPORTS void cvtTwoPlaneYUVtoBGR(const uchar * y_data, size_t y_step, const uchar * uv_data, size_t uv_step, uchar * dst_data, size_t dst_step, int dst_width, int dst_height, - int dcn, bool swapBlue, int uIdx); + int dcn, bool swapBlue, int uIdx, + AlgorithmHint hint = ALGO_HINT_DEFAULT); CV_EXPORTS void cvtThreePlaneYUVtoBGR(const uchar * src_data, size_t src_step, uchar * dst_data, size_t dst_step, int dst_width, int dst_height, - int dcn, bool swapBlue, int uIdx); + int dcn, bool swapBlue, int uIdx, + AlgorithmHint hint = ALGO_HINT_DEFAULT); CV_EXPORTS void cvtBGRtoThreePlaneYUV(const uchar * src_data, size_t src_step, uchar * dst_data, size_t dst_step, int width, int height, - int scn, bool swapBlue, int uIdx); + int scn, bool swapBlue, int uIdx, + AlgorithmHint hint = ALGO_HINT_DEFAULT); //! Separate Y and UV planes CV_EXPORTS void cvtBGRtoTwoPlaneYUV(const uchar * src_data, size_t src_step, @@ -230,12 +238,14 @@ CV_EXPORTS void cvtBGRtoTwoPlaneYUV(const uchar * src_data, size_t src_step, CV_EXPORTS void cvtOnePlaneYUVtoBGR(const uchar * src_data, size_t src_step, uchar * dst_data, size_t dst_step, int width, int height, - int dcn, bool swapBlue, int uIdx, int ycn); + int dcn, bool swapBlue, int uIdx, int ycn, + AlgorithmHint hint = ALGO_HINT_DEFAULT); CV_EXPORTS void cvtOnePlaneBGRtoYUV(const uchar * src_data, size_t src_step, uchar * dst_data, size_t dst_step, int width, int height, - int scn, bool swapBlue, int uIdx, int ycn); + int scn, bool swapBlue, int uIdx, int ycn, + AlgorithmHint hint = ALGO_HINT_DEFAULT); CV_EXPORTS void cvtRGBAtoMultipliedRGBA(const uchar * src_data, size_t src_step, uchar * dst_data, size_t dst_step, diff --git a/modules/imgproc/src/color_yuv.dispatch.cpp b/modules/imgproc/src/color_yuv.dispatch.cpp index 89e933affa..2a869d5b4a 100644 --- a/modules/imgproc/src/color_yuv.dispatch.cpp +++ b/modules/imgproc/src/color_yuv.dispatch.cpp @@ -18,7 +18,7 @@ namespace cv { namespace hal { // 8u, 16u, 32f -static void cvtBGRtoYUV(const uchar * src_data, size_t src_step, +void cvtBGRtoYUV(const uchar * src_data, size_t src_step, uchar * dst_data, size_t dst_step, int width, int height, int depth, int scn, bool swapBlue, bool isCbCr, AlgorithmHint hint) @@ -71,7 +71,7 @@ static void cvtBGRtoYUV(const uchar * src_data, size_t src_step, CV_CPU_DISPATCH_MODES_ALL); } -static void cvtYUVtoBGR(const uchar * src_data, size_t src_step, +void cvtYUVtoBGR(const uchar * src_data, size_t src_step, uchar * dst_data, size_t dst_step, int width, int height, int depth, int dcn, bool swapBlue, bool isCbCr, AlgorithmHint hint) @@ -128,7 +128,7 @@ static void cvtYUVtoBGR(const uchar * src_data, size_t src_step, // 4:2:0, two planes: Y, UV interleaved // Y : [16, 235]; Cb, Cr: [16, 240] centered at 128 // 20-bit fixed-point arithmetics -static void cvtTwoPlaneYUVtoBGR(const uchar * y_data, size_t y_step, const uchar * uv_data, size_t uv_step, +void cvtTwoPlaneYUVtoBGR(const uchar * y_data, size_t y_step, const uchar * uv_data, size_t uv_step, uchar * dst_data, size_t dst_step, int dst_width, int dst_height, int dcn, bool swapBlue, int uIdx, AlgorithmHint hint) @@ -151,7 +151,7 @@ static void cvtTwoPlaneYUVtoBGR(const uchar * y_data, size_t y_step, const uchar // 4:2:0, two planes in one array: Y, UV interleaved // Y : [16, 235]; Cb, Cr: [16, 240] centered at 128 // 20-bit fixed-point arithmetics -static void cvtTwoPlaneYUVtoBGR(const uchar * src_data, size_t src_step, +void cvtTwoPlaneYUVtoBGR(const uchar * src_data, size_t src_step, uchar * dst_data, size_t dst_step, int dst_width, int dst_height, int dcn, bool swapBlue, int uIdx, AlgorithmHint hint) @@ -173,7 +173,7 @@ static void cvtTwoPlaneYUVtoBGR(const uchar * src_data, size_t src_step, // 4:2:0, two planes: Y, UV interleaved // Y : [16, 235]; Cb, Cr: [16, 240] centered at 128 // 20-bit fixed-point arithmetics -static void cvtTwoPlaneYUVtoBGR(const uchar * y_data, const uchar * uv_data, size_t src_step, +void cvtTwoPlaneYUVtoBGR(const uchar * y_data, const uchar * uv_data, size_t src_step, uchar * dst_data, size_t dst_step, int dst_width, int dst_height, int dcn, bool swapBlue, int uIdx, AlgorithmHint hint) @@ -186,7 +186,7 @@ static void cvtTwoPlaneYUVtoBGR(const uchar * y_data, const uchar * uv_data, siz // 4:2:0, three planes in one array: Y, U, V // Y : [16, 235]; Cb, Cr: [16, 240] centered at 128 // 20-bit fixed-point arithmetics -static void cvtThreePlaneYUVtoBGR(const uchar * src_data, size_t src_step, +void cvtThreePlaneYUVtoBGR(const uchar * src_data, size_t src_step, uchar * dst_data, size_t dst_step, int dst_width, int dst_height, int dcn, bool swapBlue, int uIdx, AlgorithmHint hint) @@ -207,7 +207,7 @@ static void cvtThreePlaneYUVtoBGR(const uchar * src_data, size_t src_step, // 4:2:0, three planes in one array: Y, U, V // Y : [16, 235]; Cb, Cr: [16, 240] centered at 128 // 20-bit fixed-point arithmetics -static void cvtBGRtoThreePlaneYUV(const uchar * src_data, size_t src_step, +void cvtBGRtoThreePlaneYUV(const uchar * src_data, size_t src_step, uchar * dst_data, size_t dst_step, int width, int height, int scn, bool swapBlue, int uIdx, AlgorithmHint hint) @@ -225,10 +225,27 @@ static void cvtBGRtoThreePlaneYUV(const uchar * src_data, size_t src_step, CV_CPU_DISPATCH_MODES_ALL); } +// 4:2:0, two planes: Y, UV interleaved +// Y : [16, 235]; Cb, Cr: [16, 240] centered at 128 +// 20-bit fixed-point arithmetics +void cvtBGRtoTwoPlaneYUV(const uchar * src_data, size_t src_step, + uchar * y_data, uchar * uv_data, size_t dst_step, + int width, int height, + int scn, bool swapBlue, int uIdx) +{ + CV_INSTRUMENT_REGION(); + + CALL_HAL(cvtBGRtoTwoPlaneYUV, cv_hal_cvtBGRtoTwoPlaneYUV, + src_data, src_step, y_data, dst_step, uv_data, dst_step, width, height, scn, swapBlue, uIdx); + + CV_CPU_DISPATCH(cvtBGRtoTwoPlaneYUV, (src_data, src_step, y_data, uv_data, dst_step, width, height, scn, swapBlue, uIdx), + CV_CPU_DISPATCH_MODES_ALL); +} + // 4:2:2 interleaved // Y : [16, 235]; Cb, Cr: [16, 240] centered at 128 // 20-bit fixed-point arithmetics -static void cvtOnePlaneYUVtoBGR(const uchar * src_data, size_t src_step, +void cvtOnePlaneYUVtoBGR(const uchar * src_data, size_t src_step, uchar * dst_data, size_t dst_step, int width, int height, int dcn, bool swapBlue, int uIdx, int ycn, AlgorithmHint hint) @@ -249,7 +266,7 @@ static void cvtOnePlaneYUVtoBGR(const uchar * src_data, size_t src_step, // 4:2:2 interleaved // Y : [16, 235]; Cb, Cr: [16, 240] centered at 128 // 14-bit fixed-point arithmetics is used -static void cvtOnePlaneBGRtoYUV(const uchar * src_data, size_t src_step, +void cvtOnePlaneBGRtoYUV(const uchar * src_data, size_t src_step, uchar * dst_data, size_t dst_step, int width, int height, int scn, bool swapBlue, int uIdx, int ycn, AlgorithmHint hint) From b385767c1c0c5a5221a05e87000c5d91b32b729f Mon Sep 17 00:00:00 2001 From: Suleyman TURKMEN Date: Sun, 24 Nov 2024 04:29:47 +0300 Subject: [PATCH 003/102] Update drawing.cpp and test_contours.cpp --- modules/imgproc/src/drawing.cpp | 124 ++++++++++++------------- modules/imgproc/src/precomp.hpp | 1 + modules/imgproc/test/test_contours.cpp | 51 ++++++++-- 3 files changed, 105 insertions(+), 71 deletions(-) diff --git a/modules/imgproc/src/drawing.cpp b/modules/imgproc/src/drawing.cpp index c6efd4f21a..7ba512f7f8 100644 --- a/modules/imgproc/src/drawing.cpp +++ b/modules/imgproc/src/drawing.cpp @@ -2477,21 +2477,70 @@ void cv::drawContours( InputOutputArray _image, InputArrayOfArrays _contours, CV_Assert(ncontours <= (size_t)std::numeric_limits::max()); if (lineType == cv::LINE_AA && _image.depth() != CV_8U) lineType = 8; - Mat image = _image.getMat(), hierarchy = _hierarchy.getMat(); + Mat image = _image.getMat(); + Mat_ hierarchy = _hierarchy.getMat(); - if (thickness >= 0) // contour lines + int i = 0, end = (int)ncontours; + if (contourIdx >= 0) { - double color_buf[4] {}; - scalarToRawData(color, color_buf, _image.type(), 0 ); - int i = 0, end = (int)ncontours; - if (contourIdx >= 0) + i = contourIdx; + end = i + 1; + } + std::vector indexesToFill; + if (hierarchy.empty() || maxLevel == 0) + { + indexesToFill.resize(end - i); + std::iota(indexesToFill.begin(), indexesToFill.end(), i); + } + else + { + std::stack indexes; + for (; i != end; ++i) { - i = contourIdx; - end = i + 1; + // either all from the top level or a single contour + if (hierarchy(i)[3] < 0 || contourIdx >= 0) + indexes.push(i); } - for (; i < end; ++i) + while (!indexes.empty()) + { + // get current element + const int cur = indexes.top(); + indexes.pop(); + + // check current element depth + int curLevel = -1; + int par = cur; + while (par >= 0) + { + par = hierarchy(par)[3]; // parent + ++curLevel; + } + if (curLevel <= maxLevel) + { + indexesToFill.push_back(cur); + } + + int next = hierarchy(cur)[2]; // first child + while (next > 0) + { + indexes.push(next); + next = hierarchy(next)[0]; // next sibling + } + } + } + std::vector contoursToFill; + contoursToFill.reserve(indexesToFill.size()); + for (const int& idx : indexesToFill) + contoursToFill.emplace_back(_contours.getMat(idx)); + + if (thickness < 0) + fillPoly(image, contoursToFill, color, lineType, 0, offset); + else + { + double color_buf[4]{}; + scalarToRawData(color, color_buf, _image.type(), 0); + for (const Mat& cnt : contoursToFill) { - Mat cnt = _contours.getMat(i); if (cnt.empty()) continue; const int npoints = cnt.checkVector(2, CV_32S); @@ -2505,61 +2554,6 @@ void cv::drawContours( InputOutputArray _image, InputArrayOfArrays _contours, } } } - else // filled polygons - { - int i = 0, end = (int)ncontours; - if (contourIdx >= 0) - { - i = contourIdx; - end = i + 1; - } - std::vector indexesToFill; - if (hierarchy.empty() || maxLevel == 0) - { - for (; i != end; ++i) - indexesToFill.push_back(i); - } - else - { - std::stack indexes; - for (; i != end; ++i) - { - // either all from the top level or a single contour - if (hierarchy.at(i)[3] < 0 || contourIdx >= 0) - indexes.push(i); - } - while (!indexes.empty()) - { - // get current element - const int cur = indexes.top(); - indexes.pop(); - - // check current element depth - int curLevel = -1; - int par = cur; - while (par >= 0) - { - par = hierarchy.at(par)[3]; // parent - ++curLevel; - } - if (curLevel <= maxLevel) - { - indexesToFill.push_back(cur); - } - - int next = hierarchy.at(cur)[2]; // first child - while (next > 0) - { - indexes.push(next); - next = hierarchy.at(next)[0]; // next sibling - } - } - } - std::vector contoursToFill; - for (const int & idx : indexesToFill) - contoursToFill.push_back(_contours.getMat(idx)); - fillPoly(image, contoursToFill, color, lineType, 0, offset); - } } diff --git a/modules/imgproc/src/precomp.hpp b/modules/imgproc/src/precomp.hpp index 33a921c78a..e0962fe0aa 100644 --- a/modules/imgproc/src/precomp.hpp +++ b/modules/imgproc/src/precomp.hpp @@ -62,6 +62,7 @@ #include #include #include +#include #define GET_OPTIMIZED(func) (func) diff --git a/modules/imgproc/test/test_contours.cpp b/modules/imgproc/test/test_contours.cpp index fbd3cabff8..5fb88dce32 100644 --- a/modules/imgproc/test/test_contours.cpp +++ b/modules/imgproc/test/test_contours.cpp @@ -446,9 +446,9 @@ static void d2xy(int n, int d, int *x, int *y) } } -TEST(Imgproc_FindContours, hilbert) +static Mat draw_hilbert(int n = 64, int scale = 10) { - int n = 64, n2 = n*n, scale = 10, w = (n + 2)*scale; + int n2 = n*n, w = (n + 2)*scale; Point ofs(scale, scale); Mat img(w, w, CV_8U); img.setTo(Scalar::all(0)); @@ -462,12 +462,19 @@ TEST(Imgproc_FindContours, hilbert) p = q; } dilate(img, img, Mat()); + return img; +} + +TEST(Imgproc_FindContours, hilbert) +{ + Mat img = draw_hilbert(); vector > contours; + + findContours(img, contours, noArray(), RETR_LIST, CHAIN_APPROX_NONE); + ASSERT_EQ(1, (int)contours.size()); + ASSERT_EQ(78632, (int)contours[0].size()); + findContours(img, contours, noArray(), RETR_LIST, CHAIN_APPROX_SIMPLE); - img.setTo(Scalar::all(0)); - - drawContours(img, contours, 0, Scalar::all(255), 1); - ASSERT_EQ(1, (int)contours.size()); ASSERT_EQ(9832, (int)contours[0].size()); } @@ -539,6 +546,38 @@ TEST(Imgproc_FindContours, regression_4363_shared_nbd) } } +TEST(Imgproc_DrawContours, regression_26264) +{ + Mat img = draw_hilbert(32); + img.push_back(~img); + + for (int i = 50; i < 200; i += 17) + { + rectangle(img, Rect(i, i, img.cols - (i*2), img.rows - (i*2)), Scalar(0), 7); + rectangle(img, Rect(i, i, img.cols - (i*2), img.rows - (i*2)), Scalar(255), 1); + } + + vector > contours; + vector hierarchy; + findContours(img, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE); + img.setTo(Scalar::all(0)); + Mat img1 = img.clone(); + Mat img2 = img.clone(); + Mat img3 = img.clone(); + + int idx = 0; + while (idx >= 0) + { + drawContours(img, contours, idx, Scalar::all(255), FILLED, LINE_8, hierarchy); + drawContours(img2, contours, idx, Scalar::all(255), 1, LINE_8, hierarchy); + idx = hierarchy[idx][0]; + } + + drawContours(img1, contours, -1, Scalar::all(255), FILLED, LINE_8, hierarchy); + drawContours(img3, contours, -1, Scalar::all(255), 1, LINE_8, hierarchy); + ASSERT_EQ(0, cvtest::norm(img, img1, NORM_INF)); + ASSERT_EQ(0, cvtest::norm(img2, img3, NORM_INF)); +} TEST(Imgproc_PointPolygonTest, regression_10222) { From e953fcfaa465a34ee62616dd604bfa0055fb317c Mon Sep 17 00:00:00 2001 From: Maksim Shabunin Date: Tue, 26 Nov 2024 18:17:51 +0300 Subject: [PATCH 004/102] objdetect: fix invalid vector access in QR encoder --- modules/objdetect/src/qrcode_encoder.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/modules/objdetect/src/qrcode_encoder.cpp b/modules/objdetect/src/qrcode_encoder.cpp index 41b69ebe46..d1c05d2525 100644 --- a/modules/objdetect/src/qrcode_encoder.cpp +++ b/modules/objdetect/src/qrcode_encoder.cpp @@ -711,17 +711,17 @@ void QRCodeEncoderImpl::padBitStream() else if (pad_num <= 4) { int payload_size = (int)payload.size(); - writeDecNumber(0, payload_size, payload); + payload.insert(payload.end(), payload_size, 0); } else { - writeDecNumber(0, 4, payload); + payload.insert(payload.end(), 4, 0); int i = payload.size() % bits; if (i != 0) { - writeDecNumber(0, bits - i, payload); + payload.insert(payload.end(), bits - i, 0); } pad_num = total_data - (int)payload.size(); @@ -1329,11 +1329,12 @@ private: int val = 0; while (bits >= actualBits) { + CV_CheckLT(idx, data.size(), "Not enough bits in the bitstream"); val |= data[idx++] << (bits - actualBits); bits -= actualBits; actualBits = 8; } - if (bits) { + if (bits && idx < data.size()) { val |= data[idx] >> (actualBits - bits); actualBits -= bits; data[idx] &= 255 >> (8 - actualBits); From 6eaa77461ede4ffa5c0d55f9c0d6b04d7454bc6a Mon Sep 17 00:00:00 2001 From: Suleyman TURKMEN Date: Wed, 6 Nov 2024 15:29:03 +0300 Subject: [PATCH 005/102] add some functions and tests applyColorMap approxPolyN arrowedLine blendLinear boxPoints clipLine convertMaps createHanningWindow divSpectrums drawMarker findContoursLinkRuns fitEllipseAMS fitEllipseDirect getFontScaleFromHeight getRectSubPix HuMoments intersectConvexConvex invertAffineTransform minEnclosingTriangle preCornerDetect rotatedRectangleIntersection sqrBoxFilter spatialGradient stackBlur --- modules/imgproc/misc/js/gen_dict.json | 47 ++- modules/js/src/core_bindings.cpp | 45 +- modules/js/test/test_core.js | 376 ++++++++++++++++- modules/js/test/test_imgproc.js | 572 ++++++++------------------ modules/js/test/test_utils.js | 3 + platforms/js/opencv_js.config.py | 47 ++- 6 files changed, 639 insertions(+), 451 deletions(-) diff --git a/modules/imgproc/misc/js/gen_dict.json b/modules/imgproc/misc/js/gen_dict.json index 7b8c959fdd..a26808e38b 100644 --- a/modules/imgproc/misc/js/gen_dict.json +++ b/modules/imgproc/misc/js/gen_dict.json @@ -2,82 +2,105 @@ "whitelist": { "": [ - "Canny", - "GaussianBlur", - "Laplacian", - "HoughLines", - "HoughLinesP", - "HoughCircles", - "Scharr", - "Sobel", "adaptiveThreshold", + "applyColorMap", "approxPolyDP", + "approxPolyN", "arcLength", + "arrowedLine", "bilateralFilter", + "blendLinear", "blur", "boundingRect", "boxFilter", "calcBackProject", "calcHist", + "Canny", "circle", + "clipLine", "compareHist", "connectedComponents", "connectedComponentsWithStats", "contourArea", + "convertMaps", "convexHull", "convexityDefects", "cornerHarris", "cornerMinEigenVal", "createCLAHE", + "createHanningWindow", "createLineSegmentDetector", "cvtColor", "demosaicing", "dilate", "distanceTransform", "distanceTransformWithLabels", + "divSpectrums", "drawContours", + "drawMarker", "ellipse", "ellipse2Poly", "equalizeHist", "erode", + "fillConvexPoly", + "fillPoly", "filter2D", "findContours", + "findContoursLinkRuns", "fitEllipse", + "fitEllipseAMS", + "fitEllipseDirect", "fitLine", "floodFill", + "GaussianBlur", "getAffineTransform", + "getFontScaleFromHeight", "getPerspectiveTransform", + "getRectSubPix", "getRotationMatrix2D", "getStructuringElement", "goodFeaturesToTrack", "grabCut", + "HoughCircles", + "HoughLines", + "HoughLinesP", + "HuMoments", "integral", "integral2", + "intersectConvexConvex", + "invertAffineTransform", "isContourConvex", + "Laplacian", "line", "matchShapes", "matchTemplate", "medianBlur", "minAreaRect", "minEnclosingCircle", + "minEnclosingTriangle", "moments", "morphologyEx", "pointPolygonTest", + "polylines", + "preCornerDetect", "putText", "pyrDown", "pyrUp", "rectangle", "remap", "resize", + "rotatedRectangleIntersection", + "Scharr", "sepFilter2D", + "Sobel", + "spatialGradient", + "sqrBoxFilter", + "stackBlur", "threshold", "warpAffine", "warpPerspective", "warpPolar", - "watershed", - "fillPoly", - "fillConvexPoly", - "polylines" + "watershed" ], "CLAHE": ["apply", "collectGarbage", "getClipLimit", "getTilesGridSize", "setClipLimit", "setTilesGridSize"], "segmentation_IntelligentScissorsMB": [ diff --git a/modules/js/src/core_bindings.cpp b/modules/js/src/core_bindings.cpp index d5bf9b076c..0494485e3c 100644 --- a/modules/js/src/core_bindings.cpp +++ b/modules/js/src/core_bindings.cpp @@ -378,7 +378,6 @@ namespace binding_utils return result; } - void Tracker_init_wrapper(cv::Tracker& arg0, const cv::Mat& arg1, const Rect& arg2) { return arg0.init(arg1, arg2); @@ -614,10 +613,6 @@ EMSCRIPTEN_BINDINGS(binding_utils) .field("size", &cv::RotatedRect::size) .field("angle", &cv::RotatedRect::angle); - function("rotatedRectPoints", select_overload(&binding_utils::rotatedRectPoints)); - function("rotatedRectBoundingRect", select_overload(&binding_utils::rotatedRectBoundingRect)); - function("rotatedRectBoundingRect2f", select_overload(&binding_utils::rotatedRectBoundingRect2f)); - emscripten::value_object("KeyPoint") .field("angle", &cv::KeyPoint::angle) .field("class_id", &cv::KeyPoint::class_id) @@ -644,10 +639,25 @@ EMSCRIPTEN_BINDINGS(binding_utils) .field("minLoc", &binding_utils::MinMaxLoc::minLoc) .field("maxLoc", &binding_utils::MinMaxLoc::maxLoc); + emscripten::value_object("Exception") + .field("code", &cv::Exception::code) + .field("msg", &binding_utils::getExceptionMsg, &binding_utils::setExceptionMsg); + emscripten::value_object("Circle") .field("center", &binding_utils::Circle::center) .field("radius", &binding_utils::Circle::radius); + function("boxPoints", select_overload(&binding_utils::rotatedRectPoints)); + function("rotatedRectPoints", select_overload(&binding_utils::rotatedRectPoints)); + function("rotatedRectBoundingRect", select_overload(&binding_utils::rotatedRectBoundingRect)); + function("rotatedRectBoundingRect2f", select_overload(&binding_utils::rotatedRectBoundingRect2f)); + function("exceptionFromPtr", &binding_utils::exceptionFromPtr, allow_raw_pointers()); + function("minMaxLoc", select_overload(&binding_utils::minMaxLoc)); + function("minMaxLoc", select_overload(&binding_utils::minMaxLoc_1)); + function("CV_MAT_DEPTH", &binding_utils::cvMatDepth); + function("getBuildInformation", &binding_utils::getBuildInformation); + +#ifdef HAVE_OPENCV_IMGPROC emscripten::value_object("Moments") .field("m00", &cv::Moments::m00) .field("m10", &cv::Moments::m10) @@ -674,49 +684,24 @@ EMSCRIPTEN_BINDINGS(binding_utils) .field("nu12", &cv::Moments::nu12) .field("nu03", &cv::Moments::nu03); - emscripten::value_object("Exception") - .field("code", &cv::Exception::code) - .field("msg", &binding_utils::getExceptionMsg, &binding_utils::setExceptionMsg); - - function("exceptionFromPtr", &binding_utils::exceptionFromPtr, allow_raw_pointers()); - -#ifdef HAVE_OPENCV_IMGPROC function("minEnclosingCircle", select_overload(&binding_utils::minEnclosingCircle)); - function("floodFill", select_overload(&binding_utils::floodFill_wrapper)); - function("floodFill", select_overload(&binding_utils::floodFill_wrapper_1)); - function("floodFill", select_overload(&binding_utils::floodFill_wrapper_2)); - function("floodFill", select_overload(&binding_utils::floodFill_wrapper_3)); - function("floodFill", select_overload(&binding_utils::floodFill_wrapper_4)); -#endif - - function("minMaxLoc", select_overload(&binding_utils::minMaxLoc)); - - function("minMaxLoc", select_overload(&binding_utils::minMaxLoc_1)); - -#ifdef HAVE_OPENCV_IMGPROC function("morphologyDefaultBorderValue", &cv::morphologyDefaultBorderValue); #endif - function("CV_MAT_DEPTH", &binding_utils::cvMatDepth); - #ifdef HAVE_OPENCV_VIDEO function("CamShift", select_overload(&binding_utils::CamShiftWrapper)); - function("meanShift", select_overload(&binding_utils::meanShiftWrapper)); emscripten::class_("Tracker") .function("init", select_overload(&binding_utils::Tracker_init_wrapper), pure_virtual()) .function("update", select_overload(&binding_utils::Tracker_update_wrapper), pure_virtual()); - #endif - function("getBuildInformation", &binding_utils::getBuildInformation); - #ifdef HAVE_PTHREADS_PF function("parallel_pthreads_set_threads_num", &cv::parallel_pthreads_set_threads_num); function("parallel_pthreads_get_threads_num", &cv::parallel_pthreads_get_threads_num); diff --git a/modules/js/test/test_core.js b/modules/js/test/test_core.js index bfd9d2cd0d..3cd352ef83 100644 --- a/modules/js/test/test_core.js +++ b/modules/js/test/test_core.js @@ -4,8 +4,380 @@ QUnit.module('Core', {}); +QUnit.test('test_operations_on_arrays', function(assert) { + // Transpose + { + let mat1 = cv.Mat.eye(9, 7, cv.CV_8UC3); + let mat2 = new cv.Mat(); + + cv.transpose(mat1, mat2); + + // Verify result. + let size = mat2.size(); + assert.equal(mat2.channels(), 3); + assert.equal(size.height, 7); + assert.equal(size.width, 9); + } + + // Concat + { + let mat = cv.Mat.ones({height: 10, width: 5}, cv.CV_8UC3); + let mat2 = cv.Mat.eye({height: 10, width: 5}, cv.CV_8UC3); + let mat3 = cv.Mat.eye({height: 10, width: 5}, cv.CV_8UC3); + + let out = new cv.Mat(); + let input = new cv.MatVector(); + input.push_back(mat); + input.push_back(mat2); + input.push_back(mat3); + + cv.vconcat(input, out); + + // Verify result. + let size = out.size(); + assert.equal(out.channels(), 3); + assert.equal(size.height, 30); + assert.equal(size.width, 5); + assert.equal(out.elemSize1(), 1); + + cv.hconcat(input, out); + + // Verify result. + size = out.size(); + assert.equal(out.channels(), 3); + assert.equal(size.height, 10); + assert.equal(size.width, 15); + assert.equal(out.elemSize1(), 1); + + input.delete(); + out.delete(); + } + + // Min, Max + { + let data1 = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9]); + let data2 = new Uint8Array([0, 4, 0, 8, 0, 12, 0, 16, 0]); + + let expectedMin = new Uint8Array([0, 2, 0, 4, 0, 6, 0, 8, 0]); + let expectedMax = new Uint8Array([1, 4, 3, 8, 5, 12, 7, 16, 9]); + + let dataPtr = cv._malloc(3*3*1); + let dataPtr2 = cv._malloc(3*3*1); + + let dataHeap = new Uint8Array(cv.HEAPU8.buffer, dataPtr, 3*3*1); + dataHeap.set(new Uint8Array(data1.buffer)); + + let dataHeap2 = new Uint8Array(cv.HEAPU8.buffer, dataPtr2, 3*3*1); + dataHeap2.set(new Uint8Array(data2.buffer)); + + let mat1 = new cv.Mat(3, 3, cv.CV_8UC1, dataPtr, 0); + let mat2 = new cv.Mat(3, 3, cv.CV_8UC1, dataPtr2, 0); + let mat3 = new cv.Mat(); + + cv.min(mat1, mat2, mat3); + + // Verify result. + let size = mat2.size(); + assert.equal(mat2.channels(), 1); + assert.equal(size.height, 3); + assert.equal(size.width, 3); + + assert.deepEqual(mat3.data, expectedMin); + + cv.max(mat1, mat2, mat3); + + // Verify result. + size = mat2.size(); + assert.equal(mat2.channels(), 1); + assert.equal(size.height, 3); + assert.equal(size.width, 3); + + assert.deepEqual(mat3.data, expectedMax); + + cv._free(dataPtr); + cv._free(dataPtr2); + } + + // Bitwise operations + { + let data1 = new Uint8Array([0, 1, 2, 4, 8, 16, 32, 64, 128]); + let data2 = new Uint8Array([255, 255, 255, 255, 255, 255, 255, 255, 255]); + + let expectedAnd = new Uint8Array([0, 1, 2, 4, 8, 16, 32, 64, 128]); + let expectedOr = new Uint8Array([255, 255, 255, 255, 255, 255, 255, 255, 255]); + let expectedXor = new Uint8Array([255, 254, 253, 251, 247, 239, 223, 191, 127]); + + let expectedNot = new Uint8Array([255, 254, 253, 251, 247, 239, 223, 191, 127]); + + let dataPtr = cv._malloc(3*3*1); + let dataPtr2 = cv._malloc(3*3*1); + + let dataHeap = new Uint8Array(cv.HEAPU8.buffer, dataPtr, 3*3*1); + dataHeap.set(new Uint8Array(data1.buffer)); + + let dataHeap2 = new Uint8Array(cv.HEAPU8.buffer, dataPtr2, 3*3*1); + dataHeap2.set(new Uint8Array(data2.buffer)); + + let mat1 = new cv.Mat(3, 3, cv.CV_8UC1, dataPtr, 0); + let mat2 = new cv.Mat(3, 3, cv.CV_8UC1, dataPtr2, 0); + let mat3 = new cv.Mat(); + let none = new cv.Mat(); + + cv.bitwise_not(mat1, mat3, none); + + // Verify result. + let size = mat3.size(); + assert.equal(mat3.channels(), 1); + assert.equal(size.height, 3); + assert.equal(size.width, 3); + + assert.deepEqual(mat3.data, expectedNot); + + cv.bitwise_and(mat1, mat2, mat3, none); + + // Verify result. + size = mat3.size(); + assert.equal(mat3.channels(), 1); + assert.equal(size.height, 3); + assert.equal(size.width, 3); + + assert.deepEqual(mat3.data, expectedAnd); + + cv.bitwise_or(mat1, mat2, mat3, none); + + // Verify result. + size = mat3.size(); + assert.equal(mat3.channels(), 1); + assert.equal(size.height, 3); + assert.equal(size.width, 3); + + assert.deepEqual(mat3.data, expectedOr); + + cv.bitwise_xor(mat1, mat2, mat3, none); + + // Verify result. + size = mat3.size(); + assert.equal(mat3.channels(), 1); + assert.equal(size.height, 3); + assert.equal(size.width, 3); + + assert.deepEqual(mat3.data, expectedXor); + + cv._free(dataPtr); + cv._free(dataPtr2); + } + + // Arithmetic operations + { + let data1 = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8]); + let data2 = new Uint8Array([0, 2, 4, 6, 8, 10, 12, 14, 16]); + let data3 = new Uint8Array([0, 1, 0, 1, 0, 1, 0, 1, 0]); + + // |data1 - data2| + let expectedAbsDiff = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8]); + let expectedAdd = new Uint8Array([0, 3, 6, 9, 12, 15, 18, 21, 24]); + + const alpha = 4; + const beta = -1; + const gamma = 3; + // 4*data1 - data2 + 3 + let expectedWeightedAdd = new Uint8Array([3, 5, 7, 9, 11, 13, 15, 17, 19]); + + let dataPtr = cv._malloc(3*3*1); + let dataPtr2 = cv._malloc(3*3*1); + let dataPtr3 = cv._malloc(3*3*1); + + let dataHeap = new Uint8Array(cv.HEAPU8.buffer, dataPtr, 3*3*1); + dataHeap.set(new Uint8Array(data1.buffer)); + let dataHeap2 = new Uint8Array(cv.HEAPU8.buffer, dataPtr2, 3*3*1); + dataHeap2.set(new Uint8Array(data2.buffer)); + let dataHeap3 = new Uint8Array(cv.HEAPU8.buffer, dataPtr3, 3*3*1); + dataHeap3.set(new Uint8Array(data3.buffer)); + + let mat1 = new cv.Mat(3, 3, cv.CV_8UC1, dataPtr, 0); + let mat2 = new cv.Mat(3, 3, cv.CV_8UC1, dataPtr2, 0); + let mat3 = new cv.Mat(3, 3, cv.CV_8UC1, dataPtr3, 0); + + let dst = new cv.Mat(); + let none = new cv.Mat(); + + cv.absdiff(mat1, mat2, dst); + + // Verify result. + let size = dst.size(); + assert.equal(dst.channels(), 1); + assert.equal(size.height, 3); + assert.equal(size.width, 3); + + assert.deepEqual(dst.data, expectedAbsDiff); + + cv.add(mat1, mat2, dst, none, -1); + + // Verify result. + size = dst.size(); + assert.equal(dst.channels(), 1); + assert.equal(size.height, 3); + assert.equal(size.width, 3); + assert.deepEqual(dst.data, expectedAdd); + + cv.addWeighted(mat1, alpha, mat2, beta, gamma, dst, -1); + + // Verify result. + size = dst.size(); + assert.equal(dst.channels(), 1); + assert.equal(size.height, 3); + assert.equal(size.width, 3); + assert.deepEqual(dst.data, expectedWeightedAdd); + + // default parameter + cv.addWeighted(mat1, alpha, mat2, beta, gamma, dst); + + // Verify result. + size = dst.size(); + assert.equal(dst.channels(), 1); + assert.equal(size.height, 3); + assert.equal(size.width, 3); + + assert.deepEqual(dst.data, expectedWeightedAdd); + + mat1.delete(); + mat2.delete(); + mat3.delete(); + dst.delete(); + none.delete(); + } + + // Invert + { + let inv1 = new cv.Mat(); + let inv2 = new cv.Mat(); + let inv3 = new cv.Mat(); + let inv4 = new cv.Mat(); + + let data1 = new Float32Array([1, 0, 0, + 0, 1, 0, + 0, 0, 1]); + let data2 = new Float32Array([0, 0, 0, + 0, 5, 0, + 0, 0, 0]); + let data3 = new Float32Array([1, 1, 1, 0, + 0, 3, 1, 2, + 2, 3, 1, 0, + 1, 0, 2, 1]); + let data4 = new Float32Array([1, 4, 5, + 4, 2, 2, + 5, 2, 2]); + + let expected1 = new Float32Array([1, 0, 0, + 0, 1, 0, + 0, 0, 1]); + // Inverse does not exist! + let expected3 = new Float32Array([-3, -1/2, 3/2, 1, + 1, 1/4, -1/4, -1/2, + 3, 1/4, -5/4, -1/2, + -3, 0, 1, 1]); + let expected4 = new Float32Array([0, -1, 1, + -1, 23/2, -9, + 1, -9, 7]); + + let dataPtr1 = cv._malloc(3*3*4); + let dataPtr2 = cv._malloc(3*3*4); + let dataPtr3 = cv._malloc(4*4*4); + let dataPtr4 = cv._malloc(3*3*4); + + let dataHeap = new Float32Array(cv.HEAP32.buffer, dataPtr1, 3*3); + dataHeap.set(new Float32Array(data1.buffer)); + let dataHeap2 = new Float32Array(cv.HEAP32.buffer, dataPtr2, 3*3); + dataHeap2.set(new Float32Array(data2.buffer)); + let dataHeap3 = new Float32Array(cv.HEAP32.buffer, dataPtr3, 4*4); + dataHeap3.set(new Float32Array(data3.buffer)); + let dataHeap4 = new Float32Array(cv.HEAP32.buffer, dataPtr4, 3*3); + dataHeap4.set(new Float32Array(data4.buffer)); + + let mat1 = new cv.Mat(3, 3, cv.CV_32FC1, dataPtr1, 0); + let mat2 = new cv.Mat(3, 3, cv.CV_32FC1, dataPtr2, 0); + let mat3 = new cv.Mat(4, 4, cv.CV_32FC1, dataPtr3, 0); + let mat4 = new cv.Mat(3, 3, cv.CV_32FC1, dataPtr4, 0); + + QUnit.assert.deepEqualWithTolerance = function( value, expected, tolerance ) { + for (let i = 0; i < value.length; i= i+1) { + this.pushResult( { + result: Math.abs(value[i]-expected[i]) < tolerance, + actual: value[i], + expected: expected[i], + } ); + } + }; + + cv.invert(mat1, inv1, 0); + + // Verify result. + let size = inv1.size(); + assert.equal(inv1.channels(), 1); + assert.equal(size.height, 3); + assert.equal(size.width, 3); + assert.deepEqualWithTolerance(inv1.data32F, expected1, 0.0001); + + cv.invert(mat2, inv2, 0); + + // Verify result. + assert.deepEqualWithTolerance(inv3.data32F, expected3, 0.0001); + + cv.invert(mat3, inv3, 0); + + // Verify result. + size = inv3.size(); + assert.equal(inv3.channels(), 1); + assert.equal(size.height, 4); + assert.equal(size.width, 4); + assert.deepEqualWithTolerance(inv3.data32F, expected3, 0.0001); + + cv.invert(mat3, inv3, 1); + + // Verify result. + assert.deepEqualWithTolerance(inv3.data32F, expected3, 0.0001); + + cv.invert(mat4, inv4, 2); + + // Verify result. + assert.deepEqualWithTolerance(inv4.data32F, expected4, 0.0001); + + cv.invert(mat4, inv4, 3); + + // Verify result. + assert.deepEqualWithTolerance(inv4.data32F, expected4, 0.0001); + + mat1.delete(); + mat2.delete(); + mat3.delete(); + mat4.delete(); + inv1.delete(); + inv2.delete(); + inv3.delete(); + inv4.delete(); + } + + //Rotate + { + let dst = new cv.Mat(); + let src = cv.matFromArray(3, 2, cv.CV_8U, [1,2,3,4,5,6]); + + cv.rotate(src, dst, cv.ROTATE_90_CLOCKWISE); + + let size = dst.size(); + assert.equal(size.height, 2, "ROTATE_HEIGHT"); + assert.equal(size.width, 3, "ROTATE_WIGTH"); + + let expected = new Uint8Array([5,3,1,6,4,2]); + + assert.deepEqual(dst.data, expected); + + dst.delete(); + src.delete(); + } +}); + QUnit.test('test_LUT', function(assert) { - // test LUT { let src = cv.matFromArray(3, 3, cv.CV_8UC1, [255, 128, 0, 0, 128, 255, 1, 2, 254]); let lutTable = []; @@ -18,7 +390,7 @@ QUnit.test('test_LUT', function(assert) { cv.LUT(src, lut, dst); - //console.log(dst.data); + // Verify result. assert.equal(dst.ucharAt(0), 0); assert.equal(dst.ucharAt(1), 127); assert.equal(dst.ucharAt(2), 255); diff --git a/modules/js/test/test_imgproc.js b/modules/js/test/test_imgproc.js index 65b3cb74af..24da9b9266 100644 --- a/modules/js/test/test_imgproc.js +++ b/modules/js/test/test_imgproc.js @@ -70,6 +70,54 @@ QUnit.module('Image Processing', {}); +QUnit.test('applyColorMap', function(assert) { + { + let src = cv.matFromArray(2, 1, cv.CV_8U, [50,100]); + cv.applyColorMap(src, src, cv.COLORMAP_BONE); + + // Verify result. + let expected = new Uint8Array([60,44,44,119,89,87]); + + assert.deepEqual(src.data, expected); + src.delete(); + } +}); + +QUnit.test('blendLinear', function(assert) { + { + let src1 = cv.matFromArray(2, 1, cv.CV_8U, [50,100]); + let src2 = cv.matFromArray(2, 1, cv.CV_8U, [200,20]); + let weights1 = cv.matFromArray(2, 1, cv.CV_32F, [0.4,0.5]); + let weights2 = cv.matFromArray(2, 1, cv.CV_32F, [0.6,0.5]); + let dst = new cv.Mat(); + cv.blendLinear(src1, src2, weights1, weights2, dst); + + // Verify result. + let expected = new Uint8Array([140,60]); + + assert.deepEqual(dst.data, expected); + src1.delete(); + src2.delete(); + weights1.delete(); + weights2.delete(); + dst.delete(); + } +}); + +QUnit.test('createHanningWindow', function(assert) { + { + let dst = new cv.Mat(); + cv.createHanningWindow(dst, new cv.Size(5, 3), cv.CV_32F); + + // Verify result. + let expected = cv.matFromArray(3, 5, cv.CV_32F, [0.,0.,0.,0.,0.,0.,0.70710677,1.,0.70710677,0.,0.,0.,0.,0.,0.]); + + assert.deepEqual(dst.data, expected.data); + dst.delete(); + expected.delete(); + } +}); + QUnit.test('test_imgProc', function(assert) { // calcHist { @@ -127,6 +175,7 @@ QUnit.test('test_imgProc', function(assert) { dest.delete(); source.delete(); } + // equalizeHist { let source = new cv.Mat(10, 10, cv.CV_8UC1); @@ -196,7 +245,9 @@ QUnit.test('test_imgProc', function(assert) { expected_img.delete(); compare_result.delete(); } +}); +QUnit.test('Drawing Functions', function(assert) { // fillPoly { let img_width = 6; @@ -359,6 +410,7 @@ QUnit.test('test_shape', function(assert) { }); QUnit.test('test_min_enclosing', function(assert) { + // minEnclosingCircle { let points = new cv.Mat(4, 1, cv.CV_32FC2); @@ -378,6 +430,31 @@ QUnit.test('test_min_enclosing', function(assert) { points.delete(); } + + // minEnclosingTriangle + { + let dst = cv.Mat.zeros(80, 80, cv.CV_8U); + let contours = new cv.MatVector(); + let hierarchy = new cv.Mat(); + let triangle = new cv.Mat(); + + cv.drawMarker(dst, new cv.Point(40, 40), new cv.Scalar(255)); + cv.findContoursLinkRuns(dst,contours,hierarchy); + cv.minEnclosingTriangle(contours.get(0),triangle); + + // Verify result. + const triangleData = triangle.data32F; + assert.deepEqual(triangleData[0], triangleData[4]); + assert.deepEqual(triangleData[1], 20); + assert.deepEqual(triangleData[2], 30); + assert.deepEqual(triangleData[3], 40); + assert.deepEqual(triangleData[5], 60); + + dst.delete(); + contours.delete(); + hierarchy.delete(); + triangle.delete(); + } }); QUnit.test('test_filter', function(assert) { @@ -427,6 +504,58 @@ QUnit.test('test_filter', function(assert) { assert.equal(mat2.channels(), 1); assert.equal(size.height, 7); assert.equal(size.width, 7); + mat1.delete(); + mat2.delete(); + } + + // spatialGradient + { + let src = cv.matFromArray(4, 4, cv.CV_8U, [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]); + let dx = new cv.Mat(); + let dy = new cv.Mat(); + cv.spatialGradient(src, dx, dy); + + // Verify result. + let expected_dx = new cv.Mat(); + let expected_dy = new cv.Mat(); + cv.Sobel(src, expected_dx, cv.CV_16SC1, 1, 0, 3); + cv.Sobel(src, expected_dy, cv.CV_16SC1, 0, 1, 3); + + assert.deepEqual(dx.data, expected_dx.data); + assert.deepEqual(dy.data, expected_dy.data); + + src.delete(); + dx.delete(); + dy.delete(); + expected_dx.delete(); + expected_dy.delete(); + } + + // sqrBoxFilter + { + let src = cv.matFromArray(2, 3, cv.CV_8U, [1,2,1,1,2,1]); + let dst = new cv.Mat(); + cv.sqrBoxFilter(src, dst, cv.CV_32F, new cv.Size(3, 3)); + + // Verify result. + let expected = cv.matFromArray(2, 3, cv.CV_32F,[3.0,2.0,3.0,3.0,2.0,3.0]); + + assert.deepEqual(dst.data, expected.data); + src.delete(); + dst.delete(); + expected.delete(); + } + + // stackBlur + { + let src = cv.matFromArray(2, 3, cv.CV_8U, [10,25,30,45,50,60]); + cv.stackBlur(src, src, new cv.Size(3, 3)); + + // Verify result. + let expected = new Uint8Array([22,29,36,38,43,50]); + + assert.deepEqual(src.data, expected); + src.delete(); } // medianBlur @@ -438,23 +567,12 @@ QUnit.test('test_filter', function(assert) { // Verify result. let size = mat2.size(); + assert.equal(mat2.channels(), 3); assert.equal(size.height, 9); assert.equal(size.width, 9); - } - - // Transpose - { - let mat1 = cv.Mat.eye(9, 9, cv.CV_8UC3); - let mat2 = new cv.Mat(); - - cv.transpose(mat1, mat2); - - // Verify result. - let size = mat2.size(); - assert.equal(mat2.channels(), 3); - assert.equal(size.height, 9); - assert.equal(size.width, 9); + mat1.delete(); + mat2.delete(); } // bilateralFilter @@ -481,8 +599,9 @@ QUnit.test('test_filter', function(assert) { mat1.delete(); mat2.delete(); } +}); - // Watershed +QUnit.test('test_watershed', function(assert) { { let mat = cv.Mat.ones(11, 11, cv.CV_8UC3); let out = new cv.Mat(11, 11, cv.CV_32SC1); @@ -499,44 +618,9 @@ QUnit.test('test_filter', function(assert) { mat.delete(); out.delete(); } +}); - // Concat - { - let mat = cv.Mat.ones({height: 10, width: 5}, cv.CV_8UC3); - let mat2 = cv.Mat.eye({height: 10, width: 5}, cv.CV_8UC3); - let mat3 = cv.Mat.eye({height: 10, width: 5}, cv.CV_8UC3); - - - let out = new cv.Mat(); - let input = new cv.MatVector(); - input.push_back(mat); - input.push_back(mat2); - input.push_back(mat3); - - cv.vconcat(input, out); - - // Verify result. - let size = out.size(); - assert.equal(out.channels(), 3); - assert.equal(size.height, 30); - assert.equal(size.width, 5); - assert.equal(out.elemSize1(), 1); - - cv.hconcat(input, out); - - // Verify result. - size = out.size(); - assert.equal(out.channels(), 3); - assert.equal(size.height, 10); - assert.equal(size.width, 15); - assert.equal(out.elemSize1(), 1); - - input.delete(); - out.delete(); - } - - - // distanceTransform letiants +QUnit.test('test_distanceTransform', function(assert) { { let mat = cv.Mat.ones(11, 11, cv.CV_8UC1); let out = new cv.Mat(11, 11, cv.CV_32FC1); @@ -551,7 +635,6 @@ QUnit.test('test_filter', function(assert) { assert.equal(size.width, 11); assert.equal(out.elemSize1(), 4); - cv.distanceTransformWithLabels(mat, out, labels, cv.DIST_L2, maskSize, cv.DIST_LABEL_CCOMP); @@ -572,200 +655,9 @@ QUnit.test('test_filter', function(assert) { out.delete(); labels.delete(); } +}); - // Min, Max - { - let data1 = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9]); - let data2 = new Uint8Array([0, 4, 0, 8, 0, 12, 0, 16, 0]); - - let expectedMin = new Uint8Array([0, 2, 0, 4, 0, 6, 0, 8, 0]); - let expectedMax = new Uint8Array([1, 4, 3, 8, 5, 12, 7, 16, 9]); - - let dataPtr = cv._malloc(3*3*1); - let dataPtr2 = cv._malloc(3*3*1); - - let dataHeap = new Uint8Array(cv.HEAPU8.buffer, dataPtr, 3*3*1); - dataHeap.set(new Uint8Array(data1.buffer)); - - let dataHeap2 = new Uint8Array(cv.HEAPU8.buffer, dataPtr2, 3*3*1); - dataHeap2.set(new Uint8Array(data2.buffer)); - - - let mat1 = new cv.Mat(3, 3, cv.CV_8UC1, dataPtr, 0); - let mat2 = new cv.Mat(3, 3, cv.CV_8UC1, dataPtr2, 0); - - let mat3 = new cv.Mat(); - - cv.min(mat1, mat2, mat3); - // Verify result. - let size = mat2.size(); - assert.equal(mat2.channels(), 1); - assert.equal(size.height, 3); - assert.equal(size.width, 3); - - assert.deepEqual(mat3.data, expectedMin); - - - cv.max(mat1, mat2, mat3); - // Verify result. - size = mat2.size(); - assert.equal(mat2.channels(), 1); - assert.equal(size.height, 3); - assert.equal(size.width, 3); - - assert.deepEqual(mat3.data, expectedMax); - - cv._free(dataPtr); - cv._free(dataPtr2); - } - - // Bitwise operations - { - let data1 = new Uint8Array([0, 1, 2, 4, 8, 16, 32, 64, 128]); - let data2 = new Uint8Array([255, 255, 255, 255, 255, 255, 255, 255, 255]); - - let expectedAnd = new Uint8Array([0, 1, 2, 4, 8, 16, 32, 64, 128]); - let expectedOr = new Uint8Array([255, 255, 255, 255, 255, 255, 255, 255, 255]); - let expectedXor = new Uint8Array([255, 254, 253, 251, 247, 239, 223, 191, 127]); - - let expectedNot = new Uint8Array([255, 254, 253, 251, 247, 239, 223, 191, 127]); - - let dataPtr = cv._malloc(3*3*1); - let dataPtr2 = cv._malloc(3*3*1); - - let dataHeap = new Uint8Array(cv.HEAPU8.buffer, dataPtr, 3*3*1); - dataHeap.set(new Uint8Array(data1.buffer)); - - let dataHeap2 = new Uint8Array(cv.HEAPU8.buffer, dataPtr2, 3*3*1); - dataHeap2.set(new Uint8Array(data2.buffer)); - - - let mat1 = new cv.Mat(3, 3, cv.CV_8UC1, dataPtr, 0); - let mat2 = new cv.Mat(3, 3, cv.CV_8UC1, dataPtr2, 0); - - let mat3 = new cv.Mat(); - let none = new cv.Mat(); - - cv.bitwise_not(mat1, mat3, none); - // Verify result. - let size = mat3.size(); - assert.equal(mat3.channels(), 1); - assert.equal(size.height, 3); - assert.equal(size.width, 3); - - assert.deepEqual(mat3.data, expectedNot); - - cv.bitwise_and(mat1, mat2, mat3, none); - // Verify result. - size = mat3.size(); - assert.equal(mat3.channels(), 1); - assert.equal(size.height, 3); - assert.equal(size.width, 3); - - assert.deepEqual(mat3.data, expectedAnd); - - - cv.bitwise_or(mat1, mat2, mat3, none); - // Verify result. - size = mat3.size(); - assert.equal(mat3.channels(), 1); - assert.equal(size.height, 3); - assert.equal(size.width, 3); - - assert.deepEqual(mat3.data, expectedOr); - - cv.bitwise_xor(mat1, mat2, mat3, none); - // Verify result. - size = mat3.size(); - assert.equal(mat3.channels(), 1); - assert.equal(size.height, 3); - assert.equal(size.width, 3); - - assert.deepEqual(mat3.data, expectedXor); - - cv._free(dataPtr); - cv._free(dataPtr2); - } - - // Arithmetic operations - { - let data1 = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8]); - let data2 = new Uint8Array([0, 2, 4, 6, 8, 10, 12, 14, 16]); - let data3 = new Uint8Array([0, 1, 0, 1, 0, 1, 0, 1, 0]); - - // |data1 - data2| - let expectedAbsDiff = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8]); - let expectedAdd = new Uint8Array([0, 3, 6, 9, 12, 15, 18, 21, 24]); - - const alpha = 4; - const beta = -1; - const gamma = 3; - // 4*data1 - data2 + 3 - let expectedWeightedAdd = new Uint8Array([3, 5, 7, 9, 11, 13, 15, 17, 19]); - - let dataPtr = cv._malloc(3*3*1); - let dataPtr2 = cv._malloc(3*3*1); - let dataPtr3 = cv._malloc(3*3*1); - - let dataHeap = new Uint8Array(cv.HEAPU8.buffer, dataPtr, 3*3*1); - dataHeap.set(new Uint8Array(data1.buffer)); - let dataHeap2 = new Uint8Array(cv.HEAPU8.buffer, dataPtr2, 3*3*1); - dataHeap2.set(new Uint8Array(data2.buffer)); - let dataHeap3 = new Uint8Array(cv.HEAPU8.buffer, dataPtr3, 3*3*1); - dataHeap3.set(new Uint8Array(data3.buffer)); - - let mat1 = new cv.Mat(3, 3, cv.CV_8UC1, dataPtr, 0); - let mat2 = new cv.Mat(3, 3, cv.CV_8UC1, dataPtr2, 0); - let mat3 = new cv.Mat(3, 3, cv.CV_8UC1, dataPtr3, 0); - - let dst = new cv.Mat(); - let none = new cv.Mat(); - - cv.absdiff(mat1, mat2, dst); - // Verify result. - let size = dst.size(); - assert.equal(dst.channels(), 1); - assert.equal(size.height, 3); - assert.equal(size.width, 3); - - assert.deepEqual(dst.data, expectedAbsDiff); - - cv.add(mat1, mat2, dst, none, -1); - // Verify result. - size = dst.size(); - assert.equal(dst.channels(), 1); - assert.equal(size.height, 3); - assert.equal(size.width, 3); - - assert.deepEqual(dst.data, expectedAdd); - - cv.addWeighted(mat1, alpha, mat2, beta, gamma, dst, -1); - // Verify result. - size = dst.size(); - assert.equal(dst.channels(), 1); - assert.equal(size.height, 3); - assert.equal(size.width, 3); - - assert.deepEqual(dst.data, expectedWeightedAdd); - - // default parameter - cv.addWeighted(mat1, alpha, mat2, beta, gamma, dst); - // Verify result. - size = dst.size(); - assert.equal(dst.channels(), 1); - assert.equal(size.height, 3); - assert.equal(size.width, 3); - - assert.deepEqual(dst.data, expectedWeightedAdd); - - mat1.delete(); - mat2.delete(); - mat3.delete(); - dst.delete(); - none.delete(); - } - - // Integral letiants +QUnit.test('test_integral', function(assert) { { let mat = cv.Mat.eye({height: 100, width: 100}, cv.CV_8UC3); let sum = new cv.Mat(); @@ -797,162 +689,55 @@ QUnit.test('test_filter', function(assert) { sqSum.delete(); title.delete(); } +}); - // Mean, meanSTDev +QUnit.test('test_rotatedRectangleIntersection', function(assert) { { - let mat = cv.Mat.eye({height: 100, width: 100}, cv.CV_8UC3); - let sum = new cv.Mat(); - let sqSum = new cv.Mat(); - let title = new cv.Mat(); + let dst = cv.Mat.zeros(80, 80, cv.CV_8U); + let contours = new cv.MatVector(); + let hierarchy = new cv.Mat(); + let intersectionPoints = new cv.Mat(); - cv.integral(mat, sum, -1); + cv.drawMarker(dst, new cv.Point(40, 40), new cv.Scalar(255)); + cv.findContoursLinkRuns(dst,contours,hierarchy); + let rr1 = cv.minAreaRect(contours.get(0)); + let rr2 = cv.minAreaRect(contours.get(0)); + let rr3 = new cv.RotatedRect({x: 40, y: 40}, {height: 10, width: 20}, 45); + + let intersectionType = cv.rotatedRectangleIntersection(rr1, rr2, intersectionPoints); // Verify result. - let size = sum.size(); - assert.equal(sum.channels(), 3); - assert.equal(size.height, 100+1); - assert.equal(size.width, 100+1); + assert.deepEqual(intersectionType, cv.INTERSECT_FULL); + intersectionPoints.convertTo(intersectionPoints, cv.CV_32S); + let intersectionPointsData = intersectionPoints.data32S; + assert.deepEqual(intersectionPointsData[0], 30); + assert.deepEqual(intersectionPointsData[1], 40); + assert.deepEqual(intersectionPointsData[2], 40); + assert.deepEqual(intersectionPointsData[3], 30); + assert.deepEqual(intersectionPointsData[4], 50); + assert.deepEqual(intersectionPointsData[5], 40); + assert.deepEqual(intersectionPointsData[6], 40); + assert.deepEqual(intersectionPointsData[7], 50); + + intersectionType = cv.rotatedRectangleIntersection(rr1, rr3, intersectionPoints); - cv.integral2(mat, sum, sqSum, -1, -1); // Verify result. - size = sum.size(); - assert.equal(sum.channels(), 3); - assert.equal(size.height, 100+1); - assert.equal(size.width, 100+1); - - size = sqSum.size(); - assert.equal(sqSum.channels(), 3); - assert.equal(size.height, 100+1); - assert.equal(size.width, 100+1); - - mat.delete(); - sum.delete(); - sqSum.delete(); - title.delete(); - } - - // Invert - { - let inv1 = new cv.Mat(); - let inv2 = new cv.Mat(); - let inv3 = new cv.Mat(); - let inv4 = new cv.Mat(); - - - let data1 = new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - let data2 = new Float32Array([0, 0, 0, - 0, 5, 0, - 0, 0, 0]); - let data3 = new Float32Array([1, 1, 1, 0, - 0, 3, 1, 2, - 2, 3, 1, 0, - 1, 0, 2, 1]); - let data4 = new Float32Array([1, 4, 5, - 4, 2, 2, - 5, 2, 2]); - - let expected1 = new Float32Array([1, 0, 0, - 0, 1, 0, - 0, 0, 1]); - // Inverse does not exist! - let expected3 = new Float32Array([-3, -1/2, 3/2, 1, - 1, 1/4, -1/4, -1/2, - 3, 1/4, -5/4, -1/2, - -3, 0, 1, 1]); - let expected4 = new Float32Array([0, -1, 1, - -1, 23/2, -9, - 1, -9, 7]); - - let dataPtr1 = cv._malloc(3*3*4); - let dataPtr2 = cv._malloc(3*3*4); - let dataPtr3 = cv._malloc(4*4*4); - let dataPtr4 = cv._malloc(3*3*4); - - let dataHeap = new Float32Array(cv.HEAP32.buffer, dataPtr1, 3*3); - dataHeap.set(new Float32Array(data1.buffer)); - let dataHeap2 = new Float32Array(cv.HEAP32.buffer, dataPtr2, 3*3); - dataHeap2.set(new Float32Array(data2.buffer)); - let dataHeap3 = new Float32Array(cv.HEAP32.buffer, dataPtr3, 4*4); - dataHeap3.set(new Float32Array(data3.buffer)); - let dataHeap4 = new Float32Array(cv.HEAP32.buffer, dataPtr4, 3*3); - dataHeap4.set(new Float32Array(data4.buffer)); - - let mat1 = new cv.Mat(3, 3, cv.CV_32FC1, dataPtr1, 0); - let mat2 = new cv.Mat(3, 3, cv.CV_32FC1, dataPtr2, 0); - let mat3 = new cv.Mat(4, 4, cv.CV_32FC1, dataPtr3, 0); - let mat4 = new cv.Mat(3, 3, cv.CV_32FC1, dataPtr4, 0); - - QUnit.assert.deepEqualWithTolerance = function( value, expected, tolerance ) { - for (let i = 0; i < value.length; i= i+1) { - this.pushResult( { - result: Math.abs(value[i]-expected[i]) < tolerance, - actual: value[i], - expected: expected[i], - } ); - } - }; - - cv.invert(mat1, inv1, 0); - // Verify result. - let size = inv1.size(); - assert.equal(inv1.channels(), 1); - assert.equal(size.height, 3); - assert.equal(size.width, 3); - assert.deepEqualWithTolerance(inv1.data32F, expected1, 0.0001); - - - cv.invert(mat2, inv2, 0); - // Verify result. - assert.deepEqualWithTolerance(inv3.data32F, expected3, 0.0001); - - cv.invert(mat3, inv3, 0); - // Verify result. - size = inv3.size(); - assert.equal(inv3.channels(), 1); - assert.equal(size.height, 4); - assert.equal(size.width, 4); - assert.deepEqualWithTolerance(inv3.data32F, expected3, 0.0001); - - cv.invert(mat3, inv3, 1); - // Verify result. - assert.deepEqualWithTolerance(inv3.data32F, expected3, 0.0001); - - cv.invert(mat4, inv4, 2); - // Verify result. - assert.deepEqualWithTolerance(inv4.data32F, expected4, 0.0001); - - cv.invert(mat4, inv4, 3); - // Verify result. - assert.deepEqualWithTolerance(inv4.data32F, expected4, 0.0001); - - mat1.delete(); - mat2.delete(); - mat3.delete(); - mat4.delete(); - inv1.delete(); - inv2.delete(); - inv3.delete(); - inv4.delete(); - } - //Rotate - { - let dst = new cv.Mat(); - let src = cv.matFromArray(3, 2, cv.CV_8U, [1,2,3,4,5,6]); - - cv.rotate(src, dst, cv.ROTATE_90_CLOCKWISE); - - let size = dst.size(); - assert.equal(size.height, 2, "ROTATE_HEIGHT"); - assert.equal(size.width, 3, "ROTATE_WIGTH"); - - let expected = new Uint8Array([5,3,1,6,4,2]); - - assert.deepEqual(dst.data, expected); + assert.deepEqual(intersectionType, cv.INTERSECT_PARTIAL); + intersectionPoints.convertTo(intersectionPoints, cv.CV_32S); + intersectionPointsData = intersectionPoints.data32S; + assert.deepEqual(intersectionPointsData[0], 39); + assert.deepEqual(intersectionPointsData[1], 31); + assert.deepEqual(intersectionPointsData[2], 49); + assert.deepEqual(intersectionPointsData[3], 41); + assert.deepEqual(intersectionPointsData[4], 41); + assert.deepEqual(intersectionPointsData[5], 49); + assert.deepEqual(intersectionPointsData[6], 31); + assert.deepEqual(intersectionPointsData[7], 39); dst.delete(); - src.delete(); + contours.delete(); + hierarchy.delete(); + intersectionPoints.delete(); } }); @@ -973,7 +758,6 @@ QUnit.test('warpPolar', function(assert) { ]); }); - QUnit.test('IntelligentScissorsMB', function(assert) { const lines = new cv.Mat(50, 100, cv.CV_8U, new cv.Scalar(0)); lines.row(10).setTo(new cv.Scalar(255)); diff --git a/modules/js/test/test_utils.js b/modules/js/test/test_utils.js index 03096aec74..c02b4f682a 100644 --- a/modules/js/test/test_utils.js +++ b/modules/js/test/test_utils.js @@ -245,5 +245,8 @@ QUnit.test('test_rotated_rect', function(assert) { assert.equal(points[0].x, cv.RotatedRect.boundingRect2f(rect).x); assert.equal(points[1].y, cv.RotatedRect.boundingRect2f(rect).y); + + let points1 = cv.boxPoints(rect); + assert.deepEqual(points, points1); } }); diff --git a/platforms/js/opencv_js.config.py b/platforms/js/opencv_js.config.py index 1251062e4c..93dc865306 100644 --- a/platforms/js/opencv_js.config.py +++ b/platforms/js/opencv_js.config.py @@ -16,84 +16,105 @@ core = { imgproc = { '': [ - 'Canny', - 'GaussianBlur', - 'Laplacian', - 'HoughLines', - 'HoughLinesP', - 'HoughCircles', - 'Scharr', - 'Sobel', 'adaptiveThreshold', + 'applyColorMap', 'approxPolyDP', + 'approxPolyN', 'arcLength', + 'arrowedLine', 'bilateralFilter', + 'blendLinear', 'blur', 'boundingRect', 'boxFilter', 'calcBackProject', 'calcHist', + 'Canny', 'circle', + 'clipLine', 'compareHist', 'connectedComponents', 'connectedComponentsWithStats', 'contourArea', + 'convertMaps', 'convexHull', 'convexityDefects', 'cornerHarris', 'cornerMinEigenVal', 'createCLAHE', + 'createHanningWindow', 'createLineSegmentDetector', 'cvtColor', 'demosaicing', 'dilate', 'distanceTransform', 'distanceTransformWithLabels', + 'divSpectrums', 'drawContours', + 'drawMarker', 'ellipse', 'ellipse2Poly', 'equalizeHist', 'erode', + 'fillConvexPoly', + 'fillPoly', 'filter2D', 'findContours', + 'findContoursLinkRuns', 'fitEllipse', + 'fitEllipseAMS', + 'fitEllipseDirect', 'fitLine', 'floodFill', + 'GaussianBlur', 'getAffineTransform', + 'getFontScaleFromHeight', 'getPerspectiveTransform', + 'getRectSubPix', 'getRotationMatrix2D', 'getStructuringElement', 'goodFeaturesToTrack', 'grabCut', - #'initUndistortRectifyMap', # 4.x: moved to calib3d + 'HoughLines', + 'HoughLinesP', + 'HoughCircles', + 'HuMoments', 'integral', 'integral2', + 'intersectConvexConvex', + 'invertAffineTransform', 'isContourConvex', + 'Laplacian', 'line', 'matchShapes', 'matchTemplate', 'medianBlur', 'minAreaRect', 'minEnclosingCircle', + 'minEnclosingTriangle', 'moments', 'morphologyEx', 'pointPolygonTest', + 'polylines', + 'preCornerDetect', 'putText', 'pyrDown', 'pyrUp', 'rectangle', 'remap', 'resize', + 'rotatedRectangleIntersection', + 'Scharr', 'sepFilter2D', + 'Sobel', + 'spatialGradient', + 'sqrBoxFilter', + 'stackBlur', 'threshold', - #'undistort', # 4.x: moved to calib3d 'warpAffine', 'warpPerspective', 'warpPolar', 'watershed', - 'fillPoly', - 'fillConvexPoly', - 'polylines', ], 'CLAHE': ['apply', 'collectGarbage', 'getClipLimit', 'getTilesGridSize', 'setClipLimit', 'setTilesGridSize'], 'segmentation_IntelligentScissorsMB': [ From 5f1b05af0ebc0f6a396feee2b28dc9e94c1f7034 Mon Sep 17 00:00:00 2001 From: Alexander Smorkalov <2536374+asmorkalov@users.noreply.github.com> Date: Mon, 2 Dec 2024 10:50:38 +0300 Subject: [PATCH 006/102] Merge pull request #26556 from asmorkalov:FastcvHAL_1stPost Added Fastcv HAL changes in the 3rdparty folder. Code Changes includes HAL code , Fastcv libs and Headers Change-Id: I2f0ddb1f57515c82ae86ba8c2a82965b1a9626ec Requires binaries from https://github.com/opencv/opencv_3rdparty/pull/86. Related patch to opencv_contrib: https://github.com/opencv/opencv_contrib/pull/3811 ### 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/CMakeLists.txt | 34 ++ 3rdparty/fastcv/fastcv.cmake | 43 +++ 3rdparty/fastcv/include/fastcv_hal_core.hpp | 155 ++++++++ .../fastcv/include/fastcv_hal_imgproc.hpp | 151 ++++++++ 3rdparty/fastcv/include/fastcv_hal_utils.hpp | 83 +++++ 3rdparty/fastcv/src/fastcv_hal_core.cpp | 334 ++++++++++++++++++ 3rdparty/fastcv/src/fastcv_hal_imgproc.cpp | 318 +++++++++++++++++ 3rdparty/fastcv/src/fastcv_hal_utils.cpp | 56 +++ CMakeLists.txt | 20 ++ cmake/OpenCVFindLibsPerf.cmake | 20 ++ 10 files changed, 1214 insertions(+) create mode 100644 3rdparty/fastcv/CMakeLists.txt create mode 100644 3rdparty/fastcv/fastcv.cmake create mode 100644 3rdparty/fastcv/include/fastcv_hal_core.hpp create mode 100644 3rdparty/fastcv/include/fastcv_hal_imgproc.hpp create mode 100644 3rdparty/fastcv/include/fastcv_hal_utils.hpp create mode 100644 3rdparty/fastcv/src/fastcv_hal_core.cpp create mode 100644 3rdparty/fastcv/src/fastcv_hal_imgproc.cpp create mode 100644 3rdparty/fastcv/src/fastcv_hal_utils.cpp diff --git a/3rdparty/fastcv/CMakeLists.txt b/3rdparty/fastcv/CMakeLists.txt new file mode 100644 index 0000000000..268d261a98 --- /dev/null +++ b/3rdparty/fastcv/CMakeLists.txt @@ -0,0 +1,34 @@ +if(HAVE_FASTCV) + set(FASTCV_HAL_VERSION 0.0.1 CACHE INTERNAL "") + set(FASTCV_HAL_LIBRARIES "fastcv_hal" CACHE INTERNAL "") + set(FASTCV_HAL_INCLUDE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/include" CACHE INTERNAL "") + set(FASTCV_HAL_HEADERS + "${CMAKE_CURRENT_SOURCE_DIR}/include/fastcv_hal_core.hpp" + "${CMAKE_CURRENT_SOURCE_DIR}/include/fastcv_hal_imgproc.hpp" + CACHE INTERNAL "") + + file(GLOB FASTCV_HAL_FILES "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp") + + add_library(fastcv_hal STATIC ${FASTCV_HAL_FILES}) + + target_include_directories(fastcv_hal PRIVATE + ${CMAKE_SOURCE_DIR}/modules/core/include + ${CMAKE_SOURCE_DIR}/modules/imgproc/include + ${FASTCV_HAL_INCLUDE_DIRS} ${FastCV_INCLUDE_PATH}) + + target_link_libraries(fastcv_hal PUBLIC ${FASTCV_LIBRARY}) + + set_target_properties(fastcv_hal PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${3P_LIBRARY_OUTPUT_PATH}) + + if(NOT BUILD_SHARED_LIBS) + ocv_install_target(fastcv_hal EXPORT OpenCVModules ARCHIVE DESTINATION ${OPENCV_3P_LIB_INSTALL_PATH} COMPONENT dev) + endif() + + ocv_install_3rdparty_licenses(FastCV "${OpenCV_BINARY_DIR}/3rdparty/fastcv/LICENSE") + + if(ENABLE_SOLUTION_FOLDERS) + set_target_properties(fastcv_hal PROPERTIES FOLDER "3rdparty") + endif() +else() + message(STATUS "FastCV is not available, disabling related HAL") +endif(HAVE_FASTCV) diff --git a/3rdparty/fastcv/fastcv.cmake b/3rdparty/fastcv/fastcv.cmake new file mode 100644 index 0000000000..a3c452aa8e --- /dev/null +++ b/3rdparty/fastcv/fastcv.cmake @@ -0,0 +1,43 @@ +function(download_fastcv root_dir) + + # Commit SHA in the opencv_3rdparty repo + set(FASTCV_COMMIT "b8f0d48fa9dbebb0237d3e0abd206f9930c89db6") + + # Define actual FastCV versions + if(ANDROID) + if(AARCH64) + message(STATUS "Download FastCV for Android aarch64") + set(FCV_PACKAGE_NAME "fastcv_android_aarch64_2024_10_24.tgz") + set(FCV_PACKAGE_HASH "14486af00dc0282dac591dc9ccdd957e") + else() + message(STATUS "Download FastCV for Android armv7") + set(FCV_PACKAGE_NAME "fastcv_android_arm32_2024_10_24.tgz") + set(FCV_PACKAGE_HASH "b5afadd5a5b55f8f6c2e7361f225fa21") + endif() + elseif(UNIX AND NOT APPLE AND NOT IOS AND NOT XROS) + if(AARCH64) + set(FCV_PACKAGE_NAME "fastcv_linux_aarch64_2024_10_24.tgz") + set(FCV_PACKAGE_HASH "d15c7b77f2d3577ba46bd94e6cf15230") + else() + message("FastCV: fastcv lib for 32-bit Linux is not supported for now!") + endif() + endif(ANDROID) + + # Download Package + set(OPENCV_FASTCV_URL "https://raw.githubusercontent.com/opencv/opencv_3rdparty/${FASTCV_COMMIT}/fastcv/") + + ocv_download( FILENAME ${FCV_PACKAGE_NAME} + HASH ${FCV_PACKAGE_HASH} + URL ${OPENCV_FASTCV_URL} + DESTINATION_DIR ${root_dir} + ID FASTCV + STATUS res + UNPACK + RELATIVE_URL) + if(res) + set(HAVE_FASTCV TRUE CACHE BOOL "FastCV status") + else() + message(WARNING "FastCV: package download failed!") + endif() + +endfunction() diff --git a/3rdparty/fastcv/include/fastcv_hal_core.hpp b/3rdparty/fastcv/include/fastcv_hal_core.hpp new file mode 100644 index 0000000000..5753ee96bd --- /dev/null +++ b/3rdparty/fastcv/include/fastcv_hal_core.hpp @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 +*/ + +#ifndef OPENCV_FASTCV_HAL_CORE_HPP_INCLUDED +#define OPENCV_FASTCV_HAL_CORE_HPP_INCLUDED + +#include + +#undef cv_hal_lut +#define cv_hal_lut fastcv_hal_lut +#undef cv_hal_normHammingDiff8u +#define cv_hal_normHammingDiff8u fastcv_hal_normHammingDiff8u +#undef cv_hal_mul8u16u +#define cv_hal_mul8u16u fastcv_hal_mul8u16u +#undef cv_hal_sub8u32f +#define cv_hal_sub8u32f fastcv_hal_sub8u32f +#undef cv_hal_transpose2d +#define cv_hal_transpose2d fastcv_hal_transpose2d +#undef cv_hal_meanStdDev +#define cv_hal_meanStdDev fastcv_hal_meanStdDev +#undef cv_hal_flip +#define cv_hal_flip fastcv_hal_flip +#undef cv_hal_rotate90 +#define cv_hal_rotate90 fastcv_hal_rotate + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/// @brief look-up table transform of an array. +/// @param src_data Source image data +/// @param src_step Source image step +/// @param src_type Source image type +/// @param lut_data Pointer to lookup table +/// @param lut_channel_size Size of each channel in bytes +/// @param lut_channels Number of channels in lookup table +/// @param dst_data Destination data +/// @param dst_step Destination step +/// @param width Width of images +/// @param height Height of images +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +int fastcv_hal_lut( + const uchar* src_data, + size_t src_step, + size_t src_type, + const uchar* lut_data, + size_t lut_channel_size, + size_t lut_channels, + uchar* dst_data, + size_t dst_step, + int width, + int height); + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/// @brief Hamming distance between two vectors +/// @param a pointer to first vector data +/// @param b pointer to second vector data +/// @param n length of vectors +/// @param cellSize how many bits of the vectors will be added and treated as a single bit, can be 1 (standard Hamming distance), 2 or 4 +/// @param result pointer to result output +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +int fastcv_hal_normHammingDiff8u(const uchar* a, const uchar* b, int n, int cellSize, int* result); + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +int fastcv_hal_mul8u16u( + const uchar * src1_data, + size_t src1_step, + const uchar * src2_data, + size_t src2_step, + ushort * dst_data, + size_t dst_step, + int width, + int height, + double scale); + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +int fastcv_hal_sub8u32f( + const uchar *src1_data, + size_t src1_step, + const uchar *src2_data, + size_t src2_step, + float *dst_data, + size_t dst_step, + int width, + int height); + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +int fastcv_hal_transpose2d( + const uchar* src_data, + size_t src_step, + uchar* dst_data, + size_t dst_step, + int src_width, + int src_height, + int element_size); + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +int fastcv_hal_meanStdDev( + const uchar * src_data, + size_t src_step, + int width, + int height, + int src_type, + double * mean_val, + double * stddev_val, + uchar * mask, + size_t mask_step); + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/// @brief Flips a 2D array around vertical, horizontal, or both axes +/// @param src_type source and destination image type +/// @param src_data source image data +/// @param src_step source image step +/// @param src_width source and destination image width +/// @param src_height source and destination image height +/// @param dst_data destination image data +/// @param dst_step destination image step +/// @param flip_mode 0 flips around x-axis, 1 around y-axis, -1 both +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +int fastcv_hal_flip( + 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 flip_mode); + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/// @brief Rotates a 2D array in multiples of 90 degrees. +/// @param src_type source and destination image type +/// @param src_data source image data +/// @param src_step source image step +/// @param src_width source image width +/// @If angle has value [180] it is also destination image width +/// If angle has values [90, 270] it is also destination image height +/// @param src_height source and destination image height (destination image width for angles [90, 270]) +/// If angle has value [180] it is also destination image height +/// If angle has values [90, 270] it is also destination image width +/// @param dst_data destination image data +/// @param dst_step destination image step +/// @param angle clockwise angle for rotation in degrees from set [90, 180, 270] +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +int fastcv_hal_rotate( + 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 angle); + +#endif diff --git a/3rdparty/fastcv/include/fastcv_hal_imgproc.hpp b/3rdparty/fastcv/include/fastcv_hal_imgproc.hpp new file mode 100644 index 0000000000..dce20f927e --- /dev/null +++ b/3rdparty/fastcv/include/fastcv_hal_imgproc.hpp @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 +*/ + +#ifndef OPENCV_FASTCV_HAL_IMGPROC_HPP_INCLUDED +#define OPENCV_FASTCV_HAL_IMGPROC_HPP_INCLUDED + +#include + +#undef cv_hal_medianBlur +#define cv_hal_medianBlur fastcv_hal_medianBlur +#undef cv_hal_sobel +#define cv_hal_sobel fastcv_hal_sobel +#undef cv_hal_boxFilter +#define cv_hal_boxFilter fastcv_hal_boxFilter +#undef cv_hal_adaptiveThreshold +#define cv_hal_adaptiveThreshold fastcv_hal_adaptiveThreshold + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/// @brief Calculate medianBlur filter +/// @param src_data Source image data +/// @param src_step Source image step +/// @param dst_data Destination image data +/// @param dst_step Destination image step +/// @param width Source image width +/// @param height Source image height +/// @param depth Depths of source and destination image +/// @param cn Number of channels +/// @param ksize Size of kernel +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +int fastcv_hal_medianBlur( + const uchar* src_data, + size_t src_step, + uchar* dst_data, + size_t dst_step, + int width, + int height, + int depth, + int cn, + int ksize); + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/// @brief Computes Sobel derivatives +/// +/// @param src_data Source image data +/// @param src_step Source image step +/// @param dst_data Destination image data +/// @param dst_step Destination image step +/// @param width Source image width +/// @param height Source image height +/// @param src_depth Depth of source image +/// @param dst_depth Depths of destination image +/// @param cn Number of channels +/// @param margin_left Left margins for source image +/// @param margin_top Top margins for source image +/// @param margin_right Right margins for source image +/// @param margin_bottom Bottom margins for source image +/// @param dx orders of the derivative x +/// @param dy orders of the derivative y +/// @param ksize Size of kernel +/// @param scale Scale factor for the computed derivative values +/// @param delta Delta value that is added to the results prior to storing them in dst +/// @param border_type Border type +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +int fastcv_hal_sobel( + const uchar* src_data, + size_t src_step, + uchar* dst_data, + size_t dst_step, + int width, + int height, + int src_depth, + int dst_depth, + int cn, + int margin_left, + int margin_top, + int margin_right, + int margin_bottom, + int dx, + int dy, + int ksize, + double scale, + double delta, + int border_type); + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/// @brief Canny edge detector +/// @param src_data Source image data +/// @param src_step Source image step +/// @param dst_data Destination image data +/// @param dst_step Destination image step +/// @param width Source image width +/// @param height Source image height +/// @param cn Number of channels +/// @param lowThreshold low thresholds value +/// @param highThreshold high thresholds value +/// @param ksize Kernel size for Sobel operator. +/// @param L2gradient Flag, indicating use L2 or L1 norma. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +int fastcv_hal_canny( + const uchar* src_data, + size_t src_step, + uchar* dst_data, + size_t dst_step, + int width, + int height, + int cn, + double lowThreshold, + double highThreshold, + int ksize, + bool L2gradient); + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +int fastcv_hal_boxFilter( + const uchar* src_data, + size_t src_step, + uchar* dst_data, + size_t dst_step, + int width, + int height, + int src_depth, + int dst_depth, + int cn, + int margin_left, + int margin_top, + int margin_right, + int margin_bottom, + size_t ksize_width, + size_t ksize_height, + int anchor_x, + int anchor_y, + bool normalize, + int border_type); + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +int fastcv_hal_adaptiveThreshold( + const uchar* src_data, + size_t src_step, + uchar* dst_data, + size_t dst_step, + int width, + int height, + double maxValue, + int adaptiveMethod, + int thresholdType, + int blockSize, + double C); + +#endif diff --git a/3rdparty/fastcv/include/fastcv_hal_utils.hpp b/3rdparty/fastcv/include/fastcv_hal_utils.hpp new file mode 100644 index 0000000000..781e3cb5f6 --- /dev/null +++ b/3rdparty/fastcv/include/fastcv_hal_utils.hpp @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 +*/ + +#ifndef OPENCV_FASTCV_HAL_UTILS_HPP_INCLUDED +#define OPENCV_FASTCV_HAL_UTILS_HPP_INCLUDED + +#include "fastcv.h" +#include + +#define INITIALIZATION_CHECK \ +{ \ + if (!FastCvContext::getContext().isInitialized) \ + { \ + return CV_HAL_ERROR_UNKNOWN; \ + } \ +} + +#define CV_HAL_RETURN(status, func) \ +{ \ + if( status == FASTCV_SUCCESS ) \ + { \ + CV_LOG_DEBUG(NULL, "FastCV HAL for "<<#func<<" run successfully!"); \ + return CV_HAL_ERROR_OK; \ + } \ + else if(status == FASTCV_EBADPARAM || status == FASTCV_EUNALIGNPARAM || \ + status == FASTCV_EUNSUPPORTED || status == FASTCV_EHWQDSP || \ + status == FASTCV_EHWGPU) \ + { \ + CV_LOG_DEBUG(NULL, "FastCV status:"< +#include + + +class ParallelTableLookup : public cv::ParallelLoopBody +{ +public: + + ParallelTableLookup(const uchar* src_data_, int width_, size_t src_step_, const uchar* lut_data_, uchar* dst_data_, size_t dst_step_) : + cv::ParallelLoopBody(), src_data(src_data_), width(width_), src_step(src_step_), lut_data(lut_data_), dst_data(dst_data_), dst_step(dst_step_) + { + } + + virtual void operator()(const cv::Range& range) const CV_OVERRIDE + { + fcvStatus status = FASTCV_SUCCESS; + for (int y = range.start; y < range.end; y++) { + status = fcvTableLookupu8((uint8_t*)src_data + y * src_step, width, 1, src_step, (uint8_t*)lut_data, (uint8_t*)dst_data + y * dst_step, dst_step); + if(status != FASTCV_SUCCESS) + CV_LOG_ERROR(NULL,"FastCV error:"< +#include + + +int fastcv_hal_medianBlur( + const uchar* src_data, + size_t src_step, + uchar* dst_data, + size_t dst_step, + int width, + int height, + int depth, + int cn, + int ksize) +{ + // Do not support inplace case + if (src_data == dst_data) + CV_HAL_RETURN_NOT_IMPLEMENTED("Inplace is not supported"); + + // The input image width and height should greater than kernel size + if ((height <= ksize) || (width <= ksize)) + CV_HAL_RETURN_NOT_IMPLEMENTED("Input image size should be larger than kernel size"); + + // The input channel should be 1 + if (cn != 1) + CV_HAL_RETURN_NOT_IMPLEMENTED("Multi-channels is not supported"); + + INITIALIZATION_CHECK; + + fcvStatus status; + int fcvFuncType = FCV_MAKETYPE(ksize,depth); + + switch (fcvFuncType) + { + case FCV_MAKETYPE(3,CV_8U): + { + status = fcvFilterMedian3x3u8_v3(src_data, width, height, src_step, dst_data, dst_step, + fcvBorderType::FASTCV_BORDER_REPLICATE, 0); + break; + } + default: + CV_HAL_RETURN_NOT_IMPLEMENTED(cv::format("Ksize:%d, depth:%s is not supported", ksize, cv::depthToString(depth))); + } + + CV_HAL_RETURN(status, hal_medianBlur); +} + +int fastcv_hal_sobel( + const uchar* src_data, + size_t src_step, + uchar* dst_data, + size_t dst_step, + int width, + int height, + int src_depth, + int dst_depth, + int cn, + int margin_left, + int margin_top, + int margin_right, + int margin_bottom, + int dx, + int dy, + int ksize, + double scale, + double delta, + int border_type) +{ + + if(scale != 1.0f || delta != 0.0f) + CV_HAL_RETURN_NOT_IMPLEMENTED(cv::format("Scale:%f, delta:%f is not supported", scale, delta)); + + // Do not support inplace case + if (src_data == dst_data) + CV_HAL_RETURN_NOT_IMPLEMENTED("Inplace is not supported"); + + // The input image width and height should greater than kernel size + if ((height <= ksize) || (width <= ksize)) + CV_HAL_RETURN_NOT_IMPLEMENTED("Input image size should be larger than kernel size"); + + // The input channel should be 1 + if (cn != 1) + CV_HAL_RETURN_NOT_IMPLEMENTED("Multi-channels is not supported"); + + // Do not support for ROI case + if((margin_left!=0) || (margin_top != 0) || (margin_right != 0) || (margin_bottom !=0)) + CV_HAL_RETURN_NOT_IMPLEMENTED("ROI is not supported"); + + // 1. When ksize <= 0, OpenCV will use Scharr Derivatives + // 2. When ksize == 1, OpenCV will use 3×1 or 1×3 kernel(no Gaussian smoothing is done) + // FastCV doesn't support above two situation + if (ksize <= 1) + CV_HAL_RETURN_NOT_IMPLEMENTED("Scharr derivatives or non square kernel are not supported"); + + // Only support the result type is CV_16S + if (dst_depth != CV_16S) + CV_HAL_RETURN_NOT_IMPLEMENTED(cv::format("Dst depth:%s is not supported", cv::depthToString(dst_depth))); + + INITIALIZATION_CHECK; + + // Only support one direction derivatives and the order is 1.(dx=1 && dy=0)||(dx=0 && dy=1) + int16_t *dxBuffer, *dyBuffer; + + if ((dx == 1) && (dy == 0)) + { + dxBuffer = (int16_t*)dst_data; + dyBuffer = NULL; + } + else if ((dx == 0) && (dy == 1)) + { + dxBuffer = NULL; + dyBuffer = (int16_t*)dst_data; + } + else + CV_HAL_RETURN_NOT_IMPLEMENTED(cv::format("Dx:%d Dy:%d is not supported",dx, dy)); + + fcvStatus status; + fcvBorderType fcvBorder; + + switch (border_type) + { + // For constant border, there are no border value, OpenCV default value is 0 + case cv::BorderTypes::BORDER_CONSTANT: + { + fcvBorder = fcvBorderType::FASTCV_BORDER_CONSTANT; + break; + } + case cv::BorderTypes::BORDER_REPLICATE: + { + fcvBorder = fcvBorderType::FASTCV_BORDER_REPLICATE; + break; + } + default: + CV_HAL_RETURN_NOT_IMPLEMENTED(cv::format("Border type:%s is not supported", borderToString(border_type))); + } + + int fcvFuncType = FCV_MAKETYPE(ksize,src_depth); + + switch (fcvFuncType) + { + case FCV_MAKETYPE(3,CV_8U): + { + status = fcvFilterSobel3x3u8s16(src_data, width, height, src_step, dxBuffer, dyBuffer, dst_step, fcvBorder, 0); + break; + } + case FCV_MAKETYPE(5,CV_8U): + { + status = fcvFilterSobel5x5u8s16(src_data, width, height, src_step, dxBuffer, dyBuffer, dst_step, fcvBorder, 0); + break; + } + case FCV_MAKETYPE(7,CV_8U): + { + status = fcvFilterSobel7x7u8s16(src_data, width, height, src_step, dxBuffer, dyBuffer, dst_step, fcvBorder, 0); + break; + } + default: + CV_HAL_RETURN_NOT_IMPLEMENTED(cv::format("Ksize:%d, src_depth:%s, border type:%s is not supported", + ksize, cv::depthToString(src_depth), borderToString(border_type))); + } + + CV_HAL_RETURN(status, hal_sobel); +} + +int fastcv_hal_boxFilter( + const uchar* src_data, + size_t src_step, + uchar* dst_data, + size_t dst_step, + int width, + int height, + int src_depth, + int dst_depth, + int cn, + int margin_left, + int margin_top, + int margin_right, + int margin_bottom, + size_t ksize_width, + size_t ksize_height, + int anchor_x, + int anchor_y, + bool normalize, + int border_type) +{ + if((width*height) < (320*240)) + { + CV_HAL_RETURN_NOT_IMPLEMENTED("input size not supported"); + } + else if(src_data == dst_data) + { + CV_HAL_RETURN_NOT_IMPLEMENTED("in-place processing not supported"); + } + else if(src_depth != CV_8U || cn != 1) + { + CV_HAL_RETURN_NOT_IMPLEMENTED("src type not supported"); + } + else if(dst_depth != src_depth) + { + CV_HAL_RETURN_NOT_IMPLEMENTED("same src and dst type supported"); + } + else if(ksize_width != ksize_height || + (ksize_width != 3 && ksize_width != 5)) + { + CV_HAL_RETURN_NOT_IMPLEMENTED("kernel size not supported"); + } + else if(anchor_x != -1 || anchor_y != -1 || + margin_left != 0 || margin_top != 0 || + margin_right != 0 || margin_bottom != 0) + { + CV_HAL_RETURN_NOT_IMPLEMENTED("ROI not supported"); + } + + INITIALIZATION_CHECK; + + fcvBorderType bdr; + uint8_t bdrVal = 0; + switch(border_type) + { + case cv::BORDER_REPLICATE: + bdr = FASTCV_BORDER_REPLICATE; + break; + case cv::BORDER_REFLECT: + bdr = FASTCV_BORDER_REFLECT; + break; + case cv::BORDER_REFLECT101: // cv::BORDER_REFLECT_101, BORDER_DEFAULT + bdr = FASTCV_BORDER_REFLECT_V2; + break; + default: + CV_HAL_RETURN_NOT_IMPLEMENTED("border type not supported"); + } + + fcvStatus status = FASTCV_SUCCESS; + if(ksize_width == 3) + { + status = fcvBoxFilter3x3u8_v3(src_data, width, height, src_step, + dst_data, dst_step, normalize, bdr, bdrVal); + } + else if(ksize_width == 5) + { + status = fcvBoxFilter5x5u8_v2(src_data, width, height, src_step, + dst_data, dst_step, normalize, bdr, bdrVal); + } + + CV_HAL_RETURN(status,hal_boxFilter); +} + +int fastcv_hal_adaptiveThreshold( + const uchar* src_data, + size_t src_step, + uchar* dst_data, + size_t dst_step, + int width, + int height, + double maxValue, + int adaptiveMethod, + int thresholdType, + int blockSize, + double C) +{ + if((width*height) < (320*240)) + { + CV_HAL_RETURN_NOT_IMPLEMENTED("input size not supported"); + } + else if (src_data == dst_data) + { + CV_HAL_RETURN_NOT_IMPLEMENTED("In place processing not supported"); + } + + int value = (thresholdType == cv::THRESH_BINARY) ? cvCeil(C) : cvFloor(C); + + if ((maxValue < 1) || (maxValue > 255)) + { + CV_HAL_RETURN_NOT_IMPLEMENTED("max value 1-255 supported"); + } + + INITIALIZATION_CHECK; + + uchar maxVal = cv::saturate_cast(maxValue); + + fcvThreshType threshType = (thresholdType == cv::THRESH_BINARY) ? FCV_THRESH_BINARY : FCV_THRESH_BINARY_INV; + + fcvStatus status = FASTCV_SUCCESS; + if(adaptiveMethod == cv::ADAPTIVE_THRESH_GAUSSIAN_C) + { + if(blockSize == 3) + status = fcvAdaptiveThresholdGaussian3x3u8_v2(src_data, width, height, src_step, maxVal, threshType, value, dst_data, dst_step); + else if(blockSize == 5) + status = fcvAdaptiveThresholdGaussian5x5u8_v2(src_data, width, height, src_step, maxVal, threshType, value, dst_data, dst_step); + else + { + CV_HAL_RETURN_NOT_IMPLEMENTED("block size not supported"); + } + } + else if(adaptiveMethod == cv::ADAPTIVE_THRESH_MEAN_C) + { + if(blockSize == 3) + status = fcvAdaptiveThresholdMean3x3u8_v2(src_data, width, height, src_step, maxVal, threshType, value, dst_data, dst_step); + else if(blockSize == 5) + status = fcvAdaptiveThresholdMean5x5u8_v2(src_data, width, height, src_step, maxVal, threshType, value, dst_data, dst_step); + else + { + CV_HAL_RETURN_NOT_IMPLEMENTED("block size not supported"); + } + } + else + { + CV_HAL_RETURN_NOT_IMPLEMENTED("adaptive method not supported"); + } + + CV_HAL_RETURN(status,hal_adaptiveThreshold); +} diff --git a/3rdparty/fastcv/src/fastcv_hal_utils.cpp b/3rdparty/fastcv/src/fastcv_hal_utils.cpp new file mode 100644 index 0000000000..23fc6e2439 --- /dev/null +++ b/3rdparty/fastcv/src/fastcv_hal_utils.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 +*/ + +#include "fastcv_hal_utils.hpp" + +const char* getFastCVErrorString(int status) +{ + switch(status) + { + case FASTCV_SUCCESS: return "Successful"; + case FASTCV_EFAIL: return "General failure"; + case FASTCV_EUNALIGNPARAM: return "Unaligned pointer parameter"; + case FASTCV_EBADPARAM: return "Bad parameters"; + case FASTCV_EINVALSTATE: return "Called at invalid state"; + case FASTCV_ENORES: return "Insufficient resources, memory, thread, etc"; + case FASTCV_EUNSUPPORTED: return "Unsupported feature"; + case FASTCV_EHWQDSP: return "Hardware QDSP failed to respond"; + case FASTCV_EHWGPU: return "Hardware GPU failed to respond"; + default: return "Unknown FastCV Error"; + } +} + +const char* borderToString(int border) +{ + switch (border) + { + case 0: return "BORDER_CONSTANT"; + case 1: return "BORDER_REPLICATE"; + case 2: return "BORDER_REFLECT"; + case 3: return "BORDER_WRAP"; + case 4: return "BORDER_REFLECT_101"; + case 5: return "BORDER_TRANSPARENT"; + default: return "Unknown border type"; + } +} + +const char* interpolationToString(int interpolation) +{ + switch (interpolation) + { + case 0: return "INTER_NEAREST"; + case 1: return "INTER_LINEAR"; + case 2: return "INTER_CUBIC"; + case 3: return "INTER_AREA"; + case 4: return "INTER_LANCZOS4"; + case 5: return "INTER_LINEAR_EXACT"; + case 6: return "INTER_NEAREST_EXACT"; + case 7: return "INTER_MAX"; + case 8: return "WARP_FILL_OUTLIERS"; + case 16: return "WARP_INVERSE_MAP"; + case 32: return "WARP_RELATIVE_MAP"; + default: return "Unknown interpolation type"; + } +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 3520e92e06..2be9a87a9e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -233,6 +233,8 @@ OCV_OPTION(WITH_NDSRVP "Use Andes RVP extension" (NOT CV_DISABLE_OPTIMIZATION) VISIBLE_IF RISCV) OCV_OPTION(WITH_HAL_RVV "Use HAL RVV optimizations" (NOT CV_DISABLE_OPTIMIZATION) VISIBLE_IF RISCV) +OCV_OPTION(WITH_FASTCV "Use Qualcomm FastCV acceleration library for ARM platform" OFF + VISIBLE_IF ((ARM OR AARCH64) AND (ANDROID OR (UNIX AND NOT APPLE AND NOT IOS AND NOT XROS)))) OCV_OPTION(WITH_CPUFEATURES "Use cpufeatures Android library" ON VISIBLE_IF ANDROID VERIFY HAVE_CPUFEATURES) @@ -935,6 +937,13 @@ if(HAVE_OPENVX) endif() endif() +if(HAVE_FASTCV) + ocv_debug_message(STATUS "Enable FastCV acceleration") + if(NOT ";${OpenCV_HAL};" MATCHES ";fastcv;") + set(OpenCV_HAL "fastcv;${OpenCV_HAL}") + endif() +endif() + if(HAVE_KLEIDICV) ocv_debug_message(STATUS "Enable KleidiCV acceleration") if(NOT ";${OpenCV_HAL};" MATCHES ";kleidicv;") @@ -972,6 +981,14 @@ foreach(hal ${OpenCV_HAL}) else() message(STATUS "Carotene: NEON is not available, disabling carotene...") endif() + elseif(hal STREQUAL "fastcv") + if((ARM OR AARCH64) AND (ANDROID OR (UNIX AND NOT APPLE AND NOT IOS AND NOT XROS))) + add_subdirectory(3rdparty/fastcv) + ocv_hal_register(FASTCV_HAL_LIBRARIES FASTCV_HAL_HEADERS FASTCV_HAL_INCLUDE_DIRS) + list(APPEND OpenCV_USED_HAL "fastcv (ver ${FASTCV_HAL_VERSION})") + else() + message(STATUS "FastCV: fastcv is not available, disabling fastcv...") + endif() elseif(hal STREQUAL "kleidicv") add_subdirectory(3rdparty/kleidicv) ocv_hal_register(KLEIDICV_HAL_LIBRARIES KLEIDICV_HAL_HEADERS KLEIDICV_HAL_INCLUDE_DIRS) @@ -1793,6 +1810,9 @@ endif() if(WITH_OPENVX OR HAVE_OPENVX) status(" OpenVX:" HAVE_OPENVX THEN "YES (${OPENVX_LIBRARIES})" ELSE "NO") endif() +if(WITH_FASTCV OR HAVE_FASTCV) + status(" FastCV:" HAVE_FASTCV THEN "YES (${FASTCV_LIBRARY})" ELSE "NO") +endif() status(" Custom HAL:" OpenCV_USED_HAL THEN "YES (${OpenCV_USED_HAL})" ELSE "NO") diff --git a/cmake/OpenCVFindLibsPerf.cmake b/cmake/OpenCVFindLibsPerf.cmake index c2bff4fb1c..4459e2e2e5 100644 --- a/cmake/OpenCVFindLibsPerf.cmake +++ b/cmake/OpenCVFindLibsPerf.cmake @@ -176,3 +176,23 @@ if(WITH_KLEIDICV) endif() endif() endif(WITH_KLEIDICV) + +# --- FastCV --- +if(WITH_FASTCV) + if((EXISTS ${FastCV_INCLUDE_PATH}) AND (EXISTS ${FastCV_LIB_PATH})) + set(HAVE_FASTCV TRUE CACHE BOOL "FastCV status") + else() + include("${OpenCV_SOURCE_DIR}/3rdparty/fastcv/fastcv.cmake") + set(FCV_ROOT_DIR "${OpenCV_BINARY_DIR}/3rdparty/fastcv") + download_fastcv(${FCV_ROOT_DIR}) + if (HAVE_FASTCV) + set(FastCV_INCLUDE_PATH "${FCV_ROOT_DIR}/inc" CACHE PATH "FastCV includes directory") + set(FastCV_LIB_PATH "${FCV_ROOT_DIR}/libs" CACHE PATH "FastCV library directory") + else() + set(HAVE_FASTCV FALSE CACHE BOOL "FastCV status") + endif() + if (HAVE_FASTCV) + set(FASTCV_LIBRARY "${FastCV_LIB_PATH}/libfastcvopt.so" CACHE PATH "FastCV library") + endif() +endif() +endif(WITH_FASTCV) From f4db63ca711ff5c788c34ee50c7c95982c762d62 Mon Sep 17 00:00:00 2001 From: Maksim Shabunin Date: Mon, 2 Dec 2024 12:06:38 +0300 Subject: [PATCH 007/102] doc: fixed issue with doxygen 1.12 --- modules/core/include/opencv2/core/mat.hpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/core/include/opencv2/core/mat.hpp b/modules/core/include/opencv2/core/mat.hpp index 4b1ee648c7..2282e12aee 100644 --- a/modules/core/include/opencv2/core/mat.hpp +++ b/modules/core/include/opencv2/core/mat.hpp @@ -79,9 +79,10 @@ It is defined as: @code typedef const _InputArray& InputArray; @endcode -where _InputArray is a class that can be constructed from `Mat`, `Mat_`, `Matx`, -`std::vector`, `std::vector >`, `std::vector`, `std::vector >`, -`UMat`, `std::vector` or `double`. It can also be constructed from a matrix expression. +where \ref cv::_InputArray is a class that can be constructed from \ref cv::Mat, \ref cv::Mat_, +\ref cv::Matx, std::vector, std::vector>, std::vector, +std::vector>, \ref cv::UMat, std::vector or `double`. It can also be constructed from +a matrix expression. Since this is mostly implementation-level class, and its interface may change in future versions, we do not describe it in details. There are a few key things, though, that should be kept in mind: From c58b6bf11f9913c2a84f18029d414bcc9e241608 Mon Sep 17 00:00:00 2001 From: Maksim Shabunin Date: Thu, 28 Nov 2024 19:20:14 +0300 Subject: [PATCH 008/102] Fixed several cases of unaligned pointer cast --- modules/core/src/stat.simd.hpp | 18 ++++++++++++++---- modules/imgcodecs/src/utils.hpp | 6 +++++- modules/imgproc/src/smooth.simd.hpp | 4 +++- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/modules/core/src/stat.simd.hpp b/modules/core/src/stat.simd.hpp index a5fb05476d..e363313e5b 100644 --- a/modules/core/src/stat.simd.hpp +++ b/modules/core/src/stat.simd.hpp @@ -48,12 +48,16 @@ int normHamming(const uchar* a, int n) # if defined CV_POPCNT_U64 for(; i <= n - 8; i += 8) { - result += (int)CV_POPCNT_U64(*(uint64*)(a + i)); + uint64_t val; + std::memcpy(&val, a + i, sizeof(val)); + result += (int)CV_POPCNT_U64(val); } # endif for(; i <= n - 4; i += 4) { - result += CV_POPCNT_U32(*(uint*)(a + i)); + uint32_t val; + std::memcpy(&val, a + i, sizeof(val)); + result += CV_POPCNT_U32(val); } } #endif @@ -92,12 +96,18 @@ int normHamming(const uchar* a, const uchar* b, int n) # if defined CV_POPCNT_U64 for(; i <= n - 8; i += 8) { - result += (int)CV_POPCNT_U64(*(uint64*)(a + i) ^ *(uint64*)(b + i)); + uint64_t val_a, val_b; + std::memcpy(&val_a, a + i, sizeof(val_a)); + std::memcpy(&val_b, b + i, sizeof(val_b)); + result += (int)CV_POPCNT_U64(val_a ^ val_b); } # endif for(; i <= n - 4; i += 4) { - result += CV_POPCNT_U32(*(uint*)(a + i) ^ *(uint*)(b + i)); + uint32_t val_a, val_b; + std::memcpy(&val_a, a + i, sizeof(val_a)); + std::memcpy(&val_b, b + i, sizeof(val_b)); + result += (int)CV_POPCNT_U32(val_a ^ val_b); } } #endif diff --git a/modules/imgcodecs/src/utils.hpp b/modules/imgcodecs/src/utils.hpp index 2a9451422e..95deebde31 100644 --- a/modules/imgcodecs/src/utils.hpp +++ b/modules/imgcodecs/src/utils.hpp @@ -137,7 +137,11 @@ uchar* FillGrayRow1( uchar* data, uchar* indices, int len, uchar* palette ); CV_INLINE bool isBigEndian( void ) { - return (((const int*)"\0\x1\x2\x3\x4\x5\x6\x7")[0] & 255) != 0; +#ifdef WORDS_BIGENDIAN + return true; +#else + return false; +#endif } } // namespace diff --git a/modules/imgproc/src/smooth.simd.hpp b/modules/imgproc/src/smooth.simd.hpp index 7389cdbce9..dd9d40e1b3 100644 --- a/modules/imgproc/src/smooth.simd.hpp +++ b/modules/imgproc/src/smooth.simd.hpp @@ -1324,7 +1324,9 @@ void vlineSmooth3N(const ufixedpoint16* const * src, con ufixedpoint32 val[] = { (m[0] + m[1] + m[2]) * ufixedpoint16((uint8_t)128) }; v_128_4 = vx_setall_s32(*((int32_t*)val)); } - v_int16 v_mul01 = v_reinterpret_as_s16(vx_setall_u32(*((uint32_t*)m))); + uint32_t val01; + std::memcpy(&val01, m, sizeof(val01)); + v_int16 v_mul01 = v_reinterpret_as_s16(vx_setall_u32(val01)); v_int16 v_mul2 = v_reinterpret_as_s16(vx_setall_u16(*((uint16_t*)(m + 2)))); for (; i <= len - 4*VECSZ; i += 4*VECSZ) { From 31d04f8fd94d9f88186a9e50cd1a7b69d5bf1198 Mon Sep 17 00:00:00 2001 From: Rostislav Vasilikhin Date: Mon, 2 Dec 2024 16:46:37 +0100 Subject: [PATCH 009/102] 5x5 added for boxfilter perf tests --- modules/imgproc/perf/perf_blur.cpp | 34 +++++++++++++++++++----------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/modules/imgproc/perf/perf_blur.cpp b/modules/imgproc/perf/perf_blur.cpp index d1f5a6b1ca..a0904156a0 100644 --- a/modules/imgproc/perf/perf_blur.cpp +++ b/modules/imgproc/perf/perf_blur.cpp @@ -45,6 +45,10 @@ typedef perf::TestBaseWithParam Size_MatType_BorderTy typedef tuple Size_ksize_BorderType_t; typedef perf::TestBaseWithParam Size_ksize_BorderType; +typedef tuple Size_MatType_BorderType_ksize_t; +typedef perf::TestBaseWithParam Size_MatType_BorderType_ksize; + + PERF_TEST_P(Size_MatType_BorderType3x3, gaussianBlur3x3, testing::Combine( testing::Values(szODD, szQVGA, szVGA, sz720p), @@ -114,24 +118,27 @@ PERF_TEST_P(Size_MatType_BorderType, blur16x16, SANITY_CHECK(dst, eps); } -PERF_TEST_P(Size_MatType_BorderType3x3, box3x3, +PERF_TEST_P(Size_MatType_BorderType_ksize, box, testing::Combine( testing::Values(szODD, szQVGA, szVGA, sz720p), testing::Values(CV_8UC1, CV_16SC1, CV_32SC1, CV_32FC1, CV_32FC3), - BorderType3x3::all() + BorderType::all(), + testing::Values(3, 5) ) ) { - Size size = get<0>(GetParam()); - int type = get<1>(GetParam()); - BorderType3x3 btype = get<2>(GetParam()); + auto p = GetParam(); + Size size = get<0>(p); + int type = get<1>(p); + BorderType btype = get<2>(p); + int ksize = get<3>(p); Mat src(size, type); Mat dst(size, type); declare.in(src, WARMUP_RNG).out(dst); - TEST_CYCLE() boxFilter(src, dst, -1, Size(3,3), Point(-1,-1), false, btype); + TEST_CYCLE() boxFilter(src, dst, -1, Size(ksize, ksize), Point(-1,-1), false, btype); SANITY_CHECK(dst, 1e-6, ERROR_RELATIVE); } @@ -158,17 +165,20 @@ PERF_TEST_P(Size_ksize_BorderType, box_CV8U_CV16U, SANITY_CHECK(dst, 1e-6, ERROR_RELATIVE); } -PERF_TEST_P(Size_MatType_BorderType3x3, box3x3_inplace, +PERF_TEST_P(Size_MatType_BorderType_ksize, box_inplace, testing::Combine( testing::Values(szODD, szQVGA, szVGA, sz720p), testing::Values(CV_8UC1, CV_16SC1, CV_32SC1, CV_32FC1, CV_32FC3), - BorderType3x3::all() + BorderType::all(), + testing::Values(3, 5) ) ) { - Size size = get<0>(GetParam()); - int type = get<1>(GetParam()); - BorderType3x3 btype = get<2>(GetParam()); + auto p = GetParam(); + Size size = get<0>(p); + int type = get<1>(p); + BorderType btype = get<2>(p); + int ksize = get<3>(p); Mat src(size, type); Mat dst(size, type); @@ -179,7 +189,7 @@ PERF_TEST_P(Size_MatType_BorderType3x3, box3x3_inplace, { src.copyTo(dst); startTimer(); - boxFilter(dst, dst, -1, Size(3,3), Point(-1,-1), false, btype); + boxFilter(dst, dst, -1, Size(ksize, ksize), Point(-1,-1), false, btype); stopTimer(); } From 23fcea0d33b1a17525ce84b31400d409ccc14b6a Mon Sep 17 00:00:00 2001 From: Amir Hassan Date: Wed, 4 Dec 2024 15:20:15 +0100 Subject: [PATCH 010/102] Merge pull request #26563 from kallaballa:wayland_and_xkbcommon_missing_include_dirs Missing include directories needed for wayland-util and xkbcommon #26563 See: #26561 ### 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/highgui/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/highgui/CMakeLists.txt b/modules/highgui/CMakeLists.txt index ab5e5391ec..f0a668e4b5 100644 --- a/modules/highgui/CMakeLists.txt +++ b/modules/highgui/CMakeLists.txt @@ -83,6 +83,7 @@ if(WITH_WAYLAND AND HAVE_WAYLAND) endif() endif() + ocv_module_include_directories(${WAYLAND_CLIENT_INCLUDE_DIRS} ${XKBCOMMON_INCLUDE_DIRS}) elseif(HAVE_QT) set(OPENCV_HIGHGUI_BUILTIN_BACKEND "QT${QT_VERSION_MAJOR}") add_definitions(-DHAVE_QT) From 3d91d75f1a48dbba46f8cb7f264485a37e6102a2 Mon Sep 17 00:00:00 2001 From: Skreg <85214856+shyama7004@users.noreply.github.com> Date: Fri, 6 Dec 2024 11:25:41 +0530 Subject: [PATCH 011/102] Merge pull request #26564 from shyama7004:improve-macos-install-docs Improvement of macOS installation guide in documentation #26564 ### 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 --- .../macos_install/macos_install.markdown | 133 +++++++++++++----- 1 file changed, 94 insertions(+), 39 deletions(-) diff --git a/doc/tutorials/introduction/macos_install/macos_install.markdown b/doc/tutorials/introduction/macos_install/macos_install.markdown index f979f50b94..739ca84bca 100644 --- a/doc/tutorials/introduction/macos_install/macos_install.markdown +++ b/doc/tutorials/introduction/macos_install/macos_install.markdown @@ -9,41 +9,49 @@ Installation in MacOS {#tutorial_macos_install} | Original author | `@sajarindider` | | Compatibility | OpenCV >= 3.4 | -The following steps have been tested for MacOSX (Mavericks) but should work with other versions as well. +The following steps have been tested for macOS (Mavericks) but should work with other versions as well. Required Packages ----------------- - CMake 3.9 or higher - Git -- Python 2.7 or later and Numpy 1.5 or later +- Python 3.x and NumPy 1.5 or later This tutorial will assume you have [Python](https://docs.python.org/3/using/mac.html), -[Numpy](https://docs.scipy.org/doc/numpy-1.10.1/user/install.html) and -[Git](https://www.atlassian.com/git/tutorials/install-git) installed on your machine. +[NumPy](https://numpy.org/install/) and +[Git](https://git-scm.com/downloads/mac) installed on your machine. @note -OSX comes with Python 2.7 by default, you will need to install Python 3.8 if you want to use it specifically. +- macOS up to 12.2 (Monterey): Comes with Python 2.7 pre-installed. +- macOS 12.3 and later: Python 2.7 has been removed, and no version of Python is included by default. + +It is recommended to install the latest version of Python 3.x (at least Python 3.8) for compatibility with the latest OpenCV Python bindings. @note -If you XCode and XCode Command Line-Tools installed, you already have git installed on your machine. +If you have Xcode and Xcode Command Line Tools installed, Git is already available on your machine. Installing CMake ---------------- -# Find the version for your system and download CMake from their release's [page](https://cmake.org/download/) --# Install the dmg package and launch it from Applications. That will give you the UI app of CMake +-# Install the `.dmg` package and launch it from Applications. That will give you the UI app of CMake -# From the CMake app window, choose menu Tools --> How to Install For Command Line Use. Then, follow the instructions from the pop-up there. --# Install folder will be /usr/bin/ by default, submit it by choosing Install command line links. +-# The install folder will be `/usr/local/bin/` by default. Complete the installation by choosing Install command line links. + +-# Test that CMake is installed correctly by running: --# Test that it works by running @code{.bash} cmake --version @endcode -@note You can use [Homebrew](https://brew.sh/) to install CMake with @code{.bash} brew install cmake @endcode +@note You can use [Homebrew](https://brew.sh/) to install CMake with: + + @code{.bash} + brew install cmake + @endcode Getting OpenCV Source Code -------------------------- @@ -53,20 +61,22 @@ You can use the latest stable OpenCV version or you can grab the latest snapshot ### Getting the Latest Stable OpenCV Version -- Go to our [downloads page](https://opencv.org/releases). -- Download the source archive and unpack it. +- Go to our [OpenCV releases page](https://opencv.org/releases). +- Download the source archive of the latest version (e.g., OpenCV 4.x) and unpack it. ### Getting the Cutting-edge OpenCV from the Git Repository -Launch Git client and clone [OpenCV repository](http://github.com/opencv/opencv). -If you need modules from [OpenCV contrib repository](http://github.com/opencv/opencv_contrib) then clone it as well. +Launch Git client and clone [OpenCV repository](https://github.com/opencv/opencv). +If you need modules from [OpenCV contrib repository](https://github.com/opencv/opencv_contrib) then clone it as well. + +For example: + + @code{.bash} + cd ~/ + git clone https://github.com/opencv/opencv.git + git clone https://github.com/opencv/opencv_contrib.git + @endcode -For example -@code{.bash} -cd ~/ -git clone https://github.com/opencv/opencv.git -git clone https://github.com/opencv/opencv_contrib.git -@endcode Building OpenCV from Source Using CMake --------------------------------------- @@ -74,53 +84,98 @@ Building OpenCV from Source Using CMake the generated Makefiles, project files as well the object files and output binaries and enter there. - For example + For example: + @code{.bash} mkdir build_opencv cd build_opencv @endcode - @note It is good practice to keep clean your source code directories. Create build directory outside of source tree. + @note It is good practice to keep your source code directories clean. Create the build directory outside of the source tree. -# Configuring. Run `cmake [] ` - For example + For example: + @code{.bash} cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_EXAMPLES=ON ../opencv @endcode - or cmake-gui + Alternatively, you can use the CMake GUI (`cmake-gui`): - - set the OpenCV source code path to, e.g. `/home/user/opencv` - - set the binary build path to your CMake build directory, e.g. `/home/user/build_opencv` + - set the OpenCV source code path to, e.g. `/Users/your_username/opencv` + - set the binary build path to your CMake build directory, e.g. `/Users/your_username/build_opencv` - set optional parameters - run: "Configure" - run: "Generate" -# Description of some parameters - - build type: `CMAKE_BUILD_TYPE=Release` (or `Debug`) - - to build with modules from opencv_contrib set `OPENCV_EXTRA_MODULES_PATH` to `/modules` - - set `BUILD_DOCS=ON` for building documents (doxygen is required) - - set `BUILD_EXAMPLES=ON` to build all examples + - build type: `-DCMAKE_BUILD_TYPE=Release` (or `Debug`). + - include Extra Modules: If you cloned the `opencv_contrib` repository and want to include its modules, set: + + @code{.bash} + -DOPENCV_EXTRA_MODULES_PATH=../opencv_contrib/modules + @endcode + - set `-DBUILD_DOCS=ON` for building documents (doxygen is required) + - set `-DBUILD_EXAMPLES=ON` to build all examples -# [optional] Building python. Set the following python parameters: - - `PYTHON3_EXECUTABLE = ` - - `PYTHON3_INCLUDE_DIR = /usr/include/python` - - `PYTHON3_NUMPY_INCLUDE_DIRS = - /usr/lib/python/dist-packages/numpy/core/include/` + - `-DPYTHON3_EXECUTABLE=$(which python3)` + - `-DPYTHON3_INCLUDE_DIR=$(python3 -c "from sysconfig import get_paths as gp; print(gp()['include'])")` + - `-DPYTHON3_NUMPY_INCLUDE_DIRS=$(python3 -c "import numpy; print(numpy.get_include())")` @note - To specify Python2 versions, you can replace `PYTHON3_` with `PYTHON2_` in the above parameters. + Python 2 has reached its end of life. It is recommended to use Python 3 for building Python bindings. -# Build. From build directory execute *make*, it is recommended to do this in several threads - For example + For example: + @code{.bash} - make -j7 # runs 7 jobs in parallel + make -j$(sysctl -n hw.ncpu) # runs the build using all available CPU cores @endcode --# To use OpenCV in your CMake-based projects through `find_package(OpenCV)` specify `OpenCV_DIR=` variable. +-# After building, you can install OpenCV system-wide using: + + @code{.bash} + sudo make install + @endcode + +-# To use OpenCV in your CMake-based projects through `find_package(OpenCV)`, specify the `OpenCV_DIR` variable pointing to the build or install directory. + + For example: + + @code{.bash} + cmake -DOpenCV_DIR=~/build_opencv .. + @endcode + +### Verifying the OpenCV Installation + +After building (and optionally installing) OpenCV, you can verify the installation by checking the version using Python: + + @code{.bash} + python3 -c "import cv2; print(cv2.__version__)" + @endcode + +This command should output the version of OpenCV you have installed. @note You can also use a package manager like [Homebrew](https://brew.sh/) or [pip](https://pip.pypa.io/en/stable/) to install releases of OpenCV only (Not the cutting edge). + +- Installing via Homebrew: + + For example: + + @code{.bash} + brew install opencv + @endcode + +- Installing via pip: + + For example: + + @code{.bash} + pip install opencv-python + @endcode + + @note To access the extra modules from `opencv_contrib`, install the `opencv-contrib-python` package using `pip install opencv-contrib-python`. \ No newline at end of file From 7edfb57f5aa5b6fa894b9a5f64fb08035c3658c4 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Fri, 6 Dec 2024 08:12:36 +0000 Subject: [PATCH 012/102] videoio(test): filter unstable GStreamer tests - observed on Ubuntu 24.04 --- modules/videoio/test/test_video_io.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/modules/videoio/test/test_video_io.cpp b/modules/videoio/test/test_video_io.cpp index 9c9cdddbff..001035f35a 100644 --- a/modules/videoio/test/test_video_io.cpp +++ b/modules/videoio/test/test_video_io.cpp @@ -47,6 +47,14 @@ public: throw SkipTestException(cv::String("Backend is not available/disabled: ") + cv::videoio_registry::getBackendName(apiPref)); if (cvtest::skipUnstableTests && apiPref == CAP_MSMF && (ext == "h264" || ext == "h265" || ext == "mpg")) throw SkipTestException("Unstable MSMF test"); +#ifdef __linux__ + if (cvtest::skipUnstableTests && apiPref == CAP_GSTREAMER && + (ext == "avi" || ext == "mkv") && + (video_file.find("MPEG") != std::string::npos)) + { + throw SkipTestException("Unstable GSTREAMER test"); + } +#endif writeVideo(); VideoCapture cap; ASSERT_NO_THROW(cap.open(video_file, apiPref)); @@ -852,6 +860,13 @@ TEST_P(videowriter_acceleration, write) std::string backend_name = cv::videoio_registry::getBackendName(backend); if (!videoio_registry::hasBackend(backend)) throw SkipTestException(cv::String("Backend is not available/disabled: ") + backend_name); +#ifdef __linux__ + if (cvtest::skipUnstableTests && backend == CAP_GSTREAMER && + (extension == "mkv") && (codecid == "MPEG")) + { + throw SkipTestException("Unstable GSTREAMER test"); + } +#endif const Size sz(640, 480); const int frameNum = 15; From ed9d64c9d3c76149ce5277cfb0c669f48cb12244 Mon Sep 17 00:00:00 2001 From: Letu Ren Date: Fri, 6 Dec 2024 17:16:36 +0800 Subject: [PATCH 013/102] Fix python re warning in gen_objc --- modules/objc/generator/gen_objc.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/objc/generator/gen_objc.py b/modules/objc/generator/gen_objc.py index c6f8f47be5..71bdf0fb01 100755 --- a/modules/objc/generator/gen_objc.py +++ b/modules/objc/generator/gen_objc.py @@ -1120,7 +1120,7 @@ class ObjectiveCWrapperGenerator(object): name = line[p0:p1] for arg in args: if arg.name == name: - toWrite.append(re.sub('\*\s*@param ', '* @param ', line)) + toWrite.append(re.sub(r'\*\s*@param ', '* @param ', line)) break else: s0 = line.find("@see") @@ -1512,13 +1512,13 @@ def escape_underscore(str): return str.replace('_', '\\_') def escape_texttt(str): - return re.sub(re.compile('texttt{(.*?)\}', re.DOTALL), lambda x: 'texttt{' + escape_underscore(x.group(1)) + '}', str) + return re.sub(re.compile('texttt{(.*?)}', re.DOTALL), lambda x: 'texttt{' + escape_underscore(x.group(1)) + '}', str) def get_macros(tex): out = "" - if re.search("\\\\fork\s*{", tex): + if re.search(r"\\fork\s*{", tex): out += "\\newcommand{\\fork}[4]{ \\left\\{ \\begin{array}{l l} #1 & \\text{#2}\\\\\\\\ #3 & \\text{#4}\\\\\\\\ \\end{array} \\right.} " - if re.search("\\\\vecthreethree\s*{", tex): + if re.search(r"\\vecthreethree\s*{", tex): out += "\\newcommand{\\vecthreethree}[9]{ \\begin{bmatrix} #1 & #2 & #3\\\\\\\\ #4 & #5 & #6\\\\\\\\ #7 & #8 & #9 \\end{bmatrix} } " return out From 082cd7a74eab9a435c414e4fe7a5c0803d4d3b4d Mon Sep 17 00:00:00 2001 From: Super <12210305@mail.sustech.edu.cn> Date: Sat, 7 Dec 2024 15:17:41 +0800 Subject: [PATCH 014/102] Merge pull request #25691 from redhecker:gifSupport [GSoC] Add GIF decode and encode for imgcodecs #25691 this is related to #24855 we add gif support for `imread`, `imreadmulti`, `imwrite` and `imwritemulti` opencv_extra: https://github.com/opencv/opencv_extra/pull/1203 ### 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 --- CMakeLists.txt | 7 + cmake/OpenCVFindLibsGrfmt.cmake | 5 + modules/imgcodecs/CMakeLists.txt | 4 + .../imgcodecs/include/opencv2/imgcodecs.hpp | 21 +- modules/imgcodecs/src/grfmt_gif.cpp | 1144 +++++++++++++++++ modules/imgcodecs/src/grfmt_gif.hpp | 183 +++ modules/imgcodecs/src/grfmts.hpp | 1 + modules/imgcodecs/src/loadsave.cpp | 12 +- modules/imgcodecs/test/test_gif.cpp | 357 +++++ 9 files changed, 1729 insertions(+), 5 deletions(-) create mode 100644 modules/imgcodecs/src/grfmt_gif.cpp create mode 100644 modules/imgcodecs/src/grfmt_gif.hpp create mode 100644 modules/imgcodecs/test/test_gif.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 2be9a87a9e..7af059f8dd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -432,6 +432,9 @@ OCV_OPTION(WITH_ITT "Include Intel ITT support" ON OCV_OPTION(WITH_PROTOBUF "Enable libprotobuf" ON VISIBLE_IF TRUE VERIFY HAVE_PROTOBUF) +OCV_OPTION(WITH_IMGCODEC_GIF "Include GIF support" OFF + VISIBLE_IF TRUE + VERIFY HAVE_IMGCODEC_GIF) OCV_OPTION(WITH_IMGCODEC_HDR "Include HDR support" ON VISIBLE_IF TRUE VERIFY HAVE_IMGCODEC_HDR) @@ -1557,6 +1560,10 @@ if(WITH_GDCM OR HAVE_GDCM) status(" GDCM:" HAVE_GDCM THEN "YES (${GDCM_VERSION})" ELSE "NO") endif() +if(WITH_IMGCODEC_GIF OR DEFINED HAVE_IMGCODEC_GIF) + status(" GIF:" HAVE_IMGCODEC_GIF THEN "YES" ELSE "NO") +endif() + if(WITH_IMGCODEC_HDR OR DEFINED HAVE_IMGCODEC_HDR) status(" HDR:" HAVE_IMGCODEC_HDR THEN "YES" ELSE "NO") endif() diff --git a/cmake/OpenCVFindLibsGrfmt.cmake b/cmake/OpenCVFindLibsGrfmt.cmake index e059305938..240df36e4d 100644 --- a/cmake/OpenCVFindLibsGrfmt.cmake +++ b/cmake/OpenCVFindLibsGrfmt.cmake @@ -371,6 +371,11 @@ if(WITH_GDCM) endif() endif() +if(WITH_IMGCODEC_GIF) + set(HAVE_IMGCODEC_GIF ON) +elseif(DEFINED WITH_IMGCODEC_GIF) + set(HAVE_IMGCODEC_GIF OFF) +endif() if(WITH_IMGCODEC_HDR) set(HAVE_IMGCODEC_HDR ON) elseif(DEFINED WITH_IMGCODEC_HDR) diff --git a/modules/imgcodecs/CMakeLists.txt b/modules/imgcodecs/CMakeLists.txt index 45b19da643..0c52ce7cfb 100644 --- a/modules/imgcodecs/CMakeLists.txt +++ b/modules/imgcodecs/CMakeLists.txt @@ -88,6 +88,10 @@ if(HAVE_GDAL) list(APPEND GRFMT_LIBS ${GDAL_LIBRARY}) endif() +if(HAVE_IMGCODEC_GIF) + add_definitions(-DHAVE_IMGCODEC_GIF) +endif() + if(HAVE_IMGCODEC_HDR) add_definitions(-DHAVE_IMGCODEC_HDR) endif() diff --git a/modules/imgcodecs/include/opencv2/imgcodecs.hpp b/modules/imgcodecs/include/opencv2/imgcodecs.hpp index 1b045c5079..cd38f22325 100644 --- a/modules/imgcodecs/include/opencv2/imgcodecs.hpp +++ b/modules/imgcodecs/include/opencv2/imgcodecs.hpp @@ -111,7 +111,13 @@ enum ImwriteFlags { IMWRITE_JPEG2000_COMPRESSION_X1000 = 272,//!< For JPEG2000, use to specify the target compression rate (multiplied by 1000). The value can be from 0 to 1000. Default is 1000. IMWRITE_AVIF_QUALITY = 512,//!< For AVIF, it can be a quality between 0 and 100 (the higher the better). Default is 95. IMWRITE_AVIF_DEPTH = 513,//!< For AVIF, it can be 8, 10 or 12. If >8, it is stored/read as CV_32F. Default is 8. - IMWRITE_AVIF_SPEED = 514 //!< For AVIF, it is between 0 (slowest) and (fastest). Default is 9. + IMWRITE_AVIF_SPEED = 514,//!< For AVIF, it is between 0 (slowest) and (fastest). Default is 9. + IMWRITE_GIF_LOOP = 1024,//!< For GIF, it can be a loop flag from 0 to 65535. Default is 0 - loop forever. + IMWRITE_GIF_SPEED = 1025,//!< For GIF, it is between 1 (slowest) and 100 (fastest). Default is 96. + IMWRITE_GIF_QUALITY = 1026, //!< For GIF, it can be a quality from 1 to 8. Default is 2. See cv::ImwriteGifCompressionFlags. + IMWRITE_GIF_DITHER = 1027, //!< For GIF, it can be a quality from -1(most dither) to 3(no dither). Default is 0. + IMWRITE_GIF_TRANSPARENCY = 1028, //!< For GIF, the alpha channel lower than this will be set to transparent. Default is 1. + IMWRITE_GIF_COLORTABLE = 1029 //!< For GIF, 0 means global color table is used, 1 means local color table is used. Default is 0. }; enum ImwriteJPEGSamplingFactorParams { @@ -216,6 +222,18 @@ enum ImwriteHDRCompressionFlags { IMWRITE_HDR_COMPRESSION_RLE = 1 }; +//! Imwrite GIF specific values for IMWRITE_GIF_QUALITY parameter key, if larger than 3, then its related to the size of the color table. +enum ImwriteGIFCompressionFlags { + IMWRITE_GIF_FAST_NO_DITHER = 1, + IMWRITE_GIF_FAST_FLOYD_DITHER = 2, + IMWRITE_GIF_COLORTABLE_SIZE_8 = 3, + IMWRITE_GIF_COLORTABLE_SIZE_16 = 4, + IMWRITE_GIF_COLORTABLE_SIZE_32 = 5, + IMWRITE_GIF_COLORTABLE_SIZE_64 = 6, + IMWRITE_GIF_COLORTABLE_SIZE_128 = 7, + IMWRITE_GIF_COLORTABLE_SIZE_256 = 8 +}; + //! @} imgcodecs_flags /** @brief Loads an image from a file. @@ -229,6 +247,7 @@ returns an empty matrix. Currently, the following file formats are supported: - Windows bitmaps - \*.bmp, \*.dib (always supported) +- GIF files - \*.gif (always supported) - JPEG files - \*.jpeg, \*.jpg, \*.jpe (see the *Note* section) - JPEG 2000 files - \*.jp2 (see the *Note* section) - Portable Network Graphics - \*.png (see the *Note* section) diff --git a/modules/imgcodecs/src/grfmt_gif.cpp b/modules/imgcodecs/src/grfmt_gif.cpp new file mode 100644 index 0000000000..caf8a8fc4f --- /dev/null +++ b/modules/imgcodecs/src/grfmt_gif.cpp @@ -0,0 +1,1144 @@ +// 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 "precomp.hpp" +#include "grfmt_gif.hpp" + +#ifdef HAVE_IMGCODEC_GIF +namespace cv +{ +////////////////////////////////////////////////////////////////////// +//// GIF Decoder //// +////////////////////////////////////////////////////////////////////// +GifDecoder::GifDecoder() { + m_signature = R"(GIF)"; + m_type = CV_8UC4; + bgColor = -1; + m_buf_supported = true; + globalColorTableSize = 0; + localColorTableSize = 0; + localColorTable.resize(3 * 256); // maximum size of a color table + lzwMinCodeSize = 0; + hasRead = false; + hasTransparentColor = false; + transparentColor = 0; + opMode = GRFMT_GIF_Nothing; + top = 0, left = 0, width = 0, height = 0; + depth = 8; + idx = 0; +} + +GifDecoder::~GifDecoder() { + close(); +} + +bool GifDecoder::readHeader() { + if (!m_buf.empty()) { + if (!m_strm.open(m_buf)) { + return false; + } + } else if (!m_strm.open(m_filename)) { + return false; + } + + String signature(6, ' '); + m_strm.getBytes((uchar*)signature.data(), 6); + CV_Assert(signature == R"(GIF87a)" || signature == R"(GIF89a)"); + + // #1: read logical screen descriptor + m_width = m_strm.getWord(); + m_height = m_strm.getWord(); + CV_Assert(m_width > 0 && m_height > 0); + + char flags = (char)m_strm.getByte(); + + // the background color -> index in the global color table, valid only if the global color table is present + bgColor = m_strm.getByte(); + m_strm.skip(1); // Skip the aspect ratio + + // #2: read global color table + depth = ((flags & 0x70) >> 4) + 1; + if (flags & 0x80) { + globalColorTableSize = 1 << ((flags & 0x07) + 1); + globalColorTable.resize(3 * globalColorTableSize); + for (int i = 0; i < 3 * globalColorTableSize; i++) { + globalColorTable[i] = (uchar)m_strm.getByte(); + } + } + + // get the frame count + bool success = getFrameCount_(); + + hasRead = false; + return success; +} + +bool GifDecoder::readData(Mat &img) { + if (hasRead) { + lastImage.copyTo(img); + return true; + } + + readExtensions(); + // Image separator + CV_Assert(!(m_strm.getByte()^0x2C)); + left = m_strm.getWord(); + top = m_strm.getWord(); + width = m_strm.getWord(); + height = m_strm.getWord(); + CV_Assert(width > 0 && height > 0 && left + width <= m_width && top + height <= m_height); + + imgCodeStream.resize(width * height); + Mat img_; + + switch (opMode) { + case GifOpMode::GRFMT_GIF_PreviousImage: + if (lastImage.empty()){ + img_ = Mat::zeros(m_height, m_width, CV_8UC4); + } else { + img_ = lastImage; + } + break; + case GifOpMode::GRFMT_GIF_Background: + // background color is valid iff global color table exists + CV_Assert(globalColorTableSize > 0); + if (hasTransparentColor && transparentColor == bgColor) { + img_ = Mat(m_height, m_width, CV_8UC4, + Scalar(globalColorTable[bgColor * 3 + 2], + globalColorTable[bgColor * 3 + 1], + globalColorTable[bgColor * 3], 0)); + } else { + img_ = Mat(m_height, m_width, CV_8UC4, + Scalar(globalColorTable[bgColor * 3 + 2], + globalColorTable[bgColor * 3 + 1], + globalColorTable[bgColor * 3], 255)); + } + break; + case GifOpMode::GRFMT_GIF_Nothing: + case GifOpMode::GRFMT_GIF_Cover: + // default value + img_ = Mat::zeros(m_height, m_width, CV_8UC4); + break; + default: + CV_Assert(false); + } + lastImage.release(); + + auto flags = (uchar)m_strm.getByte(); + if (flags & 0x80) { + // local color table + localColorTableSize = 1 << ((flags & 0x07) + 1); + for (int i = 0; i < 3 * localColorTableSize; i++) { + localColorTable[i] = (uchar)m_strm.getByte(); + } + } else if (globalColorTableSize) { + /* + * According to the GIF Specification at https://www.w3.org/Graphics/GIF/spec-gif89a.txt: + * "Both types of color tables are optional, making it possible for a Data Stream to contain + * numerous graphics without a color table at all." + * The specification recommended that the decoder save the last Global Color Table used + * until another Global Color Table is encountered, here we also save the last Local Color Table used + * in case of there is no such thing as "last Global Color Table used". Thus, we only refresh the + * Local Color Table when a Global Color Table or last Global Color Table used is present. + */ + localColorTableSize = 0; + } + + // lzw decompression to get the code stream + hasRead = lzwDecode(); + + // convert code stream into pixels on the image + if (hasRead) { + idx = 0; + if (!(flags & 0x40)) { + // no interlace, simply convert the code stream into pixels from top to down + code2pixel(img_, 0, 1); + } else { + // consider the interlace mode, the image will be rendered in four separate passes + code2pixel(img_, 0, 8); + code2pixel(img_, 4, 8); + code2pixel(img_, 2, 4); + code2pixel(img_, 1, 2); + } + } + + lastImage = img_; + if (!img.empty()) { + if (img.channels() == 3){ + if (m_use_rgb) { + cvtColor(img_, img, COLOR_BGRA2RGB); + } else { + cvtColor(img_, img, COLOR_BGRA2BGR); + } + } else { + if (m_use_rgb) { + cvtColor(img_, img, COLOR_BGRA2RGBA); + } else { + img_.copyTo(img); + } + } + } + + // release the memory + img_.release(); + + return hasRead; +} + +bool GifDecoder::nextPage() { + if (hasRead) { + hasRead = false; + // end of a gif file + if(!(m_strm.getByte() ^ 0x3B)) return false; + m_strm.setPos(m_strm.getPos() - 1); + return true; + } else { + bool success; + try { + Mat emptyImg; + success = readData(emptyImg); + emptyImg.release(); + } catch(...) { + return false; + } + return success; + } +} + +void GifDecoder::readExtensions() { + uchar len; + while (!(m_strm.getByte() ^ 0x21)) { + auto extensionType = (uchar)m_strm.getByte(); + + // read graphic control extension + // the scope of this extension is the next image or plain text extension + if (!(extensionType ^ 0xF9)) { + hasTransparentColor = false; + opMode = GifOpMode::GRFMT_GIF_Nothing;// default value + len = (uchar)m_strm.getByte(); + CV_Assert(len == 4); + auto flags = (uchar)m_strm.getByte(); + m_strm.getWord(); // delay time, not used + opMode = (GifOpMode)((flags & 0x1C) >> 2); + hasTransparentColor = flags & 0x01; + transparentColor = (uchar)m_strm.getByte(); + } + + // skip other kinds of extensions + len = (uchar)m_strm.getByte(); + while (len) { + m_strm.skip(len); + len = (uchar)m_strm.getByte(); + } + } + // roll back to the block identifier + m_strm.setPos(m_strm.getPos() - 1); +} + +void GifDecoder::code2pixel(Mat& img, int start, int k){ + for (int i = start; i < height; i += k) { + for (int j = 0; j < width; j++) { + uchar colorIdx = imgCodeStream[idx++]; + if (hasTransparentColor && colorIdx == transparentColor) { + if (opMode != GifOpMode::GRFMT_GIF_PreviousImage) { + if (colorIdx < localColorTableSize) { + img.at(top + i, left + j) = + Vec4b(localColorTable[colorIdx * 3 + 2], // B + localColorTable[colorIdx * 3 + 1], // G + localColorTable[colorIdx * 3], // R + 0); // A + } else if (colorIdx < globalColorTableSize) { + img.at(top + i, left + j) = + Vec4b(globalColorTable[colorIdx * 3 + 2], // B + globalColorTable[colorIdx * 3 + 1], // G + globalColorTable[colorIdx * 3], // R + 0); // A + } else { + img.at(top + i, left + j) = Vec4b(0, 0, 0, 0); + } + } + continue; + } + if (colorIdx < localColorTableSize) { + img.at(top + i, left + j) = + Vec4b(localColorTable[colorIdx * 3 + 2], // B + localColorTable[colorIdx * 3 + 1], // G + localColorTable[colorIdx * 3], // R + 255); // A + } else if (colorIdx < globalColorTableSize) { + img.at(top + i, left + j) = + Vec4b(globalColorTable[colorIdx * 3 + 2], // B + globalColorTable[colorIdx * 3 + 1], // G + globalColorTable[colorIdx * 3], // R + 255); // A + } else if (!(localColorTableSize || globalColorTableSize)) { + /* + * According to the GIF Specification at https://www.w3.org/Graphics/GIF/spec-gif89a.txt: + * "If no color table is available at all, the decoder is free to use a system color table + * or a table of its own. In that case, the decoder may use a color table with as many colors + * as its hardware is able to support; it is recommended that such a table have black and + * white as its first two entries, so that monochrome images can be rendered adequately." + */ + uchar intensity = colorIdx ^ 1 ? colorIdx : 255; + img.at(top + i, left + j) = + Vec4b(intensity, intensity, intensity, 255); + } else { + CV_Assert(false); + } + } + } +} + +bool GifDecoder::lzwDecode() { + // initialization + lzwMinCodeSize = m_strm.getByte(); + int lzwCodeSize = lzwMinCodeSize + 1; + int clearCode = 1 << lzwMinCodeSize; + int exitCode = clearCode + 1; + CV_Assert(lzwCodeSize > 2 && lzwCodeSize <= 12); + std::vector lzwExtraTable((1 << 12) + 1); + int colorTableSize = clearCode; + int lzwTableSize = exitCode; + + idx = 0; + int leftBits = 0; + uint32_t src = 0; + auto blockLen = (uchar)m_strm.getByte(); + while (blockLen) { + if (leftBits < lzwCodeSize) { + src |= m_strm.getByte() << leftBits; + blockLen --; + leftBits += 8; + } + + while (leftBits >= lzwCodeSize) { + // get the code + uint16_t code = src & ((1 << lzwCodeSize) - 1); + src >>= lzwCodeSize; + leftBits -= lzwCodeSize; + + // clear code + if (!(code ^ clearCode)) { + lzwExtraTable.clear(); + // reset the code size, the same as that in the initialization part + lzwCodeSize = lzwMinCodeSize + 1; + lzwTableSize = exitCode; + continue; + } + // end of information + if (!(code ^ exitCode)) { + lzwExtraTable.clear(); + lzwCodeSize = lzwMinCodeSize + 1; + lzwTableSize = exitCode; + break; + } + + // check if the code stream is full + if (idx >= width * height) { + return idx == width * height && blockLen == 0 && !m_strm.getByte(); + } + + // output code + // 1. renew the lzw extra table + if (code < colorTableSize) { + lzwExtraTable[lzwTableSize].suffix = (uchar)code; + lzwTableSize ++; + lzwExtraTable[lzwTableSize].prefix.clear(); + lzwExtraTable[lzwTableSize].prefix.push_back((uchar)code); + lzwExtraTable[lzwTableSize].length = 2; + } else if (code <= lzwTableSize) { + lzwExtraTable[lzwTableSize].suffix = lzwExtraTable[code].prefix[0]; + lzwTableSize ++; + lzwExtraTable[lzwTableSize].prefix = lzwExtraTable[code].prefix; + lzwExtraTable[lzwTableSize].prefix.push_back(lzwExtraTable[code].suffix); + lzwExtraTable[lzwTableSize].length = lzwExtraTable[code].length + 1; + } else { + return false; + } + + // 2. output to the code stream + if (code < colorTableSize) { + imgCodeStream[idx++] = (uchar)code; + } else { + for (int i = 0; i < lzwExtraTable[code].length - 1; i++) { + imgCodeStream[idx++] = lzwExtraTable[code].prefix[i]; + } + imgCodeStream[idx++] = lzwExtraTable[code].suffix; + } + + // check if the code size is full + if (lzwTableSize > (1 << 12)) { + return false; + } + + // check if the bit length is full + if (lzwTableSize == (1 << lzwCodeSize)) { + lzwCodeSize < 12 ? lzwCodeSize++ : lzwCodeSize; + } + } + + // go to the next block if this block has been read out + if (!blockLen) { + blockLen = (uchar)m_strm.getByte(); + } + } + + return idx == width * height; +} + +ImageDecoder GifDecoder::newDecoder() const { + return makePtr(); +} + +void GifDecoder::close() { + while (!lastImage.empty()) lastImage.release(); + m_strm.close(); +} + +bool GifDecoder::getFrameCount_() { + m_frame_count = 0; + auto type = (uchar)m_strm.getByte(); + while (type != 0x3B) { + if (!(type ^ 0x21)) { + // skip all kinds of the extensions + m_strm.skip(1); + int len = m_strm.getByte(); + while (len) { + m_strm.skip(len); + len = m_strm.getByte(); + } + } else if (!(type ^ 0x2C)) { + // skip image data + m_frame_count ++; + // skip left, top, width, height + m_strm.skip(8); + int flags = m_strm.getByte(); + // skip local color table + if (flags & 0x80) { + m_strm.skip(3 * (1 << ((flags & 0x07) + 1))); + } + // skip lzw min code size + m_strm.skip(1); + int len = m_strm.getByte(); + while (len) { + m_strm.skip(len); + len = m_strm.getByte(); + } + } else { + CV_Assert(false); + } + type = (uchar)m_strm.getByte(); + } + // roll back to the block identifier + m_strm.setPos(0); + return skipHeader(); +} + +bool GifDecoder::skipHeader() { + String signature(6, ' '); + m_strm.getBytes((uchar *) signature.data(), 6); + // skip height and width + m_strm.skip(4); + char flags = (char) m_strm.getByte(); + // skip the background color and the aspect ratio + m_strm.skip(2); + // skip the global color table + if (flags & 0x80) { + m_strm.skip(3 * (1 << ((flags & 0x07) + 1))); + } + return signature == R"(GIF87a)" || signature == R"(GIF89a)"; +} + +} // namespace cv + +namespace cv +{ +////////////////////////////////////////////////////////////////////// +//// GIF Encoder //// +////////////////////////////////////////////////////////////////////// +static const char* fmtGifHeader = "GIF89a"; +GifEncoder::GifEncoder() { + m_description = "Graphics Interchange Format 89a(*.gif)"; + m_height = 0, m_width = 0; + width = 0, height = 0, top = 0, left = 0; + m_buf_supported = true; + opMode = GRFMT_GIF_Cover; + transparentColor = 0; // index of the transparent color, default 0. currently it is a constant number + transparentRGB = Vec3b(0, 0, 0); // the transparent color, default black + lzwMaxCodeSize = 12; // the maximum code size, default 12. currently it is a constant number + + // default value of the params + fast = true; + loopCount = 0; // infinite loops by default + criticalTransparency = 1; // critical transparency, default 1, range from 0 to 255, 0 means no transparency + frameDelay = 5; // 20fps by default, 10ms per unit + bitDepth = 8; // the number of bits per pixel, default 8, currently it is a constant number + lzwMinCodeSize = 8; // the minimum code size, default 8, this changes as the color number changes + colorNum = 256; // the number of colors in the color table, default 256 + dithering = 0; // the level dithering, default 0 + globalColorTableSize = 256, localColorTableSize = 0; +} + +GifEncoder::~GifEncoder() { + close(); +} + +bool GifEncoder::isFormatSupported(int depth) const { + return depth == CV_8U; +} + +bool GifEncoder::write(const Mat &img, const std::vector ¶ms) { + std::vector img_vec(1, img); + return writeFrames(img_vec, params); +} + +bool GifEncoder::writemulti(const std::vector &img_vec, const std::vector ¶ms) { + return writeFrames(img_vec, params); +} + +bool GifEncoder::writeFrames(const std::vector& img_vec, + const std::vector& params) { + if (img_vec.empty()) { + return false; + } + + if (m_buf) { + if (!strm.open(*m_buf)) { + return false; + } + } else if (!strm.open(m_filename)) { + return false; + } + + // confirm the params + for (size_t i = 0; i < params.size(); i += 2) { + switch (params[i]) { + case IMWRITE_GIF_LOOP: + loopCount = std::min(std::max(params[i + 1], 0), 65535); // loop count is in 2 bytes + break; + case IMWRITE_GIF_SPEED: + frameDelay = 100 - std::min(std::max(params[i + 1] - 1, 0), 99); // from 10ms to 1000ms + break; + case IMWRITE_GIF_DITHER: + dithering = std::min(std::max(params[i + 1], -1), 3); + fast = false; + break; + case IMWRITE_GIF_TRANSPARENCY: + criticalTransparency = (uchar)std::min(std::max(params[i + 1], 0), 255); + break; + case IMWRITE_GIF_COLORTABLE: + localColorTableSize = std::min(std::max(params[i + 1], 0), 1); + break; + case IMWRITE_GIF_QUALITY: + switch (params[i + 1]) { + case IMWRITE_GIF_FAST_FLOYD_DITHER: + fast = true; + dithering = GRFMT_GIF_FloydSteinberg; + break; + case IMWRITE_GIF_FAST_NO_DITHER: + fast = true; + dithering = GRFMT_GIF_None; + break; + default: + lzwMinCodeSize = std::min(std::max(params[i + 1], 3), 8); + colorNum = 1 << lzwMinCodeSize; + globalColorTableSize = colorNum; + fast = false; + break; + } + break; // case IMWRITE_GIF_QUALITY + } + } + if (criticalTransparency) { + lzwMinCodeSize = std::min(8, lzwMinCodeSize + 1); + colorNum = 1 << lzwMinCodeSize; + globalColorTableSize = colorNum; + } + localColorTableSize = localColorTableSize ? colorNum : 0; + + std::vector img_vec_; + if (fast) { + const uchar transparent = 0x92; // 1001_0010: the middle of the color table + if (dithering == GRFMT_GIF_None) { + img_vec_ = img_vec; + transparentColor = transparent; + } else { + localColorTableSize = 0; + int transRGB; + const int depth = 3 << 8 | 3 << 4 | 2; // r:g:b = 3:3:2 + for (auto &img: img_vec) { + Mat img_(img.size(), img.type()); + transRGB = ditheringKernel(img, img_, depth, criticalTransparency); + if (transRGB >= 0) { + transparentRGB = Vec3b((transRGB >> 16) & 0xFF, (transRGB >> 8) & 0xFF, transRGB & 0xFF); + transparentColor = transparent; + } + img_vec_.push_back(img_); + } + if (transparentColor == 0) { + criticalTransparency = 0; + } + } + } else if (dithering != GRFMT_GIF_None) { + int depth = (int)floor(log2(colorNum) / 3) + dithering; + depth = depth << 8 | depth << 4 | depth; + for (auto &img : img_vec) { + Mat img_(img.size(), img.type()); + ditheringKernel(img, img_, depth, criticalTransparency); + img_vec_.push_back(img_); + } + } else { + img_vec_ = img_vec; + } + bool result = writeHeader(img_vec_); + if (!result) { + strm.close(); + return false; + } + + for (const auto &img : img_vec_) { + result = writeFrame(img); + } + + strm.putByte(0x3B); // trailer + strm.close(); + return result; +} + +ImageEncoder GifEncoder::newEncoder() const { + return makePtr(); +} + +bool GifEncoder::writeFrame(const Mat &img) { + if (img.empty()) { + return false; + } + height = m_height, width = m_width; + + // graphic control extension + strm.putByte(0x21); // extension introducer + strm.putByte(0xF9); // graphic control label + strm.putByte(0x04); // block size, fixed number + // flag is a packed field, and the first 3 bits are reserved + uchar flag = opMode << 2; + if (criticalTransparency) + flag |= 1; + strm.putByte(flag); + strm.putWord(frameDelay); + strm.putByte(transparentColor); + strm.putByte(0x00); // end of the extension + + // image descriptor + strm.putByte(0x2C); // image separator + strm.putWord(left); + strm.putWord(top); + strm.putWord(width); + strm.putWord(height); + flag = localColorTableSize > 0 ? 0x80 : 0x00; + if (localColorTableSize > 0) { + std::vector img_vec(1, img); + getColorTable(img_vec, false); + } + flag |= lzwMinCodeSize - 1; + strm.putByte(flag); + if (localColorTableSize > 0) { + strm.putBytes(localColorTable.data(), localColorTableSize * 3); + } + + imgCodeStream.resize(width * height); + bool result = pixel2code(img); + if (result) result = lzwEncode(); + + return result; +} + +bool GifEncoder::lzwEncode() { + strm.putByte(lzwMinCodeSize); + int lzwCodeSize = lzwMinCodeSize + 1; + // add clear code to the head of the output stream + int bitLeft = lzwCodeSize; + size_t output = (size_t)1 << lzwMinCodeSize; + + lzwTable.resize((1 << 12) * 256); + // clear lzwTable + memset(lzwTable.data(), 0, (1 << 20) * sizeof(int16_t)); // 20 = 12 + 8 = 2^12(max lzw table size) * 256 + + // next code + auto idx = (int16_t)((1 << lzwMinCodeSize) + 2); + + int bufferLen = 0; + uchar buffer[256]; + + //initialize + int32_t prev = imgCodeStream[0]; + + for (int64_t i = 1; i < height * width; i++) { + // add the output code to the output buffer + while (bitLeft >= 8) { + buffer[bufferLen++] = (uchar)output; + output >>= 8; + bitLeft -= 8; + if(bufferLen == 255) { + strm.putByte(255); + strm.putBytes(buffer, 255); + bufferLen = 0; + } + } + + uchar c = imgCodeStream[i]; + // prev + currentCode(c) is not in the table + if(lzwTable[prev * 256 + c] == 0){ + output |= ((size_t)prev << bitLeft); + bitLeft += lzwCodeSize; + lzwTable[prev * 256 + c] = idx; + prev = c; + // check if the bit length is full + if(idx == (1 << lzwCodeSize)){ + lzwCodeSize ++; + } + idx ++; + // if the lzwTable is full, add clear code to the output + if(idx == (1 << lzwMaxCodeSize)){ + output |= (((size_t)1 << lzwMinCodeSize) << bitLeft); + bitLeft += lzwCodeSize; + memset(lzwTable.data(), 0, (1 << 20) * sizeof(int16_t)); // clear lzwTable + // next code + idx = (int16_t)((1 << lzwMinCodeSize) + 2); + lzwCodeSize = lzwMinCodeSize + 1; + } + } else{ + prev = lzwTable[prev * 256 + c]; + } + } + + // end of the code + output |= ((size_t)prev << bitLeft); + bitLeft += lzwCodeSize; + output |= ((((size_t)1 << lzwMinCodeSize) | 1) << bitLeft); + bitLeft += lzwCodeSize; + while (bitLeft >= 8) { + buffer[bufferLen++] = (uchar)output; + output >>= 8; + bitLeft -= 8; + if(bufferLen == 255) { + strm.putByte(255); + strm.putBytes(buffer, 255); + bufferLen = 0; + } + } + if (bitLeft > 0) { + buffer[bufferLen++] = (uchar)output; + } + if (bufferLen > 0){ + strm.putByte(bufferLen); + strm.putBytes(buffer, bufferLen); + } + // end of the block + strm.putByte(0); + + return true; +} + +bool GifEncoder::writeHeader(const std::vector& img_vec) { + strm.putBytes(fmtGifHeader, (int)strlen(fmtGifHeader)); + + if (img_vec[0].empty()) { + return false; + } + m_width = img_vec[0].cols, m_height = img_vec[0].rows; + if (m_width <= 0 || m_height <= 0 || m_width > 65535 || m_height > 65535) { + return false; + } + strm.putWord(m_width); + strm.putWord(m_height); + + // by default, set the global color table + uchar flags = (globalColorTableSize > 0) << 7; // global color table flag + getColorTable(img_vec, true); + flags |= (bitDepth - 1) << 4; // bit depth + flags |= (lzwMinCodeSize - 1); // global color table size + strm.putByte(flags); + strm.putByte(0); // background color, default value + strm.putByte(0); // aspect ratio, default value + if (globalColorTableSize > 0) { + strm.putBytes(globalColorTable.data(), globalColorTableSize * 3); + } + + + // add application extension to set the loop count + strm.putByte(0x21); // GIF extension code + strm.putByte(0xFF); // application extension table + strm.putByte(0x0B); // length of application block, in decimal is 11 + strm.putBytes(R"(NETSCAPE2.0)", 11); // application authentication code + strm.putByte(0x03); // length of application block, in decimal is 3 + strm.putByte(0x01); // identifier + strm.putWord(loopCount); + strm.putByte(0x00); // end of the extension + + return true; +} + +bool GifEncoder::pixel2code(const Mat &img) { + if(img.empty()) return false; + CV_Assert(img.rows == (top + height) && img.cols == (left + width)); + + if (fast) { + if (img.type() == CV_8UC3) { + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + uchar colorIdx = (img.at(i, j)[2] & 0xe0) | + ((img.at(i, j)[1] >> 3) & 0x1c) | + ((img.at(i, j)[0] >> 6) & 0x03); + if (criticalTransparency && colorIdx == transparentColor) { + imgCodeStream[i * width + j] = + transparentColor - 4; // 4 means the minimum color change of green channel + } else { + imgCodeStream[i * width + j] = colorIdx; + } + } + } + } else if (img.type() == CV_8UC4) { + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + if (img.at(i, j)[3] < criticalTransparency) { + imgCodeStream[i * width + j] = transparentColor; + continue; + } + uchar colorIdx = (img.at(i, j)[2] & 0xe0) | + ((img.at(i, j)[1] >> 3) & 0x1c) | + ((img.at(i, j)[0] >> 6) & 0x03); + if (criticalTransparency && colorIdx == transparentColor) { + imgCodeStream[i * width + j] = + transparentColor - 4; // 4 means the minimum color change of green channel + } else { + imgCodeStream[i * width + j] = colorIdx; + } + } + } + } else { + CV_Assert(false); + } + return true; + } + + // turn the image into the code stream and set the colorNum + CV_Assert(colorNum <= 256 && (colorNum <= localColorTableSize || colorNum <= globalColorTableSize)); + OctreeColorQuant quant = localColorTableSize > 0 ? quantL : quantG; + + if (img.type() == CV_8UC3) { + for(int i = 0; i < height; i++){ + for(int j = 0; j < width; j++){ + // set codeStream + imgCodeStream[i * width + j] = quant.getLeaf(img.at(i, j)[2], + img.at(i, j)[1], + img.at(i, j)[0]); + } + } + } else if (img.type() == CV_8UC4) { + for(int i = 0; i < height; i++){ + for(int j = 0; j < width; j++){ + if (img.at(i, j)[3] < criticalTransparency) { + imgCodeStream[i * width + j] = transparentColor; + continue; + } + imgCodeStream[i * width + j] = quant.getLeaf(img.at(i, j)[2], + img.at(i, j)[1], + img.at(i, j)[0]); + } + } + } else { + CV_Assert(false); + } + return true; +} + +void GifEncoder::getColorTable(const std::vector &img_vec, bool isGlobal) { + // generate the global/local color table (color quantification) + if (img_vec.empty()) return; + CV_Assert(isGlobal || img_vec.size() == 1); + if (fast) { + globalColorTable.resize(colorNum * 3); + for (int i = 0; i < 256; i++) { + globalColorTable[i * 3] = ((i >> 5) & 7) * 36; + globalColorTable[i * 3 + 1] = ((i >> 2) & 7) * 36; + globalColorTable[i * 3 + 2] = (i & 3) * 85; + } + globalColorTable[transparentColor * 3] = transparentRGB[0]; + globalColorTable[transparentColor * 3 + 1] = transparentRGB[1]; + globalColorTable[transparentColor * 3 + 2] = transparentRGB[2]; + return; + } + if (isGlobal) { + quantG = OctreeColorQuant(colorNum, bitDepth, criticalTransparency); + quantG.addMats(img_vec); + globalColorTable.resize(colorNum * 3); + quantG.getPalette(globalColorTable.data()); + } else { + quantL = OctreeColorQuant(colorNum, bitDepth, criticalTransparency); + quantL.addMats(img_vec); + localColorTable.resize(colorNum * 3); + quantL.getPalette(localColorTable.data()); + } +} + +int GifEncoder::ditheringKernel(const Mat &img, Mat &img_, int depth, uchar criticalTransparency) { + int transparentRGB = -1; + if (img.empty()) { + return -1; + } else if (img.type() == CV_8UC3){ + Mat error = Mat::zeros(img.rows + 2, img.cols + 2, CV_32FC3); + int constant_r = 255 / ((1 << ((depth >> 8) & 0xf)) - 1); + int constant_g = 255 / ((1 << ((depth >> 4) & 0xf)) - 1); + int constant_b = 255 / ((1 << ((depth) & 0xf)) - 1); + for (int i = 0; i < img.rows; i++) { + for (int j = 0; j < img.cols; j++) { + Vec3f old_pixel = (Vec3f)img.at(i, j) + error.at(i + 1, j + 1); + Vec3b new_pixel; + new_pixel[0] = (uchar)(std::lround(std::min(std::max(old_pixel[0], 0.0f), 255.0f) / (float)constant_b) * constant_b); + new_pixel[1] = (uchar)(std::lround(std::min(std::max(old_pixel[1], 0.0f), 255.0f) / (float)constant_g) * constant_g); + new_pixel[2] = (uchar)(std::lround(std::min(std::max(old_pixel[2], 0.0f), 255.0f) / (float)constant_r) * constant_r); + img_.at(i, j) = new_pixel; + Vec3f diff = old_pixel - (Vec3f)new_pixel; + error.at(i + 1, j + 2) += diff * 7 / 16; // (i, j + 1) + error.at(i + 2, j) += diff * 3 / 16; // (i + 1, j - 1) + error.at(i + 2, j + 1) += diff * 5 / 16; // (i + 1, j) + error.at(i + 2, j + 2) += diff / 16; // (i + 1, j + 1) + } + } + } else if (img.type() == CV_8UC4) { + Mat error = Mat::zeros(img.rows + 2, img.cols + 2, CV_32FC4); + int constant_r = 255 / ((1 << ((depth >> 8) & 0xf)) - 1); + int constant_g = 255 / ((1 << ((depth >> 4) & 0xf)) - 1); + int constant_b = 255 / ((1 << ((depth) & 0xf)) - 1); + for (int i = 0; i < img.rows; i++) { + for (int j = 0; j < img.cols; j++) { + // transparent color should not be dithered + if (img.at(i, j)[3] < criticalTransparency) { + transparentRGB = (img.at(i, j)[2] << 16) | + (img.at(i, j)[1] << 8) | + (img.at(i, j)[0]); + img_.at(i, j) = img.at(i, j); + continue; + } + Vec4f old_pixel = (Vec4f)img.at(i, j) + error.at(i + 1, j + 1); + Vec4b new_pixel; + new_pixel[0] = (uchar)(std::lround(std::min(std::max(old_pixel[0], 0.0f), 255.0f) / (float)constant_b) * constant_b); + new_pixel[1] = (uchar)(std::lround(std::min(std::max(old_pixel[1], 0.0f), 255.0f) / (float)constant_g) * constant_g); + new_pixel[2] = (uchar)(std::lround(std::min(std::max(old_pixel[2], 0.0f), 255.0f) / (float)constant_r) * constant_r); + new_pixel[3] = img.at(i, j)[3]; + img_.at(i, j) = new_pixel; + Vec4f diff = old_pixel - (Vec4f)new_pixel; + error.at(i + 1, j + 2) += diff * 7 / 16; // (i, j + 1) + error.at(i + 2, j) += diff * 3 / 16; // (i + 1, j - 1) + error.at(i + 2, j + 1) += diff * 5 / 16; // (i + 1, j) + error.at(i + 2, j + 2) += diff / 16; // (i + 1, j + 1) + } + } + } else { + CV_Assert(false); + } + return transparentRGB; +} + +void GifEncoder::close() { + if (strm.isOpened()) { + strm.close(); + } +} + + +////////////////////////////////////////////////////////////////////// +//// Color Quantization //// +////////////////////////////////////////////////////////////////////// +GifEncoder::OctreeColorQuant::OctreeNode::OctreeNode() { + this->isLeaf = false; + level = 0; + index = 0; + for (auto &i: children) { + i = nullptr; + } + leaf = 0, pixelCount = 0; + redSum = greenSum = blueSum = 0; +} + +GifEncoder::OctreeColorQuant::OctreeColorQuant(int maxColors, int bitLength, uchar criticalTransparency) { + m_maxColors = maxColors; + m_bitLength = bitLength; + m_leafCount = criticalTransparency ? 1 : 0; + m_criticalTransparency = criticalTransparency; + root = std::make_shared(); + r = g = b = 0; + for (int i = 0; i < bitLength; i++) { + m_nodeList[i] = std::vector>(); + } +} + +void GifEncoder::OctreeColorQuant::addMat(const Mat &img) { + if (img.empty()) { + return; + } else if (img.type() == CV_8UC3) { + for (int i = 0; i < img.rows; i++) { + for (int j = 0; j < img.cols; j++) { + addColor(img.at(i, j)[2], + img.at(i, j)[1], + img.at(i, j)[0]); + } + } + } else if (img.type() == CV_8UC4) { + for (int i = 0; i < img.rows; i++) { + for (int j = 0; j < img.cols; j++) { + if (img.at(i, j)[3] < m_criticalTransparency) { + r = img.at(i, j)[2]; + g = img.at(i, j)[1]; + b = img.at(i, j)[0]; + continue; + } + addColor(img.at(i, j)[2], + img.at(i, j)[1], + img.at(i, j)[0]); + } + } + } else { + CV_Assert(false); + } +} + +void GifEncoder::OctreeColorQuant::addMats(const std::vector &img_vec) { + for (const auto& img: img_vec) { + addMat(img); + } + if (m_maxColors < m_leafCount) { + reduceTree(); + } +} + +void GifEncoder::OctreeColorQuant::addColor(int red, int green, int blue) { + std::shared_ptr node = root; + for (int level = 0; level < m_bitLength; level++) { + node -> pixelCount++; + node -> redSum += red; + node -> greenSum += green; + node -> blueSum += blue; + if(node -> isLeaf){ + break; + } + int shift = m_bitLength - level; + int index = ((red >> shift) & 1) << 2 | ((green >> shift) & 1) << 1 | ((blue >> shift) & 1); + if (node->children[index] == nullptr) { + node->children[index] = std::make_shared(); + m_nodeList[level].push_back(node->children[index]); + } + node = node->children[index]; + if (level == m_bitLength - 1){ + node -> pixelCount++; + node -> redSum += red; + node -> greenSum += green; + node -> blueSum += blue; + } + } + if (!(node -> isLeaf)) { + m_leafCount++; + node -> isLeaf = true; + } +} + +// return the relative index of the leaf node +uchar GifEncoder::OctreeColorQuant::getLeaf(uchar red, uchar green, uchar blue) { + std::shared_ptr node = root; + for (int level = 0; level <= m_bitLength; level++) { + if (node->isLeaf) { + break; + } + int shift = m_bitLength - level; + int index = ((red >> shift) & 1) << 2 | ((green >> shift) & 1) << 1 | ((blue >> shift) & 1); + if (node->children[index] == nullptr) { + CV_Assert(false); + } + node = node->children[index]; + } + return node->index; +} + +// get the palette +int GifEncoder::OctreeColorQuant::getPalette(uchar* colorTable) { + CV_Assert(colorTable != nullptr); + uchar index = 0; + if (m_criticalTransparency) { + colorTable[index * 3] = r; + colorTable[index * 3 + 1] = g; + colorTable[index * 3 + 2] = b; + index++; + } + for (int i = 0; i < m_bitLength; i++) { + for (const auto& node : m_nodeList[i]) { + if (node -> isLeaf) { + colorTable[index * 3] = (uchar)(node -> redSum / node -> pixelCount); + colorTable[index * 3 + 1] = (uchar)(node -> greenSum / node -> pixelCount); + colorTable[index * 3 + 2] = (uchar)(node -> blueSum / node -> pixelCount); + node -> index = index++; + } + if (index == m_leafCount) { + break; + } + } + } + return m_leafCount; +} + +void GifEncoder::OctreeColorQuant::reduceTree() { + // reduce to max color + int level = 0; + for (int i = 0; i < m_bitLength; i++) { + auto size = (int32_t)m_nodeList[i].size() + 1; + if (m_maxColors < size) { + level = i - 1; + break; + } + } + for (const auto& node : m_nodeList[level + 1]) { + recurseReduce(node); + } + + while(m_maxColors < m_leafCount) { + int minPixelCount = INT_MAX; + std::shared_ptr minNode = nullptr; + for (const auto& node : m_nodeList[level]) { + if (node->pixelCount < minPixelCount && !(node->isLeaf)) { + minPixelCount = node->pixelCount; + minNode = node; + } + } + CV_Assert(minNode != nullptr); + recurseReduce(minNode); + } +} + +void GifEncoder::OctreeColorQuant::recurseReduce(const std::shared_ptr& node) { + // reduce all the children of the node + if (node == nullptr || node->isLeaf) { + return; + } + std::vector> stack; + stack.push_back(node); + + while (!stack.empty()) { + std::shared_ptr child = stack.back(); + stack.pop_back(); + if (child->isLeaf) { + m_leafCount--; + child->isLeaf = false; + } else { + for (int i = 0; i < m_bitLength; i++) { + if (child->children[i] != nullptr) { + stack.push_back(child->children[i]); + } + } + } + } + m_leafCount++; + node -> isLeaf = true; +} + +} // namespace cv2 +#endif diff --git a/modules/imgcodecs/src/grfmt_gif.hpp b/modules/imgcodecs/src/grfmt_gif.hpp new file mode 100644 index 0000000000..284a9cf945 --- /dev/null +++ b/modules/imgcodecs/src/grfmt_gif.hpp @@ -0,0 +1,183 @@ +// 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 OPENCV_GRFMT_GIF_HPP +#define OPENCV_GRFMT_GIF_HPP +#ifdef HAVE_IMGCODEC_GIF + +#include "grfmt_base.hpp" + +namespace cv +{ + +enum GifOpMode +{ + GRFMT_GIF_Nothing = 0, + GRFMT_GIF_PreviousImage = 1, + GRFMT_GIF_Background = 2, + GRFMT_GIF_Cover = 3 +}; + +////////////////////////////////////////////////////////////////////// +//// GIF Decoder //// +////////////////////////////////////////////////////////////////////// + +class GifDecoder CV_FINAL : public BaseImageDecoder +{ +public: + GifDecoder(); + ~GifDecoder() CV_OVERRIDE; + + bool readHeader() CV_OVERRIDE; + bool readData(Mat& img) CV_OVERRIDE; + bool nextPage() CV_OVERRIDE; + void close(); + + ImageDecoder newDecoder() const CV_OVERRIDE; + +protected: + RLByteStream m_strm; + + int bgColor; + int depth; + int idx; + + GifOpMode opMode; + bool hasTransparentColor; + uchar transparentColor; + int top, left, width, height; + + bool hasRead; + std::vector globalColorTable; + std::vector localColorTable; + + int lzwMinCodeSize; + int globalColorTableSize; + int localColorTableSize; + + Mat lastImage; + std::vector imgCodeStream; + + struct lzwNodeD + { + int length; + uchar suffix; + std::vector prefix; + }; + + void readExtensions(); + void code2pixel(Mat& img, int start, int k); + bool lzwDecode(); + bool getFrameCount_(); + bool skipHeader(); +}; + + + +////////////////////////////////////////////////////////////////////// +//// GIF Encoder //// +////////////////////////////////////////////////////////////////////// +class GifEncoder CV_FINAL : public BaseImageEncoder { +public: + GifEncoder(); + ~GifEncoder() CV_OVERRIDE; + + bool isFormatSupported(int depth) const CV_OVERRIDE; + + bool write(const Mat& img, const std::vector& params) CV_OVERRIDE; + + bool writemulti(const std::vector& img_vec, + const std::vector& params) CV_OVERRIDE; + + ImageEncoder newEncoder() const CV_OVERRIDE; + +private: +/** Color Quantization **/ + class OctreeColorQuant + { + struct OctreeNode + { + bool isLeaf; + std::shared_ptr children[8]{}; + int level; + uchar index; + int leaf; + int pixelCount; + size_t redSum, greenSum, blueSum; + + OctreeNode(); + }; + + std::shared_ptr root; + std::vector> m_nodeList[8]; + int32_t m_bitLength; + int32_t m_maxColors; + int32_t m_leafCount; + uchar m_criticalTransparency; + uchar r, g, b; // color under transparent color + + public: + explicit OctreeColorQuant(int maxColors = 256, int bitLength = 8, uchar criticalTransparency = 1); + + int getPalette(uchar* colorTable); + uchar getLeaf(uchar red, uchar green, uchar blue); + + void addMat(const Mat& img); + void addMats(const std::vector& img_vec); + void addColor(int red, int green, int blue); + void reduceTree(); + void recurseReduce(const std::shared_ptr& node); + }; + + enum GifDithering // normal dithering level is -1 to 2 + { + GRFMT_GIF_None = 3, + GRFMT_GIF_FloydSteinberg = 4 + }; + + WLByteStream strm; + int m_width, m_height; + + int globalColorTableSize; + int localColorTableSize; + + uchar opMode; + uchar criticalTransparency; + uchar transparentColor; + Vec3b transparentRGB; + int top, left, width, height; + + OctreeColorQuant quantG; + OctreeColorQuant quantL; + + std::vector lzwTable; + std::vector imgCodeStream; + + std::vector globalColorTable; + std::vector localColorTable; + + // params + int loopCount; + int frameDelay; + int colorNum; + int bitDepth; + int dithering; + int lzwMinCodeSize, lzwMaxCodeSize; + bool fast; + + bool writeFrames(const std::vector& img_vec, const std::vector& params); + bool writeHeader(const std::vector& img_vec); + bool writeFrame(const Mat& img); + bool pixel2code(const Mat& img); + void getColorTable(const std::vector& img_vec, bool isGlobal); + static int ditheringKernel(const Mat &img, Mat &img_, int depth, uchar transparency); + bool lzwEncode(); + void close(); +}; + + +} // namespace cv + +#endif // HAVE_IMGCODEC_GIF +#endif //OPENCV_GRFMT_GIF_HPP diff --git a/modules/imgcodecs/src/grfmts.hpp b/modules/imgcodecs/src/grfmts.hpp index 46b79ff96c..198588630c 100644 --- a/modules/imgcodecs/src/grfmts.hpp +++ b/modules/imgcodecs/src/grfmts.hpp @@ -45,6 +45,7 @@ #include "grfmt_base.hpp" #include "grfmt_avif.hpp" #include "grfmt_bmp.hpp" +#include "grfmt_gif.hpp" #include "grfmt_sunras.hpp" #include "grfmt_jpeg.hpp" #include "grfmt_pxm.hpp" diff --git a/modules/imgcodecs/src/loadsave.cpp b/modules/imgcodecs/src/loadsave.cpp index b057b35a9f..25f32a789c 100644 --- a/modules/imgcodecs/src/loadsave.cpp +++ b/modules/imgcodecs/src/loadsave.cpp @@ -151,14 +151,18 @@ struct ImageCodecInitializer */ ImageCodecInitializer() { -#ifdef HAVE_AVIF - decoders.push_back(makePtr()); - encoders.push_back(makePtr()); -#endif /// BMP Support decoders.push_back( makePtr() ); encoders.push_back( makePtr() ); + #ifdef HAVE_IMGCODEC_GIF + decoders.push_back( makePtr() ); + encoders.push_back( makePtr() ); + #endif + #ifdef HAVE_AVIF + decoders.push_back(makePtr()); + encoders.push_back(makePtr()); + #endif #ifdef HAVE_IMGCODEC_HDR decoders.push_back( makePtr() ); encoders.push_back( makePtr() ); diff --git a/modules/imgcodecs/test/test_gif.cpp b/modules/imgcodecs/test/test_gif.cpp new file mode 100644 index 0000000000..b9c49c0bdb --- /dev/null +++ b/modules/imgcodecs/test/test_gif.cpp @@ -0,0 +1,357 @@ +// 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" + +#ifdef HAVE_IMGCODEC_GIF + +namespace opencv_test { namespace { + +const string gifsuite_files_multi[]={ + "basi3p01", + "basi3p02", + "basi3p04", + "basn3p01", + "basn3p02", + "basn3p04", + "ccwn3p08", + "ch1n3p04", + "cs3n3p08", + "cs5n3p08", + "cs8n3p08", + "g03n3p04", + "g04n3p04", + "g05n3p04", + "g07n3p04", + "g10n3p04", + "g25n3p04", + "s32i3p04", + "s32n3p04", + "tp0n3p08", +}; + +const string gifsuite_files_read_single[] = { + "basi3p01", + "basi3p02", + "basi3p04", + "basn3p01", + "basn3p02", + "basn3p04", + "ccwn3p08", + "cdfn2c08", + "cdhn2c08", + "cdsn2c08", + "cdun2c08", + "ch1n3p04", + "cs3n3p08", + "cs5n2c08", + "cs5n3p08", + "cs8n2c08", + "cs8n3p08", + "exif2c08", + "g03n2c08", + "g03n3p04", + "g04n2c08", + "g04n3p04", + "g05n2c08", + "g05n3p04", + "g07n2c08", + "g07n3p04", + "g10n2c08", + "g10n3p04" +}; + +const string gifsuite_files_read_write_suite[]={ + "g25n2c08", + "g25n3p04", + "s01i3p01", + "s01n3p01", + "s02i3p01", + "s02n3p01", + "s03i3p01", + "s03n3p01", + "s04i3p01", + "s04n3p01", + "s05i3p02", + "s05n3p02", + "s06i3p02", + "s06n3p02", + "s07i3p02", + "s07n3p02", + "s08i3p02", + "s08n3p02", + "s09i3p02", + "s09n3p02", + "s32i3p04", + "s32n3p04", + "s33i3p04", + "s33n3p04", + "s34i3p04", + "s34n3p04", + "s35i3p04", + "s35n3p04", + "s36i3p04", + "s36n3p04", + "s37i3p04", + "s37n3p04", + "s38i3p04", + "s38n3p04", + "s39i3p04", + "s39n3p04", + "s40i3p04", + "s40n3p04", + "tp0n3p08", +}; + +const std::pair gifsuite_files_bgra[]={ + make_pair("gif_bgra1",53287), + make_pair("gif_bgra2",52651), + make_pair("gif_bgra3",54809), + make_pair("gif_bgra4",57562), + make_pair("gif_bgra5",56733), + make_pair("gif_bgra6",52110), +}; + +TEST(Imgcodecs_Gif, read_gif_multi) +{ + const string root = cvtest::TS::ptr()->get_data_path(); + const string filename = root + "gifsuite/gif_multi.gif"; + vector img_vec_8UC4; + ASSERT_NO_THROW(cv::imreadmulti(filename, img_vec_8UC4,0,20,IMREAD_UNCHANGED)); + EXPECT_EQ(img_vec_8UC4.size(), imcount(filename)); + vector img_vec_8UC3; + for(const auto & i : img_vec_8UC4){ + cv::Mat img_tmp; + cvtColor(i,img_tmp,COLOR_BGRA2BGR); + img_vec_8UC3.push_back(img_tmp); + } + const long unsigned int expected_size=20; + EXPECT_EQ(img_vec_8UC3.size(),expected_size); + for(long unsigned int i=0;i Imgcodecs_Gif_GifSuite_SingleFrame; + +TEST_P(Imgcodecs_Gif_GifSuite_SingleFrame, read_gif_single) +{ + const string root = cvtest::TS::ptr()->get_data_path(); + const string filename = root + "gifsuite/" + GetParam() + ".gif"; + const string png_filename=root + "pngsuite/" + GetParam() + ".png"; + const long unsigned int expected_size = 1; + + EXPECT_EQ(expected_size, imcount(filename)); + cv::Mat img_8UC4; + ASSERT_NO_THROW(img_8UC4 = cv::imread(filename, IMREAD_UNCHANGED)); + ASSERT_FALSE(img_8UC4.empty()); + cv::Mat img_8UC3; + ASSERT_NO_THROW(cvtColor(img_8UC4, img_8UC3, COLOR_BGRA2BGR)); + cv::Mat img_png; + ASSERT_NO_THROW(img_png = cv::imread(png_filename, IMREAD_UNCHANGED)); + ASSERT_FALSE(img_png.empty()); + EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), img_8UC3, img_png); +} + +INSTANTIATE_TEST_CASE_P(/*nothing*/, Imgcodecs_Gif_GifSuite_SingleFrame, + testing::ValuesIn(gifsuite_files_read_single)); + +TEST(Imgcodecs_Gif, read_gif_big){ + const string root = cvtest::TS::ptr()->get_data_path(); + const string gif_filename = root + "gifsuite/gif_big.gif"; + const string png_filename = root + "gifsuite/gif_big.png"; + cv::Mat img_8UC4; + ASSERT_NO_THROW(img_8UC4 = imread(gif_filename, IMREAD_UNCHANGED)); + ASSERT_FALSE(img_8UC4.empty()); + cv::Mat img_8UC3; + const int expected_col=1303; + const int expected_row=1391; + EXPECT_EQ(expected_col, img_8UC4.cols); + EXPECT_EQ(expected_row, img_8UC4.rows); + ASSERT_NO_THROW(cvtColor(img_8UC4, img_8UC3,COLOR_BGRA2BGR)); + EXPECT_EQ(expected_col, img_8UC3.cols); + EXPECT_EQ(expected_row, img_8UC3.rows); + cv::Mat img_png; + ASSERT_NO_THROW(img_png=imread(png_filename, IMREAD_UNCHANGED)); + ASSERT_FALSE(img_png.empty()); + cv::Mat img_png_8UC3; + ASSERT_NO_THROW(cvtColor(img_png,img_png_8UC3, COLOR_BGRA2BGR)); + EXPECT_EQ(img_8UC3.size, img_png_8UC3.size); + EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), img_8UC3, img_png_8UC3); +} + +typedef testing::TestWithParam> Imgcodecs_Gif_GifSuite_SingleFrame_BGRA; + +TEST_P(Imgcodecs_Gif_GifSuite_SingleFrame_BGRA, read_gif_single_bgra){ + const string root = cvtest::TS::ptr()->get_data_path(); + const string gif_filename = root + "gifsuite/" + GetParam().first + ".gif"; + const string png_filename = root + "gifsuite/" + GetParam().first + ".png"; + cv::Mat gif_img; + cv::Mat png_img; + ASSERT_NO_THROW(gif_img = cv::imread(gif_filename, IMREAD_UNCHANGED)); + ASSERT_FALSE(gif_img.empty()); + ASSERT_NO_THROW(png_img = cv::imread(png_filename, IMREAD_UNCHANGED)); + ASSERT_FALSE(png_img.empty()); + EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), gif_img, png_img); + int transparent_count = 0; + for(int i=0; i(i,j); + if((int)(pixel1[3]) == 0){ + transparent_count++; + } + } + } + EXPECT_EQ(transparent_count,GetParam().second); +} + +INSTANTIATE_TEST_CASE_P(/*nothing*/, Imgcodecs_Gif_GifSuite_SingleFrame_BGRA , + testing::ValuesIn(gifsuite_files_bgra)); + +TEST(Imgcodecs_Gif,read_gif_multi_bgra){ + const string root = cvtest::TS::ptr()->get_data_path(); + const string gif_filename = root + "gifsuite/gif_multi_bgra.gif"; + vector img_vec; + ASSERT_NO_THROW(cv::imreadmulti(gif_filename, img_vec, IMREAD_UNCHANGED)); + EXPECT_EQ(imcount(gif_filename), img_vec.size()); + const int fixed_transparent_count = 53211; + for(auto & frame_count : img_vec){ + int transparent_count=0; + for(int i=0; i(i,j); + if((int)(pixel1[3]) == 0){ + transparent_count++; + } + } + } + EXPECT_EQ(fixed_transparent_count,transparent_count); + } +} + +TEST(Imgcodecs_Gif, read_gif_special){ + const string root = cvtest::TS::ptr()->get_data_path(); + const string gif_filename1 = root + "gifsuite/special1.gif"; + const string png_filename1 = root + "gifsuite/special1.png"; + const string gif_filename2 = root + "gifsuite/special2.gif"; + const string png_filename2 = root + "gifsuite/special2.png"; + cv::Mat gif_img1; + ASSERT_NO_THROW(gif_img1 = cv::imread(gif_filename1,IMREAD_UNCHANGED)); + ASSERT_FALSE(gif_img1.empty()); + cv::Mat png_img1; + ASSERT_NO_THROW(png_img1 = cv::imread(png_filename1,IMREAD_UNCHANGED)); + ASSERT_FALSE(png_img1.empty()); + EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), gif_img1, png_img1); + cv::Mat gif_img2; + ASSERT_NO_THROW(gif_img2 = cv::imread(gif_filename2,IMREAD_UNCHANGED)); + ASSERT_FALSE(gif_img2.empty()); + cv::Mat png_img2; + ASSERT_NO_THROW(png_img2 = cv::imread(png_filename2,IMREAD_UNCHANGED)); + ASSERT_FALSE(png_img2.empty()); + EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), gif_img2, png_img2); +} + +TEST(Imgcodecs_Gif,write_gif_flags){ + const string root = cvtest::TS::ptr()->get_data_path(); + const string png_filename = root + "gifsuite/special1.png"; + vector buff; + const int expected_rows=611; + const int expected_cols=293; + Mat img_gt = Mat::ones(expected_rows, expected_cols, CV_8UC1); + vector param; + param.push_back(IMWRITE_GIF_QUALITY); + param.push_back(7); + param.push_back(IMWRITE_GIF_DITHER); + param.push_back(2); + EXPECT_NO_THROW(imencode(".png", img_gt, buff, param)); + Mat img; + EXPECT_NO_THROW(img = imdecode(buff, IMREAD_ANYDEPTH)); // hang + EXPECT_FALSE(img.empty()); + EXPECT_EQ(img.cols, expected_cols); + EXPECT_EQ(img.rows, expected_rows); + EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), img, img_gt); +} + +TEST(Imgcodecs_Gif, write_gif_big) { + const string root = cvtest::TS::ptr()->get_data_path(); + const string png_filename = root + "gifsuite/gif_big.png"; + const string gif_filename = cv::tempfile(".png"); + cv::Mat img; + ASSERT_NO_THROW(img = cv::imread(png_filename, IMREAD_UNCHANGED)); + ASSERT_FALSE(img.empty()); + EXPECT_EQ(1303, img.cols); + EXPECT_EQ(1391, img.rows); + ASSERT_NO_THROW(imwrite(gif_filename, img)); + cv::Mat img_gif; + ASSERT_NO_THROW(img_gif = cv::imread(gif_filename, IMREAD_UNCHANGED)); + ASSERT_FALSE(img_gif.empty()); + EXPECT_EQ(1303, img_gif.cols); + EXPECT_EQ(1391, img_gif.rows); + EXPECT_EQ(0, remove(gif_filename.c_str())); +} + +typedef testing::TestWithParam Imgcodecs_Gif_GifSuite_Read_Write_Suite; + +TEST_P(Imgcodecs_Gif_GifSuite_Read_Write_Suite ,read_gif_single) +{ + const string root = cvtest::TS::ptr()->get_data_path(); + const string png_filename = root + "pngsuite/"+GetParam()+".png"; + const string gif_filename = cv::tempfile(".gif"); + cv::Mat img; + ASSERT_NO_THROW(img = cv::imread(png_filename, IMREAD_UNCHANGED)); + ASSERT_FALSE(img.empty()); + vector param; + param.push_back(IMWRITE_GIF_QUALITY); + param.push_back(8); + param.push_back(IMWRITE_GIF_DITHER); + param.push_back(3); + ASSERT_NO_THROW(imwrite(gif_filename, img, param)); + cv::Mat img_gif; + ASSERT_NO_THROW(img_gif = cv::imread(gif_filename, IMREAD_UNCHANGED)); + ASSERT_FALSE(img_gif.empty()); + cv::Mat img_8UC3; + ASSERT_NO_THROW(cv::cvtColor(img_gif, img_8UC3, COLOR_BGRA2BGR)); + EXPECT_PRED_FORMAT2(cvtest::MatComparator(29, 0), img, img_8UC3); + EXPECT_EQ(0, remove(gif_filename.c_str())); +} + +INSTANTIATE_TEST_CASE_P(/*nothing*/, Imgcodecs_Gif_GifSuite_Read_Write_Suite , + testing::ValuesIn(gifsuite_files_read_write_suite)); + +TEST(Imgcodecs_Gif, write_gif_multi) { + const string root = cvtest::TS::ptr()->get_data_path(); + const string gif_filename = cv::tempfile(".gif"); + vector img_vec; + for (long unsigned int i = 0; i < 20; i++) { + const string png_filename = root + "pngsuite/" + gifsuite_files_multi[i] + ".png"; + cv::Mat img; + ASSERT_NO_THROW(img = cv::imread(png_filename, IMREAD_UNCHANGED)); + ASSERT_FALSE(img.empty()); + img_vec.push_back(img); + } + vector param; + param.push_back(IMWRITE_GIF_QUALITY); + param.push_back(8); + param.push_back(IMWRITE_GIF_DITHER); + param.push_back(3); + ASSERT_NO_THROW(cv::imwritemulti(gif_filename, img_vec, param)); + vector img_vec_gif; + ASSERT_NO_THROW(cv::imreadmulti(gif_filename, img_vec_gif)); + EXPECT_EQ(img_vec.size(), img_vec_gif.size()); + for (long unsigned int i = 0; i < img_vec.size(); i++) { + cv::Mat img_8UC3; + ASSERT_NO_THROW(cv::cvtColor(img_vec_gif[i], img_8UC3, COLOR_BGRA2BGR)); + EXPECT_PRED_FORMAT2(cvtest::MatComparator(29, 0), img_vec[i], img_8UC3); + } + EXPECT_EQ(0, remove(gif_filename.c_str())); +} + +}//opencv_test +}//namespace + +#endif From acdb707ba4a336657a320c892e1682a456859551 Mon Sep 17 00:00:00 2001 From: shyama7004 Date: Sun, 8 Dec 2024 13:41:48 +0530 Subject: [PATCH 015/102] Fix typo: rename 'search_widow_size' to 'search_window_size' --- modules/photo/perf/perf_cuda.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/modules/photo/perf/perf_cuda.cpp b/modules/photo/perf/perf_cuda.cpp index b179129962..c95aaa469c 100644 --- a/modules/photo/perf/perf_cuda.cpp +++ b/modules/photo/perf/perf_cuda.cpp @@ -71,7 +71,7 @@ PERF_TEST_P(Sz_Depth_Cn_WinSz_BlockSz, CUDA_NonLocalMeans, const cv::Size size = GET_PARAM(0); const int depth = GET_PARAM(1); const int channels = GET_PARAM(2); - const int search_widow_size = GET_PARAM(3); + const int search_window_size = GET_PARAM(3); const int block_size = GET_PARAM(4); const float h = 10; @@ -87,7 +87,7 @@ PERF_TEST_P(Sz_Depth_Cn_WinSz_BlockSz, CUDA_NonLocalMeans, const cv::cuda::GpuMat d_src(src); cv::cuda::GpuMat dst; - TEST_CYCLE() cv::cuda::nonLocalMeans(d_src, dst, h, search_widow_size, block_size, borderMode); + TEST_CYCLE() cv::cuda::nonLocalMeans(d_src, dst, h, search_window_size, block_size, borderMode); CUDA_SANITY_CHECK(dst); } @@ -114,7 +114,7 @@ PERF_TEST_P(Sz_Depth_Cn_WinSz_BlockSz, CUDA_FastNonLocalMeans, const cv::Size size = GET_PARAM(0); const int depth = GET_PARAM(1); - const int search_widow_size = GET_PARAM(2); + const int search_window_size = GET_PARAM(2); const int block_size = GET_PARAM(3); const float h = 10; @@ -128,7 +128,7 @@ PERF_TEST_P(Sz_Depth_Cn_WinSz_BlockSz, CUDA_FastNonLocalMeans, const cv::cuda::GpuMat d_src(src); cv::cuda::GpuMat dst; - TEST_CYCLE() cv::cuda::fastNlMeansDenoising(d_src, dst, h, search_widow_size, block_size); + TEST_CYCLE() cv::cuda::fastNlMeansDenoising(d_src, dst, h, search_window_size, block_size); CUDA_SANITY_CHECK(dst); } @@ -136,7 +136,7 @@ PERF_TEST_P(Sz_Depth_Cn_WinSz_BlockSz, CUDA_FastNonLocalMeans, { cv::Mat dst; - TEST_CYCLE() cv::fastNlMeansDenoising(src, dst, h, block_size, search_widow_size); + TEST_CYCLE() cv::fastNlMeansDenoising(src, dst, h, block_size, search_window_size); CPU_SANITY_CHECK(dst); } @@ -157,7 +157,7 @@ PERF_TEST_P(Sz_Depth_WinSz_BlockSz, CUDA_FastNonLocalMeansColored, const cv::Size size = GET_PARAM(0); const int depth = GET_PARAM(1); - const int search_widow_size = GET_PARAM(2); + const int search_window_size = GET_PARAM(2); const int block_size = GET_PARAM(3); const float h = 10; @@ -171,7 +171,7 @@ PERF_TEST_P(Sz_Depth_WinSz_BlockSz, CUDA_FastNonLocalMeansColored, const cv::cuda::GpuMat d_src(src); cv::cuda::GpuMat dst; - TEST_CYCLE() cv::cuda::fastNlMeansDenoisingColored(d_src, dst, h, h, search_widow_size, block_size); + TEST_CYCLE() cv::cuda::fastNlMeansDenoisingColored(d_src, dst, h, h, search_window_size, block_size); CUDA_SANITY_CHECK(dst); } @@ -179,7 +179,7 @@ PERF_TEST_P(Sz_Depth_WinSz_BlockSz, CUDA_FastNonLocalMeansColored, { cv::Mat dst; - TEST_CYCLE() cv::fastNlMeansDenoisingColored(src, dst, h, h, block_size, search_widow_size); + TEST_CYCLE() cv::fastNlMeansDenoisingColored(src, dst, h, h, block_size, search_window_size); CPU_SANITY_CHECK(dst); } From 1db93911aeb65599f22db47d5d39f75bc94a821d Mon Sep 17 00:00:00 2001 From: Yannis Guyon Date: Mon, 9 Dec 2024 10:24:01 +0100 Subject: [PATCH 016/102] Support string_view in caffe_importer An upcoming change in Protobuf will change the return types of various methods like Descriptor::name() and Message::GetTypeName() from const std::string& or std::string to absl::string_view. This CL fixes users of those methods to work both before and after the change. --- modules/dnn/src/caffe/caffe_importer.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/dnn/src/caffe/caffe_importer.cpp b/modules/dnn/src/caffe/caffe_importer.cpp index 50e1fbe93f..fc6cbcdd28 100644 --- a/modules/dnn/src/caffe/caffe_importer.cpp +++ b/modules/dnn/src/caffe/caffe_importer.cpp @@ -126,8 +126,8 @@ public: const google::protobuf::UnknownField& field = unknownFields.field(i); CV_Assert(field.type() == google::protobuf::UnknownField::TYPE_GROUP); CV_CheckGE(field.group().field_count(), 2, "UnknownField should have at least 2 items: name and value"); - std::string fieldName = field.group().field(0).length_delimited(); - std::string fieldValue = field.group().field(1).length_delimited(); + std::string fieldName(field.group().field(0).length_delimited()); + std::string fieldValue(field.group().field(1).length_delimited()); params.set(fieldName, fieldValue); } } @@ -137,7 +137,7 @@ public: const Reflection *refl = msg.GetReflection(); int type = field->cpp_type(); bool isRepeated = field->is_repeated(); - const std::string &name = field->name(); + const std::string name(field->name()); #define SET_UP_FILED(getter, arrayConstr, gtype) \ if (isRepeated) { \ @@ -189,7 +189,7 @@ public: params.set(name, DictValue::arrayString(buf.begin(), size)); } else { - params.set(name, refl->GetEnum(msg, field)->name()); + params.set(name, std::string(refl->GetEnum(msg, field)->name())); } break; default: @@ -212,7 +212,7 @@ public: { const FieldDescriptor *fd = msgDesc->field(fieldId); - if (!isInternal && !ends_with_param(fd->name())) + if (!isInternal && !ends_with_param(std::string(fd->name()))) continue; const google::protobuf::UnknownFieldSet& unknownFields = msgRefl->GetUnknownFields(msg); From 3a8d7ec75a4c8fd485cd3cf17b973e8cce60e34b Mon Sep 17 00:00:00 2001 From: MurtazaSaherwala <149921079+MurtazaSaherwala@users.noreply.github.com> Date: Mon, 9 Dec 2024 18:53:07 +0530 Subject: [PATCH 017/102] Merge pull request #26524 from MurtazaSaherwala:DocumentationUpdation Updated trackbar callback function and improved documentation #26524 This Fixes #26467 Description: This pull request improve the OpenCV documentation regarding the Trackbar functionality. The current documentation does not provide clear guidance on certain aspects, such as handling the value pointer deprecation and utilizing callback arguments in C. This update addresses those gaps and provides an updated example for better clarity. Changes: Updated Documentation: Clarified the usage of the value pointer and explained how to pass an initial value, since the value pointer is deprecated. Added more detailed explanations about callback arguments in C, ensuring that users understand how to access and use them in Trackbar callbacks. Added a note on how to properly handle initial value passing without relying on the deprecated value pointer. Updated Tutorial Example: Renamed and used callback function parameters to make them more understandable. Included a demonstration on how to utilize userdata in the callback function. Additional Notes: Removed reliance on the value pointer for updating trackbar values. Users are now encouraged to use other mechanisms as per the current implementation to avoid the runtime warning. ### 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 --- apps/createsamples/utility.cpp | 3 - modules/highgui/include/opencv2/highgui.hpp | 24 ++++---- .../HighGUI/AddingImagesTrackbar.cpp | 59 ++++++++++--------- 3 files changed, 44 insertions(+), 42 deletions(-) diff --git a/apps/createsamples/utility.cpp b/apps/createsamples/utility.cpp index b57d3fcb81..87ba4eac5a 100644 --- a/apps/createsamples/utility.cpp +++ b/apps/createsamples/utility.cpp @@ -766,7 +766,6 @@ CvBackgroundData* icvCreateBackgroundData( const char* filename, Size winsize ) } if( count > 0 ) { - //rewind( input ); fseek( input, 0, SEEK_SET ); datasize += sizeof( *data ) + sizeof( char* ) * count; data = (CvBackgroundData*) fastMalloc( datasize ); @@ -872,8 +871,6 @@ void icvGetNextFromBackgroundData( CvBackgroundData* data, reader->src = img; - //reader->offset.x = round % data->winsize.width; - //reader->offset.y = round / data->winsize.width; reader->offset = offset; reader->point = reader->offset; reader->scale = MAX( diff --git a/modules/highgui/include/opencv2/highgui.hpp b/modules/highgui/include/opencv2/highgui.hpp index 35b64bceae..dec9af5b46 100644 --- a/modules/highgui/include/opencv2/highgui.hpp +++ b/modules/highgui/include/opencv2/highgui.hpp @@ -531,16 +531,20 @@ control panel. Clicking the label of each trackbar enables editing the trackbar values manually. @param trackbarname Name of the created trackbar. -@param winname Name of the window that will be used as a parent of the created trackbar. -@param value Optional pointer to an integer variable whose value reflects the position of the -slider. Upon creation, the slider position is defined by this variable. -@param count Maximal position of the slider. The minimal position is always 0. -@param onChange Pointer to the function to be called every time the slider changes position. This -function should be prototyped as void Foo(int,void\*); , where the first parameter is the trackbar -position and the second parameter is the user data (see the next parameter). If the callback is -the NULL pointer, no callbacks are called, but only value is updated. -@param userdata User data that is passed as is to the callback. It can be used to handle trackbar -events without using global variables. +@param winname Name of the window that will contain the trackbar. +@param value Pointer to the integer value that will be changed by the trackbar. +Pass `nullptr` if the value pointer is not used. In this case, manually handle +the trackbar position in the callback function. +@param count Maximum position of the trackbar. +@param onChange Pointer to the function to be called every time the slider changes position. +This function should have the prototype void Foo(int, void\*);, where the first parameter is +the trackbar position, and the second parameter is the user data (see the next parameter). +If the callback is a nullptr, no callbacks are called, but the trackbar's value will still be +updated automatically. +@param userdata Optional user data that is passed to the callback. +@note If the `value` pointer is `nullptr`, the trackbar position must be manually managed. +Call the callback function manually with the desired initial value to avoid runtime warnings. +@see #tutorial_trackbar */ CV_EXPORTS int createTrackbar(const String& trackbarname, const String& winname, int* value, int count, diff --git a/samples/cpp/tutorial_code/HighGUI/AddingImagesTrackbar.cpp b/samples/cpp/tutorial_code/HighGUI/AddingImagesTrackbar.cpp index 82bbb3743a..3650b15dea 100644 --- a/samples/cpp/tutorial_code/HighGUI/AddingImagesTrackbar.cpp +++ b/samples/cpp/tutorial_code/HighGUI/AddingImagesTrackbar.cpp @@ -27,12 +27,12 @@ Mat dst; * @function on_trackbar * @brief Callback for trackbar */ -static void on_trackbar( int, void* ) -{ - alpha = (double) alpha_slider/alpha_slider_max ; - beta = ( 1.0 - alpha ); - addWeighted( src1, alpha, src2, beta, 0.0, dst); - imshow( "Linear Blend", dst ); +static void on_trackbar(int pos, void* userdata) { + (void) userdata; + alpha = (double)pos / alpha_slider_max; + beta = (1.0 - alpha); + addWeighted(src1, alpha, src2, beta, 0.0, dst); + imshow("Linear Blend", dst); } //![on_trackbar] @@ -40,34 +40,35 @@ static void on_trackbar( int, void* ) * @function main * @brief Main function */ -int main( void ) +int main(void) { - //![load] - /// Read images ( both have to be of the same size and type ) - src1 = imread( samples::findFile("LinuxLogo.jpg") ); - src2 = imread( samples::findFile("WindowsLogo.jpg") ); - //![load] + //![load] + /// Read images (both must be of the same size and type) + src1 = imread(samples::findFile("LinuxLogo.jpg")); + src2 = imread(samples::findFile("WindowsLogo.jpg")); + //![load] - if( src1.empty() ) { cout << "Error loading src1 \n"; return -1; } - if( src2.empty() ) { cout << "Error loading src2 \n"; return -1; } + if (src1.empty()) { cout << "Error loading src1 \n"; return -1; } + if (src2.empty()) { cout << "Error loading src2 \n"; return -1; } - /// Initialize values - alpha_slider = 0; + // Initialize trackbar value + alpha_slider = 0; - //![window] - namedWindow("Linear Blend", WINDOW_AUTOSIZE); // Create Window - //![window] + //![window] + namedWindow("Linear Blend", WINDOW_AUTOSIZE); //Create Window + //![window] - //![create_trackbar] - char TrackbarName[50]; - snprintf( TrackbarName, sizeof(TrackbarName), "Alpha x %d", alpha_slider_max ); - createTrackbar( TrackbarName, "Linear Blend", &alpha_slider, alpha_slider_max, on_trackbar ); - //![create_trackbar] + //![create_trackbar] + char TrackbarName[50]; + snprintf(TrackbarName, sizeof(TrackbarName), "Alpha x %d", alpha_slider_max); + // Example userdata: Pass a pointer to an integer as userdata + createTrackbar(TrackbarName, "Linear Blend", &alpha_slider, alpha_slider_max, on_trackbar); + //![create_trackbar] - /// Show some stuff - on_trackbar( alpha_slider, 0 ); + /// Show initial result + on_trackbar(alpha_slider, nullptr); - /// Wait until user press some key - waitKey(0); - return 0; + /// Wait for user input + waitKey(0); + return 0; } From 4ade7931e145cdb71f61cf18f149f94ce911776a Mon Sep 17 00:00:00 2001 From: Maksim Shabunin Date: Mon, 9 Dec 2024 16:53:26 +0300 Subject: [PATCH 018/102] doc: upgraded for compatibility with doxygen 1.12 --- doc/CMakeLists.txt | 4 +- doc/Doxyfile.in | 72 +- doc/footer.html | 11 +- doc/header.html | 48 +- doc/stylesheet.css | 2290 ++++++++++++++++++- modules/core/include/opencv2/core.hpp | 4 - modules/highgui/include/opencv2/highgui.hpp | 2 +- 7 files changed, 2374 insertions(+), 57 deletions(-) diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index 411b77808a..c9b3ea2742 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -16,8 +16,8 @@ endif() find_package(Doxygen) if(DOXYGEN_FOUND) - if (DOXYGEN_VERSION VERSION_LESS 1.9.8) - message(WARNING "Found doxygen ${DOXYGEN_VERSION}, version 1.9.8 is used for testing, there is " + if (DOXYGEN_VERSION VERSION_LESS 1.12) + message(WARNING "Found doxygen ${DOXYGEN_VERSION}, version 1.12 is used for testing, there is " "a chance your documentation will look different or have some limitations.") endif() add_custom_target(doxygen) diff --git a/doc/Doxyfile.in b/doc/Doxyfile.in index d757c6f92e..a8a3d83888 100644 --- a/doc/Doxyfile.in +++ b/doc/Doxyfile.in @@ -1,9 +1,14 @@ +# Doxyfile 1.12.0 + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- DOXYFILE_ENCODING = UTF-8 PROJECT_NAME = OpenCV PROJECT_NUMBER = @OPENCV_VERSION@ PROJECT_BRIEF = "Open Source Computer Vision" PROJECT_LOGO = @CMAKE_CURRENT_SOURCE_DIR@/opencv-logo-small.png -#PROJECT_ICON = +PROJECT_ICON = @CMAKE_CURRENT_SOURCE_DIR@/opencv.ico OUTPUT_DIRECTORY = @CMAKE_DOXYGEN_OUTPUT_PATH@ CREATE_SUBDIRS = YES CREATE_SUBDIRS_LEVEL = 8 @@ -25,7 +30,8 @@ ABBREVIATE_BRIEF = "The $name class" \ ALWAYS_DETAILED_SEC = NO INLINE_INHERITED_MEMB = NO FULL_PATH_NAMES = YES -STRIP_FROM_PATH = @CMAKE_SOURCE_DIR@/modules @CMAKE_DOXYGEN_INCLUDE_ROOTS@ +STRIP_FROM_PATH = @CMAKE_SOURCE_DIR@/modules \ + @CMAKE_DOXYGEN_INCLUDE_ROOTS@ STRIP_FROM_INC_PATH = @CMAKE_DOXYGEN_INCLUDE_ROOTS@ SHORT_NAMES = NO JAVADOC_AUTOBRIEF = NO @@ -67,6 +73,9 @@ TYPEDEF_HIDES_STRUCT = YES LOOKUP_CACHE_SIZE = 0 NUM_PROC_THREADS = 1 TIMESTAMP = YES +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- EXTRACT_ALL = YES EXTRACT_PRIVATE = NO EXTRACT_PRIV_VIRTUAL = NO @@ -107,6 +116,10 @@ SHOW_NAMESPACES = YES FILE_VERSION_FILTER = LAYOUT_FILE = @CMAKE_DOXYGEN_LAYOUT@ CITE_BIB_FILES = @CMAKE_EXTRA_BIB_FILES@ +EXTERNAL_TOOL_PATH = +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- QUIET = YES WARNINGS = YES WARN_IF_UNDOCUMENTED = YES @@ -118,6 +131,9 @@ WARN_AS_ERROR = NO WARN_FORMAT = "$file:$line: $text" WARN_LINE_FORMAT = "at line $line of file $file" WARN_LOGFILE = +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- INPUT = @CMAKE_DOXYGEN_INPUT_LIST@ INPUT_ENCODING = UTF-8 INPUT_FILE_ENCODING = @@ -136,6 +152,10 @@ FILTER_PATTERNS = FILTER_SOURCE_FILES = NO FILTER_SOURCE_PATTERNS = USE_MDFILE_AS_MAINPAGE = +FORTRAN_COMMENT_AFTER = 72 +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- SOURCE_BROWSER = NO INLINE_SOURCES = NO STRIP_CODE_COMMENTS = YES @@ -149,8 +169,14 @@ CLANG_ASSISTED_PARSING = NO CLANG_ADD_INC_PATHS = YES CLANG_OPTIONS = CLANG_DATABASE_PATH = +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- ALPHABETICAL_INDEX = YES IGNORE_PREFIX = +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- GENERATE_HTML = YES HTML_OUTPUT = html HTML_FILE_EXTENSION = .html @@ -166,8 +192,8 @@ HTML_COLORSTYLE_GAMMA = 80 HTML_DYNAMIC_MENUS = YES HTML_DYNAMIC_SECTIONS = NO HTML_CODE_FOLDING = YES -#HTML_COPY_CLIPBOARD = YES -#HTML_PROJECT_COOKIE = +HTML_COPY_CLIPBOARD = YES +HTML_PROJECT_COOKIE = HTML_INDEX_NUM_ENTRIES = 100 GENERATE_DOCSET = NO DOCSET_FEEDNAME = "Doxygen generated docs" @@ -197,6 +223,7 @@ DISABLE_INDEX = NO GENERATE_TREEVIEW = NO FULL_SIDEBAR = NO ENUM_VALUES_PER_LINE = 1 +SHOW_ENUM_VALUES = NO TREEVIEW_WIDTH = 250 EXT_LINKS_IN_WINDOW = YES OBFUSCATE_EMAILS = YES @@ -216,6 +243,9 @@ SEARCHENGINE_URL = SEARCHDATA_FILE = searchdata.xml EXTERNAL_SEARCH_ID = EXTRA_SEARCH_MAPPINGS = +#--------------------------------------------------------------------------- +# Configuration options related to the LaTeX output +#--------------------------------------------------------------------------- GENERATE_LATEX = NO LATEX_OUTPUT = latex LATEX_CMD_NAME = latex @@ -234,31 +264,56 @@ LATEX_BATCHMODE = NO LATEX_HIDE_INDICES = NO LATEX_BIB_STYLE = plain LATEX_EMOJI_DIRECTORY = +#--------------------------------------------------------------------------- +# Configuration options related to the RTF output +#--------------------------------------------------------------------------- GENERATE_RTF = NO RTF_OUTPUT = rtf COMPACT_RTF = NO RTF_HYPERLINKS = NO RTF_STYLESHEET_FILE = RTF_EXTENSIONS_FILE = +RTF_EXTRA_FILES = +#--------------------------------------------------------------------------- +# Configuration options related to the man page output +#--------------------------------------------------------------------------- GENERATE_MAN = NO MAN_OUTPUT = man MAN_EXTENSION = .3 MAN_SUBDIR = MAN_LINKS = NO +#--------------------------------------------------------------------------- +# Configuration options related to the XML output +#--------------------------------------------------------------------------- GENERATE_XML = NO XML_OUTPUT = xml XML_PROGRAMLISTING = YES XML_NS_MEMB_FILE_SCOPE = NO +#--------------------------------------------------------------------------- +# Configuration options related to the DOCBOOK output +#--------------------------------------------------------------------------- GENERATE_DOCBOOK = NO DOCBOOK_OUTPUT = docbook +#--------------------------------------------------------------------------- +# Configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- GENERATE_AUTOGEN_DEF = NO +#--------------------------------------------------------------------------- +# Configuration options related to Sqlite3 output +#--------------------------------------------------------------------------- GENERATE_SQLITE3 = NO SQLITE3_OUTPUT = sqlite3 SQLITE3_RECREATE_DB = YES +#--------------------------------------------------------------------------- +# Configuration options related to the Perl module output +#--------------------------------------------------------------------------- GENERATE_PERLMOD = NO PERLMOD_LATEX = NO PERLMOD_PRETTY = YES PERLMOD_MAKEVAR_PREFIX = +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- ENABLE_PREPROCESSING = YES MACRO_EXPANSION = YES EXPAND_ONLY_PREDEF = NO @@ -307,18 +362,24 @@ PREDEFINED = __cplusplus=1 \ CV_DEPRECATED= EXPAND_AS_DEFINED = SKIP_FUNCTION_MACROS = YES +#--------------------------------------------------------------------------- +# Configuration options related to external references +#--------------------------------------------------------------------------- TAGFILES = GENERATE_TAGFILE = @CMAKE_DOXYGEN_OUTPUT_PATH@/html/opencv.tag ALLEXTERNALS = NO EXTERNAL_GROUPS = YES EXTERNAL_PAGES = YES +#--------------------------------------------------------------------------- +# Configuration options related to diagram generator tools +#--------------------------------------------------------------------------- HIDE_UNDOC_RELATIONS = NO HAVE_DOT = @CMAKECONFIG_HAVE_DOT@ DOT_NUM_THREADS = 0 -DOT_FONTPATH = DOT_COMMON_ATTR = "fontname=Helvetica,fontsize=10" DOT_EDGE_ATTR = "labelfontname=Helvetica,labelfontsize=10" DOT_NODE_ATTR = "shape=box,height=0.2,width=0.4" +DOT_FONTPATH = CLASS_GRAPH = NO COLLABORATION_GRAPH = YES GROUP_GRAPHS = NO @@ -338,6 +399,7 @@ DOT_IMAGE_FORMAT = @CMAKECONFIG_DOT_IMAGE_FORMAT@ INTERACTIVE_SVG = @CMAKECONFIG_INTERACTIVE_SVG@ DOT_PATH = @CMAKECONFIG_DOT_PATH@ DOTFILE_DIRS = +DIA_PATH = DIAFILE_DIRS = PLANTUML_JAR_PATH = PLANTUML_CFG_FILE = diff --git a/doc/footer.html b/doc/footer.html index 8817f301dd..a019a4c007 100644 --- a/doc/footer.html +++ b/doc/footer.html @@ -1,21 +1,18 @@ - + + + + + + + $treeview $search $mathjax +$darkmode $extrastylesheet + + +
+ + +
@@ -26,31 +44,37 @@ $extrastylesheet - + - + - - - + + + + + + + +
-
$projectname -  $projectnumber +
+
$projectname $projectnumber
$projectbrief
+
$projectbrief
$searchbox$searchbox
$searchbox
diff --git a/doc/stylesheet.css b/doc/stylesheet.css index eae05885f7..787ed67db1 100644 --- a/doc/stylesheet.css +++ b/doc/stylesheet.css @@ -1,54 +1,2292 @@ -/* The standard CSS for doxygen 1.8.6 */ +/* The standard CSS for doxygen 1.12.0*/ -body, table, div, p, dl { - font: 400 14px/22px Helvetica, 'Segoe UI', Arial, freesans, sans-serif; - word-wrap: break-word; +html { + /* page base colors */ + --page-background-color: white; + --page-foreground-color: black; + --page-link-color: #3D578C; + --page-visited-link-color: #4665A2; + + /* index */ + --index-odd-item-bg-color: #F8F9FC; + --index-even-item-bg-color: white; + --index-header-color: black; + --index-separator-color: #A0A0A0; + + /* header */ + --header-background-color: #F9FAFC; + --header-separator-color: #C4CFE5; + --header-gradient-image: url('nav_h.png'); + --group-header-separator-color: #879ECB; + --group-header-color: #354C7B; + --inherit-header-color: gray; + + --footer-foreground-color: #2A3D61; + --footer-logo-width: 104px; + --citation-label-color: #334975; + --glow-color: cyan; + + --title-background-color: white; + --title-separator-color: #5373B4; + --directory-separator-color: #9CAFD4; + --separator-color: #4A6AAA; + + --blockquote-background-color: #F7F8FB; + --blockquote-border-color: #9CAFD4; + + --scrollbar-thumb-color: #9CAFD4; + --scrollbar-background-color: #F9FAFC; + + --icon-background-color: #728DC1; + --icon-foreground-color: white; + --icon-doc-image: url('doc.svg'); + --icon-folder-open-image: url('folderopen.svg'); + --icon-folder-closed-image: url('folderclosed.svg'); + + /* brief member declaration list */ + --memdecl-background-color: #F9FAFC; + --memdecl-separator-color: #DEE4F0; + --memdecl-foreground-color: #555; + --memdecl-template-color: #4665A2; + + /* detailed member list */ + --memdef-border-color: #A8B8D9; + --memdef-title-background-color: #E2E8F2; + --memdef-title-gradient-image: url('nav_f.png'); + --memdef-proto-background-color: #DFE5F1; + --memdef-proto-text-color: #253555; + --memdef-proto-text-shadow: 0px 1px 1px rgba(255, 255, 255, 0.9); + --memdef-doc-background-color: white; + --memdef-param-name-color: #602020; + --memdef-template-color: #4665A2; + + /* tables */ + --table-cell-border-color: #2D4068; + --table-header-background-color: #374F7F; + --table-header-foreground-color: #FFFFFF; + + /* labels */ + --label-background-color: #728DC1; + --label-left-top-border-color: #5373B4; + --label-right-bottom-border-color: #C4CFE5; + --label-foreground-color: white; + + /** navigation bar/tree/menu */ + --nav-background-color: #F9FAFC; + --nav-foreground-color: #364D7C; + --nav-gradient-image: url('tab_b.png'); + --nav-gradient-hover-image: url('tab_h.png'); + --nav-gradient-active-image: url('tab_a.png'); + --nav-gradient-active-image-parent: url("../tab_a.png"); + --nav-separator-image: url('tab_s.png'); + --nav-breadcrumb-image: url('bc_s.png'); + --nav-breadcrumb-border-color: #C2CDE4; + --nav-splitbar-image: url('splitbar.png'); + --nav-font-size-level1: 13px; + --nav-font-size-level2: 10px; + --nav-font-size-level3: 9px; + --nav-text-normal-color: #283A5D; + --nav-text-hover-color: white; + --nav-text-active-color: white; + --nav-text-normal-shadow: 0px 1px 1px rgba(255, 255, 255, 0.9); + --nav-text-hover-shadow: 0px 1px 1px rgba(0, 0, 0, 1.0); + --nav-text-active-shadow: 0px 1px 1px rgba(0, 0, 0, 1.0); + --nav-menu-button-color: #364D7C; + --nav-menu-background-color: white; + --nav-menu-foreground-color: #555555; + --nav-menu-toggle-color: rgba(255, 255, 255, 0.5); + --nav-arrow-color: #9CAFD4; + --nav-arrow-selected-color: #9CAFD4; + + /* table of contents */ + --toc-background-color: #F4F6FA; + --toc-border-color: #D8DFEE; + --toc-header-color: #4665A2; + --toc-down-arrow-image: url("data:image/svg+xml;utf8,&%238595;"); + + /** search field */ + --search-background-color: white; + --search-foreground-color: #909090; + --search-magnification-image: url('mag.svg'); + --search-magnification-select-image: url('mag_sel.svg'); + --search-active-color: black; + --search-filter-background-color: #F9FAFC; + --search-filter-foreground-color: black; + --search-filter-border-color: #90A5CE; + --search-filter-highlight-text-color: white; + --search-filter-highlight-bg-color: #3D578C; + --search-results-foreground-color: #425E97; + --search-results-background-color: #EEF1F7; + --search-results-border-color: black; + --search-box-shadow: inset 0.5px 0.5px 3px 0px #555; + + /** code fragments */ + --code-keyword-color: #008000; + --code-type-keyword-color: #604020; + --code-flow-keyword-color: #E08000; + --code-comment-color: #800000; + --code-preprocessor-color: #806020; + --code-string-literal-color: #002080; + --code-char-literal-color: #008080; + --code-xml-cdata-color: black; + --code-vhdl-digit-color: #FF00FF; + --code-vhdl-char-color: #000000; + --code-vhdl-keyword-color: #700070; + --code-vhdl-logic-color: #FF0000; + --code-link-color: #4665A2; + --code-external-link-color: #4665A2; + --fragment-foreground-color: black; + --fragment-background-color: #FBFCFD; + --fragment-border-color: #C4CFE5; + --fragment-lineno-border-color: #00FF00; + --fragment-lineno-background-color: #E8E8E8; + --fragment-lineno-foreground-color: black; + --fragment-lineno-link-fg-color: #4665A2; + --fragment-lineno-link-bg-color: #D8D8D8; + --fragment-lineno-link-hover-fg-color: #4665A2; + --fragment-lineno-link-hover-bg-color: #C8C8C8; + --fragment-copy-ok-color: #2EC82E; + --tooltip-foreground-color: black; + --tooltip-background-color: white; + --tooltip-border-color: gray; + --tooltip-doc-color: grey; + --tooltip-declaration-color: #006318; + --tooltip-link-color: #4665A2; + --tooltip-shadow: 1px 1px 7px gray; + --fold-line-color: #808080; + --fold-minus-image: url('minus.svg'); + --fold-plus-image: url('plus.svg'); + --fold-minus-image-relpath: url('../../minus.svg'); + --fold-plus-image-relpath: url('../../plus.svg'); + + /** font-family */ + --font-family-normal: Helvetica, 'Segoe UI', Arial, freesans, sans-serif; + --font-family-monospace: 'SFMono-Regular', Consolas, "Liberation Mono", Menlo, Courier, monospace; + --font-family-nav: 'Lucida Grande', Geneva, Helvetica, Arial, sans-serif; + --font-family-title: Tahoma, Arial, sans-serif; + --font-family-toc: Verdana, 'DejaVu Sans', Geneva, sans-serif; + --font-family-search: Arial, Verdana, sans-serif; + --font-family-icon: Arial, Helvetica; + --font-family-tooltip: Helvetica, 'Segoe UI', Arial, freesans, sans-serif; + + /** special sections */ + --warning-color-bg: #f8d1cc; + --warning-color-hl: #b61825; + --warning-color-text: #75070f; + --note-color-bg: #faf3d8; + --note-color-hl: #f3a600; + --note-color-text: #5f4204; + --todo-color-bg: #e4f3ff; + --todo-color-hl: #1879C4; + --todo-color-text: #274a5c; + --test-color-bg: #e8e8ff; + --test-color-hl: #3939C4; + --test-color-text: #1a1a5c; + --deprecated-color-bg: #ecf0f3; + --deprecated-color-hl: #5b6269; + --deprecated-color-text: #43454a; + --bug-color-bg: #e4dafd; + --bug-color-hl: #5b2bdd; + --bug-color-text: #2a0d72; + --invariant-color-bg: #d8f1e3; + --invariant-color-hl: #44b86f; + --invariant-color-text: #265532; } -code { - font-size: 85%; - font-family: "SFMono-Regular",Consolas,"Liberation Mono",Menlo,Courier,monospace; - white-space: pre-wrap; - padding: 1px 5px; - background-color: rgb(223, 229, 241); - vertical-align: baseline; +@media (prefers-color-scheme: dark) { + html:not(.dark-mode) { + color-scheme: dark; + + /* page base colors */ + --page-background-color: black; + --page-foreground-color: #C9D1D9; + --page-link-color: #90A5CE; + --page-visited-link-color: #A3B4D7; + + /* index */ + --index-odd-item-bg-color: #0B101A; + --index-even-item-bg-color: black; + --index-header-color: #C4CFE5; + --index-separator-color: #334975; + + /* header */ + --header-background-color: #070B11; + --header-separator-color: #141C2E; + --header-gradient-image: url('nav_hd.png'); + --group-header-separator-color: #283A5D; + --group-header-color: #90A5CE; + --inherit-header-color: #A0A0A0; + + --footer-foreground-color: #5B7AB7; + --footer-logo-width: 60px; + --citation-label-color: #90A5CE; + --glow-color: cyan; + + --title-background-color: #090D16; + --title-separator-color: #354C79; + --directory-separator-color: #283A5D; + --separator-color: #283A5D; + + --blockquote-background-color: #101826; + --blockquote-border-color: #283A5D; + + --scrollbar-thumb-color: #283A5D; + --scrollbar-background-color: #070B11; + + --icon-background-color: #334975; + --icon-foreground-color: #C4CFE5; + --icon-doc-image: url('docd.svg'); + --icon-folder-open-image: url('folderopend.svg'); + --icon-folder-closed-image: url('folderclosedd.svg'); + + /* brief member declaration list */ + --memdecl-background-color: #0B101A; + --memdecl-separator-color: #2C3F65; + --memdecl-foreground-color: #BBB; + --memdecl-template-color: #7C95C6; + + /* detailed member list */ + --memdef-border-color: #233250; + --memdef-title-background-color: #1B2840; + --memdef-title-gradient-image: url('nav_fd.png'); + --memdef-proto-background-color: #19243A; + --memdef-proto-text-color: #9DB0D4; + --memdef-proto-text-shadow: 0px 1px 1px rgba(0, 0, 0, 0.9); + --memdef-doc-background-color: black; + --memdef-param-name-color: #D28757; + --memdef-template-color: #7C95C6; + + /* tables */ + --table-cell-border-color: #283A5D; + --table-header-background-color: #283A5D; + --table-header-foreground-color: #C4CFE5; + + /* labels */ + --label-background-color: #354C7B; + --label-left-top-border-color: #4665A2; + --label-right-bottom-border-color: #283A5D; + --label-foreground-color: #CCCCCC; + + /** navigation bar/tree/menu */ + --nav-background-color: #101826; + --nav-foreground-color: #364D7C; + --nav-gradient-image: url('tab_bd.png'); + --nav-gradient-hover-image: url('tab_hd.png'); + --nav-gradient-active-image: url('tab_ad.png'); + --nav-gradient-active-image-parent: url("../tab_ad.png"); + --nav-separator-image: url('tab_sd.png'); + --nav-breadcrumb-image: url('bc_sd.png'); + --nav-breadcrumb-border-color: #2A3D61; + --nav-splitbar-image: url('splitbard.png'); + --nav-font-size-level1: 13px; + --nav-font-size-level2: 10px; + --nav-font-size-level3: 9px; + --nav-text-normal-color: #B6C4DF; + --nav-text-hover-color: #DCE2EF; + --nav-text-active-color: #DCE2EF; + --nav-text-normal-shadow: 0px 1px 1px black; + --nav-text-hover-shadow: 0px 1px 1px rgba(0, 0, 0, 1.0); + --nav-text-active-shadow: 0px 1px 1px rgba(0, 0, 0, 1.0); + --nav-menu-button-color: #B6C4DF; + --nav-menu-background-color: #05070C; + --nav-menu-foreground-color: #BBBBBB; + --nav-menu-toggle-color: rgba(255, 255, 255, 0.2); + --nav-arrow-color: #334975; + --nav-arrow-selected-color: #90A5CE; + + /* table of contents */ + --toc-background-color: #151E30; + --toc-border-color: #202E4A; + --toc-header-color: #A3B4D7; + --toc-down-arrow-image: url("data:image/svg+xml;utf8,&%238595;"); + + /** search field */ + --search-background-color: black; + --search-foreground-color: #C5C5C5; + --search-magnification-image: url('mag_d.svg'); + --search-magnification-select-image: url('mag_seld.svg'); + --search-active-color: #C5C5C5; + --search-filter-background-color: #101826; + --search-filter-foreground-color: #90A5CE; + --search-filter-border-color: #7C95C6; + --search-filter-highlight-text-color: #BCC9E2; + --search-filter-highlight-bg-color: #283A5D; + --search-results-background-color: #101826; + --search-results-foreground-color: #90A5CE; + --search-results-border-color: #7C95C6; + --search-box-shadow: inset 0.5px 0.5px 3px 0px #2F436C; + + /** code fragments */ + --code-keyword-color: #CC99CD; + --code-type-keyword-color: #AB99CD; + --code-flow-keyword-color: #E08000; + --code-comment-color: #717790; + --code-preprocessor-color: #65CABE; + --code-string-literal-color: #7EC699; + --code-char-literal-color: #00E0F0; + --code-xml-cdata-color: #C9D1D9; + --code-vhdl-digit-color: #FF00FF; + --code-vhdl-char-color: #C0C0C0; + --code-vhdl-keyword-color: #CF53C9; + --code-vhdl-logic-color: #FF0000; + --code-link-color: #79C0FF; + --code-external-link-color: #79C0FF; + --fragment-foreground-color: #C9D1D9; + --fragment-background-color: #090D16; + --fragment-border-color: #30363D; + --fragment-lineno-border-color: #30363D; + --fragment-lineno-background-color: black; + --fragment-lineno-foreground-color: #6E7681; + --fragment-lineno-link-fg-color: #6E7681; + --fragment-lineno-link-bg-color: #303030; + --fragment-lineno-link-hover-fg-color: #8E96A1; + --fragment-lineno-link-hover-bg-color: #505050; + --fragment-copy-ok-color: #0EA80E; + --tooltip-foreground-color: #C9D1D9; + --tooltip-background-color: #202020; + --tooltip-border-color: #C9D1D9; + --tooltip-doc-color: #D9E1E9; + --tooltip-declaration-color: #20C348; + --tooltip-link-color: #79C0FF; + --tooltip-shadow: none; + --fold-line-color: #808080; + --fold-minus-image: url('minusd.svg'); + --fold-plus-image: url('plusd.svg'); + --fold-minus-image-relpath: url('../../minusd.svg'); + --fold-plus-image-relpath: url('../../plusd.svg'); + + /** font-family */ + --font-family-normal: Helvetica, 'Segoe UI', Arial, freesans, sans-serif; + --font-family-monospace: 'SFMono-Regular', Consolas, "Liberation Mono", Menlo, Courier, monospace; + --font-family-nav: 'Lucida Grande', Geneva, Helvetica, Arial, sans-serif; + --font-family-title: Tahoma, Arial, sans-serif; + --font-family-toc: Verdana, 'DejaVu Sans', Geneva, sans-serif; + --font-family-search: Arial, Verdana, sans-serif; + --font-family-icon: Arial, Helvetica; + --font-family-tooltip: Helvetica, 'Segoe UI', Arial, freesans, sans-serif; + + /** special sections */ + --warning-color-bg: #2e1917; + --warning-color-hl: #ad2617; + --warning-color-text: #f5b1aa; + --note-color-bg: #3b2e04; + --note-color-hl: #f1b602; + --note-color-text: #ceb670; + --todo-color-bg: #163750; + --todo-color-hl: #1982D2; + --todo-color-text: #dcf0fa; + --test-color-bg: #121258; + --test-color-hl: #4242cf; + --test-color-text: #c0c0da; + --deprecated-color-bg: #2e323b; + --deprecated-color-hl: #738396; + --deprecated-color-text: #abb0bd; + --bug-color-bg: #2a2536; + --bug-color-hl: #7661b3; + --bug-color-text: #ae9ed6; + --invariant-color-bg: #303a35; + --invariant-color-hl: #76ce96; + --invariant-color-text: #cceed5; + } } body { - background-image: url(bodybg.png); - margin: 0 auto; + background-color: var(--page-background-color); + color: var(--page-foreground-color); } -div.fragment { - padding: 3px; - padding-bottom: 0px; +body, +table, +div, +p, +dl { + font-weight: 400; + font-size: 14px; + font-family: var(--font-family-normal); + line-height: 22px; +} + +/* @group Heading Levels */ + +.title { + font-family: var(--font-family-normal); + line-height: 28px; + font-size: 150%; + font-weight: bold; + margin: 10px 2px; +} + +h1.groupheader { + font-size: 150%; +} + +h2.groupheader { + border-bottom: 1px solid var(--group-header-separator-color); + color: var(--group-header-color); + font-size: 150%; + font-weight: normal; + margin-top: 1.75em; + padding-top: 8px; + padding-bottom: 4px; + width: 100%; +} + +h3.groupheader { + font-size: 100%; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + -webkit-transition: text-shadow 0.5s linear; + -moz-transition: text-shadow 0.5s linear; + -ms-transition: text-shadow 0.5s linear; + -o-transition: text-shadow 0.5s linear; + transition: text-shadow 0.5s linear; + margin-right: 15px; +} + +h1.glow, +h2.glow, +h3.glow, +h4.glow, +h5.glow, +h6.glow { + text-shadow: 0 0 15px var(--glow-color); +} + +dt { + font-weight: bold; +} + +p.startli, +p.startdd { + margin-top: 2px; +} + +th p.starttd, +th p.intertd, +th p.endtd { + font-size: 100%; + font-weight: 700; +} + +p.starttd { + margin-top: 0px; +} + +p.endli { + margin-bottom: 0px; +} + +p.enddd { + margin-bottom: 4px; +} + +p.endtd { + margin-bottom: 2px; +} + +/* @end */ + +caption { + font-weight: bold; +} + +span.legend { + font-size: 70%; + text-align: center; +} + +h3.version { + font-size: 90%; + text-align: center; +} + +div.navtab { + padding-right: 15px; + text-align: right; + line-height: 110%; +} + +div.navtab table { + border-spacing: 0; +} + +td.navtab { + padding-right: 6px; + padding-left: 6px; +} + +td.navtabHL { + background-image: var(--nav-gradient-active-image); + background-repeat: repeat-x; + padding-right: 6px; + padding-left: 6px; +} + +td.navtabHL a, +td.navtabHL a:visited { + color: var(--nav-text-hover-color); + text-shadow: var(--nav-text-hover-shadow); +} + +a.navtab { + font-weight: bold; +} + +div.qindex { + text-align: center; + width: 100%; + line-height: 140%; + font-size: 130%; + color: var(--index-separator-color); +} + +#main-menu a:focus { + outline: auto; + z-index: 10; + position: relative; +} + +dt.alphachar { + font-size: 180%; + font-weight: bold; +} + +.alphachar a { + color: var(--index-header-color); +} + +.alphachar a:hover, +.alphachar a:visited { + text-decoration: none; +} + +.classindex dl { + padding: 25px; + column-count: 1 +} + +.classindex dd { + display: inline-block; + margin-left: 50px; + width: 90%; + line-height: 1.15em; +} + +.classindex dl.even { + background-color: var(--index-even-item-bg-color); +} + +.classindex dl.odd { + background-color: var(--index-odd-item-bg-color); +} + +@media(min-width: 1120px) { + .classindex dl { + column-count: 2 + } +} + +@media(min-width: 1320px) { + .classindex dl { + column-count: 3 + } +} + + +/* @group Link Styling */ + +a { + color: var(--page-link-color); + font-weight: normal; + text-decoration: none; +} + +.contents a:visited { + color: var(--page-visited-link-color); +} + +a:hover { + text-decoration: none; + background: linear-gradient(to bottom, transparent 0, transparent calc(100% - 1px), currentColor 100%); +} + +a:hover>span.arrow { + text-decoration: none; + background: var(--nav-background-color); +} + +a.el { + font-weight: bold; +} + +a.code, +a.code:visited, +a.line, +a.line:visited { + color: var(--code-link-color); +} + +a.codeRef, +a.codeRef:visited, +a.lineRef, +a.lineRef:visited { + color: var(--code-external-link-color); +} + +/* @end */ + +dl.el { + margin-left: -1cm; +} + +ul.check { + list-style: none; + text-indent: -16px; + padding-left: 38px; +} + +li.unchecked:before { + content: "\2610\A0"; +} + +li.checked:before { + content: "\2611\A0"; +} + +ol { + text-indent: 0px; +} + +ul { + text-indent: 0px; + overflow: visible; +} + +ul.multicol { + -moz-column-gap: 1em; + -webkit-column-gap: 1em; + column-gap: 1em; + -moz-column-count: 3; + -webkit-column-count: 3; + column-count: 3; + list-style-type: none; +} + +#side-nav ul { + overflow: visible; + /* reset ul rule for scroll bar in GENERATE_TREEVIEW window */ +} + +#main-nav ul { + overflow: visible; + /* reset ul rule for the navigation bar drop down lists */ +} + +.fragment { + text-align: left; + direction: ltr; + overflow-x: auto; + overflow-y: hidden; + position: relative; + min-height: 12px; + margin: 10px 0px; + padding: 10px 10px; + border: 1px solid var(--fragment-border-color); + border-radius: 4px; + background-color: var(--fragment-background-color); + color: var(--fragment-foreground-color); +} + +pre.fragment { + word-wrap: break-word; + font-size: 10pt; + line-height: 125%; + font-family: var(--font-family-monospace); +} + +.clipboard { + width: 24px; + height: 24px; + right: 5px; + top: 5px; + opacity: 0; + position: absolute; + display: inline; + overflow: auto; + fill: var(--fragment-foreground-color); + justify-content: center; + align-items: center; + cursor: pointer; +} + +.clipboard.success { + border: 1px solid var(--fragment-foreground-color); + border-radius: 4px; +} + +.fragment:hover .clipboard, +.clipboard.success { + opacity: .28; +} + +.clipboard:hover, +.clipboard.success { + opacity: 1 !important; +} + +.clipboard:active:not([class~=success]) svg { + transform: scale(.91); +} + +.clipboard.success svg { + fill: var(--fragment-copy-ok-color); +} + +.clipboard.success { + border-color: var(--fragment-copy-ok-color); } div.line { - padding-bottom: 3px; - font-family: "SFMono-Regular",Consolas,"Liberation Mono",Menlo,Courier,monospace; + font-family: var(--font-family-monospace); + font-size: 13px; + min-height: 13px; + line-height: 1.2; + text-wrap: unrestricted; + white-space: -moz-pre-wrap; + /* Moz */ + white-space: -pre-wrap; + /* Opera 4-6 */ + white-space: -o-pre-wrap; + /* Opera 7 */ + white-space: pre-wrap; + /* CSS3 */ + word-wrap: break-word; + /* IE 5.5+ */ + text-indent: -53px; + padding-left: 53px; + padding-bottom: 0px; + margin: 0px; + -webkit-transition-property: background-color, box-shadow; + -webkit-transition-duration: 0.5s; + -moz-transition-property: background-color, box-shadow; + -moz-transition-duration: 0.5s; + -ms-transition-property: background-color, box-shadow; + -ms-transition-duration: 0.5s; + -o-transition-property: background-color, box-shadow; + -o-transition-duration: 0.5s; + transition-property: background-color, box-shadow; + transition-duration: 0.5s; +} + +div.line:after { + content: "\000A"; + white-space: pre; +} + +div.line.glow { + background-color: var(--glow-color); + box-shadow: 0 0 10px var(--glow-color); +} + +span.fold { + margin-left: 5px; + margin-right: 1px; + margin-top: 0px; + margin-bottom: 0px; + padding: 0px; + display: inline-block; + width: 12px; + height: 12px; + background-repeat: no-repeat; + background-position: center; +} + +span.lineno { + padding-right: 4px; + margin-right: 9px; + text-align: right; + border-right: 2px solid var(--fragment-lineno-border-color); + color: var(--fragment-lineno-foreground-color); + background-color: var(--fragment-lineno-background-color); + white-space: pre; +} + +span.lineno a, +span.lineno a:visited { + color: var(--fragment-lineno-link-fg-color); + background-color: var(--fragment-lineno-link-bg-color); +} + +span.lineno a:hover { + color: var(--fragment-lineno-link-hover-fg-color); + background-color: var(--fragment-lineno-link-hover-bg-color); +} + +.lineno { + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +div.classindex ul { + list-style: none; + padding-left: 0; +} + +div.classindex span.ai { + display: inline-block; +} + +div.groupHeader { + margin-left: 16px; + margin-top: 12px; + font-weight: bold; +} + +div.groupText { + margin-left: 16px; + font-style: italic; +} + +body { + color: var(--page-foreground-color); + margin: 0; } div.contents { + /* OpenCV */ width: 980px; margin: 0 auto; padding: 15px 15px; - border: 1px solid rgb(10, 80, 122); - background-color: #fff; } -span.arrow { - height: 13px; +p.formulaDsp { + text-align: center; } -div.image img{ - max-width: 900px; +img.dark-mode-visible { + display: none; } -#projectlogo -{ +img.light-mode-visible { + display: none; +} + +img.formulaInl, +img.inline { + vertical-align: middle; +} + +div.center { + text-align: center; + margin-top: 0px; + margin-bottom: 0px; + padding: 0px; +} + +div.center img { + border: 0px; +} + +address.footer { + text-align: right; + padding-right: 12px; +} + +img.footer { + border: 0px; + vertical-align: middle; + width: var(--footer-logo-width); +} + +.compoundTemplParams { + color: var(--memdecl-template-color); + font-size: 80%; + line-height: 120%; +} + +/* @group Code Colorization */ + +span.keyword { + color: var(--code-keyword-color); +} + +span.keywordtype { + color: var(--code-type-keyword-color); +} + +span.keywordflow { + color: var(--code-flow-keyword-color); +} + +span.comment { + color: var(--code-comment-color); +} + +span.preprocessor { + color: var(--code-preprocessor-color); +} + +span.stringliteral { + color: var(--code-string-literal-color); +} + +span.charliteral { + color: var(--code-char-literal-color); +} + +span.xmlcdata { + color: var(--code-xml-cdata-color); +} + +span.vhdldigit { + color: var(--code-vhdl-digit-color); +} + +span.vhdlchar { + color: var(--code-vhdl-char-color); +} + +span.vhdlkeyword { + color: var(--code-vhdl-keyword-color); +} + +span.vhdllogic { + color: var(--code-vhdl-logic-color); +} + +blockquote { + background-color: var(--blockquote-background-color); + border-left: 2px solid var(--blockquote-border-color); + margin: 0 24px 0 4px; + padding: 0 12px 0 16px; +} + +/* @end */ + +td.tiny { + font-size: 75%; +} + +.dirtab { + padding: 4px; + border-collapse: collapse; + border: 1px solid var(--table-cell-border-color); +} + +th.dirtab { + background-color: var(--table-header-background-color); + color: var(--table-header-foreground-color); + font-weight: bold; +} + +hr { + height: 0px; + border: none; + border-top: 1px solid var(--separator-color); +} + +hr.footer { + height: 1px; +} + +/* @group Member Descriptions */ + +table.memberdecls { + border-spacing: 0px; + padding: 0px; +} + +.memberdecls td, +.fieldtable tr { + -webkit-transition-property: background-color, box-shadow; + -webkit-transition-duration: 0.5s; + -moz-transition-property: background-color, box-shadow; + -moz-transition-duration: 0.5s; + -ms-transition-property: background-color, box-shadow; + -ms-transition-duration: 0.5s; + -o-transition-property: background-color, box-shadow; + -o-transition-duration: 0.5s; + transition-property: background-color, box-shadow; + transition-duration: 0.5s; +} + +.memberdecls td.glow, +.fieldtable tr.glow { + background-color: var(--glow-color); + box-shadow: 0 0 15px var(--glow-color); +} + +.mdescLeft, +.mdescRight, +.memItemLeft, +.memItemRight, +.memTemplItemLeft, +.memTemplItemRight, +.memTemplParams { + background-color: var(--memdecl-background-color); + border: none; + margin: 4px; + padding: 1px 0 0 8px; +} + +.mdescLeft, +.mdescRight { + padding: 0px 8px 4px 8px; + color: var(--memdecl-foreground-color); +} + +.memSeparator { + border-bottom: 1px solid var(--memdecl-separator-color); + line-height: 1px; + margin: 0px; + padding: 0px; +} + +.memItemLeft, +.memTemplItemLeft { + white-space: nowrap; +} + +.memItemRight, +.memTemplItemRight { + width: 100%; +} + +.memTemplParams { + color: var(--memdecl-template-color); + white-space: nowrap; + font-size: 80%; +} + +/* @end */ + +/* @group Member Details */ + +/* Styles for detailed member documentation */ + +.memtitle { + padding: 8px; + border-top: 1px solid var(--memdef-border-color); + border-left: 1px solid var(--memdef-border-color); + border-right: 1px solid var(--memdef-border-color); + border-top-right-radius: 4px; + border-top-left-radius: 4px; + margin-bottom: -1px; + background-image: var(--memdef-title-gradient-image); + background-repeat: repeat-x; + background-color: var(--memdef-title-background-color); + line-height: 1.25; + font-weight: 300; + float: left; +} + +.permalink { + font-size: 65%; + display: inline-block; + vertical-align: middle; +} + +.memtemplate { + font-size: 80%; + color: var(--memdef-template-color); + font-weight: normal; + margin-left: 9px; +} + +.mempage { + width: 100%; +} + +.memitem { + padding: 0; + margin-bottom: 10px; + margin-right: 5px; + -webkit-transition: box-shadow 0.5s linear; + -moz-transition: box-shadow 0.5s linear; + -ms-transition: box-shadow 0.5s linear; + -o-transition: box-shadow 0.5s linear; + transition: box-shadow 0.5s linear; + display: table !important; + width: 100%; +} + +.memitem.glow { + box-shadow: 0 0 15px var(--glow-color); +} + +.memname { + font-weight: 400; + margin-left: 6px; +} + +.memname td { + vertical-align: bottom; +} + +.memproto, +dl.reflist dt { + border-top: 1px solid var(--memdef-border-color); + border-left: 1px solid var(--memdef-border-color); + border-right: 1px solid var(--memdef-border-color); + padding: 6px 0px 6px 0px; + color: var(--memdef-proto-text-color); + font-weight: bold; + text-shadow: var(--memdef-proto-text-shadow); + background-color: var(--memdef-proto-background-color); + box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.15); + border-top-right-radius: 4px; +} + +.overload { + font-family: var(--font-family-monospace); + font-size: 65%; +} + +.memdoc, +dl.reflist dd { + border-bottom: 1px solid var(--memdef-border-color); + border-left: 1px solid var(--memdef-border-color); + border-right: 1px solid var(--memdef-border-color); + padding: 6px 10px 2px 10px; + border-top-width: 0; + background-image: url('nav_g.png'); + background-repeat: repeat-x; + background-color: var(--memdef-doc-background-color); + /* opera specific markup */ + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.15); + /* firefox specific markup */ + -moz-border-radius-bottomleft: 4px; + -moz-border-radius-bottomright: 4px; + -moz-box-shadow: rgba(0, 0, 0, 0.15) 5px 5px 5px; + /* webkit specific markup */ + -webkit-border-bottom-left-radius: 4px; + -webkit-border-bottom-right-radius: 4px; + -webkit-box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.15); +} + +dl.reflist dt { + padding: 5px; +} + +dl.reflist dd { + margin: 0px 0px 10px 0px; + padding: 5px; +} + +.paramkey { + text-align: right; +} + +.paramtype { + white-space: nowrap; + padding: 0px; + padding-bottom: 1px; +} + +.paramname { + white-space: nowrap; + padding: 0px; + padding-bottom: 1px; + margin-left: 2px; +} + +.paramname em { + color: var(--memdef-param-name-color); + font-style: normal; + margin-right: 1px; +} + +.paramname .paramdefval { + font-family: var(--font-family-monospace); +} + +.params, +.retval, +.exception, +.tparams { + margin-left: 0px; + padding-left: 0px; +} + +.params .paramname, +.retval .paramname, +.tparams .paramname, +.exception .paramname { + font-weight: bold; + vertical-align: top; +} + +.params .paramtype, +.tparams .paramtype { + font-style: italic; + vertical-align: top; +} + +.params .paramdir, +.tparams .paramdir { + font-family: var(--font-family-monospace); + vertical-align: top; +} + +table.mlabels { + border-spacing: 0px; +} + +td.mlabels-left { + width: 100%; + padding: 0px; +} + +td.mlabels-right { + vertical-align: bottom; + padding: 0px; + white-space: nowrap; +} + +span.mlabels { + margin-left: 8px; +} + +span.mlabel { + background-color: var(--label-background-color); + border-top: 1px solid var(--label-left-top-border-color); + border-left: 1px solid var(--label-left-top-border-color); + border-right: 1px solid var(--label-right-bottom-border-color); + border-bottom: 1px solid var(--label-right-bottom-border-color); + text-shadow: none; + color: var(--label-foreground-color); + margin-right: 4px; + padding: 2px 3px; + border-radius: 3px; + font-size: 7pt; + white-space: nowrap; + vertical-align: middle; +} + + + +/* @end */ + +/* these are for tree view inside a (index) page */ + +div.directory { + margin: 10px 0px; + border-top: 1px solid var(--directory-separator-color); + border-bottom: 1px solid var(--directory-separator-color); + width: 100%; +} + +.directory table { + border-collapse: collapse; +} + +.directory td { + margin: 0px; + padding: 0px; + vertical-align: top; +} + +.directory td.entry { + white-space: nowrap; + padding-right: 6px; + padding-top: 3px; +} + +.directory td.entry a { + outline: none; +} + +.directory td.entry a img { + border: none; +} + +.directory td.desc { + width: 100%; + padding-left: 6px; + padding-right: 6px; + padding-top: 3px; + border-left: 1px solid rgba(0, 0, 0, 0.05); +} + +.directory tr.odd { + padding-left: 6px; + background-color: var(--index-odd-item-bg-color); +} + +.directory tr.even { + padding-left: 6px; + background-color: var(--index-even-item-bg-color); +} + +.directory img { + vertical-align: -30%; +} + +.directory .levels { + white-space: nowrap; + width: 100%; + text-align: right; + font-size: 9pt; +} + +.directory .levels span { + cursor: pointer; + padding-left: 2px; + padding-right: 2px; + color: var(--page-link-color); +} + +.arrow { + color: var(--nav-arrow-color); + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + cursor: pointer; + font-size: 80%; + display: inline-block; + width: 16px; + height: 22px; +} + +.icon { + font-family: var(--font-family-icon); + line-height: normal; + font-weight: bold; + font-size: 12px; + height: 14px; + width: 16px; + display: inline-block; + background-color: var(--icon-background-color); + color: var(--icon-foreground-color); + text-align: center; + border-radius: 4px; + margin-left: 2px; + margin-right: 2px; +} + +.icona { + width: 24px; + height: 22px; + display: inline-block; +} + +.iconfopen { + width: 24px; + height: 18px; + margin-bottom: 4px; + background-image: var(--icon-folder-open-image); + background-repeat: repeat-y; + vertical-align: top; + display: inline-block; +} + +.iconfclosed { + width: 24px; + height: 18px; + margin-bottom: 4px; + background-image: var(--icon-folder-closed-image); + background-repeat: repeat-y; + vertical-align: top; + display: inline-block; +} + +.icondoc { + width: 24px; + height: 18px; + margin-bottom: 4px; + background-image: var(--icon-doc-image); + background-position: 0px -4px; + background-repeat: repeat-y; + vertical-align: top; + display: inline-block; +} + +/* @end */ + +div.dynheader { + margin-top: 8px; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +address { + font-style: normal; + color: var(--footer-foreground-color); +} + +table.doxtable caption { + caption-side: top; +} + +table.doxtable { + border-collapse: collapse; + margin-top: 4px; + margin-bottom: 4px; +} + +table.doxtable td, +table.doxtable th { + border: 1px solid var(--table-cell-border-color); + padding: 3px 7px 2px; +} + +table.doxtable th { + background-color: var(--table-header-background-color); + color: var(--table-header-foreground-color); + font-size: 110%; + padding-bottom: 4px; + padding-top: 5px; +} + +table.fieldtable { + margin-bottom: 10px; + border: 1px solid var(--memdef-border-color); + border-spacing: 0px; + border-radius: 4px; + box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.15); +} + +.fieldtable td, +.fieldtable th { + padding: 3px 7px 2px; +} + +.fieldtable td.fieldtype, +.fieldtable td.fieldname, +.fieldtable td.fieldinit { + white-space: nowrap; + border-right: 1px solid var(--memdef-border-color); + border-bottom: 1px solid var(--memdef-border-color); + vertical-align: top; +} + +.fieldtable td.fieldname { + padding-top: 3px; +} + +.fieldtable td.fieldinit { + padding-top: 3px; + text-align: right; +} + + +.fieldtable td.fielddoc { + border-bottom: 1px solid var(--memdef-border-color); +} + +.fieldtable td.fielddoc p:first-child { + margin-top: 0px; +} + +.fieldtable td.fielddoc p:last-child { + margin-bottom: 2px; +} + +.fieldtable tr:last-child td { + border-bottom: none; +} + +.fieldtable th { + background-image: var(--memdef-title-gradient-image); + background-repeat: repeat-x; + background-color: var(--memdef-title-background-color); + font-size: 90%; + color: var(--memdef-proto-text-color); + padding-bottom: 4px; + padding-top: 5px; + text-align: left; + font-weight: 400; + border-top-left-radius: 4px; + border-top-right-radius: 4px; + border-bottom: 1px solid var(--memdef-border-color); +} + + +.tabsearch { + top: 0px; + left: 10px; + height: 36px; + background-image: var(--nav-gradient-image); + z-index: 101; + overflow: hidden; + font-size: 13px; +} + +.navpath ul { + font-size: 11px; + background-image: var(--nav-gradient-image); + background-repeat: repeat-x; + background-position: 0 -5px; + height: 30px; + line-height: 30px; + color: var(--nav-text-normal-color); + border: solid 1px var(--nav-breadcrumb-border-color); + overflow: hidden; + margin: 0px; + padding: 0px; +} + +.navpath li { + list-style-type: none; + float: left; + padding-left: 10px; + padding-right: 15px; + background-image: var(--nav-breadcrumb-image); + background-repeat: no-repeat; + background-position: right; + color: var(--nav-foreground-color); +} + +.navpath li.navelem a { + height: 32px; + display: block; + outline: none; + color: var(--nav-text-normal-color); + font-family: var(--font-family-nav); + text-shadow: var(--nav-text-normal-shadow); + text-decoration: none; +} + +.navpath li.navelem a:hover { + color: var(--nav-text-hover-color); + text-shadow: var(--nav-text-hover-shadow); +} + +.navpath li.footer { + list-style-type: none; + float: right; + padding-left: 10px; + padding-right: 15px; + background-image: none; + background-repeat: no-repeat; + background-position: right; + color: var(--footer-foreground-color); + font-size: 8pt; +} + + +div.summary { + float: right; + font-size: 8pt; + padding-right: 5px; + width: 50%; + text-align: right; +} + +div.summary a { + white-space: nowrap; +} + +table.classindex { + margin: 10px; + white-space: nowrap; + margin-left: 3%; + margin-right: 3%; + width: 94%; + border: 0; + border-spacing: 0; + padding: 0; +} + +div.ingroups { + font-size: 8pt; + width: 50%; + text-align: left; +} + +div.ingroups a { + white-space: nowrap; +} + +div.header { + background-image: var(--header-gradient-image); + background-repeat: repeat-x; + background-color: var(--header-background-color); + margin: 0px; + border-bottom: 1px solid var(--header-separator-color); +} + +div.headertitle { + padding: 5px 5px 5px 10px; +} + +.PageDocRTL-title div.headertitle { + text-align: right; + direction: rtl; +} + +dl { + padding: 0 0 0 0; +} + +dl.bug dt a, +dl.deprecated dt a, +dl.todo dt a, +dl.test a { + font-weight: bold !important; +} + +dl.warning, +dl.attention, +dl.important, +dl.note, +dl.deprecated, +dl.bug, +dl.invariant, +dl.pre, +dl.post, +dl.todo, +dl.test, +dl.remark { + padding: 10px; + margin: 10px 0px; + overflow: hidden; + margin-left: 0; + border-radius: 4px; +} + +dl.section dd { + margin-bottom: 2px; +} + +dl.warning, +dl.attention, +dl.important { + background: var(--warning-color-bg); + border-left: 8px solid var(--warning-color-hl); + color: var(--warning-color-text); +} + +dl.warning dt, +dl.attention dt, +dl.important dt { + color: var(--warning-color-hl); +} + +dl.note, +dl.remark { + background: var(--note-color-bg); + border-left: 8px solid var(--note-color-hl); + color: var(--note-color-text); +} + +dl.note dt, +dl.remark dt { + color: var(--note-color-hl); +} + +dl.todo { + background: var(--todo-color-bg); + border-left: 8px solid var(--todo-color-hl); + color: var(--todo-color-text); +} + +dl.todo dt { + color: var(--todo-color-hl); +} + +dl.test { + background: var(--test-color-bg); + border-left: 8px solid var(--test-color-hl); + color: var(--test-color-text); +} + +dl.test dt { + color: var(--test-color-hl); +} + +dl.bug dt a { + color: var(--bug-color-hl) !important; +} + +dl.bug { + background: var(--bug-color-bg); + border-left: 8px solid var(--bug-color-hl); + color: var(--bug-color-text); +} + +dl.bug dt a { + color: var(--bug-color-hl) !important; +} + +dl.deprecated { + background: var(--deprecated-color-bg); + border-left: 8px solid var(--deprecated-color-hl); + color: var(--deprecated-color-text); +} + +dl.deprecated dt a { + color: var(--deprecated-color-hl) !important; +} + +dl.note dd, +dl.warning dd, +dl.pre dd, +dl.post dd, +dl.remark dd, +dl.attention dd, +dl.important dd, +dl.invariant dd, +dl.bug dd, +dl.deprecated dd, +dl.todo dd, +dl.test dd { + margin-inline-start: 0px; +} + +dl.invariant, +dl.pre, +dl.post { + background: var(--invariant-color-bg); + border-left: 8px solid var(--invariant-color-hl); + color: var(--invariant-color-text); +} + +dl.invariant dt, +dl.pre dt, +dl.post dt { + color: var(--invariant-color-hl); +} + + +#projectrow { + height: 56px; +} + +#projectlogo { + /* OpenCV */ text-align: center; vertical-align: middle; border-collapse: separate; padding-left: 0.5em; } + +#projectalign { + vertical-align: middle; + padding-left: 0.5em; +} + +#projectname { + font-size: 200%; + font-family: var(--font-family-title); + margin: 0px; + padding: 2px 0px; +} + +#projectbrief { + font-size: 90%; + font-family: var(--font-family-title); + margin: 0px; + padding: 0px; +} + +#projectnumber { + font-size: 50%; + font-family: 50% var(--font-family-title); + margin: 0px; + padding: 0px; +} + +#titlearea { + padding: 0px; + margin: 0px; + width: 100%; + border-bottom: 1px solid var(--title-separator-color); + background-color: var(--title-background-color); +} + +.image { + text-align: center; +} + +.dotgraph { + text-align: center; +} + +.mscgraph { + text-align: center; +} + +.plantumlgraph { + text-align: center; +} + +.diagraph { + text-align: center; +} + +.caption { + font-weight: bold; +} + +dl.citelist { + margin-bottom: 50px; +} + +dl.citelist dt { + color: var(--citation-label-color); + float: left; + font-weight: bold; + margin-right: 10px; + padding: 5px; + text-align: right; + width: 52px; +} + +dl.citelist dd { + margin: 2px 0 2px 72px; + padding: 5px 0; +} + +div.toc { + padding: 14px 25px; + background-color: var(--toc-background-color); + border: 1px solid var(--toc-border-color); + border-radius: 7px 7px 7px 7px; + float: right; + height: auto; + margin: 0 8px 10px 10px; + width: 200px; +} + +div.toc li { + background: var(--toc-down-arrow-image) no-repeat scroll 0 5px transparent; + font: 10px/1.2 var(--font-family-toc); + margin-top: 5px; + padding-left: 10px; + padding-top: 2px; +} + +div.toc h3 { + font: bold 12px/1.2 var(--font-family-toc); + color: var(--toc-header-color); + border-bottom: 0 none; + margin: 0; +} + +div.toc ul { + list-style: none outside none; + border: medium none; + padding: 0px; +} + +div.toc li[class^='level'] { + margin-left: 15px; +} + +div.toc li.level1 { + margin-left: 0px; +} + +div.toc li.empty { + background-image: none; + margin-top: 0px; +} + +span.obfuscator { + display: none; +} + +.inherit_header { + font-weight: bold; + color: var(--inherit-header-color); + cursor: pointer; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.inherit_header td { + padding: 6px 0px 2px 5px; +} + +.inherit { + display: none; +} + +tr.heading h2 { + margin-top: 12px; + margin-bottom: 4px; +} + +/* tooltip related style info */ + +.ttc { + position: absolute; + display: none; +} + +#powerTip { + cursor: default; + /*white-space: nowrap;*/ + color: var(--tooltip-foreground-color); + background-color: var(--tooltip-background-color); + border: 1px solid var(--tooltip-border-color); + border-radius: 4px 4px 4px 4px; + box-shadow: var(--tooltip-shadow); + display: none; + font-size: smaller; + max-width: 80%; + opacity: 0.9; + padding: 1ex 1em 1em; + position: absolute; + z-index: 2147483647; +} + +#powerTip div.ttdoc { + color: var(--tooltip-doc-color); + font-style: italic; +} + +#powerTip div.ttname a { + font-weight: bold; +} + +#powerTip a { + color: var(--tooltip-link-color); +} + +#powerTip div.ttname { + font-weight: bold; +} + +#powerTip div.ttdeci { + color: var(--tooltip-declaration-color); +} + +#powerTip div { + margin: 0px; + padding: 0px; + font-size: 12px; + font-family: var(--font-family-tooltip); + line-height: 16px; +} + +#powerTip:before, +#powerTip:after { + content: ""; + position: absolute; + margin: 0px; +} + +#powerTip.n:after, +#powerTip.n:before, +#powerTip.s:after, +#powerTip.s:before, +#powerTip.w:after, +#powerTip.w:before, +#powerTip.e:after, +#powerTip.e:before, +#powerTip.ne:after, +#powerTip.ne:before, +#powerTip.se:after, +#powerTip.se:before, +#powerTip.nw:after, +#powerTip.nw:before, +#powerTip.sw:after, +#powerTip.sw:before { + border: solid transparent; + content: " "; + height: 0; + width: 0; + position: absolute; +} + +#powerTip.n:after, +#powerTip.s:after, +#powerTip.w:after, +#powerTip.e:after, +#powerTip.nw:after, +#powerTip.ne:after, +#powerTip.sw:after, +#powerTip.se:after { + border-color: rgba(255, 255, 255, 0); +} + +#powerTip.n:before, +#powerTip.s:before, +#powerTip.w:before, +#powerTip.e:before, +#powerTip.nw:before, +#powerTip.ne:before, +#powerTip.sw:before, +#powerTip.se:before { + border-color: rgba(128, 128, 128, 0); +} + +#powerTip.n:after, +#powerTip.n:before, +#powerTip.ne:after, +#powerTip.ne:before, +#powerTip.nw:after, +#powerTip.nw:before { + top: 100%; +} + +#powerTip.n:after, +#powerTip.ne:after, +#powerTip.nw:after { + border-top-color: var(--tooltip-background-color); + border-width: 10px; + margin: 0px -10px; +} + +#powerTip.n:before, +#powerTip.ne:before, +#powerTip.nw:before { + border-top-color: var(--tooltip-border-color); + border-width: 11px; + margin: 0px -11px; +} + +#powerTip.n:after, +#powerTip.n:before { + left: 50%; +} + +#powerTip.nw:after, +#powerTip.nw:before { + right: 14px; +} + +#powerTip.ne:after, +#powerTip.ne:before { + left: 14px; +} + +#powerTip.s:after, +#powerTip.s:before, +#powerTip.se:after, +#powerTip.se:before, +#powerTip.sw:after, +#powerTip.sw:before { + bottom: 100%; +} + +#powerTip.s:after, +#powerTip.se:after, +#powerTip.sw:after { + border-bottom-color: var(--tooltip-background-color); + border-width: 10px; + margin: 0px -10px; +} + +#powerTip.s:before, +#powerTip.se:before, +#powerTip.sw:before { + border-bottom-color: var(--tooltip-border-color); + border-width: 11px; + margin: 0px -11px; +} + +#powerTip.s:after, +#powerTip.s:before { + left: 50%; +} + +#powerTip.sw:after, +#powerTip.sw:before { + right: 14px; +} + +#powerTip.se:after, +#powerTip.se:before { + left: 14px; +} + +#powerTip.e:after, +#powerTip.e:before { + left: 100%; +} + +#powerTip.e:after { + border-left-color: var(--tooltip-border-color); + border-width: 10px; + top: 50%; + margin-top: -10px; +} + +#powerTip.e:before { + border-left-color: var(--tooltip-border-color); + border-width: 11px; + top: 50%; + margin-top: -11px; +} + +#powerTip.w:after, +#powerTip.w:before { + right: 100%; +} + +#powerTip.w:after { + border-right-color: var(--tooltip-border-color); + border-width: 10px; + top: 50%; + margin-top: -10px; +} + +#powerTip.w:before { + border-right-color: var(--tooltip-border-color); + border-width: 11px; + top: 50%; + margin-top: -11px; +} + +@media print { + #top { + display: none; + } + + #side-nav { + display: none; + } + + #nav-path { + display: none; + } + + body { + overflow: visible; + } + + h1, + h2, + h3, + h4, + h5, + h6 { + page-break-after: avoid; + } + + .summary { + display: none; + } + + .memitem { + page-break-inside: avoid; + } + + #doc-content { + margin-left: 0 !important; + height: auto !important; + width: auto !important; + overflow: inherit; + display: inline; + } +} + +/* @group Markdown */ + +table.markdownTable { + border-collapse: collapse; + margin-top: 4px; + margin-bottom: 4px; +} + +table.markdownTable td, +table.markdownTable th { + border: 1px solid var(--table-cell-border-color); + padding: 3px 7px 2px; +} + +table.markdownTable tr {} + +th.markdownTableHeadLeft, +th.markdownTableHeadRight, +th.markdownTableHeadCenter, +th.markdownTableHeadNone { + background-color: var(--table-header-background-color); + color: var(--table-header-foreground-color); + font-size: 110%; + padding-bottom: 4px; + padding-top: 5px; +} + +th.markdownTableHeadLeft, +td.markdownTableBodyLeft { + text-align: left +} + +th.markdownTableHeadRight, +td.markdownTableBodyRight { + text-align: right +} + +th.markdownTableHeadCenter, +td.markdownTableBodyCenter { + text-align: center +} + +tt, +code, +kbd, +samp { + display: inline-block; +} + +/* @end */ + +u { + text-decoration: underline; +} + +details>summary { + list-style-type: none; +} + +details>summary::-webkit-details-marker { + display: none; +} + +details>summary::before { + content: "\25ba"; + padding-right: 4px; + font-size: 80%; +} + +details[open]>summary::before { + content: "\25bc"; + padding-right: 4px; + font-size: 80%; +} + +body { + scrollbar-color: var(--scrollbar-thumb-color) var(--scrollbar-background-color); +} + +::-webkit-scrollbar { + background-color: var(--scrollbar-background-color); + height: 12px; + width: 12px; +} + +::-webkit-scrollbar-thumb { + border-radius: 6px; + box-shadow: inset 0 0 12px 12px var(--scrollbar-thumb-color); + border: solid 2px transparent; +} + +::-webkit-scrollbar-corner { + background-color: var(--scrollbar-background-color); +} diff --git a/modules/core/include/opencv2/core.hpp b/modules/core/include/opencv2/core.hpp index 05c8bb7222..d134273753 100644 --- a/modules/core/include/opencv2/core.hpp +++ b/modules/core/include/opencv2/core.hpp @@ -463,10 +463,6 @@ The function can also be emulated with a matrix expression, for example: */ CV_EXPORTS_W void scaleAdd(InputArray src1, double alpha, InputArray src2, OutputArray dst); -/** @example samples/cpp/tutorial_code/HighGUI/AddingImagesTrackbar.cpp -Check @ref tutorial_trackbar "the corresponding tutorial" for more details -*/ - /** @brief Calculates the weighted sum of two arrays. The function addWeighted calculates the weighted sum of two arrays as follows: diff --git a/modules/highgui/include/opencv2/highgui.hpp b/modules/highgui/include/opencv2/highgui.hpp index dec9af5b46..5f69721ce9 100644 --- a/modules/highgui/include/opencv2/highgui.hpp +++ b/modules/highgui/include/opencv2/highgui.hpp @@ -544,7 +544,7 @@ updated automatically. @param userdata Optional user data that is passed to the callback. @note If the `value` pointer is `nullptr`, the trackbar position must be manually managed. Call the callback function manually with the desired initial value to avoid runtime warnings. -@see #tutorial_trackbar +@see \ref tutorial_trackbar */ CV_EXPORTS int createTrackbar(const String& trackbarname, const String& winname, int* value, int count, From d85c13bcbb5b7c866ffbcebbe6e9227cd68ca627 Mon Sep 17 00:00:00 2001 From: anandkaranubc <119553199+anandkaranubc@users.noreply.github.com> Date: Mon, 9 Dec 2024 22:21:25 -0800 Subject: [PATCH 019/102] Merge pull request #26587 from anandkaranubc:fix-nu-svc-parameter-check Fix #25812: Add error handling for invalid nu parameter in SVM NU_SVC #26587 ### 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 [Issue #25812](https://github.com/opencv/opencv/issues/25812) - [ ] 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/ml/src/svm.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/ml/src/svm.cpp b/modules/ml/src/svm.cpp index 0d62f7858a..3bc01ba8ad 100644 --- a/modules/ml/src/svm.cpp +++ b/modules/ml/src/svm.cpp @@ -41,10 +41,12 @@ //M*/ #include "precomp.hpp" +#include #include #include + /****************************************************************************************\ COPYRIGHT NOTICE ---------------- @@ -1458,9 +1460,10 @@ public: for( j = i+1; j< class_count; j++ ) { int cj = class_ranges[j+1] - class_ranges[j]; - if( nu*(ci + cj)*0.5 > std::min( ci, cj ) ) - // TODO: add some diagnostic + if( nu*(ci + cj)*0.5 > std::min( ci, cj ) ) { + CV_LOG_ERROR(NULL, "Training cases incompatible with nu parameter—try a lower value."); return false; + } } } } From 5ef062343c440037465fb29ee98f7494a64780a4 Mon Sep 17 00:00:00 2001 From: Maksim Shabunin Date: Tue, 10 Dec 2024 11:35:56 +0300 Subject: [PATCH 020/102] CI: enable AVX2 build --- .github/workflows/PR-4.x.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/PR-4.x.yaml b/.github/workflows/PR-4.x.yaml index cb0c812746..93abc8e672 100644 --- a/.github/workflows/PR-4.x.yaml +++ b/.github/workflows/PR-4.x.yaml @@ -24,6 +24,9 @@ jobs: Ubuntu2404-x64: uses: opencv/ci-gha-workflow/.github/workflows/OCV-PR-4.x-U24.yaml@main + Ubuntu2404-x64-AVX2: + uses: opencv/ci-gha-workflow/.github/workflows/OCV-PR-4.x-U24-AVX2.yaml@main + Ubuntu2004-x64-CUDA: if: "${{ contains(github.event.pull_request.labels.*.name, 'category: dnn') }} || ${{ contains(github.event.pull_request.labels.*.name, 'category: dnn (onnx)') }}" uses: opencv/ci-gha-workflow/.github/workflows/OCV-PR-4.x-U20-Cuda.yaml@main From 1d4110884b3145f77281ec8402d5dc43da7b8634 Mon Sep 17 00:00:00 2001 From: Maksim Shabunin Date: Tue, 10 Dec 2024 17:23:13 +0300 Subject: [PATCH 021/102] Revert "CI: enable AVX2 build" (#26610) --- .github/workflows/PR-4.x.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/PR-4.x.yaml b/.github/workflows/PR-4.x.yaml index 93abc8e672..cb0c812746 100644 --- a/.github/workflows/PR-4.x.yaml +++ b/.github/workflows/PR-4.x.yaml @@ -24,9 +24,6 @@ jobs: Ubuntu2404-x64: uses: opencv/ci-gha-workflow/.github/workflows/OCV-PR-4.x-U24.yaml@main - Ubuntu2404-x64-AVX2: - uses: opencv/ci-gha-workflow/.github/workflows/OCV-PR-4.x-U24-AVX2.yaml@main - Ubuntu2004-x64-CUDA: if: "${{ contains(github.event.pull_request.labels.*.name, 'category: dnn') }} || ${{ contains(github.event.pull_request.labels.*.name, 'category: dnn (onnx)') }}" uses: opencv/ci-gha-workflow/.github/workflows/OCV-PR-4.x-U20-Cuda.yaml@main From 42be822c1d615f54307e656322f2bf92fb74bda3 Mon Sep 17 00:00:00 2001 From: KangJialiang <60818198+KangJialiang@users.noreply.github.com> Date: Wed, 11 Dec 2024 20:16:21 +0800 Subject: [PATCH 022/102] Fix normalization parameters in YOLO example to support multi-channel mean and scale factors This branch and commit address an issue in the YOLO example (samples/dnn/yolo_detector.cpp) where the mean and scale parameters only affected the first channel (B) due to single-value input. The modification updates these parameters to accept multi-channel values, ensuring consistent preprocessing across all image channels. --- samples/dnn/yolo_detector.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/samples/dnn/yolo_detector.cpp b/samples/dnn/yolo_detector.cpp index bd82acff4a..2f1d6dbb49 100644 --- a/samples/dnn/yolo_detector.cpp +++ b/samples/dnn/yolo_detector.cpp @@ -44,8 +44,8 @@ std::string keys = "{ nc | 80 | Number of classes. Default is 80 (coming from COCO dataset). }" "{ thr | .5 | Confidence threshold. }" "{ nms | .4 | Non-maximum suppression threshold. }" - "{ mean | 0.0 | Normalization constant. }" - "{ scale | 1.0 | Preprocess input image by multiplying on a scale factor. }" + "{ mean | 0.0 0.0 0.0 | Normalization constant. }" + "{ scale | 1.0 1.0 1.0 | Preprocess input image by multiplying on a scale factor. }" "{ width | 640 | Preprocess input image by resizing to a specific width. }" "{ height | 640 | Preprocess input image by resizing to a specific height. }" "{ rgb | 1 | Indicate that model works with RGB input images instead BGR ones. }" @@ -221,7 +221,7 @@ int main(int argc, char** argv) bool swapRB = parser.get("rgb"); int inpWidth = parser.get("width"); int inpHeight = parser.get("height"); - Scalar scale = parser.get("scale"); + Scalar scale = parser.get("scale"); Scalar mean = parser.get("mean"); ImagePaddingMode paddingMode = static_cast(parser.get("paddingmode")); //![preprocess_params] From 25fe85bbbbcb682fed891753a9a74db4dfc3d242 Mon Sep 17 00:00:00 2001 From: KangJialiang <60818198+KangJialiang@users.noreply.github.com> Date: Thu, 12 Dec 2024 15:41:14 +0800 Subject: [PATCH 023/102] Fix `yoloPostProcessing`` to handle variable number of classes (nc) Previously, the yoloPostProcessing function assumed that the number of classes (nc) was fixed at 80. This caused incorrect behavior when a different number of classes was specified, leading to mismatched output shapes. This update modifies the code to use the provided `nc` value dynamically, ensuring that the output shapes are correctly calculated based on the specified number of classes. This prevents issues when `nc` is not equal to 80 and allows for greater flexibility in model configurations. --- modules/dnn/test/test_onnx_importer.cpp | 10 +++++----- samples/dnn/yolo_detector.cpp | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/modules/dnn/test/test_onnx_importer.cpp b/modules/dnn/test/test_onnx_importer.cpp index 8cfc469a7e..66c33eb0a5 100644 --- a/modules/dnn/test/test_onnx_importer.cpp +++ b/modules/dnn/test/test_onnx_importer.cpp @@ -2691,7 +2691,7 @@ void yoloPostProcessing( } if (model_name == "yolonas"){ - // outs contains 2 elemets of shape [1, 8400, 80] and [1, 8400, 4]. Concat them to get [1, 8400, 84] + // outs contains 2 elemets 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]); @@ -2701,12 +2701,12 @@ void yoloPostProcessing( // remove the second element outs.pop_back(); // unsqueeze the first dimension - outs[0] = outs[0].reshape(0, std::vector{1, 8400, 84}); + outs[0] = outs[0].reshape(0, std::vector{1, outs[0].size[0], outs[0].size[1]}); } - // assert if last dim is 85 or 84 - CV_CheckEQ(outs[0].dims, 3, "Invalid output shape. The shape should be [1, #anchors, 85 or 84]"); - CV_CheckEQ((outs[0].size[2] == nc + 5 || outs[0].size[2] == 80 + 4), true, "Invalid output shape: "); + // assert if last dim is nc+5 or nc+4 + CV_CheckEQ(outs[0].dims, 3, "Invalid output shape. The shape should be [1, #anchors, nc+5 or nc+4]"); + CV_CheckEQ((outs[0].size[2] == nc + 5 || outs[0].size[2] == nc + 4), true, "Invalid output shape: "); for (auto preds : outs){ diff --git a/samples/dnn/yolo_detector.cpp b/samples/dnn/yolo_detector.cpp index bd82acff4a..57493cdfb0 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, 80] and [1, 8400, 4]. Concat them to get [1, 8400, 84] + // outs contains 2 elemets 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]); @@ -135,12 +135,12 @@ void yoloPostProcessing( // remove the second element outs.pop_back(); // unsqueeze the first dimension - outs[0] = outs[0].reshape(0, std::vector{1, 8400, nc + 4}); + outs[0] = outs[0].reshape(0, std::vector{1, outs[0].size[0], outs[0].size[1]}); } - // assert if last dim is 85 or 84 - CV_CheckEQ(outs[0].dims, 3, "Invalid output shape. The shape should be [1, #anchors, 85 or 84]"); - CV_CheckEQ((outs[0].size[2] == nc + 5 || outs[0].size[2] == 80 + 4), true, "Invalid output shape: "); + // assert if last dim is nc+5 or nc+4 + CV_CheckEQ(outs[0].dims, 3, "Invalid output shape. The shape should be [1, #anchors, nc+5 or nc+4]"); + CV_CheckEQ((outs[0].size[2] == nc + 5 || outs[0].size[2] == nc + 4), true, "Invalid output shape: "); for (auto preds : outs) { From a4c8d318e612cd40c9927bc148a26f8e8ed759a9 Mon Sep 17 00:00:00 2001 From: Alexander Smorkalov Date: Fri, 13 Dec 2024 08:27:36 +0300 Subject: [PATCH 024/102] Update KleidiCV to version 0.3. --- 3rdparty/kleidicv/CMakeLists.txt | 6 +++++- 3rdparty/kleidicv/kleidicv.cmake | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/3rdparty/kleidicv/CMakeLists.txt b/3rdparty/kleidicv/CMakeLists.txt index a236483c27..9273deb3c9 100644 --- a/3rdparty/kleidicv/CMakeLists.txt +++ b/3rdparty/kleidicv/CMakeLists.txt @@ -3,5 +3,9 @@ project(kleidicv_hal) if(HAVE_KLEIDICV) option(KLEIDICV_ENABLE_SME2 "" OFF) # not compatible with some CLang versions in NDK include("${KLEIDICV_SOURCE_PATH}/adapters/opencv/CMakeLists.txt") + # HACK to suppress adapters/opencv/kleidicv_hal.cpp:343:12: warning: unused function 'from_opencv' [-Wunused-function] + target_compile_options( kleidicv_hal PRIVATE + $ + "-Wno-old-style-cast" "-Wno-unused-function" + ) endif() - diff --git a/3rdparty/kleidicv/kleidicv.cmake b/3rdparty/kleidicv/kleidicv.cmake index 57d3843ef0..2ea071dade 100644 --- a/3rdparty/kleidicv/kleidicv.cmake +++ b/3rdparty/kleidicv/kleidicv.cmake @@ -1,8 +1,8 @@ function(download_kleidicv root_var) set(${root_var} "" PARENT_SCOPE) - ocv_update(KLEIDICV_SRC_COMMIT "0.2.0") - ocv_update(KLEIDICV_SRC_HASH "dabe522e8f55ac342d07a787391dab80") + ocv_update(KLEIDICV_SRC_COMMIT "0.3.0") + ocv_update(KLEIDICV_SRC_HASH "51a77b0185c2bac2a968a2163869b1ed") set(THE_ROOT "${OpenCV_BINARY_DIR}/3rdparty/kleidicv") ocv_download(FILENAME "kleidicv-${KLEIDICV_SRC_COMMIT}.tar.gz" From 260f511dfb59b55831f3ec0e03a0d2d29ec3075f Mon Sep 17 00:00:00 2001 From: Kumataro Date: Tue, 17 Dec 2024 13:40:27 +0900 Subject: [PATCH 025/102] Merge pull request #26590 from Kumataro:fix26589 Support C++20 standard #26590 Close https://github.com/opencv/opencv/issues/26589 Related https://github.com/opencv/opencv_contrib/pull/3842 Related: https://github.com/opencv/opencv/issues/20269 - do not arithmetic enums and ( different enums or floating numeric) - remove unused variable ### 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/calib3d/src/fisheye.cpp | 4 +- modules/calib3d/src/undistort.simd.hpp | 4 +- modules/calib3d/test/test_stereomatching.cpp | 4 +- modules/core/include/opencv2/core/mat.inl.hpp | 16 +- modules/core/src/matrix_sparse.cpp | 7 + .../test/test_matchers_algorithmic.cpp | 3 - .../gstreamer/gstreamer_media_adapter.cpp | 2 +- modules/highgui/src/window.cpp | 2 +- modules/highgui/src/window_gtk.cpp | 2 +- modules/highgui/src/window_w32.cpp | 2 +- .../include/opencv2/imgproc/imgproc_c.h | 10 +- modules/imgproc/src/color_lab.cpp | 10 +- modules/imgproc/src/drawing.cpp | 13 +- modules/imgproc/src/generalized_hough.cpp | 6 +- modules/imgproc/src/histogram.cpp | 2 +- modules/imgproc/src/imgwarp.cpp | 20 +-- modules/imgproc/src/imgwarp.sse4_1.cpp | 10 +- modules/imgproc/src/templmatch.cpp | 4 +- modules/imgproc/test/test_filter.cpp | 150 +++++++----------- modules/imgproc/test/test_imgwarp.cpp | 22 +-- modules/imgproc/test/test_imgwarp_strict.cpp | 2 +- modules/ml/src/boost.cpp | 2 +- modules/ml/src/rtrees.cpp | 2 +- modules/photo/src/calibrate.cpp | 2 +- modules/photo/src/merge.cpp | 2 +- modules/videoio/include/opencv2/videoio.hpp | 16 +- modules/videoio/src/cap_gstreamer.cpp | 2 +- 27 files changed, 143 insertions(+), 178 deletions(-) diff --git a/modules/calib3d/src/fisheye.cpp b/modules/calib3d/src/fisheye.cpp index 7bbddf2838..d1bff24faf 100644 --- a/modules/calib3d/src/fisheye.cpp +++ b/modules/calib3d/src/fisheye.cpp @@ -589,8 +589,8 @@ void cv::fisheye::initUndistortRectifyMap( InputArray K, InputArray D, InputArra if( m1type == CV_16SC2 ) { - int iu = cv::saturate_cast(u*cv::INTER_TAB_SIZE); - int iv = cv::saturate_cast(v*cv::INTER_TAB_SIZE); + int iu = cv::saturate_cast(u*static_cast(cv::INTER_TAB_SIZE)); + int iv = cv::saturate_cast(v*static_cast(cv::INTER_TAB_SIZE)); m1[j*2+0] = (short)(iu >> cv::INTER_BITS); m1[j*2+1] = (short)(iv >> cv::INTER_BITS); m2[j] = (ushort)((iv & (cv::INTER_TAB_SIZE-1))*cv::INTER_TAB_SIZE + (iu & (cv::INTER_TAB_SIZE-1))); diff --git a/modules/calib3d/src/undistort.simd.hpp b/modules/calib3d/src/undistort.simd.hpp index 70bac44702..f944407058 100644 --- a/modules/calib3d/src/undistort.simd.hpp +++ b/modules/calib3d/src/undistort.simd.hpp @@ -259,8 +259,8 @@ public: double v = fy*invProj*vecTilt(1) + v0; if( m1type == CV_16SC2 ) { - int iu = saturate_cast(u*INTER_TAB_SIZE); - int iv = saturate_cast(v*INTER_TAB_SIZE); + int iu = saturate_cast(u*static_cast(INTER_TAB_SIZE)); + int iv = saturate_cast(v*static_cast(INTER_TAB_SIZE)); m1[j*2] = (short)(iu >> INTER_BITS); m1[j*2+1] = (short)(iv >> INTER_BITS); m2[j] = (ushort)((iv & (INTER_TAB_SIZE-1))*INTER_TAB_SIZE + (iu & (INTER_TAB_SIZE-1))); diff --git a/modules/calib3d/test/test_stereomatching.cpp b/modules/calib3d/test/test_stereomatching.cpp index c17d92292a..ad230509df 100644 --- a/modules/calib3d/test/test_stereomatching.cpp +++ b/modules/calib3d/test/test_stereomatching.cpp @@ -789,7 +789,7 @@ protected: calcROI = getValidDisparityROI(cROI, cROI, params.mindisp, params.ndisp, params.winSize); bm->compute( leftImg, rightImg, tempDisp ); - tempDisp.convertTo(leftDisp, CV_32F, 1./StereoMatcher::DISP_SCALE); + tempDisp.convertTo(leftDisp, CV_32F, 1./static_cast(StereoMatcher::DISP_SCALE)); //check for fixed-type disparity data type Mat_ fixedFloatDisp; @@ -802,7 +802,7 @@ protected: for (int x = 0; x < leftDisp.cols; x++) { if (leftDisp.at(y, x) < params.mindisp) - leftDisp.at(y, x) = -1./StereoMatcher::DISP_SCALE; // treat disparity < mindisp as no disparity + leftDisp.at(y, x) = -1./static_cast(StereoMatcher::DISP_SCALE); // treat disparity < mindisp as no disparity } return params.winSize/2; diff --git a/modules/core/include/opencv2/core/mat.inl.hpp b/modules/core/include/opencv2/core/mat.inl.hpp index aa293255f9..bd0ba637b5 100644 --- a/modules/core/include/opencv2/core/mat.inl.hpp +++ b/modules/core/include/opencv2/core/mat.inl.hpp @@ -460,7 +460,7 @@ CV__DEBUG_NS_END template inline Mat::Mat(const std::vector<_Tp>& vec, bool copyData) - : flags(MAGIC_VAL + traits::Type<_Tp>::value + CV_MAT_CONT_FLAG), dims(2), rows((int)vec.size()), + : flags(+MAGIC_VAL + traits::Type<_Tp>::value + CV_MAT_CONT_FLAG), dims(2), rows((int)vec.size()), cols(1), data(0), datastart(0), dataend(0), datalimit(0), allocator(0), u(0), size(&rows), step(0) { if(vec.empty()) @@ -497,7 +497,7 @@ Mat::Mat(const std::initializer_list sizes, const std::initializer_list<_Tp template inline Mat::Mat(const std::array<_Tp, _Nm>& arr, bool copyData) - : flags(MAGIC_VAL + traits::Type<_Tp>::value + CV_MAT_CONT_FLAG), dims(2), rows((int)arr.size()), + : flags(+MAGIC_VAL + traits::Type<_Tp>::value + CV_MAT_CONT_FLAG), dims(2), rows((int)arr.size()), cols(1), data(0), datastart(0), dataend(0), datalimit(0), allocator(0), u(0), size(&rows), step(0) { if(arr.empty()) @@ -514,7 +514,7 @@ Mat::Mat(const std::array<_Tp, _Nm>& arr, bool copyData) template inline Mat::Mat(const Vec<_Tp, n>& vec, bool copyData) - : flags(MAGIC_VAL + traits::Type<_Tp>::value + CV_MAT_CONT_FLAG), dims(2), rows(n), cols(1), data(0), + : flags(+MAGIC_VAL + traits::Type<_Tp>::value + CV_MAT_CONT_FLAG), dims(2), rows(n), cols(1), data(0), datastart(0), dataend(0), datalimit(0), allocator(0), u(0), size(&rows), step(0) { if( !copyData ) @@ -546,7 +546,7 @@ Mat::Mat(const Matx<_Tp,m,n>& M, bool copyData) template inline Mat::Mat(const Point_<_Tp>& pt, bool copyData) - : flags(MAGIC_VAL + traits::Type<_Tp>::value + CV_MAT_CONT_FLAG), dims(2), rows(2), cols(1), data(0), + : flags(+MAGIC_VAL + traits::Type<_Tp>::value + CV_MAT_CONT_FLAG), dims(2), rows(2), cols(1), data(0), datastart(0), dataend(0), datalimit(0), allocator(0), u(0), size(&rows), step(0) { if( !copyData ) @@ -565,7 +565,7 @@ Mat::Mat(const Point_<_Tp>& pt, bool copyData) template inline Mat::Mat(const Point3_<_Tp>& pt, bool copyData) - : flags(MAGIC_VAL + traits::Type<_Tp>::value + CV_MAT_CONT_FLAG), dims(2), rows(3), cols(1), data(0), + : flags(+MAGIC_VAL + traits::Type<_Tp>::value + CV_MAT_CONT_FLAG), dims(2), rows(3), cols(1), data(0), datastart(0), dataend(0), datalimit(0), allocator(0), u(0), size(&rows), step(0) { if( !copyData ) @@ -585,7 +585,7 @@ Mat::Mat(const Point3_<_Tp>& pt, bool copyData) template inline Mat::Mat(const MatCommaInitializer_<_Tp>& commaInitializer) - : flags(MAGIC_VAL + traits::Type<_Tp>::value + CV_MAT_CONT_FLAG), dims(0), rows(0), cols(0), data(0), + : flags(+MAGIC_VAL + traits::Type<_Tp>::value + CV_MAT_CONT_FLAG), dims(0), rows(0), cols(0), data(0), datastart(0), dataend(0), allocator(0), u(0), size(&rows) { *this = commaInitializer.operator Mat_<_Tp>(); @@ -2090,7 +2090,7 @@ SparseMatConstIterator_<_Tp> SparseMat::end() const template inline SparseMat_<_Tp>::SparseMat_() { - flags = MAGIC_VAL + traits::Type<_Tp>::value; + flags = +MAGIC_VAL + traits::Type<_Tp>::value; } template inline @@ -3248,7 +3248,7 @@ const Mat_<_Tp>& operator /= (const Mat_<_Tp>& a, const MatExpr& b) template inline UMat::UMat(const std::vector<_Tp>& vec, bool copyData) -: flags(MAGIC_VAL + traits::Type<_Tp>::value + CV_MAT_CONT_FLAG), dims(2), rows((int)vec.size()), +: flags(+MAGIC_VAL + traits::Type<_Tp>::value + CV_MAT_CONT_FLAG), dims(2), rows((int)vec.size()), cols(1), allocator(0), usageFlags(USAGE_DEFAULT), u(0), offset(0), size(&rows) { if(vec.empty()) diff --git a/modules/core/src/matrix_sparse.cpp b/modules/core/src/matrix_sparse.cpp index cfc769e791..2c9edf8b3a 100644 --- a/modules/core/src/matrix_sparse.cpp +++ b/modules/core/src/matrix_sparse.cpp @@ -171,7 +171,14 @@ void SparseMat::Hdr::clear() hashtab.clear(); hashtab.resize(HASH_SIZE0); pool.clear(); +#if defined(__GNUC__) && (__GNUC__ == 13) && !defined(__clang__) && (__cplusplus >= 202002L) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstringop-overflow" +#endif pool.resize(nodeSize); +#if defined(__GNUC__) && (__GNUC__ == 13) && !defined(__clang__) && (__cplusplus >= 202002L) +#pragma GCC diagnostic pop +#endif nodeCount = freeList = 0; } diff --git a/modules/features2d/test/test_matchers_algorithmic.cpp b/modules/features2d/test/test_matchers_algorithmic.cpp index 203b640209..0679d9ce38 100644 --- a/modules/features2d/test/test_matchers_algorithmic.cpp +++ b/modules/features2d/test/test_matchers_algorithmic.cpp @@ -43,9 +43,6 @@ namespace opencv_test { namespace { -const string FEATURES2D_DIR = "features2d"; -const string IMAGE_FILENAME = "tsukuba.png"; - /****************************************************************************************\ * Algorithmic tests for descriptor matchers * \****************************************************************************************/ diff --git a/modules/gapi/src/streaming/gstreamer/gstreamer_media_adapter.cpp b/modules/gapi/src/streaming/gstreamer/gstreamer_media_adapter.cpp index b9fce83427..770f76a4ed 100644 --- a/modules/gapi/src/streaming/gstreamer/gstreamer_media_adapter.cpp +++ b/modules/gapi/src/streaming/gstreamer/gstreamer_media_adapter.cpp @@ -23,7 +23,7 @@ GStreamerMediaAdapter::GStreamerMediaAdapter(const cv::GFrameDesc& frameDesc, { #if GST_VERSION_MINOR >= 10 // Check that GstBuffer has mono-view, so we can retrieve only one video-meta - GAPI_Assert((gst_buffer_get_flags(m_buffer) & GST_VIDEO_BUFFER_FLAG_MULTIPLE_VIEW) == 0); + GAPI_Assert((gst_buffer_get_flags(m_buffer) & static_cast(GST_VIDEO_BUFFER_FLAG_MULTIPLE_VIEW)) == 0); #endif // GST_VERSION_MINOR >= 10 GstVideoMeta* videoMeta = gst_buffer_get_video_meta(m_buffer); diff --git a/modules/highgui/src/window.cpp b/modules/highgui/src/window.cpp index 4fb2169da9..cf21feefbd 100644 --- a/modules/highgui/src/window.cpp +++ b/modules/highgui/src/window.cpp @@ -219,7 +219,7 @@ CV_IMPL void cvSetWindowProperty(const char* name, int prop_id, double prop_valu //change between fullscreen or not. case cv::WND_PROP_FULLSCREEN: - if (prop_value != cv::WINDOW_NORMAL && prop_value != cv::WINDOW_FULLSCREEN) // bad argument + if ((int)prop_value != cv::WINDOW_NORMAL && (int)prop_value != cv::WINDOW_FULLSCREEN) // bad argument break; #if defined (HAVE_QT) diff --git a/modules/highgui/src/window_gtk.cpp b/modules/highgui/src/window_gtk.cpp index 178d1b7475..3a34983b81 100644 --- a/modules/highgui/src/window_gtk.cpp +++ b/modules/highgui/src/window_gtk.cpp @@ -2177,7 +2177,7 @@ public: switch (prop) { case cv::WND_PROP_FULLSCREEN: - if (value != cv::WINDOW_NORMAL && value != cv::WINDOW_FULLSCREEN) // bad arg + if ((int)value != cv::WINDOW_NORMAL && (int)value != cv::WINDOW_FULLSCREEN) // bad arg break; setModeWindow_(window, value); return true; diff --git a/modules/highgui/src/window_w32.cpp b/modules/highgui/src/window_w32.cpp index 381c9f52a4..fa9e35bd89 100644 --- a/modules/highgui/src/window_w32.cpp +++ b/modules/highgui/src/window_w32.cpp @@ -2805,7 +2805,7 @@ public: switch ((WindowPropertyFlags)prop) { case WND_PROP_FULLSCREEN: - if (value != WINDOW_NORMAL && value != WINDOW_FULLSCREEN) // bad arg + if ((int)value != WINDOW_NORMAL && (int)value != WINDOW_FULLSCREEN) // bad arg break; setModeWindow_(window, (int)value); return true; diff --git a/modules/imgproc/include/opencv2/imgproc/imgproc_c.h b/modules/imgproc/include/opencv2/imgproc/imgproc_c.h index e97b802e69..9085b0fa74 100644 --- a/modules/imgproc/include/opencv2/imgproc/imgproc_c.h +++ b/modules/imgproc/include/opencv2/imgproc/imgproc_c.h @@ -219,7 +219,7 @@ replication border mode. @see cv::warpAffine */ CVAPI(void) cvWarpAffine( const CvArr* src, CvArr* dst, const CvMat* map_matrix, - int flags CV_DEFAULT(CV_INTER_LINEAR+CV_WARP_FILL_OUTLIERS), + int flags CV_DEFAULT(+CV_INTER_LINEAR+CV_WARP_FILL_OUTLIERS), CvScalar fillval CV_DEFAULT(cvScalarAll(0)) ); /** @brief Computes affine transform matrix for mapping src[i] to dst[i] (i=0,1,2) @@ -239,7 +239,7 @@ CVAPI(CvMat*) cv2DRotationMatrix( CvPoint2D32f center, double angle, @see cv::warpPerspective */ CVAPI(void) cvWarpPerspective( const CvArr* src, CvArr* dst, const CvMat* map_matrix, - int flags CV_DEFAULT(CV_INTER_LINEAR+CV_WARP_FILL_OUTLIERS), + int flags CV_DEFAULT(+CV_INTER_LINEAR+CV_WARP_FILL_OUTLIERS), CvScalar fillval CV_DEFAULT(cvScalarAll(0)) ); /** @brief Computes perspective transform matrix for mapping src[i] to dst[i] (i=0,1,2,3) @@ -254,7 +254,7 @@ CVAPI(CvMat*) cvGetPerspectiveTransform( const CvPoint2D32f* src, */ CVAPI(void) cvRemap( const CvArr* src, CvArr* dst, const CvArr* mapx, const CvArr* mapy, - int flags CV_DEFAULT(CV_INTER_LINEAR+CV_WARP_FILL_OUTLIERS), + int flags CV_DEFAULT(+CV_INTER_LINEAR+CV_WARP_FILL_OUTLIERS), CvScalar fillval CV_DEFAULT(cvScalarAll(0)) ); /** @brief Converts mapx & mapy from floating-point to integer formats for cvRemap @@ -268,14 +268,14 @@ CVAPI(void) cvConvertMaps( const CvArr* mapx, const CvArr* mapy, */ CVAPI(void) cvLogPolar( const CvArr* src, CvArr* dst, CvPoint2D32f center, double M, - int flags CV_DEFAULT(CV_INTER_LINEAR+CV_WARP_FILL_OUTLIERS)); + int flags CV_DEFAULT(+CV_INTER_LINEAR+CV_WARP_FILL_OUTLIERS)); /** Performs forward or inverse linear-polar image transform @see cv::warpPolar */ CVAPI(void) cvLinearPolar( const CvArr* src, CvArr* dst, CvPoint2D32f center, double maxRadius, - int flags CV_DEFAULT(CV_INTER_LINEAR+CV_WARP_FILL_OUTLIERS)); + int flags CV_DEFAULT(+CV_INTER_LINEAR+CV_WARP_FILL_OUTLIERS)); #ifdef _MSC_VER #pragma warning( pop ) diff --git a/modules/imgproc/src/color_lab.cpp b/modules/imgproc/src/color_lab.cpp index dbca43c8cd..dd6fb52949 100644 --- a/modules/imgproc/src/color_lab.cpp +++ b/modules/imgproc/src/color_lab.cpp @@ -1982,7 +1982,7 @@ struct RGB2Lab_f clipv(bvec0); clipv(bvec1); #undef clipv /* int iR = R*LAB_BASE, iG = G*LAB_BASE, iB = B*LAB_BASE, iL, ia, ib; */ - v_float32 basef = vx_setall_f32(LAB_BASE); + v_float32 basef = vx_setall_f32(static_cast(LAB_BASE)); rvec0 = v_mul(rvec0, basef), gvec0 = v_mul(gvec0, basef), bvec0 = v_mul(bvec0, basef); rvec1 = v_mul(rvec1, basef), gvec1 = v_mul(gvec1, basef), bvec1 = v_mul(bvec1, basef); @@ -2013,14 +2013,14 @@ struct RGB2Lab_f b_vec0 = v_cvt_f32(i_bvec0); b_vec1 = v_cvt_f32(i_bvec1); /* dst[i] = L*100.0f */ - v_float32 v100dBase = vx_setall_f32(100.0f/LAB_BASE); + v_float32 v100dBase = vx_setall_f32(100.0f / static_cast(LAB_BASE)); l_vec0 = v_mul(l_vec0, v100dBase); l_vec1 = v_mul(l_vec1, v100dBase); /* dst[i + 1] = a*256.0f - 128.0f; dst[i + 2] = b*256.0f - 128.0f; */ - v_float32 v256dBase = vx_setall_f32(256.0f/LAB_BASE), vm128 = vx_setall_f32(-128.f); + v_float32 v256dBase = vx_setall_f32(256.0f / static_cast(LAB_BASE)), vm128 = vx_setall_f32(-128.f); a_vec0 = v_fma(a_vec0, v256dBase, vm128); a_vec1 = v_fma(a_vec1, v256dBase, vm128); b_vec0 = v_fma(b_vec0, v256dBase, vm128); @@ -2038,10 +2038,10 @@ struct RGB2Lab_f float G = clip(src[1]); float B = clip(src[bIdx^2]); - int iR = cvRound(R*LAB_BASE), iG = cvRound(G*LAB_BASE), iB = cvRound(B*LAB_BASE); + int iR = cvRound(R*static_cast(LAB_BASE)), iG = cvRound(G*static_cast(LAB_BASE)), iB = cvRound(B*static_cast(LAB_BASE)); int iL, ia, ib; trilinearInterpolate(iR, iG, iB, LABLUVLUTs16.RGB2LabLUT_s16, iL, ia, ib); - float L = iL*1.0f/LAB_BASE, a = ia*1.0f/LAB_BASE, b = ib*1.0f/LAB_BASE; + float L = iL*1.0f/static_cast(LAB_BASE), a = ia*1.0f/static_cast(LAB_BASE), b = ib*1.0f/static_cast(LAB_BASE); dst[i] = L*100.0f; dst[i + 1] = a*256.0f - 128.0f; diff --git a/modules/imgproc/src/drawing.cpp b/modules/imgproc/src/drawing.cpp index 7ba512f7f8..d0368cfa95 100644 --- a/modules/imgproc/src/drawing.cpp +++ b/modules/imgproc/src/drawing.cpp @@ -43,7 +43,6 @@ using namespace cv; namespace cv { - enum { XY_SHIFT = 16, XY_ONE = 1 << XY_SHIFT, DRAWING_STORAGE_BLOCK = (1<<12) - 256 }; static const int MAX_THICKNESS = 32767; @@ -1027,8 +1026,8 @@ EllipseEx( Mat& img, Point2l center, Size2l axes, for (unsigned int i = 0; i < _v.size(); ++i) { Point2l pt; - pt.x = (int64)cvRound(_v[i].x / XY_ONE) << XY_SHIFT; - pt.y = (int64)cvRound(_v[i].y / XY_ONE) << XY_SHIFT; + pt.x = (int64)cvRound(_v[i].x / static_cast(XY_ONE)) << XY_SHIFT; + pt.y = (int64)cvRound(_v[i].y / static_cast(XY_ONE)) << XY_SHIFT; pt.x += cvRound(_v[i].x - pt.x); pt.y += cvRound(_v[i].y - pt.y); if (pt != prevPt) { @@ -1645,7 +1644,7 @@ static void ThickLine( Mat& img, Point2l p0, Point2l p1, const void* color, int thickness, int line_type, int flags, int shift ) { - static const double INV_XY_ONE = 1./XY_ONE; + static const double INV_XY_ONE = 1./static_cast(XY_ONE); p0.x <<= XY_SHIFT - shift; p0.y <<= XY_SHIFT - shift; @@ -1981,8 +1980,8 @@ void ellipse(InputOutputArray _img, const RotatedRect& box, const Scalar& color, int _angle = cvRound(box.angle); Point2l center(cvRound(box.center.x), cvRound(box.center.y)); - center.x = (center.x << XY_SHIFT) + cvRound((box.center.x - center.x)*XY_ONE); - center.y = (center.y << XY_SHIFT) + cvRound((box.center.y - center.y)*XY_ONE); + center.x = (center.x << XY_SHIFT) + cvRound((box.center.x - center.x)*static_cast(XY_ONE)); + center.y = (center.y << XY_SHIFT) + cvRound((box.center.y - center.y)*static_cast(XY_ONE)); Size2l axes(cvRound(box.size.width), cvRound(box.size.height)); axes.width = (axes.width << (XY_SHIFT - 1)) + cvRound((box.size.width - axes.width)*(XY_ONE>>1)); @@ -2303,7 +2302,7 @@ void putText( InputOutputArray _img, const String& text, Point org, scalarToRawData(color, buf, img.type(), 0); int base_line = -(ascii[0] & 15); - int hscale = cvRound(fontScale*XY_ONE), vscale = hscale; + int hscale = cvRound(fontScale * static_cast(XY_ONE)), vscale = hscale; if( line_type == cv::LINE_AA && img.depth() != CV_8U ) line_type = 8; diff --git a/modules/imgproc/src/generalized_hough.cpp b/modules/imgproc/src/generalized_hough.cpp index 5ff9f933ed..6a2ca5f032 100644 --- a/modules/imgproc/src/generalized_hough.cpp +++ b/modules/imgproc/src/generalized_hough.cpp @@ -692,7 +692,11 @@ namespace getContourPoints(edges, dx, dy, points); features.resize(levels_ + 1); - std::for_each(features.begin(), features.end(), [=](std::vector& e) { e.clear(); e.reserve(maxBufferSize_); }); + const size_t maxBufferSize = maxBufferSize_; + std::for_each(features.begin(), features.end(), [maxBufferSize](std::vector& e) { + e.clear(); + e.reserve(maxBufferSize); + }); for (size_t i = 0; i < points.size(); ++i) { diff --git a/modules/imgproc/src/histogram.cpp b/modules/imgproc/src/histogram.cpp index 02e97ba129..48c1223059 100644 --- a/modules/imgproc/src/histogram.cpp +++ b/modules/imgproc/src/histogram.cpp @@ -1283,7 +1283,7 @@ void cv::calcHist( InputArrayOfArrays images, const std::vector& channels, CV_OCL_RUN(images.total() == 1 && channels.size() == 1 && images.channels(0) == 1 && channels[0] == 0 && images.isUMatVector() && mask.empty() && !accumulate && histSize.size() == 1 && histSize[0] == BINS && ranges.size() == 2 && - ranges[0] == 0 && ranges[1] == BINS, + ranges[0] == 0 && ranges[1] == static_cast(BINS), ocl_calcHist(images, hist)) int i, dims = (int)histSize.size(), rsz = (int)ranges.size(), csz = (int)channels.size(); diff --git a/modules/imgproc/src/imgwarp.cpp b/modules/imgproc/src/imgwarp.cpp index f2ece02aa3..dfc718bf87 100644 --- a/modules/imgproc/src/imgwarp.cpp +++ b/modules/imgproc/src/imgwarp.cpp @@ -1274,8 +1274,8 @@ public: #endif for( ; x1 < bcols; x1++ ) { - int sx = cvRound(sX[x1]*INTER_TAB_SIZE); - int sy = cvRound(sY[x1]*INTER_TAB_SIZE); + int sx = cvRound(sX[x1]*static_cast(INTER_TAB_SIZE)); + int sy = cvRound(sY[x1]*static_cast(INTER_TAB_SIZE)); int v = (sy & (INTER_TAB_SIZE-1))*INTER_TAB_SIZE + (sx & (INTER_TAB_SIZE-1)); XY[x1*2] = saturate_cast(sx >> INTER_BITS); XY[x1*2+1] = saturate_cast(sy >> INTER_BITS); @@ -1314,8 +1314,8 @@ public: for( ; x1 < bcols; x1++ ) { - int sx = cvRound(sXY[x1*2]*INTER_TAB_SIZE); - int sy = cvRound(sXY[x1*2+1]*INTER_TAB_SIZE); + int sx = cvRound(sXY[x1*2]*static_cast(INTER_TAB_SIZE)); + int sy = cvRound(sXY[x1*2+1]*static_cast(INTER_TAB_SIZE)); int v = (sy & (INTER_TAB_SIZE-1))*INTER_TAB_SIZE + (sx & (INTER_TAB_SIZE-1)); XY[x1*2] = saturate_cast(sx >> INTER_BITS); XY[x1*2+1] = saturate_cast(sy >> INTER_BITS); @@ -1991,7 +1991,7 @@ void cv::convertMaps( InputArray _map1, InputArray _map2, bool useSSE4_1 = CV_CPU_HAS_SUPPORT_SSE4_1; #endif - const float scale = 1.f/INTER_TAB_SIZE; + const float scale = 1.f/static_cast(INTER_TAB_SIZE); int x, y; for( y = 0; y < size.height; y++ ) { @@ -2071,8 +2071,8 @@ void cv::convertMaps( InputArray _map1, InputArray _map2, #endif for( ; x < size.width; x++ ) { - int ix = saturate_cast(src1f[x]*INTER_TAB_SIZE); - int iy = saturate_cast(src2f[x]*INTER_TAB_SIZE); + int ix = saturate_cast(src1f[x]*static_cast(INTER_TAB_SIZE)); + int iy = saturate_cast(src2f[x]*static_cast(INTER_TAB_SIZE)); dst1[x*2] = saturate_cast(ix >> INTER_BITS); dst1[x*2+1] = saturate_cast(iy >> INTER_BITS); dst2[x] = (ushort)((iy & (INTER_TAB_SIZE-1))*INTER_TAB_SIZE + (ix & (INTER_TAB_SIZE-1))); @@ -2117,8 +2117,8 @@ void cv::convertMaps( InputArray _map1, InputArray _map2, #endif for( ; x < size.width; x++ ) { - int ix = saturate_cast(src1f[x*2]*INTER_TAB_SIZE); - int iy = saturate_cast(src1f[x*2+1]*INTER_TAB_SIZE); + int ix = saturate_cast(src1f[x*2]*static_cast(INTER_TAB_SIZE)); + int iy = saturate_cast(src1f[x*2+1]*static_cast(INTER_TAB_SIZE)); dst1[x*2] = saturate_cast(ix >> INTER_BITS); dst1[x*2+1] = saturate_cast(iy >> INTER_BITS); dst2[x] = (ushort)((iy & (INTER_TAB_SIZE-1))*INTER_TAB_SIZE + (ix & (INTER_TAB_SIZE-1))); @@ -3143,7 +3143,7 @@ void WarpPerspectiveLine_Process_CV_SIMD(const double *M, short* xy, short* alph for( ; x1 < bw; x1++ ) { double W = W0 + M[6]*x1; - W = W ? INTER_TAB_SIZE/W : 0; + W = W ? static_cast(INTER_TAB_SIZE)/W : 0; double fX = std::max((double)INT_MIN, std::min((double)INT_MAX, (X0 + M[0]*x1)*W)); double fY = std::max((double)INT_MIN, std::min((double)INT_MAX, (Y0 + M[3]*x1)*W)); int X = saturate_cast(fX); diff --git a/modules/imgproc/src/imgwarp.sse4_1.cpp b/modules/imgproc/src/imgwarp.sse4_1.cpp index 26ece533bc..9f7ac82fca 100644 --- a/modules/imgproc/src/imgwarp.sse4_1.cpp +++ b/modules/imgproc/src/imgwarp.sse4_1.cpp @@ -132,8 +132,8 @@ void convertMaps_32f1c16s_SSE41(const float* src1f, const float* src2f, short* d } for (; x < width; x++) { - int ix = saturate_cast(src1f[x] * INTER_TAB_SIZE); - int iy = saturate_cast(src2f[x] * INTER_TAB_SIZE); + int ix = saturate_cast(src1f[x] * static_cast(INTER_TAB_SIZE)); + int iy = saturate_cast(src2f[x] * static_cast(INTER_TAB_SIZE)); dst1[x * 2] = saturate_cast(ix >> INTER_BITS); dst1[x * 2 + 1] = saturate_cast(iy >> INTER_BITS); dst2[x] = (ushort)((iy & (INTER_TAB_SIZE - 1))*INTER_TAB_SIZE + (ix & (INTER_TAB_SIZE - 1))); @@ -165,8 +165,8 @@ void convertMaps_32f2c16s_SSE41(const float* src1f, short* dst1, ushort* dst2, i } for (; x < width; x++) { - int ix = saturate_cast(src1f[x * 2] * INTER_TAB_SIZE); - int iy = saturate_cast(src1f[x * 2 + 1] * INTER_TAB_SIZE); + int ix = saturate_cast(src1f[x * 2] * static_cast(INTER_TAB_SIZE)); + int iy = saturate_cast(src1f[x * 2 + 1] * static_cast(INTER_TAB_SIZE)); dst1[x * 2] = saturate_cast(ix >> INTER_BITS); dst1[x * 2 + 1] = saturate_cast(iy >> INTER_BITS); dst2[x] = (ushort)((iy & (INTER_TAB_SIZE - 1))*INTER_TAB_SIZE + (ix & (INTER_TAB_SIZE - 1))); @@ -444,7 +444,7 @@ public: for (; x1 < bw; x1++) { double W = W0 + M[6] * x1; - W = W ? INTER_TAB_SIZE / W : 0; + W = W ? static_cast(INTER_TAB_SIZE) / W : 0; double fX = std::max((double)INT_MIN, std::min((double)INT_MAX, (X0 + M[0] * x1)*W)); double fY = std::max((double)INT_MIN, std::min((double)INT_MAX, (Y0 + M[3] * x1)*W)); int X = saturate_cast(fX); diff --git a/modules/imgproc/src/templmatch.cpp b/modules/imgproc/src/templmatch.cpp index 5998bcbac7..5671025b0f 100644 --- a/modules/imgproc/src/templmatch.cpp +++ b/modules/imgproc/src/templmatch.cpp @@ -1056,7 +1056,7 @@ static bool ipp_crossCorr(const Mat& src, const Mat& tpl, Mat& dst, bool normed) if (ippiCrossCorrNorm==0) return false; - IppEnum funCfg = (IppEnum)(ippAlgAuto | ippiROIValid); + IppEnum funCfg = (IppEnum)(+ippAlgAuto | ippiROIValid); if(normed) funCfg |= ippiNorm; else @@ -1093,7 +1093,7 @@ static bool ipp_sqrDistance(const Mat& src, const Mat& tpl, Mat& dst) if (ippiSqrDistanceNorm==0) return false; - IppEnum funCfg = (IppEnum)(ippAlgAuto | ippiROIValid | ippiNormNone); + IppEnum funCfg = (IppEnum)(+ippAlgAuto | ippiROIValid | ippiNormNone); status = ippiSqrDistanceNormGetBufferSize(srcRoiSize, tplRoiSize, funCfg, &bufSize); if ( status < 0 ) return false; diff --git a/modules/imgproc/test/test_filter.cpp b/modules/imgproc/test/test_filter.cpp index 4038498062..685743630f 100644 --- a/modules/imgproc/test/test_filter.cpp +++ b/modules/imgproc/test/test_filter.cpp @@ -1865,107 +1865,65 @@ TEST(Imgproc_PreCornerDetect, accuracy) { CV_PreCornerDetectTest test; test.safe TEST(Imgproc_Integral, accuracy) { CV_IntegralTest test; test.safe_run(); } ////////////////////////////////////////////////////////////////////////////////// +typedef std::pair Imgproc_DepthAndDepth; +typedef testing::TestWithParam< Imgproc_DepthAndDepth> Imgproc_FilterSupportedFormats; -class CV_FilterSupportedFormatsTest : public cvtest::BaseTest +TEST_P(Imgproc_FilterSupportedFormats, normal) { -public: - CV_FilterSupportedFormatsTest() {} - ~CV_FilterSupportedFormatsTest() {} -protected: - void run(int) - { - const int depths[][2] = - { - {CV_8U, CV_8U}, - {CV_8U, CV_16U}, - {CV_8U, CV_16S}, - {CV_8U, CV_32F}, - {CV_8U, CV_64F}, - {CV_16U, CV_16U}, - {CV_16U, CV_32F}, - {CV_16U, CV_64F}, - {CV_16S, CV_16S}, - {CV_16S, CV_32F}, - {CV_16S, CV_64F}, - {CV_32F, CV_32F}, - {CV_64F, CV_64F}, - {-1, -1} - }; + // use some "odd" size to do yet another smoke + // testing of the non-SIMD loop tails + Size sz(163, 117); + Mat small_kernel(5, 5, CV_32F), big_kernel(21, 21, CV_32F); + Mat kernelX(11, 1, CV_32F), kernelY(7, 1, CV_32F); + Mat symkernelX(11, 1, CV_32F), symkernelY(7, 1, CV_32F); + randu(small_kernel, -10, 10); + randu(big_kernel, -1, 1); + randu(kernelX, -1, 1); + randu(kernelY, -1, 1); + flip(kernelX, symkernelX, 0); + symkernelX += kernelX; + flip(kernelY, symkernelY, 0); + symkernelY += kernelY; - int i = 0; - volatile int fidx = -1; - try - { - // use some "odd" size to do yet another smoke - // testing of the non-SIMD loop tails - Size sz(163, 117); - Mat small_kernel(5, 5, CV_32F), big_kernel(21, 21, CV_32F); - Mat kernelX(11, 1, CV_32F), kernelY(7, 1, CV_32F); - Mat symkernelX(11, 1, CV_32F), symkernelY(7, 1, CV_32F); - randu(small_kernel, -10, 10); - randu(big_kernel, -1, 1); - randu(kernelX, -1, 1); - randu(kernelY, -1, 1); - flip(kernelX, symkernelX, 0); - symkernelX += kernelX; - flip(kernelY, symkernelY, 0); - symkernelY += kernelY; + Mat elem_ellipse = getStructuringElement(MORPH_ELLIPSE, Size(7, 7)); + Mat elem_rect = getStructuringElement(MORPH_RECT, Size(7, 7)); - Mat elem_ellipse = getStructuringElement(MORPH_ELLIPSE, Size(7, 7)); - Mat elem_rect = getStructuringElement(MORPH_RECT, Size(7, 7)); + int sdepth = std::get<0>(GetParam()); + int ddepth = std::get<1>(GetParam()); + Mat src(sz, CV_MAKETYPE(sdepth, 5)), dst; + randu(src, 0, 100); + // non-separable filtering with a small kernel + EXPECT_NO_THROW(cv::filter2D(src, dst, ddepth, small_kernel)); + EXPECT_NO_THROW(cv::filter2D(src, dst, ddepth, big_kernel)); + EXPECT_NO_THROW(cv::sepFilter2D(src, dst, ddepth, kernelX, kernelY)); + EXPECT_NO_THROW(cv::sepFilter2D(src, dst, ddepth, symkernelX, symkernelY)); + EXPECT_NO_THROW(cv::Sobel(src, dst, ddepth, 2, 0, 5)); + EXPECT_NO_THROW(cv::Scharr(src, dst, ddepth, 0, 1)); + if( sdepth != ddepth ) + return; + EXPECT_NO_THROW(cv::GaussianBlur(src, dst, Size(5, 5), 1.2, 1.2)); + EXPECT_NO_THROW(cv::blur(src, dst, Size(11, 11))); + EXPECT_NO_THROW(cv::morphologyEx(src, dst, MORPH_GRADIENT, elem_ellipse)); + EXPECT_NO_THROW(cv::morphologyEx(src, dst, MORPH_GRADIENT, elem_rect)); +} - for( i = 0; depths[i][0] >= 0; i++ ) - { - int sdepth = depths[i][0]; - int ddepth = depths[i][1]; - Mat src(sz, CV_MAKETYPE(sdepth, 5)), dst; - randu(src, 0, 100); - // non-separable filtering with a small kernel - fidx = 0; - cv::filter2D(src, dst, ddepth, small_kernel); - fidx++; - cv::filter2D(src, dst, ddepth, big_kernel); - fidx++; - cv::sepFilter2D(src, dst, ddepth, kernelX, kernelY); - fidx++; - cv::sepFilter2D(src, dst, ddepth, symkernelX, symkernelY); - fidx++; - cv::Sobel(src, dst, ddepth, 2, 0, 5); - fidx++; - cv::Scharr(src, dst, ddepth, 0, 1); - if( sdepth != ddepth ) - continue; - fidx++; - cv::GaussianBlur(src, dst, Size(5, 5), 1.2, 1.2); - fidx++; - cv::blur(src, dst, Size(11, 11)); - fidx++; - cv::morphologyEx(src, dst, MORPH_GRADIENT, elem_ellipse); - fidx++; - cv::morphologyEx(src, dst, MORPH_GRADIENT, elem_rect); - } - } - catch(...) - { - ts->printf(cvtest::TS::LOG, "Combination of depths %d => %d in %s is not supported (yet it should be)", - depths[i][0], depths[i][1], - fidx == 0 ? "filter2D (small kernel)" : - fidx == 1 ? "filter2D (large kernel)" : - fidx == 2 ? "sepFilter2D" : - fidx == 3 ? "sepFilter2D (symmetrical/asymmetrical kernel)" : - fidx == 4 ? "Sobel" : - fidx == 5 ? "Scharr" : - fidx == 6 ? "GaussianBlur" : - fidx == 7 ? "blur" : - fidx == 8 || fidx == 9 ? "morphologyEx" : - "unknown???"); - - ts->set_failed_test_info(cvtest::TS::FAIL_MISMATCH); - } - } -}; - -TEST(Imgproc_Filtering, supportedFormats) { CV_FilterSupportedFormatsTest test; test.safe_run(); } +INSTANTIATE_TEST_CASE_P(/**/, Imgproc_FilterSupportedFormats, + testing::Values( + make_pair( CV_8U, CV_8U ), + make_pair( CV_8U, CV_16U ), + make_pair( CV_8U, CV_16S ), + make_pair( CV_8U, CV_32F), + make_pair( CV_8U, CV_64F), + make_pair( CV_16U, CV_16U), + make_pair( CV_16U, CV_32F), + make_pair( CV_16U, CV_64F), + make_pair( CV_16S, CV_16S), + make_pair( CV_16S, CV_32F), + make_pair( CV_16S, CV_64F), + make_pair( CV_32F, CV_32F), + make_pair( CV_64F, CV_64F) + ) +); TEST(Imgproc_Blur, borderTypes) { diff --git a/modules/imgproc/test/test_imgwarp.cpp b/modules/imgproc/test/test_imgwarp.cpp index 3bc89b8a13..4cf99b4704 100644 --- a/modules/imgproc/test/test_imgwarp.cpp +++ b/modules/imgproc/test/test_imgwarp.cpp @@ -282,7 +282,7 @@ void CV_ResizeTest::prepare_to_validation( int /*test_case_idx*/ ) int elem_size = CV_ELEM_SIZE(src->type); int drows = dst->rows, dcols = dst->cols; - if( interpolation == CV_INTER_NN ) + if( interpolation == cv::INTER_NEAREST ) { for( j = 0; j < dcols; j++ ) { @@ -373,7 +373,7 @@ void CV_ResizeExactTest::get_test_array_types_and_sizes(int test_case_idx, vecto ///////////////////////// static void test_remap( const Mat& src, Mat& dst, const Mat& mapx, const Mat& mapy, - Mat* mask=0, int interpolation=CV_INTER_LINEAR ) + Mat* mask=0, int interpolation=cv::INTER_LINEAR ) { int x, y, k; int drows = dst.rows, dcols = dst.cols; @@ -384,7 +384,7 @@ static void test_remap( const Mat& src, Mat& dst, const Mat& mapx, const Mat& ma int step = (int)(src.step / CV_ELEM_SIZE(depth)); int delta; - if( interpolation != CV_INTER_CUBIC ) + if( interpolation != cv::INTER_CUBIC ) { delta = 0; scols -= 1; srows -= 1; @@ -762,7 +762,7 @@ void CV_RemapTest::get_test_array_types_and_sizes( int test_case_idx, vector Date: Sat, 14 Dec 2024 04:34:43 +1000 Subject: [PATCH 027/102] Cocoa/highgui: fix leak in cvGetWindowRect_COCOA --- modules/highgui/src/window_cocoa.mm | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/modules/highgui/src/window_cocoa.mm b/modules/highgui/src/window_cocoa.mm index 7e364220fa..eccac9f468 100644 --- a/modules/highgui/src/window_cocoa.mm +++ b/modules/highgui/src/window_cocoa.mm @@ -662,14 +662,16 @@ CvRect cvGetWindowRect_COCOA( const char* name ) { CV_ERROR( CV_StsNullPtr, "NULL window" ); } else { - NSRect rect = [window frame]; + @autoreleasepool { + NSRect rect = [window frame]; #if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_6 - NSPoint pt = [window convertRectToScreen:rect].origin; + NSPoint pt = [window convertRectToScreen:rect].origin; #else - NSPoint pt = [window convertBaseToScreen:rect.origin]; + NSPoint pt = [window convertBaseToScreen:rect.origin]; #endif - NSSize sz = [[[window contentView] image] size]; - result = cvRect(pt.x, pt.y, sz.width, sz.height); + NSSize sz = [[[window contentView] image] size]; + result = cvRect(pt.x, pt.y, sz.width, sz.height); + } } __END__; From a8f401993248a0bb44a7260568ed206033afdd9d Mon Sep 17 00:00:00 2001 From: Alexander Smorkalov Date: Wed, 27 Nov 2024 14:34:33 +0300 Subject: [PATCH 028/102] Made some pre-defined Python types optional to disable modules --- .../typing_stubs_generation/generation.py | 8 + .../nodes/type_node.py | 145 +++++++++----- .../predefined_types.py | 180 ++++++++++-------- 3 files changed, 205 insertions(+), 128 deletions(-) diff --git a/modules/python/src2/typing_stubs_generation/generation.py b/modules/python/src2/typing_stubs_generation/generation.py index 563c09cc87..084a37a94b 100644 --- a/modules/python/src2/typing_stubs_generation/generation.py +++ b/modules/python/src2/typing_stubs_generation/generation.py @@ -706,6 +706,10 @@ def _generate_typing_module(root: NamespaceNode, output_path: Path) -> None: output_path (Path): Path to typing module directory, where __init__.pyi will be written. """ + + def has_all_required_modules(type_node: TypeNode) -> bool: + return all(em in root.namespaces for em in node.required_modules) + def register_alias_links_from_aggregated_type(type_node: TypeNode) -> None: assert isinstance(type_node, AggregatedTypeNode), \ f"Provided type node '{type_node.ctype_name}' is not an aggregated type" @@ -791,6 +795,10 @@ def _generate_typing_module(root: NamespaceNode, output_path: Path) -> None: # Resolve each node and register aliases TypeNode.compatible_to_runtime_usage = True for node in PREDEFINED_TYPES.values(): + # if node does not have at least one required module skip it + # e.g. GArgs requires G-API module, so if build without G-API GArgs is not included + if not has_all_required_modules(node): + continue node.resolve(root) if isinstance(node, AliasTypeNode): register_alias(node) diff --git a/modules/python/src2/typing_stubs_generation/nodes/type_node.py b/modules/python/src2/typing_stubs_generation/nodes/type_node.py index 3f242e730e..a1b7ce5b46 100644 --- a/modules/python/src2/typing_stubs_generation/nodes/type_node.py +++ b/modules/python/src2/typing_stubs_generation/nodes/type_node.py @@ -1,6 +1,7 @@ from typing import Sequence, Generator, Tuple, Optional, Union import weakref import abc +from itertools import chain from .node import ASTNode, ASTNodeType @@ -30,8 +31,9 @@ class TypeNode(abc.ABC): "typing.Optional[Size]" """ - def __init__(self, ctype_name: str) -> None: + def __init__(self, ctype_name: str, required_modules: Tuple[str, ...] = ()) -> None: self.ctype_name = ctype_name + self._required_modules = required_modules @abc.abstractproperty def typename(self) -> str: @@ -113,6 +115,10 @@ class TypeNode(abc.ABC): """ yield from () + @property + def required_modules(self) -> Tuple[str, ...]: + return self._required_modules + @property def is_resolved(self) -> bool: return True @@ -173,8 +179,10 @@ class AnyTypeNode(TypeNode): class PrimitiveTypeNode(TypeNode): """Type node representing a primitive built-in types e.g. int, float, str. """ - def __init__(self, ctype_name: str, typename: Optional[str] = None) -> None: - super().__init__(ctype_name) + def __init__(self, ctype_name: str, + typename: Optional[str] = None, + required_modules: Tuple[str, ...] = ()) -> None: + super().__init__(ctype_name, required_modules) self._typename = typename if typename is not None else ctype_name @property @@ -182,28 +190,32 @@ class PrimitiveTypeNode(TypeNode): return self._typename @classmethod - def int_(cls, ctype_name: Optional[str] = None): + def int_(cls, ctype_name: Optional[str] = None, + required_modules: Tuple[str, ...] = ()): if ctype_name is None: ctype_name = "int" - return PrimitiveTypeNode(ctype_name, typename="int") + return PrimitiveTypeNode(ctype_name, typename="int", required_modules=required_modules) @classmethod - def float_(cls, ctype_name: Optional[str] = None): + def float_(cls, ctype_name: Optional[str] = None, + required_modules: Tuple[str, ...] = ()): if ctype_name is None: ctype_name = "float" - return PrimitiveTypeNode(ctype_name, typename="float") + return PrimitiveTypeNode(ctype_name, typename="float", required_modules=required_modules) @classmethod - def bool_(cls, ctype_name: Optional[str] = None): + def bool_(cls, ctype_name: Optional[str] = None, + required_modules: Tuple[str, ...] = ()): if ctype_name is None: ctype_name = "bool" - return PrimitiveTypeNode(ctype_name, typename="bool") + return PrimitiveTypeNode(ctype_name, typename="bool", required_modules=required_modules) @classmethod - def str_(cls, ctype_name: Optional[str] = None): + def str_(cls, ctype_name: Optional[str] = None, + required_modules: Tuple[str, ...] = ()): if ctype_name is None: ctype_name = "string" - return PrimitiveTypeNode(ctype_name, "str") + return PrimitiveTypeNode(ctype_name, "str", required_modules=required_modules) class AliasRefTypeNode(TypeNode): @@ -224,8 +236,9 @@ class AliasRefTypeNode(TypeNode): ``` """ def __init__(self, alias_ctype_name: str, - alias_export_name: Optional[str] = None): - super().__init__(alias_ctype_name) + alias_export_name: Optional[str] = None, + required_modules: Tuple[str, ...] = ()): + super().__init__(alias_ctype_name, required_modules) if alias_export_name is None: self.alias_export_name = alias_ctype_name else: @@ -258,8 +271,9 @@ class AliasTypeNode(TypeNode): """ def __init__(self, ctype_name: str, value: TypeNode, export_name: Optional[str] = None, - doc: Optional[str] = None) -> None: - super().__init__(ctype_name) + doc: Optional[str] = None, + required_modules: Tuple[str, ...] = ()) -> None: + super().__init__(ctype_name, required_modules) self.value = value self._export_name = export_name self.doc = doc @@ -298,20 +312,21 @@ class AliasTypeNode(TypeNode): @classmethod def int_(cls, ctype_name: str, export_name: Optional[str] = None, - doc: Optional[str] = None): - return cls(ctype_name, PrimitiveTypeNode.int_(), export_name, doc) + doc: Optional[str] = None, required_modules: Tuple[str, ...] = ()): + return cls(ctype_name, PrimitiveTypeNode.int_(), export_name, doc, required_modules) @classmethod def float_(cls, ctype_name: str, export_name: Optional[str] = None, - doc: Optional[str] = None): - return cls(ctype_name, PrimitiveTypeNode.float_(), export_name, doc) + doc: Optional[str] = None, required_modules: Tuple[str, ...] = ()): + return cls(ctype_name, PrimitiveTypeNode.float_(), export_name, doc, required_modules) @classmethod def array_ref_(cls, ctype_name: str, array_ref_name: str, shape: Optional[Tuple[int, ...]], dtype: Optional[str] = None, export_name: Optional[str] = None, - doc: Optional[str] = None): + doc: Optional[str] = None, + required_modules: Tuple[str, ...] = ()): """Create alias to array reference alias `array_ref_name`. This is required to preserve backward compatibility with Python < 3.9 @@ -332,65 +347,74 @@ class AliasTypeNode(TypeNode): else: doc += f". NDArray(shape={shape}, dtype={dtype})" return cls(ctype_name, AliasRefTypeNode(array_ref_name), - export_name, doc) + export_name, doc, required_modules) @classmethod def union_(cls, ctype_name: str, items: Tuple[TypeNode, ...], export_name: Optional[str] = None, - doc: Optional[str] = None): + doc: Optional[str] = None, + required_modules: Tuple[str, ...] = ()): return cls(ctype_name, UnionTypeNode(ctype_name, items), - export_name, doc) + export_name, doc, required_modules) @classmethod def optional_(cls, ctype_name: str, item: TypeNode, export_name: Optional[str] = None, - doc: Optional[str] = None): - return cls(ctype_name, OptionalTypeNode(item), export_name, doc) + doc: Optional[str] = None, + required_modules: Tuple[str, ...] = ()): + return cls(ctype_name, OptionalTypeNode(item), export_name, doc, required_modules) @classmethod def sequence_(cls, ctype_name: str, item: TypeNode, export_name: Optional[str] = None, - doc: Optional[str] = None): + doc: Optional[str] = None, + required_modules: Tuple[str, ...] = ()): return cls(ctype_name, SequenceTypeNode(ctype_name, item), - export_name, doc) + export_name, doc, required_modules) @classmethod def tuple_(cls, ctype_name: str, items: Tuple[TypeNode, ...], export_name: Optional[str] = None, - doc: Optional[str] = None): + doc: Optional[str] = None, + required_modules: Tuple[str, ...] = ()): return cls(ctype_name, TupleTypeNode(ctype_name, items), - export_name, doc) + export_name, doc, required_modules) @classmethod def class_(cls, ctype_name: str, class_name: str, export_name: Optional[str] = None, - doc: Optional[str] = None): + doc: Optional[str] = None, + required_modules: Tuple[str, ...] = ()): return cls(ctype_name, ASTNodeTypeNode(class_name), - export_name, doc) + export_name, doc, required_modules) @classmethod def callable_(cls, ctype_name: str, arg_types: Union[TypeNode, Sequence[TypeNode]], ret_type: TypeNode = NoneTypeNode("void"), export_name: Optional[str] = None, - doc: Optional[str] = None): + doc: Optional[str] = None, + required_modules: Tuple[str, ...] = ()): return cls(ctype_name, CallableTypeNode(ctype_name, arg_types, ret_type), - export_name, doc) + export_name, doc, required_modules) @classmethod def ref_(cls, ctype_name: str, alias_ctype_name: str, alias_export_name: Optional[str] = None, - export_name: Optional[str] = None, doc: Optional[str] = None): + export_name: Optional[str] = None, + doc: Optional[str] = None, + required_modules: Tuple[str, ...] = ()): return cls(ctype_name, AliasRefTypeNode(alias_ctype_name, alias_export_name), - export_name, doc) + export_name, doc, required_modules) @classmethod def dict_(cls, ctype_name: str, key_type: TypeNode, value_type: TypeNode, - export_name: Optional[str] = None, doc: Optional[str] = None): + export_name: Optional[str] = None, doc: Optional[str] = None, + required_modules: Tuple[str, ...] = ()): return cls(ctype_name, DictTypeNode(ctype_name, key_type, value_type), - export_name, doc) + export_name, doc, required_modules) class ConditionalAliasTypeNode(TypeNode): @@ -451,6 +475,11 @@ class ConditionalAliasTypeNode(TypeNode): def required_usage_imports(self) -> Generator[str, None, None]: yield "import cv2.typing" + @property + def required_modules(self) -> Tuple[str, ...]: + return (*self.positive_branch_type.required_modules, + *self.negative_branch_type.required_modules) + @property def is_resolved(self) -> bool: return self.positive_branch_type.is_resolved \ @@ -519,8 +548,9 @@ class ASTNodeTypeNode(TypeNode): too expensive and error prone. """ def __init__(self, ctype_name: str, typename: Optional[str] = None, - module_name: Optional[str] = None) -> None: - super().__init__(ctype_name) + module_name: Optional[str] = None, + required_modules: Tuple[str, ...] = ()) -> None: + super().__init__(ctype_name, required_modules) self._typename = typename if typename is not None else ctype_name self._module_name = module_name self._ast_node: Optional[weakref.ProxyType[ASTNode]] = None @@ -607,14 +637,20 @@ class ASTNodeTypeNode(TypeNode): class AggregatedTypeNode(TypeNode): """Base type node for type nodes representing an aggregation of another type nodes e.g. tuple, sequence or callable.""" - def __init__(self, ctype_name: str, items: Sequence[TypeNode]) -> None: - super().__init__(ctype_name) + def __init__(self, ctype_name: str, items: Sequence[TypeNode], + required_modules: Tuple[str, ...] = ()) -> None: + super().__init__(ctype_name, required_modules) self.items = list(items) @property def is_resolved(self) -> bool: return all(item.is_resolved for item in self.items) + @property + def required_modules(self) -> Tuple[str, ...]: + return (*chain.from_iterable(item.required_modules for item in self.items), + *self._required_modules) + def resolve(self, root: ASTNode) -> None: errors = [] for item in filter(lambda item: not item.is_resolved, self): @@ -690,8 +726,9 @@ class SequenceTypeNode(ContainerTypeNode): """Type node representing a homogeneous collection of elements with possible unknown length. """ - def __init__(self, ctype_name: str, item: TypeNode) -> None: - super().__init__(ctype_name, (item, )) + def __init__(self, ctype_name: str, item: TypeNode, + required_modules: Tuple[str, ...] = ()) -> None: + super().__init__(ctype_name, (item, ), required_modules) @property def type_format(self) -> str: @@ -737,8 +774,9 @@ class OptionalTypeNode(ContainerTypeNode): """Type node representing optional type which is effectively is a union of value type node and None. """ - def __init__(self, value: TypeNode) -> None: - super().__init__(value.ctype_name, (value,)) + def __init__(self, value: TypeNode, + required_modules: Tuple[str, ...] = ()) -> None: + super().__init__(value.ctype_name, (value,), required_modules) @property def type_format(self) -> str: @@ -755,8 +793,9 @@ class DictTypeNode(ContainerTypeNode): """Type node representing a homogeneous key-value mapping. """ def __init__(self, ctype_name: str, key_type: TypeNode, - value_type: TypeNode) -> None: - super().__init__(ctype_name, (key_type, value_type)) + value_type: TypeNode, + required_modules: Tuple[str, ...] = ()) -> None: + super().__init__(ctype_name, (key_type, value_type), required_modules) @property def key_type(self) -> TypeNode: @@ -794,11 +833,12 @@ class CallableTypeNode(AggregatedTypeNode): """ def __init__(self, ctype_name: str, arg_types: Union[TypeNode, Sequence[TypeNode]], - ret_type: TypeNode = NoneTypeNode("void")) -> None: + ret_type: TypeNode = NoneTypeNode("void"), + required_modules: Tuple[str, ...] = ()) -> None: if isinstance(arg_types, TypeNode): - super().__init__(ctype_name, (arg_types, ret_type)) + super().__init__(ctype_name, (arg_types, ret_type), required_modules) else: - super().__init__(ctype_name, (*arg_types, ret_type)) + super().__init__(ctype_name, (*arg_types, ret_type), required_modules) @property def arg_types(self) -> Sequence[TypeNode]: @@ -842,8 +882,9 @@ class CallableTypeNode(AggregatedTypeNode): class ClassTypeNode(ContainerTypeNode): """Type node representing types themselves (refer to typing.Type) """ - def __init__(self, value: TypeNode) -> None: - super().__init__(value.ctype_name, (value,)) + def __init__(self, value: TypeNode, + required_modules: Tuple[str, ...] = ()) -> None: + super().__init__(value.ctype_name, (value,), required_modules) @property def type_format(self) -> str: diff --git a/modules/python/src2/typing_stubs_generation/predefined_types.py b/modules/python/src2/typing_stubs_generation/predefined_types.py index dd764f6112..c5f49b5122 100644 --- a/modules/python/src2/typing_stubs_generation/predefined_types.py +++ b/modules/python/src2/typing_stubs_generation/predefined_types.py @@ -118,38 +118,6 @@ _PREDEFINED_TYPES = ( export_name="DescriptorExtractor"), AliasTypeNode.class_("FeatureExtractor", "Feature2D", export_name="FeatureExtractor"), - AliasTypeNode.union_("GProtoArg", - items=(AliasRefTypeNode("Scalar"), - ASTNodeTypeNode("GMat"), - ASTNodeTypeNode("GOpaqueT"), - ASTNodeTypeNode("GArrayT"))), - SequenceTypeNode("GProtoArgs", AliasRefTypeNode("GProtoArg")), - AliasTypeNode.sequence_("GProtoInputArgs", AliasRefTypeNode("GProtoArg")), - AliasTypeNode.sequence_("GProtoOutputArgs", AliasRefTypeNode("GProtoArg")), - AliasTypeNode.union_( - "GRunArg", - items=(AliasRefTypeNode("Mat", "MatLike"), - AliasRefTypeNode("Scalar"), - ASTNodeTypeNode("GOpaqueT"), - ASTNodeTypeNode("GArrayT"), - SequenceTypeNode("GRunArg", AnyTypeNode("GRunArg")), - NoneTypeNode("GRunArg")) - ), - AliasTypeNode.optional_("GOptRunArg", AliasRefTypeNode("GRunArg")), - AliasTypeNode.union_("GMetaArg", - items=(ASTNodeTypeNode("GMat"), - AliasRefTypeNode("Scalar"), - ASTNodeTypeNode("GOpaqueT"), - ASTNodeTypeNode("GArrayT"))), - AliasTypeNode.union_("Prim", - items=(ASTNodeTypeNode("gapi.wip.draw.Text"), - ASTNodeTypeNode("gapi.wip.draw.Circle"), - ASTNodeTypeNode("gapi.wip.draw.Image"), - ASTNodeTypeNode("gapi.wip.draw.Line"), - ASTNodeTypeNode("gapi.wip.draw.Rect"), - ASTNodeTypeNode("gapi.wip.draw.Mosaic"), - ASTNodeTypeNode("gapi.wip.draw.Poly"))), - SequenceTypeNode("Prims", AliasRefTypeNode("Prim")), AliasTypeNode.array_ref_("Matx33f", array_ref_name="NumPyArrayFloat32", shape=(3, 3), @@ -168,44 +136,23 @@ _PREDEFINED_TYPES = ( dtype="numpy.float64"), NDArrayTypeNode("vector", dtype="numpy.uint8"), NDArrayTypeNode("vector_uchar", dtype="numpy.uint8"), - TupleTypeNode("GMat2", items=(ASTNodeTypeNode("GMat"), - ASTNodeTypeNode("GMat"))), - ASTNodeTypeNode("GOpaque", "GOpaqueT"), - ASTNodeTypeNode("GArray", "GArrayT"), - AliasTypeNode.union_("GTypeInfo", - items=(ASTNodeTypeNode("GMat"), - AliasRefTypeNode("Scalar"), - ASTNodeTypeNode("GOpaqueT"), - ASTNodeTypeNode("GArrayT"))), - SequenceTypeNode("GCompileArgs", ASTNodeTypeNode("GCompileArg")), - SequenceTypeNode("GTypesInfo", AliasRefTypeNode("GTypeInfo")), - SequenceTypeNode("GRunArgs", AliasRefTypeNode("GRunArg")), - SequenceTypeNode("GMetaArgs", AliasRefTypeNode("GMetaArg")), - SequenceTypeNode("GOptRunArgs", AliasRefTypeNode("GOptRunArg")), - AliasTypeNode.callable_( - "detail_ExtractArgsCallback", - arg_types=SequenceTypeNode("GTypesInfo", AliasRefTypeNode("GTypeInfo")), - ret_type=SequenceTypeNode("GRunArgs", AliasRefTypeNode("GRunArg")), - export_name="ExtractArgsCallback" - ), - AliasTypeNode.callable_( - "detail_ExtractMetaCallback", - arg_types=SequenceTypeNode("GTypesInfo", AliasRefTypeNode("GTypeInfo")), - ret_type=SequenceTypeNode("GMetaArgs", AliasRefTypeNode("GMetaArg")), - export_name="ExtractMetaCallback" - ), - AliasTypeNode.class_("LayerId", "DictValue"), + + # DNN, optional + AliasTypeNode.class_("LayerId", "DictValue", required_modules=("dnn",)), AliasTypeNode.dict_("LayerParams", key_type=PrimitiveTypeNode.str_(), value_type=UnionTypeNode("DictValue", items=( PrimitiveTypeNode.int_(), PrimitiveTypeNode.float_(), PrimitiveTypeNode.str_()) - )), - PrimitiveTypeNode.int_("cvflann_flann_distance_t"), - PrimitiveTypeNode.int_("flann_flann_distance_t"), - PrimitiveTypeNode.int_("cvflann_flann_algorithm_t"), - PrimitiveTypeNode.int_("flann_flann_algorithm_t"), + ), + required_modules=("dnn",)), + + # Flann, optional + PrimitiveTypeNode.int_("cvflann_flann_distance_t", required_modules=("flann",)), + PrimitiveTypeNode.int_("flann_flann_distance_t", required_modules=("flann",)), + PrimitiveTypeNode.int_("cvflann_flann_algorithm_t", required_modules=("flann",)), + PrimitiveTypeNode.int_("flann_flann_algorithm_t", required_modules=("flann",)), AliasTypeNode.dict_("flann_IndexParams", key_type=PrimitiveTypeNode.str_(), value_type=UnionTypeNode("flann_IndexParams::value", items=( @@ -213,7 +160,9 @@ _PREDEFINED_TYPES = ( PrimitiveTypeNode.int_(), PrimitiveTypeNode.float_(), PrimitiveTypeNode.str_()) - ), export_name="IndexParams"), + ), + export_name="IndexParams", + required_modules=("flann",)), AliasTypeNode.dict_("flann_SearchParams", key_type=PrimitiveTypeNode.str_(), value_type=UnionTypeNode("flann_IndexParams::value", items=( @@ -221,17 +170,96 @@ _PREDEFINED_TYPES = ( PrimitiveTypeNode.int_(), PrimitiveTypeNode.float_(), PrimitiveTypeNode.str_()) - ), export_name="SearchParams"), - AliasTypeNode.dict_("map_string_and_string", PrimitiveTypeNode.str_("map_string_and_string::key"), - PrimitiveTypeNode.str_("map_string_and_string::value")), - AliasTypeNode.dict_("map_string_and_int", PrimitiveTypeNode.str_("map_string_and_int::key"), - PrimitiveTypeNode.int_("map_string_and_int::value")), - AliasTypeNode.dict_("map_string_and_vector_size_t", PrimitiveTypeNode.str_("map_string_and_vector_size_t::key"), - SequenceTypeNode("map_string_and_vector_size_t::value", PrimitiveTypeNode.int_("size_t"))), - AliasTypeNode.dict_("map_string_and_vector_float", PrimitiveTypeNode.str_("map_string_and_vector_float::key"), - SequenceTypeNode("map_string_and_vector_float::value", PrimitiveTypeNode.float_())), - AliasTypeNode.dict_("map_int_and_double", PrimitiveTypeNode.int_("map_int_and_double::key"), - PrimitiveTypeNode.float_("map_int_and_double::value")), + ), + export_name="SearchParams", + required_modules=("flann",)), + AliasTypeNode.dict_("map_string_and_string", + PrimitiveTypeNode.str_("map_string_and_string::key"), + PrimitiveTypeNode.str_("map_string_and_string::value"), + required_modules=("flann",)), + AliasTypeNode.dict_("map_string_and_int", + PrimitiveTypeNode.str_("map_string_and_int::key"), + PrimitiveTypeNode.int_("map_string_and_int::value"), + required_modules=("flann",)), + AliasTypeNode.dict_("map_string_and_vector_size_t", + PrimitiveTypeNode.str_("map_string_and_vector_size_t::key"), + SequenceTypeNode("map_string_and_vector_size_t::value", PrimitiveTypeNode.int_("size_t")), + required_modules=("flann",)), + AliasTypeNode.dict_("map_string_and_vector_float", + PrimitiveTypeNode.str_("map_string_and_vector_float::key"), + SequenceTypeNode("map_string_and_vector_float::value", PrimitiveTypeNode.float_()), + required_modules=("flann",)), + AliasTypeNode.dict_("map_int_and_double", + PrimitiveTypeNode.int_("map_int_and_double::key"), + PrimitiveTypeNode.float_("map_int_and_double::value"), + required_modules=("flann",)), + + # G-API, optional + AliasTypeNode.union_("GProtoArg", + items=(AliasRefTypeNode("Scalar"), + ASTNodeTypeNode("GMat"), + ASTNodeTypeNode("GOpaqueT"), + ASTNodeTypeNode("GArrayT")), + required_modules=("gapi",)), + SequenceTypeNode("GProtoArgs", AliasRefTypeNode("GProtoArg"), required_modules=("gapi",)), + AliasTypeNode.sequence_("GProtoInputArgs", AliasRefTypeNode("GProtoArg"), required_modules=("gapi",)), + AliasTypeNode.sequence_("GProtoOutputArgs", AliasRefTypeNode("GProtoArg"), required_modules=("gapi",)), + AliasTypeNode.union_( + "GRunArg", + items=(AliasRefTypeNode("Mat", "MatLike"), + AliasRefTypeNode("Scalar"), + ASTNodeTypeNode("GOpaqueT"), + ASTNodeTypeNode("GArrayT"), + SequenceTypeNode("GRunArg", AnyTypeNode("GRunArg")), + NoneTypeNode("GRunArg")), + required_modules=("gapi",) + ), + AliasTypeNode.optional_("GOptRunArg", AliasRefTypeNode("GRunArg"), required_modules=("gapi",)), + AliasTypeNode.union_("GMetaArg", + items=(ASTNodeTypeNode("GMat"), + AliasRefTypeNode("Scalar"), + ASTNodeTypeNode("GOpaqueT"), + ASTNodeTypeNode("GArrayT")), + required_modules=("gapi",)), + AliasTypeNode.union_("Prim", + items=(ASTNodeTypeNode("gapi.wip.draw.Text"), + ASTNodeTypeNode("gapi.wip.draw.Circle"), + ASTNodeTypeNode("gapi.wip.draw.Image"), + ASTNodeTypeNode("gapi.wip.draw.Line"), + ASTNodeTypeNode("gapi.wip.draw.Rect"), + ASTNodeTypeNode("gapi.wip.draw.Mosaic"), + ASTNodeTypeNode("gapi.wip.draw.Poly")), + required_modules=("gapi",)), + SequenceTypeNode("Prims", AliasRefTypeNode("Prim"), required_modules=("gapi",)), + TupleTypeNode("GMat2", items=(ASTNodeTypeNode("GMat"), + ASTNodeTypeNode("GMat")), required_modules=("gapi",)), + ASTNodeTypeNode("GOpaque", "GOpaqueT", required_modules=("gapi",)), + ASTNodeTypeNode("GArray", "GArrayT", required_modules=("gapi",)), + AliasTypeNode.union_("GTypeInfo", + items=(ASTNodeTypeNode("GMat"), + AliasRefTypeNode("Scalar"), + ASTNodeTypeNode("GOpaqueT"), + ASTNodeTypeNode("GArrayT")), + required_modules=("gapi",)), + SequenceTypeNode("GCompileArgs", ASTNodeTypeNode("GCompileArg"), required_modules=("gapi",)), + SequenceTypeNode("GTypesInfo", AliasRefTypeNode("GTypeInfo"), required_modules=("gapi",)), + SequenceTypeNode("GRunArgs", AliasRefTypeNode("GRunArg"), required_modules=("gapi",)), + SequenceTypeNode("GMetaArgs", AliasRefTypeNode("GMetaArg"), required_modules=("gapi",)), + SequenceTypeNode("GOptRunArgs", AliasRefTypeNode("GOptRunArg"), required_modules=("gapi",)), + AliasTypeNode.callable_( + "detail_ExtractArgsCallback", + arg_types=SequenceTypeNode("GTypesInfo", AliasRefTypeNode("GTypeInfo")), + ret_type=SequenceTypeNode("GRunArgs", AliasRefTypeNode("GRunArg")), + export_name="ExtractArgsCallback", + required_modules=("gapi",) + ), + AliasTypeNode.callable_( + "detail_ExtractMetaCallback", + arg_types=SequenceTypeNode("GTypesInfo", AliasRefTypeNode("GTypeInfo")), + ret_type=SequenceTypeNode("GMetaArgs", AliasRefTypeNode("GMetaArg")), + export_name="ExtractMetaCallback", + required_modules=("gapi",) + ), ) PREDEFINED_TYPES = dict( From e0001903ce91e879b1637961f6cb60208fbc0b8b Mon Sep 17 00:00:00 2001 From: Vincent Rabaud Date: Tue, 17 Dec 2024 14:36:14 +0100 Subject: [PATCH 029/102] Merge pull request #26490 from vrabaud:4x_calibration_base Switch calibration.cpp to C++ #26490 The CvLevMarq code has to be kept in order to keep the same accuracy (the C++ solver is not as good). There are two ways to review this PR: by comparing to the old code, or by checking what is different from the 5.x version (which is the first commit). ### 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/calib3d/src/calibration.cpp | 802 ++++++++++++++-------------- 1 file changed, 395 insertions(+), 407 deletions(-) diff --git a/modules/calib3d/src/calibration.cpp b/modules/calib3d/src/calibration.cpp index 12ce907f48..d046a9b58e 100644 --- a/modules/calib3d/src/calibration.cpp +++ b/modules/calib3d/src/calibration.cpp @@ -58,8 +58,6 @@ namespace cv { -static const char* cvDistCoeffErr = "Distortion coefficients must be 1x4, 4x1, 1x5, 5x1, 1x8, 8x1, 1x12, 12x1, 1x14 or 14x1 floating-point vector"; - static void initIntrinsicParams2D( const Mat& objectPoints, const Mat& imagePoints, const Mat& npoints, Size imageSize, OutputArray cameraMatrix, @@ -165,51 +163,39 @@ static void subMatrix(const Mat& src, Mat& dst, } } -static double calibrateCameraInternal( const CvMat* objectPoints, - const CvMat* imagePoints, const CvMat* npoints, - CvSize imageSize, int iFixedPoint, CvMat* cameraMatrix, CvMat* distCoeffs, +static double calibrateCameraInternal( const Mat& objectPoints, + const Mat& imagePoints, const Mat& npoints, + Size imageSize, int iFixedPoint, Mat& cameraMatrix, Mat& distCoeffs, Mat rvecs, Mat tvecs, Mat newObjPoints, Mat stdDevs, - Mat perViewErr, int flags, const CvTermCriteria& termCrit ) + Mat perViewErr, int flags, const TermCriteria& termCrit ) { - const int NINTRINSIC = CV_CALIB_NINTRINSIC; + int NINTRINSIC = CALIB_NINTRINSIC; double reprojErr = 0; - Matx33d A; - double k[14] = {0}; - CvMat matA = cvMat(3, 3, CV_64F, A.val), _k; - int nimages, maxPoints = 0, ni = 0, total = 0, nparams, npstep; double aspectRatio = 0.; + int nimages = npoints.checkVector(1, CV_32S); + CV_Assert(nimages >= 1); + int ndistCoeffs = (int)distCoeffs.total(); + bool releaseObject = iFixedPoint > 0 && iFixedPoint < npoints.at(0) - 1; // 0. check the parameters & allocate buffers - if( !CV_IS_MAT(objectPoints) || !CV_IS_MAT(imagePoints) || - !CV_IS_MAT(npoints) || !CV_IS_MAT(cameraMatrix) || !CV_IS_MAT(distCoeffs) ) - CV_Error( cv::Error::StsBadArg, "One of required vector arguments is not a valid matrix" ); - if( imageSize.width <= 0 || imageSize.height <= 0 ) CV_Error( cv::Error::StsOutOfRange, "image width and height must be positive" ); - if( CV_MAT_TYPE(npoints->type) != CV_32SC1 || - (npoints->rows != 1 && npoints->cols != 1) ) - CV_Error( cv::Error::StsUnsupportedFormat, - "the array of point counters must be 1-dimensional integer vector" ); if(flags & CALIB_TILTED_MODEL) { //when the tilted sensor model is used the distortion coefficients matrix must have 14 parameters - if (distCoeffs->cols*distCoeffs->rows != 14) + if (ndistCoeffs != 14) CV_Error( cv::Error::StsBadArg, "The tilted sensor model must have 14 parameters in the distortion matrix" ); } else { //when the thin prism model is used the distortion coefficients matrix must have 12 parameters if(flags & CALIB_THIN_PRISM_MODEL) - if (distCoeffs->cols*distCoeffs->rows != 12) + if (ndistCoeffs != 12) CV_Error( cv::Error::StsBadArg, "Thin prism model must have 12 parameters in the distortion matrix" ); } - nimages = npoints->rows*npoints->cols; - npstep = npoints->rows == 1 ? 1 : npoints->step/CV_ELEM_SIZE(npoints->type); - - if( !rvecs.empty() ) { int cn = rvecs.channels(); @@ -218,8 +204,6 @@ static double calibrateCameraInternal( const CvMat* objectPoints, (rvecs.rows == 1 && rvecs.cols == nimages && cn == 3)); } - bool releaseObject = iFixedPoint > 0 && iFixedPoint < npoints->data.i[0] - 1; - if( !tvecs.empty() ) { int cn = tvecs.channels(); @@ -228,25 +212,18 @@ static double calibrateCameraInternal( const CvMat* objectPoints, (tvecs.rows == 1 && tvecs.cols == nimages && cn == 3)); } - if( (CV_MAT_TYPE(cameraMatrix->type) != CV_32FC1 && - CV_MAT_TYPE(cameraMatrix->type) != CV_64FC1) || - cameraMatrix->rows != 3 || cameraMatrix->cols != 3 ) - CV_Error( cv::Error::StsBadArg, - "Intrinsic parameters must be 3x3 floating-point matrix" ); + CV_Assert(cameraMatrix.type() == CV_32F || cameraMatrix.type() == CV_64F); + CV_Assert(cameraMatrix.rows == 3 && cameraMatrix.cols == 3); - if( (CV_MAT_TYPE(distCoeffs->type) != CV_32FC1 && - CV_MAT_TYPE(distCoeffs->type) != CV_64FC1) || - (distCoeffs->cols != 1 && distCoeffs->rows != 1) || - (distCoeffs->cols*distCoeffs->rows != 4 && - distCoeffs->cols*distCoeffs->rows != 5 && - distCoeffs->cols*distCoeffs->rows != 8 && - distCoeffs->cols*distCoeffs->rows != 12 && - distCoeffs->cols*distCoeffs->rows != 14) ) - CV_Error( cv::Error::StsBadArg, cvDistCoeffErr ); + CV_Assert(distCoeffs.type() == CV_32F || distCoeffs.type() == CV_64F); + CV_Assert(distCoeffs.rows == 1 || distCoeffs.cols == 1); + CV_Assert(ndistCoeffs == 4 || ndistCoeffs == 5 || ndistCoeffs == 8 || + ndistCoeffs == 12 || ndistCoeffs == 14); + int total = 0, maxPoints = 0; for(int i = 0; i < nimages; i++ ) { - ni = npoints->data.i[i*npstep]; + int ni = npoints.at(i); if( ni < 4 ) { CV_Error_( cv::Error::StsOutOfRange, ("The number of points in the view #%d is < 4", i)); @@ -263,7 +240,7 @@ static double calibrateCameraInternal( const CvMat* objectPoints, (newObjPoints.rows == 1 && newObjPoints.cols == maxPoints && cn == 3)); } - if( !stdDevs.empty() && releaseObject ) + if( !stdDevs.empty() ) { int cn = stdDevs.channels(); CV_Assert(stdDevs.depth() == CV_32F || stdDevs.depth() == CV_64F); @@ -277,36 +254,41 @@ static double calibrateCameraInternal( const CvMat* objectPoints, Mat _m( 1, total, CV_64FC2 ); Mat allErrors(1, total, CV_64FC2); - if(CV_MAT_CN(objectPoints->type) == 3) { - cvarrToMat(objectPoints).convertTo(matM, CV_64F); - } else { - convertPointsHomogeneous(cvarrToMat(objectPoints), matM); + if(objectPoints.channels() == 3) + objectPoints.convertTo(matM, CV_64F); + else { + convertPointsToHomogeneous(objectPoints, matM); + matM.convertTo(matM, CV_64F); } - if(CV_MAT_CN(imagePoints->type) == 2) { - cvarrToMat(imagePoints).convertTo(_m, CV_64F); - } else { - convertPointsHomogeneous(cvarrToMat(imagePoints), _m); + if(imagePoints.channels() == 2) + imagePoints.convertTo(_m, CV_64F); + else { + convertPointsFromHomogeneous(imagePoints, _m); + _m.convertTo(_m, CV_64F); } - nparams = NINTRINSIC + nimages*6; + int nparams = NINTRINSIC + nimages*6; if( releaseObject ) nparams += maxPoints * 3; - _k = cvMat( distCoeffs->rows, distCoeffs->cols, CV_MAKETYPE(CV_64F,CV_MAT_CN(distCoeffs->type)), k); - if( distCoeffs->rows*distCoeffs->cols*CV_MAT_CN(distCoeffs->type) < 8 ) + std::vector k(14, 0.0); + Mat _k( distCoeffs.rows, distCoeffs.cols, CV_64F, k.data()); + if( distCoeffs.total() < 8 ) { - if( distCoeffs->rows*distCoeffs->cols*CV_MAT_CN(distCoeffs->type) < 5 ) + if( distCoeffs.total() < 5 ) flags |= CALIB_FIX_K3; flags |= CALIB_FIX_K4 | CALIB_FIX_K5 | CALIB_FIX_K6; } const double minValidAspectRatio = 0.01; const double maxValidAspectRatio = 100.0; + Matx33d A; + cameraMatrix.convertTo(A, CV_64F); + // 1. initialize intrinsic parameters & LM solver if( flags & CALIB_USE_INTRINSIC_GUESS ) { - cvConvert( cameraMatrix, &matA ); if( A(0, 0) <= 0 || A(1, 1) <= 0 ) CV_Error( cv::Error::StsOutOfRange, "Focal length (fx and fy) must be positive" ); if( A(0, 2) < 0 || A(0, 2) >= imageSize.width || @@ -329,7 +311,7 @@ static double calibrateCameraInternal( const CvMat* objectPoints, CV_Error( cv::Error::StsOutOfRange, "The specified aspect ratio (= cameraMatrix[0][0] / cameraMatrix[1][1]) is incorrect" ); } - cvConvert( distCoeffs, &_k ); + distCoeffs.convertTo(_k, CV_64F); } else { @@ -343,23 +325,16 @@ static double calibrateCameraInternal( const CvMat* objectPoints, if( flags & CALIB_FIX_ASPECT_RATIO ) { - aspectRatio = cvmGet(cameraMatrix,0,0); - aspectRatio /= cvmGet(cameraMatrix,1,1); + aspectRatio = A(0, 0); + aspectRatio /= A(1, 1); if( aspectRatio < minValidAspectRatio || aspectRatio > maxValidAspectRatio ) CV_Error( cv::Error::StsOutOfRange, "The specified aspect ratio (= cameraMatrix[0][0] / cameraMatrix[1][1]) is incorrect" ); } - initIntrinsicParams2D( matM, _m, cvarrToMat(npoints), imageSize, cvarrToMat(&matA), aspectRatio ); + initIntrinsicParams2D( matM, _m, npoints, imageSize, A, aspectRatio ); } - CvLevMarq solver( nparams, 0, termCrit ); - - Mat _Ji( maxPoints*2, NINTRINSIC, CV_64FC1, Scalar(0)); - Mat _Je( maxPoints*2, 6, CV_64FC1 ); - Mat _err( maxPoints*2, 1, CV_64FC1 ); - - const bool allocJo = (solver.state == CvLevMarq::CALC_J) || !stdDevs.empty() || releaseObject; - Mat _Jo = allocJo ? Mat( maxPoints*2, maxPoints*3, CV_64FC1, Scalar(0) ) : Mat(); + CvLevMarq solver( nparams, 0, cvTermCriteria(termCrit) ); if(flags & CALIB_USE_LU) { solver.solveMethod = DECOMP_LU; @@ -373,7 +348,7 @@ static double calibrateCameraInternal( const CvMat* objectPoints, uchar* mask = solver.mask->data.ptr; param[0] = A(0, 0); param[1] = A(1, 1); param[2] = A(0, 2); param[3] = A(1, 2); - std::copy(k, k + 14, param + 4); + std::copy(k.begin(), k.end(), param + 4); if(flags & CALIB_FIX_ASPECT_RATIO) mask[0] = 0; @@ -430,35 +405,44 @@ static double calibrateCameraInternal( const CvMat* objectPoints, } } + Mat_ param_m = cvarrToMat(solver.param); Mat mask = cvarrToMat(solver.mask); int nparams_nz = countNonZero(mask); + if (nparams_nz >= 2 * total) CV_Error_(Error::StsBadArg, ("There should be less vars to optimize (having %d) than the number of residuals (%d = 2 per point)", nparams_nz, 2 * total)); // 2. initialize extrinsic parameters - for(int i = 0, pos = 0; i < nimages; i++, pos += ni ) + for(int i = 0, pos = 0; i < nimages; i++ ) { - CvMat _ri, _ti; - ni = npoints->data.i[i*npstep]; + int ni = npoints.at(i); - cvGetRows( solver.param, &_ri, NINTRINSIC + i*6, NINTRINSIC + i*6 + 3 ); - cvGetRows( solver.param, &_ti, NINTRINSIC + i*6 + 3, NINTRINSIC + i*6 + 6 ); + int s = NINTRINSIC + i*6; + Mat _ri = param_m.rowRange(s, s + 3); + Mat _ti = param_m.rowRange(s + 3, s + 6); - CvMat _Mi = cvMat(matM.colRange(pos, pos + ni)); - CvMat _mi = cvMat(_m.colRange(pos, pos + ni)); + Mat _Mi = matM.colRange(pos, pos + ni); + Mat _mi = _m.colRange(pos, pos + ni); - Mat r_mat = cvarrToMat(&_ri), t_mat = cvarrToMat(&_ti); - findExtrinsicCameraParams2( cvarrToMat(&_Mi), cvarrToMat(&_mi), cvarrToMat(&matA), - cvarrToMat(&_k), r_mat, t_mat, /*useExtrinsicGuess=*/0 ); + findExtrinsicCameraParams2( _Mi, _mi, Mat(A), _k, _ri, _ti, /*useExtrinsicGuess=*/0 ); + + pos += ni; } - //std::cout << "single camera calib. param before LM: " << param0.t() << "\n"; - //std::cout << "single camera calib. mask: " << mask0.t() << "\n"; - // 3. run the optimization + + Mat errBuf( maxPoints*2, 1, CV_64FC1 ); + Mat JiBuf ( maxPoints*2, NINTRINSIC, CV_64FC1, Scalar(0)); + Mat JeBuf ( maxPoints*2, 6, CV_64FC1 ); + Mat JoBuf; + if (releaseObject) + JoBuf = Mat( maxPoints*2, maxPoints*3, CV_64FC1); + for(;;) { + bool optimizeObjPoints = releaseObject; + const CvMat* _param = 0; CvMat *_JtJ = 0, *_JtErr = 0; double* _errNorm = 0; @@ -472,8 +456,11 @@ static double calibrateCameraInternal( const CvMat* objectPoints, pparam[0] = pparam[1]*aspectRatio; } - A(0, 0) = param[0]; A(1, 1) = param[1]; A(0, 2) = param[2]; A(1, 2) = param[3]; - std::copy(param + 4, param + 4 + 14, k); + double fx = param_m(0), fy = param_m(1), cx = param_m(2), cy = param_m(3); + Matx33d intrin(fx, 0, cx, + 0, fy, cy, + 0, 0, 1); + Mat dist = param_m.rowRange(4, 4+14); if ( !proceed && stdDevs.empty() && perViewErr.empty() ) break; @@ -482,77 +469,84 @@ static double calibrateCameraInternal( const CvMat* objectPoints, reprojErr = 0; - for(int i = 0, pos = 0; i < nimages; i++, pos += ni ) + int so = NINTRINSIC + nimages * 6; + int pos = 0; + for( int i = 0; i < nimages; i++ ) { - CvMat _ri, _ti; - ni = npoints->data.i[i*npstep]; + int si = NINTRINSIC + i * 6; - cvGetRows( solver.param, &_ri, NINTRINSIC + i*6, NINTRINSIC + i*6 + 3 ); - cvGetRows( solver.param, &_ti, NINTRINSIC + i*6 + 3, NINTRINSIC + i*6 + 6 ); + int ni = npoints.at(i); + Mat _ri = param_m.rowRange(si, si + 3); + Mat _ti = param_m.rowRange(si + 3, si + 6); - CvMat _Mi = cvMat(matM.colRange(pos, pos + ni)); - if( releaseObject ) + Mat _Mi = matM.colRange(pos, pos + ni); + if( optimizeObjPoints ) { - cvGetRows( solver.param, &_Mi, NINTRINSIC + nimages * 6, - NINTRINSIC + nimages * 6 + ni * 3 ); - cvReshape( &_Mi, &_Mi, 3, 1 ); + _Mi = param_m.rowRange(so, so + ni * 3); + _Mi = _Mi.reshape(3, 1); } - CvMat _mi = cvMat(_m.colRange(pos, pos + ni)); - CvMat _me = cvMat(allErrors.colRange(pos, pos + ni)); + Mat _mi = _m.colRange(pos, pos + ni); + Mat _me = allErrors.colRange(pos, pos + ni); - _Je.resize(ni*2); _Ji.resize(ni*2); _err.resize(ni*2); - _Jo.resize(ni*2); + Mat Jo; + if (optimizeObjPoints) + Jo = JoBuf(Range(0, ni*2), Range(0, ni*3)); - CvMat _mp = cvMat(_err.reshape(2, 1)); + Mat Je = JeBuf.rowRange(0, ni*2); + Mat Ji = JiBuf.rowRange(0, ni*2); + Mat err = errBuf.rowRange(0, ni*2); + Mat _mp = err.reshape(2, 1); if( calcJ ) { - projectPoints( cvarrToMat(&_Mi), cvarrToMat(&_ri), cvarrToMat(&_ti), cvarrToMat(&matA), - cvarrToMat(&_k), cvarrToMat(&_mp), _Je.colRange(0, 3), _Je.colRange(3, 6), - (flags & CALIB_FIX_FOCAL_LENGTH) ? noArray() : _Ji.colRange(0, 2), - (flags & CALIB_FIX_PRINCIPAL_POINT) ? noArray() : _Ji.colRange(2, 4), - _Ji.colRange(4, 4 + _k.cols * _k.rows), (_Jo.empty()) ? noArray() : _Jo.colRange(0, ni * 3), - (flags & CALIB_FIX_ASPECT_RATIO) ? aspectRatio : 0); + Mat _dpdr = Je.colRange(0, 3); + Mat _dpdt = Je.colRange(3, 6); + Mat _dpdf = (flags & CALIB_FIX_FOCAL_LENGTH) ? Mat() : Ji.colRange(0, 2); + Mat _dpdc = (flags & CALIB_FIX_PRINCIPAL_POINT) ? Mat() : Ji.colRange(2, 4); + Mat _dpdk = Ji.colRange(4, NINTRINSIC); + Mat _dpdo = Jo.empty() ? Mat() : Jo.colRange(0, ni * 3); + + projectPoints(_Mi, _ri, _ti, intrin, dist, _mp, _dpdr, _dpdt, + (_dpdf.empty() ? noArray() : _dpdf), + (_dpdc.empty() ? noArray() : _dpdc), + _dpdk, (_dpdo.empty() ? noArray() : _dpdo), + (flags & CALIB_FIX_ASPECT_RATIO) ? aspectRatio : 0.); } else - projectPoints( cvarrToMat(&_Mi), cvarrToMat(&_ri), cvarrToMat(&_ti), cvarrToMat(&matA), - cvarrToMat(&_k), cvarrToMat(&_mp) ); + projectPoints( _Mi, _ri, _ti, intrin, dist, _mp); - cvSub( &_mp, &_mi, &_mp ); - if (!perViewErr.empty() || !stdDevs.empty()) - cvCopy(&_mp, &_me); + subtract( _mp, _mi, _mp ); + _mp.copyTo(_me); if( calcJ ) { Mat JtJ(cvarrToMat(_JtJ)), JtErr(cvarrToMat(_JtErr)); // see HZ: (A6.14) for details on the structure of the Jacobian - JtJ(Rect(0, 0, NINTRINSIC, NINTRINSIC)) += _Ji.t() * _Ji; - JtJ(Rect(NINTRINSIC + i * 6, NINTRINSIC + i * 6, 6, 6)) = _Je.t() * _Je; - JtJ(Rect(NINTRINSIC + i * 6, 0, 6, NINTRINSIC)) = _Ji.t() * _Je; - if( releaseObject ) + JtJ(Rect(0, 0, NINTRINSIC, NINTRINSIC)) += Ji.t() * Ji; + JtJ(Rect(si, si, 6, 6)) = Je.t() * Je; + JtJ(Rect(si, 0, 6, NINTRINSIC)) = Ji.t() * Je; + if( optimizeObjPoints ) { - JtJ(Rect(NINTRINSIC + nimages * 6, 0, maxPoints * 3, NINTRINSIC)) += _Ji.t() * _Jo; - JtJ(Rect(NINTRINSIC + nimages * 6, NINTRINSIC + i * 6, maxPoints * 3, 6)) - += _Je.t() * _Jo; - JtJ(Rect(NINTRINSIC + nimages * 6, NINTRINSIC + nimages * 6, maxPoints * 3, maxPoints * 3)) - += _Jo.t() * _Jo; + JtJ(Rect(so, 0, maxPoints * 3, NINTRINSIC)) += Ji.t() * Jo; + JtJ(Rect(so, si, maxPoints * 3, 6)) += Je.t() * Jo; + JtJ(Rect(so, so, maxPoints * 3, maxPoints * 3)) += Jo.t() * Jo; } - JtErr.rowRange(0, NINTRINSIC) += _Ji.t() * _err; - JtErr.rowRange(NINTRINSIC + i * 6, NINTRINSIC + (i + 1) * 6) = _Je.t() * _err; - if( releaseObject ) + JtErr.rowRange(0, NINTRINSIC) += Ji.t() * err; + JtErr.rowRange(si, si + 6) = Je.t() * err; + if( optimizeObjPoints ) { - JtErr.rowRange(NINTRINSIC + nimages * 6, nparams) += _Jo.t() * _err; + JtErr.rowRange(so, nparams) += Jo.t() * err; } } - double viewErr = norm(_err, NORM_L2SQR); - + double viewErr = norm(err, NORM_L2SQR); if( !perViewErr.empty() ) perViewErr.at(i) = std::sqrt(viewErr / ni); reprojErr += viewErr; + pos += ni; } if( _errNorm ) *_errNorm = reprojErr; @@ -584,12 +578,16 @@ static double calibrateCameraInternal( const CvMat* objectPoints, } // 4. store the results - cvConvert( &matA, cameraMatrix ); - cvConvert( &_k, distCoeffs ); + double * param = solver.param->data.db; + A = Matx33d(param[0], 0, param[2], 0, param[1], param[3], 0, 0, 1); + Mat(A).convertTo(cameraMatrix, cameraMatrix.type()); + _k = Mat(distCoeffs.size(), CV_64F, param + 4); + _k.convertTo(distCoeffs, distCoeffs.type()); + if( !newObjPoints.empty() && releaseObject ) { int s = NINTRINSIC + nimages * 6; - Mat _Mi = cvarrToMat(solver.param).rowRange(s, s + maxPoints * 3); + Mat _Mi = param_m.rowRange(s, s + maxPoints * 3); _Mi.reshape(3, 1).convertTo(newObjPoints, newObjPoints.type()); } @@ -597,7 +595,7 @@ static double calibrateCameraInternal( const CvMat* objectPoints, { if( !rvecs.empty() ) { - Mat src = Mat(3, 1, CV_64F, solver.param->data.db + NINTRINSIC + i*6); + Mat src = Mat(3, 1, CV_64F, param + NINTRINSIC + i*6); if( rvecs.rows == nimages && rvecs.cols*rvecs.channels() == 9 ) { Mat dst(3, 3, rvecs.depth(), rvecs.ptr(i)); @@ -613,7 +611,7 @@ static double calibrateCameraInternal( const CvMat* objectPoints, } if( !tvecs.empty() ) { - Mat src(3, 1, CV_64F, solver.param->data.db + NINTRINSIC + i*6 + 3); + Mat src(3, 1, CV_64F, param + NINTRINSIC + i*6 + 3); Mat dst(3, 1, tvecs.depth(), tvecs.rows == 1 ? tvecs.data + i*tvecs.elemSize() : tvecs.ptr(i)); src.convertTo(dst, dst.type()); @@ -623,63 +621,47 @@ static double calibrateCameraInternal( const CvMat* objectPoints, return std::sqrt(reprojErr/total); } - //////////////////////////////// Stereo Calibration /////////////////////////////////// -static int dbCmp( const void* _a, const void* _b ) -{ - double a = *(const double*)_a; - double b = *(const double*)_b; - - return (a > b) - (a < b); -} - -static double stereoCalibrateImpl( const CvMat* _objectPoints, const CvMat* _imagePoints1, - const CvMat* _imagePoints2, const CvMat* _npoints, - CvMat* _cameraMatrix1, CvMat* _distCoeffs1, - CvMat* _cameraMatrix2, CvMat* _distCoeffs2, - CvSize imageSize, CvMat* matR, CvMat* matT, +static double stereoCalibrateImpl( + const Mat& _objectPoints, const Mat& _imagePoints1, + const Mat& _imagePoints2, const Mat& _npoints, + Mat& _cameraMatrix1, Mat& _distCoeffs1, + Mat& _cameraMatrix2, Mat& _distCoeffs2, + Size imageSize, Mat matR, Mat matT, Mat matE, Mat matF, Mat rvecs, Mat tvecs, Mat perViewErr, int flags, - CvTermCriteria termCrit ) + TermCriteria termCrit ) { - const int NINTRINSIC = 18; - Ptr npoints, imagePoints[2], objectPoints, RT0; + int NINTRINSIC = CALIB_NINTRINSIC; double reprojErr = 0; - double A[2][9], dk[2][14]={{0}}, rlr[9]; - CvMat K[2], Dist[2], om_LR, T_LR; - CvMat R_LR = cvMat(3, 3, CV_64F, rlr); - int i, k, p, ni = 0, ofs, nimages, pointsTotal, maxPoints = 0; - int nparams; + // initial camera intrinsicss + Vec distInitial[2]; + Matx33d A[2]; + int pointsTotal = 0, maxPoints = 0, nparams; bool recomputeIntrinsics = false; double aspectRatio[2] = {0, 0}; - CV_Assert( CV_IS_MAT(_imagePoints1) && CV_IS_MAT(_imagePoints2) && - CV_IS_MAT(_objectPoints) && CV_IS_MAT(_npoints) && - CV_IS_MAT(matR) && CV_IS_MAT(matT) ); + CV_Assert( _imagePoints1.type() == _imagePoints2.type() && + _imagePoints1.depth() == _objectPoints.depth() ); - CV_Assert( CV_ARE_TYPES_EQ(_imagePoints1, _imagePoints2) && - CV_ARE_DEPTHS_EQ(_imagePoints1, _objectPoints) ); + CV_Assert( (_npoints.cols == 1 || _npoints.rows == 1) && + _npoints.type() == CV_32S ); - CV_Assert( (_npoints->cols == 1 || _npoints->rows == 1) && - CV_MAT_TYPE(_npoints->type) == CV_32SC1 ); - - nimages = _npoints->cols + _npoints->rows - 1; - npoints.reset(cvCreateMat( _npoints->rows, _npoints->cols, _npoints->type )); - cvCopy( _npoints, npoints ); - - for( i = 0, pointsTotal = 0; i < nimages; i++ ) + int nimages = (int)_npoints.total(); + for(int i = 0; i < nimages; i++ ) { - maxPoints = MAX(maxPoints, npoints->data.i[i]); - pointsTotal += npoints->data.i[i]; + int ni = _npoints.at(i); + maxPoints = std::max(maxPoints, ni); + pointsTotal += ni; } - objectPoints.reset(cvCreateMat( _objectPoints->rows, _objectPoints->cols, - CV_64FC(CV_MAT_CN(_objectPoints->type)))); - cvConvert( _objectPoints, objectPoints ); - cvReshape( objectPoints, objectPoints, 3, 1 ); + Mat objectPoints; + Mat imagePoints[2]; + _objectPoints.convertTo(objectPoints, CV_64F); + objectPoints = objectPoints.reshape(3, 1); if( !rvecs.empty() ) { @@ -702,65 +684,61 @@ static double stereoCalibrateImpl( const CvMat* _objectPoints, const CvMat* _ima "1xn or nx1 array or 1-channel nx3 array, where n is the number of views" ); } - for( k = 0; k < 2; k++ ) + for(int k = 0; k < 2; k++ ) { - const CvMat* points = k == 0 ? _imagePoints1 : _imagePoints2; - const CvMat* cameraMatrix = k == 0 ? _cameraMatrix1 : _cameraMatrix2; - const CvMat* distCoeffs = k == 0 ? _distCoeffs1 : _distCoeffs2; + const Mat& points = k == 0 ? _imagePoints1 : _imagePoints2; + const Mat& cameraMatrix = k == 0 ? _cameraMatrix1 : _cameraMatrix2; + const Mat& distCoeffs = k == 0 ? _distCoeffs1 : _distCoeffs2; - int cn = CV_MAT_CN(_imagePoints1->type); - CV_Assert( (CV_MAT_DEPTH(_imagePoints1->type) == CV_32F || - CV_MAT_DEPTH(_imagePoints1->type) == CV_64F) && - ((_imagePoints1->rows == pointsTotal && _imagePoints1->cols*cn == 2) || - (_imagePoints1->rows == 1 && _imagePoints1->cols == pointsTotal && cn == 2)) ); + int depth = points.depth(); + int cn = points.channels(); + CV_Assert( (depth == CV_32F || depth == CV_64F) && + ((points.rows == pointsTotal && points.cols*cn == 2) || + (points.rows == 1 && points.cols == pointsTotal && cn == 2))); - K[k] = cvMat(3,3,CV_64F,A[k]); - Dist[k] = cvMat(1,14,CV_64F,dk[k]); + A[k] = Matx33d::eye(); - imagePoints[k].reset(cvCreateMat( points->rows, points->cols, CV_64FC(CV_MAT_CN(points->type)))); - cvConvert( points, imagePoints[k] ); - cvReshape( imagePoints[k], imagePoints[k], 2, 1 ); + points.convertTo(imagePoints[k], CV_64F); + imagePoints[k] = imagePoints[k].reshape(2, 1); - if( flags & (CALIB_FIX_INTRINSIC|CALIB_USE_INTRINSIC_GUESS| - CALIB_FIX_ASPECT_RATIO|CALIB_FIX_FOCAL_LENGTH) ) - cvConvert( cameraMatrix, &K[k] ); + if( flags & ( CALIB_FIX_INTRINSIC | CALIB_USE_INTRINSIC_GUESS | + CALIB_FIX_ASPECT_RATIO | CALIB_FIX_FOCAL_LENGTH ) ) + cameraMatrix.convertTo(A[k], CV_64F); - if( flags & (CALIB_FIX_INTRINSIC|CALIB_USE_INTRINSIC_GUESS| - CALIB_FIX_K1|CALIB_FIX_K2|CALIB_FIX_K3|CALIB_FIX_K4|CALIB_FIX_K5|CALIB_FIX_K6|CALIB_FIX_TANGENT_DIST) ) + if( flags & ( CALIB_FIX_INTRINSIC | CALIB_USE_INTRINSIC_GUESS | + CALIB_FIX_K1 | CALIB_FIX_K2 | CALIB_FIX_K3 | CALIB_FIX_K4 | CALIB_FIX_K5 | CALIB_FIX_K6 | + CALIB_FIX_TANGENT_DIST) ) { - CvMat tdist = cvMat( distCoeffs->rows, distCoeffs->cols, - CV_MAKETYPE(CV_64F,CV_MAT_CN(distCoeffs->type)), Dist[k].data.db ); - cvConvert( distCoeffs, &tdist ); + Mat tdist( distCoeffs.size(), CV_MAKETYPE(CV_64F, distCoeffs.channels()), distInitial[k].val); + distCoeffs.convertTo(tdist, CV_64F); } - if( !(flags & (CALIB_FIX_INTRINSIC|CALIB_USE_INTRINSIC_GUESS))) + if( !(flags & (CALIB_FIX_INTRINSIC | CALIB_USE_INTRINSIC_GUESS))) { - calibrateCameraInternal( objectPoints, imagePoints[k], - npoints, imageSize, -1, &K[k], &Dist[k], Mat(), Mat(), - Mat(), Mat(), Mat(), flags, termCrit ); + Mat mIntr(A[k], /* copyData = */ false); + Mat mDist(distInitial[k], /* copyData = */ false); + calibrateCameraInternal(objectPoints, imagePoints[k], + _npoints, imageSize, 0, mIntr, mDist, + Mat(), Mat(), Mat(), Mat(), Mat(), flags, termCrit); } } if( flags & CALIB_SAME_FOCAL_LENGTH ) { - static const int avg_idx[] = { 0, 4, 2, 5, -1 }; - for( k = 0; avg_idx[k] >= 0; k++ ) - A[0][avg_idx[k]] = A[1][avg_idx[k]] = (A[0][avg_idx[k]] + A[1][avg_idx[k]])*0.5; + A[0](0, 0) = A[1](0, 0) = (A[0](0, 0) + A[1](0, 0))*0.5; + A[0](0, 2) = A[1](0, 2) = (A[0](0, 2) + A[1](0, 2))*0.5; + A[0](1, 1) = A[1](1, 1) = (A[0](1, 1) + A[1](1, 1))*0.5; + A[0](1, 2) = A[1](1, 2) = (A[0](1, 2) + A[1](1, 2))*0.5; } if( flags & CALIB_FIX_ASPECT_RATIO ) { - for( k = 0; k < 2; k++ ) - aspectRatio[k] = A[k][0]/A[k][4]; + for(int k = 0; k < 2; k++ ) + aspectRatio[k] = A[k](0, 0) / A[k](1, 1); } recomputeIntrinsics = (flags & CALIB_FIX_INTRINSIC) == 0; - Mat err( maxPoints*2, 1, CV_64F ); - Mat Je( maxPoints*2, 6, CV_64F ); - Mat J_LR( maxPoints*2, 6, CV_64F ); - Mat Ji( maxPoints*2, NINTRINSIC, CV_64F, Scalar(0) ); - // we optimize for the inter-camera R(3),t(3), then, optionally, // for intrinisic parameters of each camera ((fx,fy,cx,cy,k1,k2,p1,p2) ~ 8 parameters). // Param mapping is: @@ -769,7 +747,10 @@ static double stereoCalibrateImpl( const CvMat* _objectPoints, const CvMat* _ima // - next NINTRINSICS: the same for for 2nd camera nparams = 6*(nimages+1) + (recomputeIntrinsics ? NINTRINSIC*2 : 0); - CvLevMarq solver( nparams, 0, termCrit ); + CvLevMarq solver( nparams, 0, cvTermCriteria(termCrit) ); + double * param = solver.param->data.db; + Mat paramM = Mat(solver.param->rows, solver.param->cols, CV_64F, param); + uchar* mask = solver.mask->data.ptr; if(flags & CALIB_USE_LU) { solver.solveMethod = DECOMP_LU; @@ -777,7 +758,7 @@ static double stereoCalibrateImpl( const CvMat* _objectPoints, const CvMat* _ima if( recomputeIntrinsics ) { - uchar* imask = solver.mask->data.ptr + nparams - NINTRINSIC*2; + size_t idx = nparams - NINTRINSIC*2; if( !(flags & CALIB_RATIONAL_MODEL) ) flags |= CALIB_FIX_K4 | CALIB_FIX_K5 | CALIB_FIX_K6; if( !(flags & CALIB_THIN_PRISM_MODEL) ) @@ -785,41 +766,43 @@ static double stereoCalibrateImpl( const CvMat* _objectPoints, const CvMat* _ima if( !(flags & CALIB_TILTED_MODEL) ) flags |= CALIB_FIX_TAUX_TAUY; if( flags & CALIB_FIX_ASPECT_RATIO ) - imask[0] = imask[NINTRINSIC] = 0; + mask[idx + 0] = mask[idx + NINTRINSIC] = 0; + if ( flags & CALIB_SAME_FOCAL_LENGTH) + mask[idx + NINTRINSIC] = mask[idx + NINTRINSIC + 1] = 0; if( flags & CALIB_FIX_FOCAL_LENGTH ) - imask[0] = imask[1] = imask[NINTRINSIC] = imask[NINTRINSIC+1] = 0; + mask[idx + 0] = mask[idx + 1] = mask[idx + NINTRINSIC] = mask[idx + NINTRINSIC+1] = 0; if( flags & CALIB_FIX_PRINCIPAL_POINT ) - imask[2] = imask[3] = imask[NINTRINSIC+2] = imask[NINTRINSIC+3] = 0; + mask[idx + 2] = mask[idx + 3] = mask[idx + NINTRINSIC+2] = mask[idx + NINTRINSIC+3] = 0; if( flags & (CALIB_ZERO_TANGENT_DIST|CALIB_FIX_TANGENT_DIST) ) - imask[6] = imask[7] = imask[NINTRINSIC+6] = imask[NINTRINSIC+7] = 0; + mask[idx + 6] = mask[idx + 7] = mask[idx + NINTRINSIC+6] = mask[idx + NINTRINSIC+7] = 0; if( flags & CALIB_FIX_K1 ) - imask[4] = imask[NINTRINSIC+4] = 0; + mask[idx + 4] = mask[idx + NINTRINSIC+4] = 0; if( flags & CALIB_FIX_K2 ) - imask[5] = imask[NINTRINSIC+5] = 0; + mask[idx + 5] = mask[idx + NINTRINSIC+5] = 0; if( flags & CALIB_FIX_K3 ) - imask[8] = imask[NINTRINSIC+8] = 0; + mask[idx + 8] = mask[idx + NINTRINSIC+8] = 0; if( flags & CALIB_FIX_K4 ) - imask[9] = imask[NINTRINSIC+9] = 0; + mask[idx + 9] = mask[idx + NINTRINSIC+9] = 0; if( flags & CALIB_FIX_K5 ) - imask[10] = imask[NINTRINSIC+10] = 0; + mask[idx + 10] = mask[idx + NINTRINSIC+10] = 0; if( flags & CALIB_FIX_K6 ) - imask[11] = imask[NINTRINSIC+11] = 0; + mask[idx + 11] = mask[idx + NINTRINSIC+11] = 0; if( flags & CALIB_FIX_S1_S2_S3_S4 ) { - imask[12] = imask[NINTRINSIC+12] = 0; - imask[13] = imask[NINTRINSIC+13] = 0; - imask[14] = imask[NINTRINSIC+14] = 0; - imask[15] = imask[NINTRINSIC+15] = 0; + mask[idx + 12] = mask[idx + NINTRINSIC+12] = 0; + mask[idx + 13] = mask[idx + NINTRINSIC+13] = 0; + mask[idx + 14] = mask[idx + NINTRINSIC+14] = 0; + mask[idx + 15] = mask[idx + NINTRINSIC+15] = 0; } if( flags & CALIB_FIX_TAUX_TAUY ) { - imask[16] = imask[NINTRINSIC+16] = 0; - imask[17] = imask[NINTRINSIC+17] = 0; + mask[idx + 16] = mask[idx + NINTRINSIC+16] = 0; + mask[idx + 17] = mask[idx + NINTRINSIC+17] = 0; } } // storage for initial [om(R){i}|t{i}] (in order to compute the median for each component) - RT0.reset(cvCreateMat( 6, nimages, CV_64F )); + std::vector rtsort(nimages*6); /* Compute initial estimate of pose For each image, compute: @@ -831,199 +814,190 @@ static double stereoCalibrateImpl( const CvMat* _objectPoints, const CvMat* _ima om = median(om_ref_list) T = median(T_ref_list) */ - for( i = ofs = 0; i < nimages; ofs += ni, i++ ) + int pos = 0; + for(int i = 0; i < nimages; i++ ) { - ni = npoints->data.i[i]; - CvMat objpt_i; - double _om[2][3], r[2][9], t[2][3]; - CvMat om[2], R[2], T[2], imgpt_i[2]; - - objpt_i = cvMat(1, ni, CV_64FC3, objectPoints->data.db + ofs*3); - for( k = 0; k < 2; k++ ) + int ni = _npoints.at(i); + Mat objpt_i = objectPoints.colRange(pos, pos + ni); + Matx33d R[2]; + Mat_ rv, T[2]; + for(int k = 0; k < 2; k++ ) { - imgpt_i[k] = cvMat(1, ni, CV_64FC2, imagePoints[k]->data.db + ofs*2); - om[k] = cvMat(3, 1, CV_64F, _om[k]); - R[k] = cvMat(3, 3, CV_64F, r[k]); - T[k] = cvMat(3, 1, CV_64F, t[k]); + Mat imgpt_ik = imagePoints[k].colRange(pos, pos + ni); + solvePnP(objpt_i, imgpt_ik, A[k], distInitial[k], rv, T[k], false, SOLVEPNP_ITERATIVE ); + findExtrinsicCameraParams2( objpt_i, imgpt_ik, Mat(A[k]), Mat(distInitial[k]), + rv, T[k], /*useExtrinsicGuess=*/0 ); + Rodrigues(rv, R[k]); - Mat r_mat = cvarrToMat(&om[k]), t_mat = cvarrToMat(&T[k]); - findExtrinsicCameraParams2( cvarrToMat(&objpt_i), cvarrToMat(&imgpt_i[k]), - cvarrToMat(&K[k]), cvarrToMat(&Dist[k]), - r_mat, t_mat, /*useExtrinsicGuess=*/0 ); - Rodrigues( cvarrToMat(&om[k]), cvarrToMat(&R[k]) ); if( k == 0 ) { // save initial om_left and T_left - solver.param->data.db[(i+1)*6] = _om[0][0]; - solver.param->data.db[(i+1)*6 + 1] = _om[0][1]; - solver.param->data.db[(i+1)*6 + 2] = _om[0][2]; - solver.param->data.db[(i+1)*6 + 3] = t[0][0]; - solver.param->data.db[(i+1)*6 + 4] = t[0][1]; - solver.param->data.db[(i+1)*6 + 5] = t[0][2]; + param[(i+1)*6 + 0] = rv(0); + param[(i+1)*6 + 1] = rv(1); + param[(i+1)*6 + 2] = rv(2); + param[(i+1)*6 + 3] = T[0](0); + param[(i+1)*6 + 4] = T[0](1); + param[(i+1)*6 + 5] = T[0](2); } } - cvGEMM( &R[1], &R[0], 1, 0, 0, &R[0], CV_GEMM_B_T ); - cvGEMM( &R[0], &T[0], -1, &T[1], 1, &T[1] ); - Rodrigues( cvarrToMat(&R[0]), cvarrToMat(&T[0]) ); - RT0->data.db[i] = t[0][0]; - RT0->data.db[i + nimages] = t[0][1]; - RT0->data.db[i + nimages*2] = t[0][2]; - RT0->data.db[i + nimages*3] = t[1][0]; - RT0->data.db[i + nimages*4] = t[1][1]; - RT0->data.db[i + nimages*5] = t[1][2]; + R[0] = R[1]*R[0].t(); + T[1] -= R[0]*T[0]; + + Rodrigues(R[0], rv); + + rtsort[i + nimages*0] = rv(0); + rtsort[i + nimages*1] = rv(1); + rtsort[i + nimages*2] = rv(2); + rtsort[i + nimages*3] = T[1](0); + rtsort[i + nimages*4] = T[1](1); + rtsort[i + nimages*5] = T[1](2); + + pos += ni; } if(flags & CALIB_USE_EXTRINSIC_GUESS) { Vec3d R, T; - cvarrToMat(matT).convertTo(T, CV_64F); + matT.convertTo(T, CV_64F); - if( matR->rows == 3 && matR->cols == 3 ) - Rodrigues(cvarrToMat(matR), R); + if( matR.rows == 3 && matR.cols == 3 ) + Rodrigues(matR, R); else - cvarrToMat(matR).convertTo(R, CV_64F); + matR.convertTo(R, CV_64F); - solver.param->data.db[0] = R[0]; - solver.param->data.db[1] = R[1]; - solver.param->data.db[2] = R[2]; - solver.param->data.db[3] = T[0]; - solver.param->data.db[4] = T[1]; - solver.param->data.db[5] = T[2]; + param[0] = R[0]; + param[1] = R[1]; + param[2] = R[2]; + param[3] = T[0]; + param[4] = T[1]; + param[5] = T[2]; } else { // find the medians and save the first 6 parameters - for( i = 0; i < 6; i++ ) + for(int i = 0; i < 6; i++ ) { - qsort( RT0->data.db + i*nimages, nimages, CV_ELEM_SIZE(RT0->type), dbCmp ); - solver.param->data.db[i] = nimages % 2 != 0 ? RT0->data.db[i*nimages + nimages/2] : - (RT0->data.db[i*nimages + nimages/2 - 1] + RT0->data.db[i*nimages + nimages/2])*0.5; + size_t idx = i*nimages; + std::nth_element(rtsort.begin() + idx, + rtsort.begin() + idx + nimages/2, + rtsort.begin() + idx + nimages); + double h = rtsort[idx + nimages/2]; + param[i] = (nimages % 2 == 0) ? (h + rtsort[idx + nimages/2 - 1]) * 0.5 : h; } } if( recomputeIntrinsics ) - for( k = 0; k < 2; k++ ) + { + for(int k = 0; k < 2; k++ ) { - double* iparam = solver.param->data.db + (nimages+1)*6 + k*NINTRINSIC; + size_t idx = (nimages+1)*6 + k*NINTRINSIC; if( flags & CALIB_ZERO_TANGENT_DIST ) - dk[k][2] = dk[k][3] = 0; - iparam[0] = A[k][0]; iparam[1] = A[k][4]; iparam[2] = A[k][2]; iparam[3] = A[k][5]; - iparam[4] = dk[k][0]; iparam[5] = dk[k][1]; iparam[6] = dk[k][2]; - iparam[7] = dk[k][3]; iparam[8] = dk[k][4]; iparam[9] = dk[k][5]; - iparam[10] = dk[k][6]; iparam[11] = dk[k][7]; - iparam[12] = dk[k][8]; - iparam[13] = dk[k][9]; - iparam[14] = dk[k][10]; - iparam[15] = dk[k][11]; - iparam[16] = dk[k][12]; - iparam[17] = dk[k][13]; + distInitial[k][2] = distInitial[k][3] = 0; + param[idx + 0] = A[k](0, 0); param[idx + 1] = A[k](1, 1); param[idx + 2] = A[k](0, 2); param[idx + 3] = A[k](1, 2); + for (int i = 0; i < 14; i++) + { + param[idx + 4 + i] = distInitial[k][i]; + } } + } - om_LR = cvMat(3, 1, CV_64F, solver.param->data.db); - T_LR = cvMat(3, 1, CV_64F, solver.param->data.db + 3); + // Preallocated place for callback calculations + Mat errBuf( maxPoints*2, 1, CV_64F ); + Mat JeBuf( maxPoints*2, 6, CV_64F ); + Mat J_LRBuf( maxPoints*2, 6, CV_64F ); + Mat JiBuf( maxPoints*2, NINTRINSIC, CV_64F, Scalar(0) ); for(;;) { - const CvMat* param = 0; + const CvMat* tmp_param = 0; CvMat *JtJ = 0, *JtErr = 0; double *_errNorm = 0; - double _omR[3], _tR[3]; - double _dr3dr1[9], _dr3dr2[9], /*_dt3dr1[9],*/ _dt3dr2[9], _dt3dt1[9], _dt3dt2[9]; - CvMat dr3dr1 = cvMat(3, 3, CV_64F, _dr3dr1); - CvMat dr3dr2 = cvMat(3, 3, CV_64F, _dr3dr2); - //CvMat dt3dr1 = cvMat(3, 3, CV_64F, _dt3dr1); - CvMat dt3dr2 = cvMat(3, 3, CV_64F, _dt3dr2); - CvMat dt3dt1 = cvMat(3, 3, CV_64F, _dt3dt1); - CvMat dt3dt2 = cvMat(3, 3, CV_64F, _dt3dt2); - CvMat om[2], T[2], imgpt_i[2]; - - if( !solver.updateAlt( param, JtJ, JtErr, _errNorm )) + Mat_ param_m(1,nparams, solver.param->data.db); + Vec3d om_LR(param_m(0), param_m(1), param_m(2)); + Vec3d T_LR(param_m(3), param_m(4), param_m(5)); + Vec3d om[2], T[2]; + Matx33d dr3dr1, dr3dr2, dt3dr2, dt3dt1, dt3dt2; + Matx33d intrin[2]; + if( !solver.updateAlt( tmp_param, JtJ, JtErr, _errNorm )) break; - reprojErr = 0; + std::vector< std::vector > distCoeffs(2, std::vector(14, 0.0)); - Rodrigues( cvarrToMat(&om_LR), cvarrToMat(&R_LR) ); - om[1] = cvMat(3,1,CV_64F,_omR); - T[1] = cvMat(3,1,CV_64F,_tR); + reprojErr = 0; if( recomputeIntrinsics ) { - double* iparam = solver.param->data.db + (nimages+1)*6; - double* ipparam = solver.prevParam->data.db + (nimages+1)*6; + int idx = (nimages+1)*6; if( flags & CALIB_SAME_FOCAL_LENGTH ) { - iparam[NINTRINSIC] = iparam[0]; - iparam[NINTRINSIC+1] = iparam[1]; - ipparam[NINTRINSIC] = ipparam[0]; - ipparam[NINTRINSIC+1] = ipparam[1]; + param_m(idx + NINTRINSIC ) = param_m(idx + 0); + param_m(idx + NINTRINSIC+1) = param_m(idx + 1); } if( flags & CALIB_FIX_ASPECT_RATIO ) { - iparam[0] = iparam[1]*aspectRatio[0]; - iparam[NINTRINSIC] = iparam[NINTRINSIC+1]*aspectRatio[1]; - ipparam[0] = ipparam[1]*aspectRatio[0]; - ipparam[NINTRINSIC] = ipparam[NINTRINSIC+1]*aspectRatio[1]; + param_m(idx + 0) = aspectRatio[0]*param_m(idx + 1 ); + param_m(idx + NINTRINSIC) = aspectRatio[1]*param_m(idx + 1 + NINTRINSIC); } - for( k = 0; k < 2; k++ ) + for(int k = 0; k < 2; k++ ) { - A[k][0] = iparam[k*NINTRINSIC+0]; - A[k][4] = iparam[k*NINTRINSIC+1]; - A[k][2] = iparam[k*NINTRINSIC+2]; - A[k][5] = iparam[k*NINTRINSIC+3]; - dk[k][0] = iparam[k*NINTRINSIC+4]; - dk[k][1] = iparam[k*NINTRINSIC+5]; - dk[k][2] = iparam[k*NINTRINSIC+6]; - dk[k][3] = iparam[k*NINTRINSIC+7]; - dk[k][4] = iparam[k*NINTRINSIC+8]; - dk[k][5] = iparam[k*NINTRINSIC+9]; - dk[k][6] = iparam[k*NINTRINSIC+10]; - dk[k][7] = iparam[k*NINTRINSIC+11]; - dk[k][8] = iparam[k*NINTRINSIC+12]; - dk[k][9] = iparam[k*NINTRINSIC+13]; - dk[k][10] = iparam[k*NINTRINSIC+14]; - dk[k][11] = iparam[k*NINTRINSIC+15]; - dk[k][12] = iparam[k*NINTRINSIC+16]; - dk[k][13] = iparam[k*NINTRINSIC+17]; + double fx = param_m(idx + k*NINTRINSIC+0), fy = param_m(idx + k*NINTRINSIC+1); + double cx = param_m(idx + k*NINTRINSIC+2), cy = param_m(idx + k*NINTRINSIC+3); + intrin[k] = Matx33d(fx, 0, cx, + 0, fy, cy, + 0, 0, 1); + for(int j = 0; j < 14; j++) + distCoeffs[k][j] = param_m(idx + k*NINTRINSIC+4+j); + } + } + else + { + for (int k = 0; k < 2; k++) + { + intrin[k] = A[k]; + for(int j = 0; j < 14; j++) + distCoeffs[k][j] = distInitial[k][j]; } } - for( i = ofs = 0; i < nimages; ofs += ni, i++ ) + int ptPos = 0; + for(int i = 0; i < nimages; i++ ) { - ni = npoints->data.i[i]; - CvMat objpt_i; + int ni = _npoints.at(i); - om[0] = cvMat(3,1,CV_64F,solver.param->data.db+(i+1)*6); - T[0] = cvMat(3,1,CV_64F,solver.param->data.db+(i+1)*6+3); + int idx = (i+1)*6; + om[0] = Vec3d(param_m(idx + 0), param_m(idx + 1), param_m(idx + 2)); + T[0] = Vec3d(param_m(idx + 3), param_m(idx + 4), param_m(idx + 5)); if( JtJ || JtErr ) - composeRT( cvarrToMat(&om[0]), cvarrToMat(&T[0]), cvarrToMat(&om_LR), - cvarrToMat(&T_LR), cvarrToMat(&om[1]), cvarrToMat(&T[1]), - cvarrToMat(&dr3dr1), noArray(), cvarrToMat(&dr3dr2), - noArray(), noArray(), cvarrToMat(&dt3dt1), cvarrToMat(&dt3dr2), - cvarrToMat(&dt3dt2 ) ); + composeRT( om[0], T[0], om_LR, T_LR, om[1], T[1], dr3dr1, noArray(), + dr3dr2, noArray(), noArray(), dt3dt1, dt3dr2, dt3dt2 ); else - composeRT( cvarrToMat(&om[0]), cvarrToMat(&T[0]), cvarrToMat(&om_LR), - cvarrToMat(&T_LR), cvarrToMat(&om[1]), cvarrToMat(&T[1]) ); + composeRT( om[0], T[0], om_LR, T_LR, om[1], T[1] ); - objpt_i = cvMat(1, ni, CV_64FC3, objectPoints->data.db + ofs*3); - err.resize(ni*2); Je.resize(ni*2); J_LR.resize(ni*2); Ji.resize(ni*2); + Mat objpt_i = objectPoints(Range::all(), Range(ptPos, ptPos + ni)); + Mat err = errBuf (Range(0, ni*2), Range::all()); + Mat Je = JeBuf (Range(0, ni*2), Range::all()); + Mat J_LR = J_LRBuf(Range(0, ni*2), Range::all()); + Mat Ji = JiBuf (Range(0, ni*2), Range::all()); - CvMat tmpimagePoints = cvMat(err.reshape(2, 1)); + Mat tmpImagePoints = err.reshape(2, 1); + Mat dpdf = Ji.colRange(0, 2); + Mat dpdc = Ji.colRange(2, 4); + Mat dpdk = Ji.colRange(4, NINTRINSIC); + Mat dpdrot = Je.colRange(0, 3); + Mat dpdt = Je.colRange(3, 6); - for( k = 0; k < 2; k++ ) + for(int k = 0; k < 2; k++ ) { - imgpt_i[k] = cvMat(1, ni, CV_64FC2, imagePoints[k]->data.db + ofs*2); + Mat imgpt_ik = imagePoints[k](Range::all(), Range(ptPos, ptPos + ni)); if( JtJ || JtErr ) - projectPoints( cvarrToMat(&objpt_i), cvarrToMat(&om[k]), cvarrToMat(&T[k]), - cvarrToMat(&K[k]), cvarrToMat(&Dist[k]), - err.reshape(2, 1), Je.colRange(0, 3), Je.colRange(3, 6), - Ji.colRange(0, 2), Ji.colRange(2, 4), Ji.colRange(4, 4 + Dist[k].cols * Dist[k].rows), noArray(), - (flags & CALIB_FIX_ASPECT_RATIO) ? aspectRatio[k] : 0); + projectPoints(objpt_i, om[k], T[k], intrin[k], distCoeffs[k], + tmpImagePoints, dpdrot, dpdt, dpdf, dpdc, dpdk, noArray(), + (flags & CALIB_FIX_ASPECT_RATIO) ? aspectRatio[k] : 0.); else - projectPoints( cvarrToMat(&objpt_i), cvarrToMat(&om[k]), cvarrToMat(&T[k]), - cvarrToMat(&K[k]), cvarrToMat(&Dist[k]), cvarrToMat(&tmpimagePoints) ); - cvSub( &tmpimagePoints, &imgpt_i[k], &tmpimagePoints ); + projectPoints(objpt_i, om[k], T[k], intrin[k], distCoeffs[k], tmpImagePoints); + subtract( tmpImagePoints, imgpt_ik, tmpImagePoints ); if( solver.state == CvLevMarq::CALC_J ) { @@ -1036,26 +1010,37 @@ static double stereoCalibrateImpl( const CvMat* _objectPoints, const CvMat* _ima { // d(err_{x|y}R) ~ de3 // convert de3/{dr3,dt3} => de3{dr1,dt1} & de3{dr2,dt2} - for( p = 0; p < ni*2; p++ ) + for(int p = 0; p < ni*2; p++ ) { - CvMat de3dr3 = cvMat( 1, 3, CV_64F, Je.ptr(p)); - CvMat de3dt3 = cvMat( 1, 3, CV_64F, de3dr3.data.db + 3 ); - CvMat de3dr2 = cvMat( 1, 3, CV_64F, J_LR.ptr(p) ); - CvMat de3dt2 = cvMat( 1, 3, CV_64F, de3dr2.data.db + 3 ); - double _de3dr1[3], _de3dt1[3]; - CvMat de3dr1 = cvMat( 1, 3, CV_64F, _de3dr1 ); - CvMat de3dt1 = cvMat( 1, 3, CV_64F, _de3dt1 ); + Matx13d de3dr3, de3dt3, de3dr2, de3dt2, de3dr1, de3dt1; + for(int j = 0; j < 3; j++) + de3dr3(j) = Je.at(p, j); - cvMatMul( &de3dr3, &dr3dr1, &de3dr1 ); - cvMatMul( &de3dt3, &dt3dt1, &de3dt1 ); + for(int j = 0; j < 3; j++) + de3dt3(j) = Je.at(p, 3+j); - cvMatMul( &de3dr3, &dr3dr2, &de3dr2 ); - cvMatMulAdd( &de3dt3, &dt3dr2, &de3dr2, &de3dr2 ); + for(int j = 0; j < 3; j++) + de3dr2(j) = J_LR.at(p, j); - cvMatMul( &de3dt3, &dt3dt2, &de3dt2 ); + for(int j = 0; j < 3; j++) + de3dt2(j) = J_LR.at(p, 3+j); - cvCopy( &de3dr1, &de3dr3 ); - cvCopy( &de3dt1, &de3dt3 ); + de3dr1 = de3dr3 * dr3dr1; + de3dt1 = de3dt3 * dt3dt1; + de3dr2 = de3dr3 * dr3dr2 + de3dt3 * dt3dr2; + de3dt2 = de3dt3 * dt3dt2; + + for(int j = 0; j < 3; j++) + Je.at(p, j) = de3dr1(j); + + for(int j = 0; j < 3; j++) + Je.at(p, 3+j) = de3dt1(j); + + for(int j = 0; j < 3; j++) + J_LR.at(p, j) = de3dr2(j); + + for(int j = 0; j < 3; j++) + J_LR.at(p, 3+j) = de3dt2(j); } _JtJ(Rect(0, 0, 6, 6)) += J_LR.t()*J_LR; @@ -1083,53 +1068,63 @@ static double stereoCalibrateImpl( const CvMat* _objectPoints, const CvMat* _ima perViewErr.at(i, k) = std::sqrt(viewErr/ni); reprojErr += viewErr; } + ptPos += ni; } if(_errNorm) *_errNorm = reprojErr; } - Rodrigues( cvarrToMat(&om_LR), cvarrToMat(&R_LR) ); - if( matR->rows == 1 || matR->cols == 1 ) - cvConvert( &om_LR, matR ); + // Extract optimized params from the param vector + + Vec3d om_LR(param[0], param[1], param[2]); + Vec3d T_LR(param[3], param[4], param[5]); + Matx33d R_LR; + Rodrigues( om_LR, R_LR ); + if( matR.rows == 1 || matR.cols == 1 ) + Mat(om_LR).convertTo(matR, matR.depth()); else - cvConvert( &R_LR, matR ); - cvConvert( &T_LR, matT ); + Mat(R_LR).convertTo(matR, matR.depth()); + Mat(T_LR).convertTo(matT, matT.depth()); if( recomputeIntrinsics ) { - cvConvert( &K[0], _cameraMatrix1 ); - cvConvert( &K[1], _cameraMatrix2 ); - - for( k = 0; k < 2; k++ ) + for(int k = 0; k < 2; k++ ) { - CvMat* distCoeffs = k == 0 ? _distCoeffs1 : _distCoeffs2; - CvMat tdist = cvMat( distCoeffs->rows, distCoeffs->cols, - CV_MAKETYPE(CV_64F,CV_MAT_CN(distCoeffs->type)), Dist[k].data.db ); - cvConvert( &tdist, distCoeffs ); + size_t idx = (nimages+1)*6 + k*NINTRINSIC; + A[k] = Matx33d(param[idx + 0], 0, param[idx + 2], 0, param[idx + 1], param[idx + 3], 0, 0, 1); + + Mat& cameraMatrix = k == 0 ? _cameraMatrix1 : _cameraMatrix2; + Mat& distCoeffs = k == 0 ? _distCoeffs1 : _distCoeffs2; + Mat(A[k]).convertTo(cameraMatrix, cameraMatrix.depth()); + + std::vector vdist(14); + for(int j = 0; j < 14; j++) + vdist[j] = param[idx + 4 + j]; + + Mat tdist( distCoeffs.size(), CV_MAKETYPE(CV_64F, distCoeffs.channels()), vdist.data()); + tdist.convertTo(distCoeffs, distCoeffs.depth()); } } if( !matE.empty() || !matF.empty() ) { - double* t = T_LR.data.db; - Matx33d Tx(0, -t[2], t[1], - t[2], 0, -t[0], - -t[1], t[0], 0); - Matx33d E = Mat(Tx*cvarrToMat(&R_LR)); + Matx33d Tx(0, -T_LR[2], T_LR[1], + T_LR[2], 0, -T_LR[0], + -T_LR[1], T_LR[0], 0); + Matx33d E = Tx*R_LR; if( !matE.empty() ) Mat(E).convertTo(matE, matE.depth()); if( !matF.empty()) { - Matx33d iA0 = Mat(cvarrToMat(&K[0]).inv()), iA1 = Mat(cvarrToMat(&K[1]).inv()); + Matx33d iA0 = A[0].inv(), iA1 = A[1].inv(); Matx33d F = iA1.t() * E * iA0; Mat(F).convertTo(matF, matF.depth(), fabs(F(2,2)) > 0 ? 1./F(2,2) : 1.); } } - double* param = solver.param->data.db; Mat r1d = rvecs.empty() ? Mat() : rvecs.reshape(1, nimages); Mat t1d = tvecs.empty() ? Mat() : tvecs.reshape(1, nimages); - for( i = 0; i < nimages; i++ ) + for(int i = 0; i < nimages; i++ ) { int idx = (i + 1) * 6; @@ -1439,16 +1434,13 @@ double calibrateCameraRO(InputArrayOfArrays _objectPoints, errorsM = _perViewErrors.getMat(); } - CvMat c_objPt = cvMat(objPt), c_imgPt = cvMat(imgPt), c_npoints = cvMat(npoints); - CvMat c_cameraMatrix = cvMat(cameraMatrix), c_distCoeffs = cvMat(distCoeffs); - double reprojErr = calibrateCameraInternal( - &c_objPt, &c_imgPt, &c_npoints, cvSize(imageSize), iFixedPoint, - &c_cameraMatrix, &c_distCoeffs, + objPt, imgPt, npoints, imageSize, iFixedPoint, + cameraMatrix, distCoeffs, rvecM, tvecM, newObjPt, stdDeviationsM, - errorsM, flags, cvTermCriteria(criteria)); + errorsM, flags, criteria); if( stddev_needed ) { @@ -1609,11 +1601,7 @@ double stereoCalibrate( InputArrayOfArrays _objectPoints, collectCalibrationData( _objectPoints, _imagePoints1, _imagePoints2, objPt, imgPt, imgPt2, npoints ); - CvMat c_objPt = cvMat(objPt), c_imgPt = cvMat(imgPt), c_imgPt2 = cvMat(imgPt2), c_npoints = cvMat(npoints); - CvMat c_cameraMatrix1 = cvMat(cameraMatrix1), c_distCoeffs1 = cvMat(distCoeffs1); - CvMat c_cameraMatrix2 = cvMat(cameraMatrix2), c_distCoeffs2 = cvMat(distCoeffs2); Mat matR = _Rmat.getMat(), matT = _Tmat.getMat(); - CvMat c_matR = cvMat(matR), c_matT = cvMat(matT); bool E_needed = _Emat.needed(), F_needed = _Fmat.needed(); bool rvecs_needed = _rvecs.needed(), tvecs_needed = _tvecs.needed(); @@ -1659,10 +1647,10 @@ double stereoCalibrate( InputArrayOfArrays _objectPoints, matErr = _perViewErrors.getMat(); } - double err = stereoCalibrateImpl(&c_objPt, &c_imgPt, &c_imgPt2, &c_npoints, &c_cameraMatrix1, - &c_distCoeffs1, &c_cameraMatrix2, &c_distCoeffs2, cvSize(imageSize), - &c_matR, &c_matT, matE, matF, rvecLM, tvecLM, - matErr, flags, cvTermCriteria(criteria)); + double err = stereoCalibrateImpl(objPt, imgPt, imgPt2, npoints, cameraMatrix1, + distCoeffs1, cameraMatrix2, distCoeffs2, imageSize, + matR, matT, matE, matF, rvecLM, tvecLM, + matErr, flags, criteria); cameraMatrix1.copyTo(_cameraMatrix1); cameraMatrix2.copyTo(_cameraMatrix2); distCoeffs1.copyTo(_distCoeffs1); From 2e21e11318c441da0a82931548ba9ec67285e7c7 Mon Sep 17 00:00:00 2001 From: Letu Ren Date: Tue, 17 Dec 2024 21:35:59 +0800 Subject: [PATCH 030/102] Fix test_exif compilation when non of JPEG, PNG, AVIF is enabled When none of JPEG, PNG, AVIF is enabled, exif_files is a zero-length array, which is prohibited by C++ reference. --- modules/imgcodecs/test/test_exif.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/imgcodecs/test/test_exif.cpp b/modules/imgcodecs/test/test_exif.cpp index 62cd471e23..d1a9e720a9 100644 --- a/modules/imgcodecs/test/test_exif.cpp +++ b/modules/imgcodecs/test/test_exif.cpp @@ -2,6 +2,9 @@ // 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 +#include + #include "test_precomp.hpp" namespace opencv_test { namespace { @@ -110,7 +113,7 @@ TEST_P(Exif, exif_orientation) } } -const string exif_files[] = +const std::vector exif_files { #ifdef HAVE_JPEG "readwrite/testExifOrientation_1.jpg", From 59b9681af6aab9c577e19150765b724a29edc7ca Mon Sep 17 00:00:00 2001 From: Letu Ren Date: Tue, 17 Dec 2024 21:51:35 +0800 Subject: [PATCH 031/102] Remove useless -Wno-long-long option According to GCC doc, -Wlong-long: Warn if long long type is used. This is enabled by either -Wpedantic or -Wtraditional in ISO C90 and C++98 modes. To inhibit the warning messages, use -Wno-long-long. OpenCV 4.x requires C++11. As result, this option is useless. Ref: https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html --- cmake/OpenCVCompilerOptions.cmake | 5 ----- 1 file changed, 5 deletions(-) diff --git a/cmake/OpenCVCompilerOptions.cmake b/cmake/OpenCVCompilerOptions.cmake index f0d6378bd7..a664e417c3 100644 --- a/cmake/OpenCVCompilerOptions.cmake +++ b/cmake/OpenCVCompilerOptions.cmake @@ -191,11 +191,6 @@ if(CV_GCC OR CV_CLANG OR CV_ICX) endif() add_extra_compiler_option(-fdiagnostics-show-option) - # The -Wno-long-long is required in 64bit systems when including system headers. - if(X86_64) - add_extra_compiler_option(-Wno-long-long) - endif() - # We need pthread's, unless we have explicitly disabled multi-thread execution. if(NOT OPENCV_DISABLE_THREAD_SUPPORT AND ( From d77abeddd0be14312f55702cc35fa15261408620 Mon Sep 17 00:00:00 2001 From: Pierre Chatelier Date: Tue, 17 Dec 2024 15:26:14 +0100 Subject: [PATCH 032/102] Merge pull request #26472 from chacha21:gpumatnd_step More convenient GpuMatND constructor #26472 Closes #26471 For convenience, GpuMatND can now accept a step.size() equal to size.size(), as long as the last step is equal to elemSize() - [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/core/include/opencv2/core/cuda.hpp | 5 +++-- modules/core/src/cuda_gpu_mat_nd.cpp | 6 ++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/modules/core/include/opencv2/core/cuda.hpp b/modules/core/include/opencv2/core/cuda.hpp index 6cd6711582..8191c00783 100644 --- a/modules/core/include/opencv2/core/cuda.hpp +++ b/modules/core/include/opencv2/core/cuda.hpp @@ -413,8 +413,9 @@ public: data, which means that no data is copied. This operation is very efficient and can be used to process external data using OpenCV functions. The external data is not automatically deallocated, so you should take care of it. - @param step Array of _size.size()-1 steps in case of a multi-dimensional array (the last step is always - set to the element size). If not specified, the matrix is assumed to be continuous. + @param step Array of _size.size() or _size.size()-1 steps in case of a multi-dimensional array + (if specified, the last step must be equal to the element size, otherwise it will be added as such). + If not specified, the matrix is assumed to be continuous. */ GpuMatND(SizeArray size, int type, void* data, StepArray step = StepArray()); diff --git a/modules/core/src/cuda_gpu_mat_nd.cpp b/modules/core/src/cuda_gpu_mat_nd.cpp index 8440f179ea..98f270d848 100644 --- a/modules/core/src/cuda_gpu_mat_nd.cpp +++ b/modules/core/src/cuda_gpu_mat_nd.cpp @@ -12,7 +12,8 @@ GpuMatND::~GpuMatND() = default; GpuMatND::GpuMatND(SizeArray _size, int _type, void* _data, StepArray _step) : flags(0), dims(0), data(static_cast(_data)), offset(0) { - CV_Assert(_step.empty() || _size.size() == _step.size() + 1); + CV_Assert(_step.empty() || _size.size() == _step.size() + 1 || + (_size.size() == _step.size() && _step.back() == (size_t)CV_ELEM_SIZE(_type))); setFields(std::move(_size), _type, std::move(_step)); } @@ -106,7 +107,8 @@ void GpuMatND::setFields(SizeArray _size, int _type, StepArray _step) else { step = std::move(_step); - step.push_back(elemSize()); + if (step.size() < size.size()) + step.push_back(elemSize()); flags = cv::updateContinuityFlag(flags, dims, size.data(), step.data()); } From 3899a060a38e14e5571416528605ac7ea606ef90 Mon Sep 17 00:00:00 2001 From: Letu Ren Date: Wed, 18 Dec 2024 05:27:10 +0800 Subject: [PATCH 033/102] Fix Syntax warning in ts summary.py --- modules/ts/misc/summary.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ts/misc/summary.py b/modules/ts/misc/summary.py index 5874d37e72..89aa693752 100755 --- a/modules/ts/misc/summary.py +++ b/modules/ts/misc/summary.py @@ -37,8 +37,8 @@ import testlog_parser, sys, os, xml, glob, re from table_formatter import * from optparse import OptionParser -numeric_re = re.compile("(\d+)") -cvtype_re = re.compile("(8U|8S|16U|16S|32S|32F|64F)C(\d{1,3})") +numeric_re = re.compile(r"(\d+)") +cvtype_re = re.compile(r"(8U|8S|16U|16S|32S|32F|64F)C(\d{1,3})") cvtypes = { '8U': 0, '8S': 1, '16U': 2, '16S': 3, '32S': 4, '32F': 5, '64F': 6 } convert = lambda text: int(text) if text.isdigit() else text From 1c28a98b3484e37e75012e555c2db8f9e98d7dc1 Mon Sep 17 00:00:00 2001 From: quic-xuezha Date: Wed, 18 Dec 2024 14:34:13 +0800 Subject: [PATCH 034/102] Merge pull request #26617 from CodeLinaro:xuezha_2ndPost FastCV-based HAL for OpenCV acceleration 2ndpost-1 #26617 ### Detailed description: - Add parallel support for cv_hal_sobel - Add cv_hal_gaussianBlurBinomial and parallel support. - Add cv_hal_addWeighted8u and parallel support - Add cv_hal_warpPerspective and parallel support Requires binary from [opencv/opencv_3rdparty#90](https://github.com/opencv/opencv_3rdparty/pull/90) Related patch to opencv_contrib: [opencv/opencv_contrib#3844](https://github.com/opencv/opencv_contrib/pull/3844) ### 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 +- 3rdparty/fastcv/include/fastcv_hal_core.hpp | 25 + .../fastcv/include/fastcv_hal_imgproc.hpp | 73 ++- 3rdparty/fastcv/include/fastcv_hal_utils.hpp | 5 +- 3rdparty/fastcv/src/fastcv_hal_core.cpp | 98 +++- 3rdparty/fastcv/src/fastcv_hal_imgproc.cpp | 506 ++++++++++++++++-- 6 files changed, 648 insertions(+), 73 deletions(-) diff --git a/3rdparty/fastcv/fastcv.cmake b/3rdparty/fastcv/fastcv.cmake index a3c452aa8e..b8172705ca 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 "b8f0d48fa9dbebb0237d3e0abd206f9930c89db6") + set(FASTCV_COMMIT "dc5d58018f3af915a8d209386d2c58c0501c0f2c") # Define actual FastCV versions if(ANDROID) if(AARCH64) message(STATUS "Download FastCV for Android aarch64") - set(FCV_PACKAGE_NAME "fastcv_android_aarch64_2024_10_24.tgz") - set(FCV_PACKAGE_HASH "14486af00dc0282dac591dc9ccdd957e") + set(FCV_PACKAGE_NAME "fastcv_android_aarch64_2024_12_11.tgz") + set(FCV_PACKAGE_HASH "9dac41e86597305f846212dae31a4a88") else() message(STATUS "Download FastCV for Android armv7") - set(FCV_PACKAGE_NAME "fastcv_android_arm32_2024_10_24.tgz") - set(FCV_PACKAGE_HASH "b5afadd5a5b55f8f6c2e7361f225fa21") + set(FCV_PACKAGE_NAME "fastcv_android_arm32_2024_12_11.tgz") + set(FCV_PACKAGE_HASH "fe2d30334180b17e3031eee92aac43b6") endif() elseif(UNIX AND NOT APPLE AND NOT IOS AND NOT XROS) if(AARCH64) - set(FCV_PACKAGE_NAME "fastcv_linux_aarch64_2024_10_24.tgz") - set(FCV_PACKAGE_HASH "d15c7b77f2d3577ba46bd94e6cf15230") + set(FCV_PACKAGE_NAME "fastcv_linux_aarch64_2024_12_11.tgz") + set(FCV_PACKAGE_HASH "7b33ad833e6f15ab6d4ec64fa3c17acd") else() message("FastCV: fastcv lib for 32-bit Linux is not supported for now!") endif() diff --git a/3rdparty/fastcv/include/fastcv_hal_core.hpp b/3rdparty/fastcv/include/fastcv_hal_core.hpp index 5753ee96bd..47025856f7 100644 --- a/3rdparty/fastcv/include/fastcv_hal_core.hpp +++ b/3rdparty/fastcv/include/fastcv_hal_core.hpp @@ -24,6 +24,8 @@ #define cv_hal_flip fastcv_hal_flip #undef cv_hal_rotate90 #define cv_hal_rotate90 fastcv_hal_rotate +#undef cv_hal_addWeighted8u +#define cv_hal_addWeighted8u fastcv_hal_addWeighted8u //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// @brief look-up table transform of an array. @@ -152,4 +154,27 @@ int fastcv_hal_rotate( size_t dst_step, int angle); +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/// @brief weighted sum of two arrays using formula: dst[i] = a * src1[i] + b * src2[i] +/// @param src1_data first source image data +/// @param src1_step first source image step +/// @param src2_data second source image data +/// @param src2_step second source image step +/// @param dst_data destination image data +/// @param dst_step destination image step +/// @param width width of the images +/// @param height height of the images +/// @param scalars numbers a, b, and c +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +int fastcv_hal_addWeighted8u( + const uchar* src1_data, + size_t src1_step, + const uchar* src2_data, + size_t src2_step, + uchar* dst_data, + size_t dst_step, + int width, + int height, + const double scalars[3]); + #endif diff --git a/3rdparty/fastcv/include/fastcv_hal_imgproc.hpp b/3rdparty/fastcv/include/fastcv_hal_imgproc.hpp index dce20f927e..7fd49bc62c 100644 --- a/3rdparty/fastcv/include/fastcv_hal_imgproc.hpp +++ b/3rdparty/fastcv/include/fastcv_hal_imgproc.hpp @@ -12,10 +12,14 @@ #define cv_hal_medianBlur fastcv_hal_medianBlur #undef cv_hal_sobel #define cv_hal_sobel fastcv_hal_sobel -#undef cv_hal_boxFilter +#undef cv_hal_boxFilter #define cv_hal_boxFilter fastcv_hal_boxFilter -#undef cv_hal_adaptiveThreshold +#undef cv_hal_adaptiveThreshold #define cv_hal_adaptiveThreshold fastcv_hal_adaptiveThreshold +#undef cv_hal_gaussianBlurBinomial +#define cv_hal_gaussianBlurBinomial fastcv_hal_gaussianBlurBinomial +#undef cv_hal_warpPerspective +#define cv_hal_warpPerspective fastcv_hal_warpPerspective //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// @brief Calculate medianBlur filter @@ -148,4 +152,69 @@ int fastcv_hal_adaptiveThreshold( int blockSize, double C); +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/// @brief Blurs an image using a Gaussian filter. +/// @param src_data Source image data +/// @param src_step Source image step +/// @param dst_data Destination image data +/// @param dst_step Destination image step +/// @param width Source image width +/// @param height Source image height +/// @param depth Depth of source and destination image +/// @param cn Number of channels +/// @param margin_left Left margins for source image +/// @param margin_top Top margins for source image +/// @param margin_right Right margins for source image +/// @param margin_bottom Bottom margins for source image +/// @param ksize Kernel size +/// @param border_type Border type +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +int fastcv_hal_gaussianBlurBinomial( + const uchar* src_data, + size_t src_step, + uchar* dst_data, + size_t dst_step, + int width, + int height, + int depth, + int cn, + size_t margin_left, + size_t margin_top, + size_t margin_right, + size_t margin_bottom, + size_t ksize, + int border_type); + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/// @brief Applies a perspective transformation to an image. +/// +/// @param src_type Source and destination image type +/// @param src_data Source image data +/// @param src_step Source image step +/// @param src_width Source image width +/// @param src_height Source image height +/// @param dst_data Destination image data +/// @param dst_step Destination image step +/// @param dst_width Destination image width +/// @param dst_height Destination image height +/// @param M 3x3 matrix with transform coefficients +/// @param interpolation Interpolation mode (CV_HAL_INTER_NEAREST, ...) +/// @param border_type Border processing mode (CV_HAL_BORDER_REFLECT, ...) +/// @param border_value Values to use for CV_HAL_BORDER_CONSTANT mode +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +int fastcv_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 border_type, + const double border_value[4]); + #endif diff --git a/3rdparty/fastcv/include/fastcv_hal_utils.hpp b/3rdparty/fastcv/include/fastcv_hal_utils.hpp index 781e3cb5f6..8492fbef58 100644 --- a/3rdparty/fastcv/include/fastcv_hal_utils.hpp +++ b/3rdparty/fastcv/include/fastcv_hal_utils.hpp @@ -29,7 +29,7 @@ status == FASTCV_EHWGPU) \ { \ CV_LOG_DEBUG(NULL, "FastCV status:"<= 128.0f) || + (scalars[1] < -128.0f) || (scalars[1] >= 128.0f) || + (scalars[2] < -(1<<23))|| (scalars[2] >= 1<<23)) + CV_HAL_RETURN_NOT_IMPLEMENTED( + cv::format("Alpha:%f,Beta:%f,Gamma:%f is not supported because it's too large or too small\n", + scalars[0],scalars[1],scalars[2])); + + INITIALIZATION_CHECK; + + fcvStatus status = FASTCV_SUCCESS; + + if (height == 1) + { + src1_step = width*sizeof(uchar); + src2_step = width*sizeof(uchar); + dst_step = width*sizeof(uchar); + + cv::parallel_for_(cv::Range(0, width), [&](const cv::Range &range){ + int rangeWidth = range.end - range.start; + const uint8_t *src1 = src1_data + range.start; + const uint8_t *src2 = src2_data + range.start; + uint8_t *dst = dst_data + range.start; + fcvAddWeightedu8_v2(src1, rangeWidth, height, src1_step, src2, src2_step, + scalars[0], scalars[1], scalars[2], dst, dst_step); + }); + } + else + { + cv::parallel_for_(cv::Range(0, height), [&](const cv::Range &range){ + int rangeHeight = range.end - range.start; + const uint8_t *src1 = src1_data + range.start * src1_step; + const uint8_t *src2 = src2_data + range.start * src2_step; + uint8_t *dst = dst_data + range.start * dst_step; + fcvAddWeightedu8_v2(src1, width, rangeHeight, src1_step, src2, src2_step, + scalars[0], scalars[1], scalars[2], dst, dst_step); + }); + } + + CV_HAL_RETURN(status, hal_addWeighted8u_v2); } \ No newline at end of file diff --git a/3rdparty/fastcv/src/fastcv_hal_imgproc.cpp b/3rdparty/fastcv/src/fastcv_hal_imgproc.cpp index a771b21113..7f128e7d1c 100644 --- a/3rdparty/fastcv/src/fastcv_hal_imgproc.cpp +++ b/3rdparty/fastcv/src/fastcv_hal_imgproc.cpp @@ -34,7 +34,7 @@ int fastcv_hal_medianBlur( INITIALIZATION_CHECK; - fcvStatus status; + fcvStatus status = FASTCV_SUCCESS; int fcvFuncType = FCV_MAKETYPE(ksize,depth); switch (fcvFuncType) @@ -52,6 +52,101 @@ int fastcv_hal_medianBlur( CV_HAL_RETURN(status, hal_medianBlur); } +class FcvSobelLoop_Invoker : public cv::ParallelLoopBody +{ + public: + + FcvSobelLoop_Invoker(const cv::Mat& _src, cv::Mat& _dst, int _dx, int _dy, int _ksize, fcvBorderType _fcvBorder, + int _fcvBorderValue) : cv::ParallelLoopBody(), src(_src), dst(_dst), dx(_dx), dy(_dy), ksize(_ksize), + fcvBorder(_fcvBorder), fcvBorderValue(_fcvBorderValue) + { + width = src.cols; + height = src.rows; + halfKernelSize = ksize/2; + fcvFuncType = FCV_MAKETYPE(ksize,src.depth()); + } + + virtual void operator()(const cv::Range& range) const CV_OVERRIDE + { + int topLines = 0; + int rangeHeight = range.end-range.start; + int paddedHeight = rangeHeight; + + // Need additional lines to be border. + if(range.start > 0) + { + topLines += halfKernelSize; + paddedHeight += halfKernelSize; + } + + if(range.end < height) + { + paddedHeight += halfKernelSize; + } + + cv::Mat srcPadded = src(cv::Rect(0, range.start-topLines, width, paddedHeight)); + cv::Mat dstPadded = cv::Mat(paddedHeight, width, dst.depth()); + + int16_t *dxBuffer = nullptr, *dyBuffer = nullptr; + + if ((dx == 1) && (dy == 0)) + { + dxBuffer = (int16_t*)dstPadded.data; + } + else if ((dx == 0) && (dy == 1)) + { + dyBuffer = (int16_t*)dstPadded.data; + } + + switch (fcvFuncType) + { + case FCV_MAKETYPE(3,CV_8U): + { + fcvFilterSobel3x3u8s16(srcPadded.data, width, paddedHeight, srcPadded.step, dxBuffer, dyBuffer, dstPadded.step, + fcvBorder, 0); + break; + } + case FCV_MAKETYPE(5,CV_8U): + { + fcvFilterSobel5x5u8s16(srcPadded.data, width, paddedHeight, srcPadded.step, dxBuffer, dyBuffer, dstPadded.step, + fcvBorder, 0); + break; + } + case FCV_MAKETYPE(7,CV_8U): + { + fcvFilterSobel7x7u8s16(srcPadded.data, width, paddedHeight, srcPadded.step, dxBuffer, dyBuffer, dstPadded.step, + fcvBorder, 0); + break; + } + default: + CV_Error(cv::Error::StsBadArg, cv::format("Ksize:%d, src_depth:%s is not supported", + ksize, cv::depthToString(src.depth()))); + break; + } + + // Only copy center part back to output image and ignore the padded lines + cv::Mat temp1 = dstPadded(cv::Rect(0, topLines, width, rangeHeight)); + cv::Mat temp2 = dst(cv::Rect(0, range.start, width, rangeHeight)); + temp1.copyTo(temp2); + } + + private: + const cv::Mat& src; + cv::Mat& dst; + int width; + int height; + int dx; + int dy; + int ksize; + int halfKernelSize; + int fcvFuncType; + fcvBorderType fcvBorder; + int fcvBorderValue; + + FcvSobelLoop_Invoker(const FcvSobelLoop_Invoker &); // = delete; + const FcvSobelLoop_Invoker& operator= (const FcvSobelLoop_Invoker &); // = delete; +}; + int fastcv_hal_sobel( const uchar* src_data, size_t src_step, @@ -73,10 +168,13 @@ int fastcv_hal_sobel( double delta, int border_type) { - - if(scale != 1.0f || delta != 0.0f) + if (!(FCV_CMP_EQ(scale, 1.0f) && FCV_CMP_EQ(delta, 0.0f))) CV_HAL_RETURN_NOT_IMPLEMENTED(cv::format("Scale:%f, delta:%f is not supported", scale, delta)); + // Only support one direction derivatives and the order is 1.(dx=1 && dy=0)||(dx=0 && dy=1) + if ((dx + dy == 0) || (dx + dy > 1)) + CV_HAL_RETURN_NOT_IMPLEMENTED(cv::format("Dx:%d Dy:%d is not supported",dx, dy)); + // Do not support inplace case if (src_data == dst_data) CV_HAL_RETURN_NOT_IMPLEMENTED("Inplace is not supported"); @@ -89,10 +187,6 @@ int fastcv_hal_sobel( if (cn != 1) CV_HAL_RETURN_NOT_IMPLEMENTED("Multi-channels is not supported"); - // Do not support for ROI case - if((margin_left!=0) || (margin_top != 0) || (margin_right != 0) || (margin_bottom !=0)) - CV_HAL_RETURN_NOT_IMPLEMENTED("ROI is not supported"); - // 1. When ksize <= 0, OpenCV will use Scharr Derivatives // 2. When ksize == 1, OpenCV will use 3×1 or 1×3 kernel(no Gaussian smoothing is done) // FastCV doesn't support above two situation @@ -103,26 +197,16 @@ int fastcv_hal_sobel( if (dst_depth != CV_16S) CV_HAL_RETURN_NOT_IMPLEMENTED(cv::format("Dst depth:%s is not supported", cv::depthToString(dst_depth))); + // Only support following ksize and src_depth as input + if ((FCV_MAKETYPE(ksize,src_depth) != FCV_MAKETYPE(3, CV_8U)) && + (FCV_MAKETYPE(ksize,src_depth) != FCV_MAKETYPE(5, CV_8U)) && + (FCV_MAKETYPE(ksize,src_depth) != FCV_MAKETYPE(7, CV_8U))) + CV_HAL_RETURN_NOT_IMPLEMENTED(cv::format("Ksize:%d, src_depth:%s is not supported", ksize, cv::depthToString(src_depth))); + INITIALIZATION_CHECK; - // Only support one direction derivatives and the order is 1.(dx=1 && dy=0)||(dx=0 && dy=1) - int16_t *dxBuffer, *dyBuffer; - - if ((dx == 1) && (dy == 0)) - { - dxBuffer = (int16_t*)dst_data; - dyBuffer = NULL; - } - else if ((dx == 0) && (dy == 1)) - { - dxBuffer = NULL; - dyBuffer = (int16_t*)dst_data; - } - else - CV_HAL_RETURN_NOT_IMPLEMENTED(cv::format("Dx:%d Dy:%d is not supported",dx, dy)); - - fcvStatus status; - fcvBorderType fcvBorder; + fcvStatus status = FASTCV_SUCCESS; + fcvBorderType fcvBorder = FASTCV_BORDER_CONSTANT; switch (border_type) { @@ -141,28 +225,89 @@ int fastcv_hal_sobel( CV_HAL_RETURN_NOT_IMPLEMENTED(cv::format("Border type:%s is not supported", borderToString(border_type))); } - int fcvFuncType = FCV_MAKETYPE(ksize,src_depth); + cv::Mat src = cv::Mat(height, width, CV_MAKE_TYPE(src_depth, 1), (void*)src_data, src_step); + cv::Mat dst = cv::Mat(height, width, CV_MAKE_TYPE(dst_depth, 1), (void*)dst_data, dst_step); - switch (fcvFuncType) + if (margin_left||margin_top||margin_top||margin_bottom) { - case FCV_MAKETYPE(3,CV_8U): + // Need additional lines to be border. + int paddedHeight = height, paddedWidth = width, startX = 0, startY = 0; + + if(margin_left != 0) { - status = fcvFilterSobel3x3u8s16(src_data, width, height, src_step, dxBuffer, dyBuffer, dst_step, fcvBorder, 0); - break; + src_data -= ksize/2; + paddedWidth += ksize/2; + startX = ksize/2; } - case FCV_MAKETYPE(5,CV_8U): + + if(margin_top != 0) { - status = fcvFilterSobel5x5u8s16(src_data, width, height, src_step, dxBuffer, dyBuffer, dst_step, fcvBorder, 0); - break; + src_data -= (ksize/2) * src_step; + paddedHeight += ksize/2; + startY = ksize/2; } - case FCV_MAKETYPE(7,CV_8U): + + if(margin_right != 0) { - status = fcvFilterSobel7x7u8s16(src_data, width, height, src_step, dxBuffer, dyBuffer, dst_step, fcvBorder, 0); - break; + paddedWidth += ksize/2; } - default: - CV_HAL_RETURN_NOT_IMPLEMENTED(cv::format("Ksize:%d, src_depth:%s, border type:%s is not supported", - ksize, cv::depthToString(src_depth), borderToString(border_type))); + + if(margin_bottom != 0) + { + paddedHeight += ksize/2; + } + + cv::Mat padded(paddedHeight, paddedWidth, src_depth); + int16_t *dxBuffer = nullptr, *dyBuffer = nullptr; + + if ((dx == 1) && (dy == 0)) + { + dxBuffer = (int16_t*)padded.data; + dyBuffer = NULL; + } + else if ((dx == 0) && (dy == 1)) + { + dxBuffer = NULL; + dyBuffer = (int16_t*)padded.data; + } + + int fcvFuncType = FCV_MAKETYPE(ksize, src_depth); + + switch (fcvFuncType) + { + case FCV_MAKETYPE(3,CV_8U): + { + status = fcvFilterSobel3x3u8s16(src_data, paddedWidth, paddedHeight, src_step, dxBuffer, dyBuffer, padded.step, + fcvBorder, 0); + break; + } + case FCV_MAKETYPE(5,CV_8U): + { + status = fcvFilterSobel5x5u8s16(src_data, paddedWidth, paddedHeight, src_step, dxBuffer, dyBuffer, padded.step, + fcvBorder, 0); + break; + } + case FCV_MAKETYPE(7,CV_8U): + { + status = fcvFilterSobel7x7u8s16(src_data, paddedWidth, paddedHeight, src_step, dxBuffer, dyBuffer, padded.step, + fcvBorder, 0); + break; + } + default: + CV_HAL_RETURN_NOT_IMPLEMENTED(cv::format("Ksize:%d, src_depth:%s is not supported", + ksize, cv::depthToString(src_depth))); + break; + } + + cv::Mat temp1 = padded(cv::Rect(startX, startY, width, height)); + temp1.copyTo(dst); + } + else + { + int nThreads = cv::getNumThreads(); + int nStripes = nThreads > 1 ? 3*nThreads : 1; + + cv::parallel_for_(cv::Range(0, height), FcvSobelLoop_Invoker(src, dst, dx, dy, ksize, fcvBorder, 0), nStripes); } CV_HAL_RETURN(status, hal_sobel); @@ -316,3 +461,286 @@ int fastcv_hal_adaptiveThreshold( CV_HAL_RETURN(status,hal_adaptiveThreshold); } + +class FcvGaussianBlurLoop_Invoker : public cv::ParallelLoopBody +{ + public: + + FcvGaussianBlurLoop_Invoker(const cv::Mat& _src, cv::Mat& _dst, int _ksize, fcvBorderType _fcvBorder, int _fcvBorderValue) : + cv::ParallelLoopBody(), src(_src),dst(_dst), ksize(_ksize), fcvBorder(_fcvBorder), fcvBorderValue(_fcvBorderValue) + { + width = src.cols; + height = src.rows; + halfKernelSize = ksize / 2; + fcvFuncType = FCV_MAKETYPE(ksize, src.depth()); + } + + virtual void operator()(const cv::Range& range) const CV_OVERRIDE + { + int topLines = 0; + int rangeHeight = range.end-range.start; + int paddedHeight = rangeHeight; + + // Need additional lines to be border. + if(range.start != 0) + { + topLines += halfKernelSize; + paddedHeight += halfKernelSize; + } + + if(range.end != height) + { + paddedHeight += halfKernelSize; + } + + const cv::Mat srcPadded = src(cv::Rect(0, range.start - topLines, width, paddedHeight)); + cv::Mat dstPadded = cv::Mat(paddedHeight, width, CV_8U); + + if (fcvFuncType == FCV_MAKETYPE(3,CV_8U)) + fcvFilterGaussian3x3u8_v4(srcPadded.data, width, paddedHeight, srcPadded.step, dstPadded.data, dstPadded.step, + fcvBorder, 0); + else if (fcvFuncType == FCV_MAKETYPE(5,CV_8U)) + fcvFilterGaussian5x5u8_v3(srcPadded.data, width, paddedHeight, srcPadded.step, dstPadded.data, dstPadded.step, + fcvBorder, 0); + + // Only copy center part back to output image and ignore the padded lines + cv::Mat temp1 = dstPadded(cv::Rect(0, topLines, width, rangeHeight)); + cv::Mat temp2 = dst(cv::Rect(0, range.start, width, rangeHeight)); + temp1.copyTo(temp2); + } + + private: + const cv::Mat& src; + cv::Mat& dst; + int width; + int height; + const int ksize; + int halfKernelSize; + int fcvFuncType; + fcvBorderType fcvBorder; + int fcvBorderValue; + + FcvGaussianBlurLoop_Invoker(const FcvGaussianBlurLoop_Invoker &); // = delete; + const FcvGaussianBlurLoop_Invoker& operator= (const FcvGaussianBlurLoop_Invoker &); // = delete; +}; + +int fastcv_hal_gaussianBlurBinomial( + const uchar* src_data, + size_t src_step, + uchar* dst_data, + size_t dst_step, + int width, + int height, + int depth, + int cn, + size_t margin_left, + size_t margin_top, + size_t margin_right, + size_t margin_bottom, + size_t ksize, + int border_type) +{ + // Do not support inplace case + if (src_data == dst_data) + CV_HAL_RETURN_NOT_IMPLEMENTED("Inplace is not supported"); + + // The input image width and height should greater than kernel size + if (((size_t)height <= ksize) || ((size_t)width <= ksize)) + CV_HAL_RETURN_NOT_IMPLEMENTED("Input image size should be larger than kernel size"); + + // The input channel should be 1 + if (cn != 1) + CV_HAL_RETURN_NOT_IMPLEMENTED("Multi-channels is not supported"); + + // Do not support for ROI case + if((margin_left!=0) || (margin_top != 0) || (margin_right != 0) || (margin_bottom !=0)) + CV_HAL_RETURN_NOT_IMPLEMENTED("ROI is not supported"); + + INITIALIZATION_CHECK; + + fcvStatus status = FASTCV_SUCCESS; + fcvBorderType fcvBorder = fcvBorderType::FASTCV_BORDER_UNDEFINED; + int fcvFuncType = FCV_MAKETYPE(ksize,depth); + + switch (border_type) + { + case cv::BorderTypes::BORDER_REPLICATE: + { + fcvBorder = fcvBorderType::FASTCV_BORDER_REPLICATE; + break; + } + // For constant border, there are no border value, OpenCV default value is 0 + case cv::BorderTypes::BORDER_CONSTANT: + { + fcvBorder = fcvBorderType::FASTCV_BORDER_CONSTANT; + break; + } + case cv::BorderTypes::BORDER_REFLECT: + { + fcvBorder = fcvBorderType::FASTCV_BORDER_REFLECT; + break; + } + case cv::BorderTypes::BORDER_REFLECT_101: + { + fcvBorder = fcvBorderType::FASTCV_BORDER_REFLECT_V2; + break; + } + default: + CV_HAL_RETURN_NOT_IMPLEMENTED(cv::format("Border type:%s is not supported", borderToString(border_type))); + } + + int nThreads = cv::getNumThreads(); + int nStripes = (nThreads > 1) ? ((height > 60) ? 3 * nThreads : 1) : 1; + + switch (fcvFuncType) + { + case FCV_MAKETYPE(3,CV_8U): + case FCV_MAKETYPE(5,CV_8U): + { + cv::Mat src = cv::Mat(height, width, CV_8UC1, (void*)src_data, src_step); + cv::Mat dst = cv::Mat(height, width, CV_8UC1, (void*)dst_data, dst_step); + cv::parallel_for_(cv::Range(0, height), FcvGaussianBlurLoop_Invoker(src, dst, ksize, fcvBorder, 0), nStripes); + break; + } + default: + CV_HAL_RETURN_NOT_IMPLEMENTED(cv::format("Ksize:%d, depth:%s is not supported", (int)ksize, cv::depthToString(depth))); + } + + CV_HAL_RETURN(status, hal_gaussianBlurBinomial); +} + +class FcvWarpPerspectiveLoop_Invoker : public cv::ParallelLoopBody +{ + public: + + FcvWarpPerspectiveLoop_Invoker(const uchar* _src_data, int _src_width, int _src_height, size_t _src_step, uchar* _dst_data, + int _dst_width, int _dst_height, size_t _dst_step, int _type, const double* _M, + fcvInterpolationType _fcvInterpolation, fcvBorderType _fcvBorder, int _fcvBorderValue) : + cv::ParallelLoopBody(), src_data(_src_data), src_width(_src_width), src_height(_src_height), src_step(_src_step), + dst_data(_dst_data), dst_width(_dst_width), dst_height(_dst_height), dst_step(_dst_step), type(_type), + M(_M), fcvInterpolation(_fcvInterpolation),fcvBorder(_fcvBorder), + fcvBorderValue(_fcvBorderValue) {} + + virtual void operator()(const cv::Range& range) const CV_OVERRIDE + { + uchar* dst = dst_data + range.start*dst_step; + int rangeHeight = range.end - range.start; + + float rangeMatrix[9]; + rangeMatrix[0] = (float)(M[0]); + rangeMatrix[1] = (float)(M[1]); + rangeMatrix[2] = (float)(M[2]+range.start*M[1]); + rangeMatrix[3] = (float)(M[3]); + rangeMatrix[4] = (float)(M[4]); + rangeMatrix[5] = (float)(M[5]+range.start*M[4]); + rangeMatrix[6] = (float)(M[6]); + rangeMatrix[7] = (float)(M[7]); + rangeMatrix[8] = (float)(M[8]+range.start*M[7]); + fcvWarpPerspectiveu8_v5(src_data, src_width, src_height, src_step, CV_MAT_CN(type), dst, dst_width, rangeHeight, + dst_step, rangeMatrix, fcvInterpolation, fcvBorder, fcvBorderValue); + } + + private: + const uchar* src_data; + const int src_width; + const int src_height; + const size_t src_step; + uchar* dst_data; + const int dst_width; + const int dst_height; + const size_t dst_step; + const int type; + const double* M; + fcvInterpolationType fcvInterpolation; + fcvBorderType fcvBorder; + int fcvBorderValue; + + FcvWarpPerspectiveLoop_Invoker(const FcvWarpPerspectiveLoop_Invoker &); // = delete; + const FcvWarpPerspectiveLoop_Invoker& operator= (const FcvWarpPerspectiveLoop_Invoker &); // = delete; +}; + +int fastcv_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 border_type, + const double border_value[4]) +{ + // Do not support inplace case + if (src_data == dst_data) + CV_HAL_RETURN_NOT_IMPLEMENTED("Inplace is not supported"); + + // The input channel should be 1 + if (CV_MAT_CN(src_type) != 1) + CV_HAL_RETURN_NOT_IMPLEMENTED("Multi-channels is not supported"); + + INITIALIZATION_CHECK; + + fcvStatus status = FASTCV_SUCCESS; + fcvBorderType fcvBorder; + uint8_t fcvBorderValue = 0; + fcvInterpolationType fcvInterpolation; + + switch (border_type) + { + case cv::BorderTypes::BORDER_CONSTANT: + { + if ((border_value[0] == border_value[1]) && + (border_value[0] == border_value[2]) && + (border_value[0] == border_value[3])) + { + fcvBorder = fcvBorderType::FASTCV_BORDER_CONSTANT; + fcvBorderValue = static_cast(border_value[0]); + break; + } + else + CV_HAL_RETURN_NOT_IMPLEMENTED("Different border value is not supported"); + } + case cv::BorderTypes::BORDER_REPLICATE: + { + fcvBorder = fcvBorderType::FASTCV_BORDER_REPLICATE; + break; + } + case cv::BorderTypes::BORDER_TRANSPARENT: + { + fcvBorder = fcvBorderType::FASTCV_BORDER_UNDEFINED; + break; + } + default: + CV_HAL_RETURN_NOT_IMPLEMENTED(cv::format("Border type:%s is not supported", borderToString(border_type))); + } + + switch(interpolation) + { + case cv::InterpolationFlags::INTER_NEAREST: + { + fcvInterpolation = FASTCV_INTERPOLATION_TYPE_NEAREST_NEIGHBOR; + break; + } + default: + CV_HAL_RETURN_NOT_IMPLEMENTED(cv::format("Interpolation type:%s is not supported", + interpolationToString(interpolation))); + } + + int nThreads = cv::getNumThreads(); + int nStripes = nThreads > 1 ? 3*nThreads : 1; + + if(CV_MAT_DEPTH(src_type) == CV_8U) + { + cv::parallel_for_(cv::Range(0, dst_height), + FcvWarpPerspectiveLoop_Invoker(src_data, src_width, src_height, src_step, dst_data, dst_width, dst_height, + dst_step, src_type, M, fcvInterpolation, fcvBorder, fcvBorderValue), nStripes); + } + else + CV_HAL_RETURN_NOT_IMPLEMENTED(cv::format("Src type:%s is not supported", cv::typeToString(src_type).c_str())); + + CV_HAL_RETURN(status, hal_warpPerspective); +} \ No newline at end of file From d369cf6d50d6b5ddd9bdf438ebe63f3f0e341ad9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Ihle?= Date: Wed, 18 Dec 2024 10:16:50 +0100 Subject: [PATCH 035/102] Merge pull request #26627 from warped-rudi:torch Android camera feature enhancements #26627 Closes https://github.com/opencv/opencv/issues/24687 ### 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 --- modules/videoio/include/opencv2/videoio.hpp | 12 ++ modules/videoio/src/cap_android_camera.cpp | 142 ++++++++++++------- modules/videoio/src/cap_android_mediandk.cpp | 2 +- modules/videoio/src/cap_interface.hpp | 4 +- 4 files changed, 109 insertions(+), 51 deletions(-) diff --git a/modules/videoio/include/opencv2/videoio.hpp b/modules/videoio/include/opencv2/videoio.hpp index 52857fabab..92fa5d39b7 100644 --- a/modules/videoio/include/opencv2/videoio.hpp +++ b/modules/videoio/include/opencv2/videoio.hpp @@ -580,6 +580,18 @@ enum { CAP_PROP_ARAVIS_AUTOTRIGGER = 600 //!< Autom //! @} ARAVIS + +/** @name Android + @{ +*/ + +//! Properties of cameras available through NDK Camera API backend +enum { CAP_PROP_ANDROID_DEVICE_TORCH = 8001, + }; + +//! @} Android + + /** @name AVFoundation framework for iOS @{ */ diff --git a/modules/videoio/src/cap_android_camera.cpp b/modules/videoio/src/cap_android_camera.cpp index 52f741c444..d0bcfb367b 100644 --- a/modules/videoio/src/cap_android_camera.cpp +++ b/modules/videoio/src/cap_android_camera.cpp @@ -33,23 +33,27 @@ using namespace cv; #define FOURCC_BGR CV_FOURCC_MACRO('B','G','R','3') #define FOURCC_RGB CV_FOURCC_MACRO('R','G','B','3') +#define FOURCC_BGRA CV_FOURCC_MACRO('B','G','R','4') +#define FOURCC_RGBA CV_FOURCC_MACRO('R','G','B','4') #define FOURCC_GRAY CV_FOURCC_MACRO('G','R','E','Y') #define FOURCC_NV21 CV_FOURCC_MACRO('N','V','2','1') #define FOURCC_YV12 CV_FOURCC_MACRO('Y','V','1','2') #define FOURCC_UNKNOWN 0xFFFFFFFF -template class RangeValue { -public: +template struct RangeValue { T min, max; /** * return absolute value from relative value * * value: in percent (50 for 50%) * */ T value(int percent) { - return static_cast(min + (max - min) * percent / 100); + return static_cast(min + ((max - min) * percent) / 100); + } + RangeValue(T minv = 0, T maxv = 0) : min(minv), max(maxv) {} + bool Supported() const { return (min != max); } + T clamp( T value ) const { + return (value > max) ? max : ((value < min) ? min : value); } - RangeValue() { min = max = static_cast(0); } - bool Supported(void) const { return (min != max); } }; static inline void deleter_ACameraManager(ACameraManager *cameraManager) { @@ -165,8 +169,7 @@ void OnCaptureFailed(void* context, * on camera. For this sample purpose, clamp to a range showing visible * video on preview: 100000ns ~ 250000000ns */ -static const long kMinExposureTime = 1000000L; -static const long kMaxExposureTime = 250000000L; +static const RangeValue exposureTimeLimits = { 1000000, 250000000 }; static double elapsedTimeFrom(std::chrono::time_point start) { return std::chrono::duration(std::chrono::system_clock::now() - start).count(); @@ -188,7 +191,7 @@ class AndroidCameraCapture : public IVideoCapture int32_t frameWidth = 0; int32_t frameStride = 0; int32_t frameHeight = 0; - int32_t colorFormat; + int32_t colorFormat = COLOR_FormatUnknown; std::vector buffer; bool sessionOutputAdded = false; bool targetAdded = false; @@ -198,8 +201,9 @@ class AndroidCameraCapture : public IVideoCapture bool settingHeight = false; int desiredWidth = 640; int desiredHeight = 480; - bool autoExposure = true; - int64_t exposureTime = 0L; + uint8_t flashMode = ACAMERA_FLASH_MODE_OFF; + uint8_t aeMode = ACAMERA_CONTROL_AE_MODE_ON; + int64_t exposureTime = 0; RangeValue exposureRange; int32_t sensitivity = 0; RangeValue sensitivityRange; @@ -212,7 +216,24 @@ public: std::condition_variable condition; public: - AndroidCameraCapture() {} + AndroidCameraCapture(const VideoCaptureParameters& params) + { + desiredWidth = params.get(CAP_PROP_FRAME_WIDTH, desiredWidth); + desiredHeight = params.get(CAP_PROP_FRAME_HEIGHT, desiredHeight); + + static const struct { + int propId; + uint32_t defaultValue; + } items[] = { + { CAP_PROP_AUTO_EXPOSURE, 1 }, + { CAP_PROP_FOURCC, FOURCC_UNKNOWN }, + { CAP_PROP_ANDROID_DEVICE_TORCH, 0 } + }; + + for (auto it = std::begin(items); it != std::end(items); ++it) { + setProperty(it->propId, params.get(it->propId, it->defaultValue)); + } + } ~AndroidCameraCapture() { cleanUp(); } @@ -357,14 +378,20 @@ public: if (colorFormat == COLOR_FormatYUV420Planar) { Mat yuv(frameHeight + frameHeight/2, frameWidth, CV_8UC1, buffer.data()); switch (fourCC) { + case FOURCC_BGRA: + cvtColor(yuv, out, COLOR_YUV2BGRA_YV12); + break; + case FOURCC_RGBA: + cvtColor(yuv, out, COLOR_YUV2RGBA_YV12); + break; case FOURCC_BGR: - cv::cvtColor(yuv, out, cv::COLOR_YUV2BGR_YV12); + cvtColor(yuv, out, COLOR_YUV2BGR_YV12); break; case FOURCC_RGB: - cv::cvtColor(yuv, out, cv::COLOR_YUV2RGB_YV12); + cvtColor(yuv, out, COLOR_YUV2RGB_YV12); break; case FOURCC_GRAY: - cv::cvtColor(yuv, out, cv::COLOR_YUV2GRAY_YV12); + cvtColor(yuv, out, COLOR_YUV2GRAY_YV12); break; case FOURCC_YV12: yuv.copyTo(out); @@ -377,14 +404,20 @@ public: Mat yuv(frameHeight + frameHeight/2, frameStride, CV_8UC1, buffer.data()); Mat tmp = (frameWidth == frameStride) ? yuv : yuv(Rect(0, 0, frameWidth, frameHeight + frameHeight / 2)); switch (fourCC) { + case FOURCC_BGRA: + cvtColor(tmp, out, COLOR_YUV2BGRA_NV21); + break; + case FOURCC_RGBA: + cvtColor(tmp, out, COLOR_YUV2RGBA_NV21); + break; case FOURCC_BGR: - cv::cvtColor(tmp, out, cv::COLOR_YUV2BGR_NV21); + cvtColor(tmp, out, COLOR_YUV2BGR_NV21); break; case FOURCC_RGB: - cv::cvtColor(tmp, out, cv::COLOR_YUV2RGB_NV21); + cvtColor(tmp, out, COLOR_YUV2RGB_NV21); break; case FOURCC_GRAY: - cv::cvtColor(tmp, out, cv::COLOR_YUV2GRAY_NV21); + cvtColor(tmp, out, COLOR_YUV2GRAY_NV21); break; case FOURCC_NV21: tmp.copyTo(out); @@ -408,13 +441,15 @@ public: case CAP_PROP_FRAME_HEIGHT: return isOpened() ? frameHeight : desiredHeight; case CAP_PROP_AUTO_EXPOSURE: - return autoExposure ? 1 : 0; + return (aeMode == ACAMERA_CONTROL_AE_MODE_ON) ? 1 : 0; case CAP_PROP_EXPOSURE: return exposureTime; case CAP_PROP_ISO_SPEED: return sensitivity; case CAP_PROP_FOURCC: return fourCC; + case CAP_PROP_ANDROID_DEVICE_TORCH: + return (flashMode == ACAMERA_FLASH_MODE_TORCH) ? 1 : 0; default: break; } @@ -452,6 +487,8 @@ public: switch (newFourCC) { case FOURCC_BGR: case FOURCC_RGB: + case FOURCC_BGRA: + case FOURCC_RGBA: case FOURCC_GRAY: fourCC = newFourCC; return true; @@ -478,29 +515,31 @@ public: } } case CAP_PROP_AUTO_EXPOSURE: - autoExposure = (value != 0); + aeMode = (value != 0) ? ACAMERA_CONTROL_AE_MODE_ON : ACAMERA_CONTROL_AE_MODE_OFF; if (isOpened()) { - uint8_t aeMode = autoExposure ? ACAMERA_CONTROL_AE_MODE_ON : ACAMERA_CONTROL_AE_MODE_OFF; - camera_status_t status = ACaptureRequest_setEntry_u8(captureRequest.get(), ACAMERA_CONTROL_AE_MODE, 1, &aeMode); - return status == ACAMERA_OK; + return submitRequest(ACaptureRequest_setEntry_u8, ACAMERA_CONTROL_AE_MODE, aeMode); } return true; case CAP_PROP_EXPOSURE: if (isOpened() && exposureRange.Supported()) { - exposureTime = (int64_t)value; + exposureTime = exposureRange.clamp(static_cast(value)); LOGI("Setting CAP_PROP_EXPOSURE will have no effect unless CAP_PROP_AUTO_EXPOSURE is off"); - camera_status_t status = ACaptureRequest_setEntry_i64(captureRequest.get(), ACAMERA_SENSOR_EXPOSURE_TIME, 1, &exposureTime); - return status == ACAMERA_OK; + return submitRequest(ACaptureRequest_setEntry_i64, ACAMERA_SENSOR_EXPOSURE_TIME, exposureTime); } return false; case CAP_PROP_ISO_SPEED: if (isOpened() && sensitivityRange.Supported()) { - sensitivity = (int32_t)value; + sensitivity = sensitivityRange.clamp(static_cast(value)); LOGI("Setting CAP_PROP_ISO_SPEED will have no effect unless CAP_PROP_AUTO_EXPOSURE is off"); - camera_status_t status = ACaptureRequest_setEntry_i32(captureRequest.get(), ACAMERA_SENSOR_SENSITIVITY, 1, &sensitivity); - return status == ACAMERA_OK; + return submitRequest(ACaptureRequest_setEntry_i32, ACAMERA_SENSOR_SENSITIVITY, sensitivity); } return false; + case CAP_PROP_ANDROID_DEVICE_TORCH: + flashMode = (value != 0) ? ACAMERA_FLASH_MODE_TORCH : ACAMERA_FLASH_MODE_OFF; + if (isOpened()) { + return submitRequest(ACaptureRequest_setEntry_u8, ACAMERA_FLASH_MODE, flashMode); + } + return true; default: break; } @@ -561,7 +600,7 @@ public: return false; } std::shared_ptr cameraMetadata = std::shared_ptr(metadata, deleter_ACameraMetadata); - ACameraMetadata_const_entry entry; + ACameraMetadata_const_entry entry = {}; ACameraMetadata_getConstEntry(cameraMetadata.get(), ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS, &entry); double bestScore = std::numeric_limits::max(); @@ -594,25 +633,19 @@ public: } LOGI("Best resolution match: %dx%d", bestMatchWidth, bestMatchHeight); - ACameraMetadata_const_entry val = { 0, }; - camera_status_t status = ACameraMetadata_getConstEntry(cameraMetadata.get(), ACAMERA_SENSOR_INFO_EXPOSURE_TIME_RANGE, &val); - if (status == ACAMERA_OK) { - exposureRange.min = val.data.i64[0]; - if (exposureRange.min < kMinExposureTime) { - exposureRange.min = kMinExposureTime; - } - exposureRange.max = val.data.i64[1]; - if (exposureRange.max > kMaxExposureTime) { - exposureRange.max = kMaxExposureTime; - } + ACameraMetadata_const_entry val; + cStatus = ACameraMetadata_getConstEntry(cameraMetadata.get(), ACAMERA_SENSOR_INFO_EXPOSURE_TIME_RANGE, &val); + if (cStatus == ACAMERA_OK) { + exposureRange.min = exposureTimeLimits.clamp(val.data.i64[0]); + exposureRange.max = exposureTimeLimits.clamp(val.data.i64[1]); exposureTime = exposureRange.value(2); } else { LOGW("Unsupported ACAMERA_SENSOR_INFO_EXPOSURE_TIME_RANGE"); - exposureRange.min = exposureRange.max = 0l; - exposureTime = 0l; + exposureRange.min = exposureRange.max = 0; + exposureTime = 0; } - status = ACameraMetadata_getConstEntry(cameraMetadata.get(), ACAMERA_SENSOR_INFO_SENSITIVITY_RANGE, &val); - if (status == ACAMERA_OK){ + cStatus = ACameraMetadata_getConstEntry(cameraMetadata.get(), ACAMERA_SENSOR_INFO_SENSITIVITY_RANGE, &val); + if (cStatus == ACAMERA_OK){ sensitivityRange.min = val.data.i32[0]; sensitivityRange.max = val.data.i32[1]; sensitivity = sensitivityRange.value(2); @@ -693,12 +726,13 @@ public: return false; } captureSession = std::shared_ptr(session, deleter_ACameraCaptureSession); - uint8_t aeMode = autoExposure ? ACAMERA_CONTROL_AE_MODE_ON : ACAMERA_CONTROL_AE_MODE_OFF; + ACaptureRequest_setEntry_u8(captureRequest.get(), ACAMERA_CONTROL_AE_MODE, 1, &aeMode); ACaptureRequest_setEntry_i32(captureRequest.get(), ACAMERA_SENSOR_SENSITIVITY, 1, &sensitivity); - if (!autoExposure) { + if (aeMode != ACAMERA_CONTROL_AE_MODE_ON) { ACaptureRequest_setEntry_i64(captureRequest.get(), ACAMERA_SENSOR_EXPOSURE_TIME, 1, &exposureTime); } + ACaptureRequest_setEntry_u8(captureRequest.get(), ACAMERA_FLASH_MODE, 1, &flashMode); cStatus = ACameraCaptureSession_setRepeatingRequest(captureSession.get(), GetCaptureCallback(), 1, &request, nullptr); if (cStatus != ACAMERA_OK) { @@ -732,6 +766,18 @@ public: cameraManager = nullptr; imageReader = nullptr; } + + template + bool submitRequest(FuncT setFn, uint32_t tag, const T &data) + { + ACaptureRequest *request = captureRequest.get(); + + return request && + setFn(request, tag, 1, &data) == ACAMERA_OK && + ACameraCaptureSession_setRepeatingRequest(captureSession.get(), + GetCaptureCallback(), + 1, &request, nullptr) == ACAMERA_OK; + } }; /******************************** Session management *******************************/ @@ -788,8 +834,8 @@ void OnCaptureFailed(void* context, /****************** Implementation of interface functions ********************/ -Ptr cv::createAndroidCapture_cam( int index ) { - Ptr res = makePtr(); +Ptr cv::createAndroidCapture_cam(int index, const VideoCaptureParameters& params) { + Ptr res = makePtr(params); if (res && res->initCapture(index)) return res; return Ptr(); diff --git a/modules/videoio/src/cap_android_mediandk.cpp b/modules/videoio/src/cap_android_mediandk.cpp index 293dcc91bf..98f9eea3f9 100644 --- a/modules/videoio/src/cap_android_mediandk.cpp +++ b/modules/videoio/src/cap_android_mediandk.cpp @@ -668,7 +668,7 @@ const AndroidMediaNdkVideoWriter::FourCCInfo AndroidMediaNdkVideoWriter::FOURCC_ /****************** Implementation of interface functions ********************/ -Ptr cv::createAndroidCapture_file(const std::string &filename) { +Ptr cv::createAndroidCapture_file(const std::string &filename, const VideoCaptureParameters& ) { Ptr res = makePtr(); if (res && res->initCapture(filename.c_str())) return res; diff --git a/modules/videoio/src/cap_interface.hpp b/modules/videoio/src/cap_interface.hpp index a57564b11e..a8e3e49dd0 100644 --- a/modules/videoio/src/cap_interface.hpp +++ b/modules/videoio/src/cap_interface.hpp @@ -391,8 +391,8 @@ Ptr createGPhoto2Capture(const std::string& deviceName); Ptr createXINECapture(const std::string &filename); -Ptr createAndroidCapture_cam( int index ); -Ptr createAndroidCapture_file(const std::string &filename); +Ptr createAndroidCapture_cam(int index, const VideoCaptureParameters& params); +Ptr createAndroidCapture_file(const std::string &filename, const VideoCaptureParameters& params); Ptr createAndroidVideoWriter(const std::string& filename, int fourcc, double fps, const Size& frameSize, const VideoWriterParameters& params); From 1fe9dd0c3bc82660c91a23cc75de12c4996e50f7 Mon Sep 17 00:00:00 2001 From: Vincent Rabaud Date: Wed, 18 Dec 2024 11:43:39 +0100 Subject: [PATCH 036/102] js: add types included in bound APIs This fixes #25239 --- modules/calib3d/misc/js/gen_dict.json | 3 ++- modules/js/src/core_bindings.cpp | 5 +++++ modules/photo/misc/js/gen_dict.json | 1 + platforms/js/opencv_js.config.py | 2 ++ 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/modules/calib3d/misc/js/gen_dict.json b/modules/calib3d/misc/js/gen_dict.json index 407ddf1e48..234d906ee8 100644 --- a/modules/calib3d/misc/js/gen_dict.json +++ b/modules/calib3d/misc/js/gen_dict.json @@ -16,6 +16,7 @@ "undistort", "fisheye_initUndistortRectifyMap", "fisheye_projectPoints" - ] + ], + "UsacParams": ["UsacParams"] } } diff --git a/modules/js/src/core_bindings.cpp b/modules/js/src/core_bindings.cpp index d5bf9b076c..db1669a825 100644 --- a/modules/js/src/core_bindings.cpp +++ b/modules/js/src/core_bindings.cpp @@ -467,11 +467,15 @@ EMSCRIPTEN_BINDINGS(binding_utils) register_vector("DoubleVector"); register_vector("StringVector"); register_vector("PointVector"); + register_vector("Point3fVector"); register_vector("MatVector"); register_vector("RectVector"); register_vector("KeyPointVector"); register_vector("DMatchVector"); + register_vector>("CharVectorVector"); register_vector>("DMatchVectorVector"); + register_vector>("KeyPointVectorVector"); + register_vector>("PointVectorVector"); emscripten::class_("Mat") @@ -598,6 +602,7 @@ EMSCRIPTEN_BINDINGS(binding_utils) EMSCRIPTEN_CV_POINT(Point) EMSCRIPTEN_CV_POINT(Point2f) + EMSCRIPTEN_CV_POINT(Point3f) #define EMSCRIPTEN_CV_RECT(type, name) \ emscripten::value_object> (name) \ diff --git a/modules/photo/misc/js/gen_dict.json b/modules/photo/misc/js/gen_dict.json index d8a6db100a..c03b081b34 100644 --- a/modules/photo/misc/js/gen_dict.json +++ b/modules/photo/misc/js/gen_dict.json @@ -6,6 +6,7 @@ "createMergeDebevec", "createMergeMertens", "createMergeRobertson", "createTonemapDrago", "createTonemapMantiuk", "createTonemapReinhard", "inpaint"], "CalibrateCRF": ["process"], + "AlignExposures": ["process"], "AlignMTB" : ["calculateShift", "shiftMat", "computeBitmaps", "getMaxBits", "setMaxBits", "getExcludeRange", "setExcludeRange", "getCut", "setCut"], "CalibrateDebevec" : ["getLambda", "setLambda", "getSamples", "setSamples", "getRandom", "setRandom"], diff --git a/platforms/js/opencv_js.config.py b/platforms/js/opencv_js.config.py index 1251062e4c..70b1ad9ff9 100644 --- a/platforms/js/opencv_js.config.py +++ b/platforms/js/opencv_js.config.py @@ -172,6 +172,7 @@ photo = {'': ['createAlignMTB', 'createCalibrateDebevec', 'createCalibrateRobert 'createMergeDebevec', 'createMergeMertens', 'createMergeRobertson', \ 'createTonemapDrago', 'createTonemapMantiuk', 'createTonemapReinhard', 'inpaint'], 'CalibrateCRF': ['process'], + 'AlignExposures': ['process'], 'AlignMTB' : ['calculateShift', 'shiftMat', 'computeBitmaps', 'getMaxBits', 'setMaxBits', \ 'getExcludeRange', 'setExcludeRange', 'getCut', 'setCut'], 'CalibrateDebevec' : ['getLambda', 'setLambda', 'getSamples', 'setSamples', 'getRandom', 'setRandom'], @@ -208,6 +209,7 @@ calib3d = { 'fisheye_initUndistortRectifyMap', 'fisheye_projectPoints', ], + 'UsacParams': ['UsacParams'] } white_list = makeWhiteList([core, imgproc, objdetect, video, dnn, features2d, photo, calib3d]) From 874e57512e80e685fee439410e3daeb1320cf85c Mon Sep 17 00:00:00 2001 From: Vincent Rabaud Date: Wed, 18 Dec 2024 11:49:08 +0100 Subject: [PATCH 037/102] js: fix helper.js to not trigger warnings --- modules/js/src/helpers.js | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/modules/js/src/helpers.js b/modules/js/src/helpers.js index 962a0b4d90..d89414209a 100644 --- a/modules/js/src/helpers.js +++ b/modules/js/src/helpers.js @@ -66,7 +66,6 @@ Module['imread'] = function(imageSource) { ctx = canvas.getContext('2d'); } else { throw new Error('Please input the valid canvas or img id.'); - return; } var imgData = ctx.getImageData(0, 0, canvas.width, canvas.height); @@ -82,11 +81,9 @@ Module['imshow'] = function(canvasSource, mat) { } if (!(canvas instanceof HTMLCanvasElement)) { throw new Error('Please input the valid canvas element or id.'); - return; } if (!(mat instanceof cv.Mat)) { throw new Error('Please input the valid cv.Mat instance.'); - return; } // convert the mat type to cv.CV_8U @@ -108,7 +105,6 @@ Module['imshow'] = function(canvasSource, mat) { break; default: throw new Error('Bad number of channels (Source image must have 1, 3 or 4 channels)'); - return; } var imgData = new ImageData(new Uint8ClampedArray(img.data), img.cols, img.rows); var ctx = canvas.getContext('2d'); @@ -128,7 +124,6 @@ Module['VideoCapture'] = function(videoSource) { } if (!(video instanceof HTMLVideoElement)) { throw new Error('Please input the valid video element or id.'); - return; } var canvas = document.createElement('canvas'); canvas.width = video.width; @@ -138,15 +133,12 @@ Module['VideoCapture'] = function(videoSource) { this.read = function(frame) { if (!(frame instanceof cv.Mat)) { throw new Error('Please input the valid cv.Mat instance.'); - return; } if (frame.type() !== cv.CV_8UC4) { throw new Error('Bad type of input mat: the type should be cv.CV_8UC4.'); - return; } if (frame.cols !== video.width || frame.rows !== video.height) { throw new Error('Bad size of input mat: the size should be same as the video.'); - return; } ctx.drawImage(video, 0, 0, video.width, video.height); frame.data.set(ctx.getImageData(0, 0, video.width, video.height).data); @@ -263,7 +255,7 @@ function Scalar(v0, v1, v2, v3) { Scalar.prototype = new Array; // eslint-disable-line no-array-constructor Scalar.all = function(v) { - return new Scalar(v, v, v, v); + return Scalar(v, v, v, v); }; Module['Scalar'] = Scalar; @@ -273,8 +265,8 @@ function MinMaxLoc() { case 0: { this.minVal = 0; this.maxVal = 0; - this.minLoc = new Point(); - this.maxLoc = new Point(); + this.minLoc = Point(0, 0); + this.maxLoc = Point(0, 0); break; } case 4: { @@ -295,7 +287,7 @@ Module['MinMaxLoc'] = MinMaxLoc; function Circle() { switch (arguments.length) { case 0: { - this.center = new Point(); + this.center = Point(0, 0); this.radius = 0; break; } From 3fbaad36d737f0576843fc084469637cfec6716b Mon Sep 17 00:00:00 2001 From: Liutong HAN Date: Thu, 19 Dec 2024 03:19:02 +0800 Subject: [PATCH 038/102] Merge pull request #26624 from hanliutong:rvv-mean Add RISC-V HAL implementation for meanStdDev #26624 `meanStdDev` benefits from the Universal Intrinsic backend of RVV, but we also found that the performance on the `8UC4` type is worse than the scalar version when there is a mask, and there is no optimization implementation on `32FC1`. This patch implements `meanStdDev` function in RVV_HAL using native intrinsic, significantly optimizing the performance for `8UC1`, `8UC4` and `32FC1`. This patch is tested on BPI-F3 for both gcc 14.2 and clang 19.1. ``` $ opencv_test_core --gtest_filter="*MeanStdDev*" $ opencv_perf_core --gtest_filter="Size_MatType_meanStdDev* ``` ![1734077611879](https://github.com/user-attachments/assets/71c85c9d-1db1-470d-81d1-bf546e27ad86) ### 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/hal_rvv/hal_rvv.hpp | 1 + 3rdparty/hal_rvv/hal_rvv_1p0/mean.hpp | 228 ++++++++++++++++++++++++++ 2 files changed, 229 insertions(+) create mode 100644 3rdparty/hal_rvv/hal_rvv_1p0/mean.hpp diff --git a/3rdparty/hal_rvv/hal_rvv.hpp b/3rdparty/hal_rvv/hal_rvv.hpp index 4765a15518..086f248a24 100644 --- a/3rdparty/hal_rvv/hal_rvv.hpp +++ b/3rdparty/hal_rvv/hal_rvv.hpp @@ -21,6 +21,7 @@ #if defined(__riscv_v) && __riscv_v == 1000000 #include "hal_rvv_1p0/merge.hpp" // core +#include "hal_rvv_1p0/mean.hpp" // core #endif #endif diff --git a/3rdparty/hal_rvv/hal_rvv_1p0/mean.hpp b/3rdparty/hal_rvv/hal_rvv_1p0/mean.hpp new file mode 100644 index 0000000000..52f99bbe0b --- /dev/null +++ b/3rdparty/hal_rvv/hal_rvv_1p0/mean.hpp @@ -0,0 +1,228 @@ +// 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 OPENCV_HAL_RVV_MEANSTDDEV_HPP_INCLUDED +#define OPENCV_HAL_RVV_MEANSTDDEV_HPP_INCLUDED + +#include + +namespace cv { namespace cv_hal_rvv { + +#undef cv_hal_meanStdDev +#define cv_hal_meanStdDev cv::cv_hal_rvv::meanStdDev + +inline int meanStdDev_8UC1(const uchar* src_data, size_t src_step, int width, int height, + double* mean_val, double* stddev_val, uchar* mask, size_t mask_step); +inline int meanStdDev_8UC4(const uchar* src_data, size_t src_step, int width, int height, + double* mean_val, double* stddev_val, uchar* mask, size_t mask_step); +inline int meanStdDev_32FC1(const uchar* src_data, size_t src_step, int width, int height, + double* mean_val, double* stddev_val, uchar* mask, size_t mask_step); + +inline int meanStdDev(const uchar* src_data, size_t src_step, int width, int height, + int src_type, double* mean_val, double* stddev_val, uchar* mask, size_t mask_step) { + switch (src_type) + { + case CV_8UC1: + return meanStdDev_8UC1(src_data, src_step, width, height, mean_val, stddev_val, mask, mask_step); + case CV_8UC4: + return meanStdDev_8UC4(src_data, src_step, width, height, mean_val, stddev_val, mask, mask_step); + case CV_32FC1: + return meanStdDev_32FC1(src_data, src_step, width, height, mean_val, stddev_val, mask, mask_step); + default: + return CV_HAL_ERROR_NOT_IMPLEMENTED; + } +} + +inline int meanStdDev_8UC1(const uchar* src_data, size_t src_step, int width, int height, + double* mean_val, double* stddev_val, uchar* mask, size_t mask_step) { + int nz = 0; + int vlmax = __riscv_vsetvlmax_e64m8(); + vuint64m8_t vec_sum = __riscv_vmv_v_x_u64m8(0, vlmax); + vuint64m8_t vec_sqsum = __riscv_vmv_v_x_u64m8(0, vlmax); + if (mask) { + for (int i = 0; i < height; ++i) { + const uchar* src_row = src_data + i * src_step; + const uchar* mask_row = mask + i * mask_step; + int j = 0, vl; + for ( ; j < width; j += vl) { + vl = __riscv_vsetvl_e8m1(width - j); + auto vec_pixel_u8 = __riscv_vle8_v_u8m1(src_row + j, vl); + auto vmask_u8 = __riscv_vle8_v_u8m1(mask_row+j, vl); + auto vec_pixel = __riscv_vzext_vf4(vec_pixel_u8, vl); + auto vmask = __riscv_vmseq_vx_u8m1_b8(vmask_u8, 1, vl); + vec_sum = __riscv_vwaddu_wv_u64m8_tumu(vmask, vec_sum, vec_sum, vec_pixel, vl); + vec_sqsum = __riscv_vwmaccu_vv_u64m8_tumu(vmask, vec_sqsum, vec_pixel, vec_pixel, vl); + nz += __riscv_vcpop_m_b8(vmask, vl); + } + } + } else { + for (int i = 0; i < height; i++) { + const uchar* src_row = src_data + i * src_step; + int j = 0, vl; + for ( ; j < width; j += vl) { + vl = __riscv_vsetvl_e8m1(width - j); + auto vec_pixel_u8 = __riscv_vle8_v_u8m1(src_row + j, vl); + auto vec_pixel = __riscv_vzext_vf4(vec_pixel_u8, vl); + vec_sum = __riscv_vwaddu_wv_u64m8_tu(vec_sum, vec_sum, vec_pixel, vl); + vec_sqsum = __riscv_vwmaccu_vv_u64m8_tu(vec_sqsum, vec_pixel, vec_pixel, vl); + } + } + nz = height * width; + } + if (nz == 0) { + if (mean_val) *mean_val = 0.0; + if (stddev_val) *stddev_val = 0.0; + return CV_HAL_ERROR_OK; + } + auto zero = __riscv_vmv_s_x_u64m1(0, vlmax); + auto vec_red = __riscv_vmv_v_x_u64m1(0, vlmax); + auto vec_reddev = __riscv_vmv_v_x_u64m1(0, vlmax); + vec_red = __riscv_vredsum(vec_sum, zero, vlmax); + vec_reddev = __riscv_vredsum(vec_sqsum, zero, vlmax); + double sum = __riscv_vmv_x(vec_red); + double mean = sum / nz; + if (mean_val) { + *mean_val = mean; + } + if (stddev_val) { + double sqsum = __riscv_vmv_x(vec_reddev); + double variance = std::max((sqsum / nz) - (mean * mean), 0.0); + double stddev = std::sqrt(variance); + *stddev_val = stddev; + } + return CV_HAL_ERROR_OK; +} + +inline int meanStdDev_8UC4(const uchar* src_data, size_t src_step, int width, int height, + double* mean_val, double* stddev_val, uchar* mask, size_t mask_step) { + int nz = 0; + int vlmax = __riscv_vsetvlmax_e64m8(); + vuint64m8_t vec_sum = __riscv_vmv_v_x_u64m8(0, vlmax); + vuint64m8_t vec_sqsum = __riscv_vmv_v_x_u64m8(0, vlmax); + if (mask) { + for (int i = 0; i < height; ++i) { + const uchar* src_row = src_data + i * src_step; + const uchar* mask_row = mask + i * mask_step; + int j = 0, jm = 0, vl, vlm; + for ( ; j < width*4; j += vl, jm += vlm) { + vl = __riscv_vsetvl_e8m1(width*4 - j); + vlm = __riscv_vsetvl_e8mf4(width - jm); + auto vec_pixel_u8 = __riscv_vle8_v_u8m1(src_row + j, vl); + auto vmask_u8mf4 = __riscv_vle8_v_u8mf4(mask_row + jm, vlm); + auto vmask_u32 = __riscv_vzext_vf4(vmask_u8mf4, vlm); + // 0 -> 0000; 1 -> 1111 + vmask_u32 = __riscv_vmul(vmask_u32, 0b00000001000000010000000100000001, vlm); + auto vmask_u8 = __riscv_vreinterpret_u8m1(vmask_u32); + auto vec_pixel = __riscv_vzext_vf4(vec_pixel_u8, vl); + auto vmask = __riscv_vmseq_vx_u8m1_b8(vmask_u8, 1, vl); + vec_sum = __riscv_vwaddu_wv_u64m8_tumu(vmask, vec_sum, vec_sum, vec_pixel, vl); + vec_sqsum = __riscv_vwmaccu_vv_u64m8_tumu(vmask, vec_sqsum, vec_pixel, vec_pixel, vl); + nz += __riscv_vcpop_m_b8(vmask, vl); + } + nz /= 4; + } + } else { + for (int i = 0; i < height; i++) { + const uchar* src_row = src_data + i * src_step; + int j = 0, vl; + for ( ; j < width*4; j += vl) { + vl = __riscv_vsetvl_e8m1(width*4 - j); + auto vec_pixel_u8 = __riscv_vle8_v_u8m1(src_row + j, vl); + auto vec_pixel = __riscv_vzext_vf4(vec_pixel_u8, vl); + vec_sum = __riscv_vwaddu_wv_u64m8_tu(vec_sum, vec_sum, vec_pixel, vl); + vec_sqsum = __riscv_vwmaccu_vv_u64m8_tu(vec_sqsum, vec_pixel, vec_pixel, vl); + } + } + nz = height * width; + } + if (nz == 0) { + if (mean_val) *mean_val = 0.0; + if (stddev_val) *stddev_val = 0.0; + return CV_HAL_ERROR_OK; + } + uint64_t s[256], sq[256], sum[4] = {0}, sqsum[4] = {0}; + __riscv_vse64(s, vec_sum, vlmax); + __riscv_vse64(sq, vec_sqsum, vlmax); + for (int i = 0; i < vlmax; ++i) + { + sum[i % 4] += s[i]; + sqsum[i % 4] += sq[i]; + } + if (mean_val) { + mean_val[0] = (double)sum[0] / nz; + mean_val[1] = (double)sum[1] / nz; + mean_val[2] = (double)sum[2] / nz; + mean_val[3] = (double)sum[3] / nz; + } + if (stddev_val) { + stddev_val[0] = std::sqrt(std::max(((double)sqsum[0] / nz) - (mean_val[0] * mean_val[0]), 0.0)); + stddev_val[1] = std::sqrt(std::max(((double)sqsum[1] / nz) - (mean_val[1] * mean_val[1]), 0.0)); + stddev_val[2] = std::sqrt(std::max(((double)sqsum[2] / nz) - (mean_val[2] * mean_val[2]), 0.0)); + stddev_val[3] = std::sqrt(std::max(((double)sqsum[3] / nz) - (mean_val[3] * mean_val[3]), 0.0)); + } + return CV_HAL_ERROR_OK; +} + +inline int meanStdDev_32FC1(const uchar* src_data, size_t src_step, int width, int height, + double* mean_val, double* stddev_val, uchar* mask, size_t mask_step) { + int nz = 0; + int vlmax = __riscv_vsetvlmax_e64m4(); + vfloat64m4_t vec_sum = __riscv_vfmv_v_f_f64m4(0, vlmax); + vfloat64m4_t vec_sqsum = __riscv_vfmv_v_f_f64m4(0, vlmax); + src_step /= sizeof(float); + if (mask) { + for (int i = 0; i < height; ++i) { + const float* src_row0 = reinterpret_cast(src_data) + i * src_step; + const uchar* mask_row = mask + i * mask_step; + int j = 0, vl; + for ( ; j < width; j += vl) { + vl = __riscv_vsetvl_e32m2(width - j); + auto vec_pixel = __riscv_vle32_v_f32m2(src_row0 + j, vl); + auto vmask_u8 = __riscv_vle8_v_u8mf2(mask_row + j, vl); + auto vmask_u32 = __riscv_vzext_vf4(vmask_u8, vl); + auto vmask = __riscv_vmseq_vx_u32m2_b16(vmask_u32, 1, vl); + vec_sum = __riscv_vfwadd_wv_f64m4_tumu(vmask, vec_sum, vec_sum, vec_pixel, vl); + vec_sqsum = __riscv_vfwmacc_vv_f64m4_tumu(vmask, vec_sqsum, vec_pixel, vec_pixel, vl); + nz += __riscv_vcpop_m_b16(vmask, vl); + } + } + } else { + for (int i = 0; i < height; i++) { + const float* src_row0 = reinterpret_cast(src_data) + i * src_step; + int j = 0, vl; + for ( ; j < width; j += vl) { + vl = __riscv_vsetvl_e32m2(width - j); + auto vec_pixel = __riscv_vle32_v_f32m2(src_row0 + j, vl); + vec_sum = __riscv_vfwadd_wv_f64m4_tu(vec_sum, vec_sum, vec_pixel, vl); + vec_sqsum = __riscv_vfwmacc_vv_f64m4_tu(vec_sqsum, vec_pixel, vec_pixel, vl); + } + } + nz = height * width; + } + if (nz == 0) { + if (mean_val) *mean_val = 0.0; + if (stddev_val) *stddev_val = 0.0; + return CV_HAL_ERROR_OK; + } + auto zero = __riscv_vfmv_v_f_f64m1(0, vlmax); + auto vec_red = __riscv_vfmv_v_f_f64m1(0, vlmax); + auto vec_reddev = __riscv_vfmv_v_f_f64m1(0, vlmax); + vec_red = __riscv_vfredusum(vec_sum, zero, vlmax); + vec_reddev = __riscv_vfredusum(vec_sqsum, zero, vlmax); + double sum = __riscv_vfmv_f(vec_red); + double mean = sum / nz; + if (mean_val) { + *mean_val = mean; + } + if (stddev_val) { + double sqsum = __riscv_vfmv_f(vec_reddev); + double variance = std::max((sqsum / nz) - (mean * mean), 0.0); + double stddev = std::sqrt(variance); + *stddev_val = stddev; + } + return CV_HAL_ERROR_OK; +} + +}} + +#endif From 773bd1a90a4ac2e5c5eb86934d35571ed184cf56 Mon Sep 17 00:00:00 2001 From: Vincent Rabaud Date: Wed, 18 Dec 2024 21:48:48 +0100 Subject: [PATCH 039/102] Rename Mat::clone binding because it is used in Emscripten. This is in emscripten 3.1.71 and above, cf https://github.com/emscripten-core/emscripten/pull/22734 There was a temptative fix upstream to no avail: https://github.com/emscripten-core/emscripten/pull/23132 --- modules/js/src/core_bindings.cpp | 2 +- modules/js/test/test_mat.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/js/src/core_bindings.cpp b/modules/js/src/core_bindings.cpp index d5bf9b076c..361a76fd54 100644 --- a/modules/js/src/core_bindings.cpp +++ b/modules/js/src/core_bindings.cpp @@ -520,7 +520,7 @@ EMSCRIPTEN_BINDINGS(binding_utils) .function("colRange", select_overload(&cv::Mat::colRange)) .function("colRange", select_overload(&cv::Mat::colRange)) .function("step1", select_overload(&cv::Mat::step1)) - .function("clone", select_overload(&cv::Mat::clone)) + .function("mat_clone", select_overload(&cv::Mat::clone)) .function("depth", select_overload(&cv::Mat::depth)) .function("col", select_overload(&cv::Mat::col)) .function("dot", select_overload(&binding_utils::matDot)) diff --git a/modules/js/test/test_mat.js b/modules/js/test/test_mat.js index a1d5c98b17..f0c5211568 100644 --- a/modules/js/test/test_mat.js +++ b/modules/js/test/test_mat.js @@ -173,7 +173,7 @@ QUnit.test('test_mat_creation', function(assert) { // clone { let mat = cv.Mat.ones(5, 5, cv.CV_8UC1); - let mat2 = mat.clone(); + let mat2 = mat.mat_clone(); assert.equal(mat.channels, mat2.channels); assert.equal(mat.size().height, mat2.size().height); From a628417f2ae38a5fcfc50a3bf8b7cad2dba0a8ab Mon Sep 17 00:00:00 2001 From: Vincent Rabaud Date: Wed, 18 Dec 2024 22:15:01 +0100 Subject: [PATCH 040/102] Fix C preprocessor stringification --- modules/js/src/core_bindings.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/js/src/core_bindings.cpp b/modules/js/src/core_bindings.cpp index d5bf9b076c..8a14a0e546 100644 --- a/modules/js/src/core_bindings.cpp +++ b/modules/js/src/core_bindings.cpp @@ -584,7 +584,7 @@ EMSCRIPTEN_BINDINGS(binding_utils) .field("epsilon", &cv::TermCriteria::epsilon); #define EMSCRIPTEN_CV_SIZE(type) \ - emscripten::value_object("#type") \ + emscripten::value_object(#type) \ .field("width", &type::width) \ .field("height", &type::height); @@ -592,7 +592,7 @@ EMSCRIPTEN_BINDINGS(binding_utils) EMSCRIPTEN_CV_SIZE(Size2f) #define EMSCRIPTEN_CV_POINT(type) \ - emscripten::value_object("#type") \ + emscripten::value_object(#type) \ .field("x", &type::x) \ .field("y", &type::y); \ From 914a83fa0ce828861adce45794c46bb1eb327950 Mon Sep 17 00:00:00 2001 From: Vincent Rabaud Date: Fri, 13 Sep 2024 11:49:38 +0200 Subject: [PATCH 041/102] Fix enum generation issues. --- modules/js/generator/embindgen.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/js/generator/embindgen.py b/modules/js/generator/embindgen.py index 00d2406046..be751589aa 100644 --- a/modules/js/generator/embindgen.py +++ b/modules/js/generator/embindgen.py @@ -132,7 +132,7 @@ white_list = None namespace_prefix_override = None # Features to be exported -export_enums = False +export_enums = True export_consts = True with_wrapped_functions = True with_default_params = True @@ -916,7 +916,7 @@ class JSWrapperGenerator(object): if ns_name.split('.')[0] != 'cv': continue for name, enum in sorted(ns.enums.items()): - if not name.endswith('.anonymous'): + if '.unnamed_' not in name: name = name.replace("cv.", "") enum_values = [] for enum_val in enum: @@ -936,7 +936,10 @@ class JSWrapperGenerator(object): for ns_name, ns in sorted(self.namespaces.items()): if ns_name.split('.')[0] != 'cv': continue + # TODO CALIB_FIX_FOCAL_LENGTH is defined both in cv:: and cv::fisheye + prefix = 'FISHEYE_' if 'fisheye' in ns_name else '' for name, const in sorted(ns.consts.items()): + name = prefix + name # print("Gen consts: ", name, const) self.bindings.append(const_template.substitute(js_name=name, value=const)) From 428d93114f54c4accb67d45224c4a38e499ce3cc Mon Sep 17 00:00:00 2001 From: Letu Ren Date: Thu, 19 Dec 2024 10:59:17 +0800 Subject: [PATCH 042/102] fix typo --- modules/imgproc/include/opencv2/imgproc/segmentation.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/imgproc/include/opencv2/imgproc/segmentation.hpp b/modules/imgproc/include/opencv2/imgproc/segmentation.hpp index c40d5011ee..b2df4fcaca 100644 --- a/modules/imgproc/include/opencv2/imgproc/segmentation.hpp +++ b/modules/imgproc/include/opencv2/imgproc/segmentation.hpp @@ -121,7 +121,7 @@ public: * * @param targetPt The target point * @param[out] contour The list of pixels which contains optimal path between the source and the target points of the image. Type is CV_32SC2 (compatible with `std::vector`) - * @param backward Flag to indicate reverse order of retrived pixels (use "true" value to fetch points from the target to the source point) + * @param backward Flag to indicate reverse order of retrieved pixels (use "true" value to fetch points from the target to the source point) */ CV_WRAP void getContour(const Point& targetPt, OutputArray contour, bool backward = false) const; From 79d019b4f1a5e89460eb3c7f2d41f646fd759a76 Mon Sep 17 00:00:00 2001 From: Vincent Rabaud Date: Thu, 19 Dec 2024 05:59:01 +0100 Subject: [PATCH 043/102] Merge pull request #26640 from vrabaud:opencv_js3 js: fix generation of "const const" in code #26640 ### 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/js/generator/embindgen.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/modules/js/generator/embindgen.py b/modules/js/generator/embindgen.py index 00d2406046..25cb90cfb9 100644 --- a/modules/js/generator/embindgen.py +++ b/modules/js/generator/embindgen.py @@ -248,16 +248,17 @@ class ArgInfo(object): self.const = True elif m == "/Ref": self.reference = True - if self.tp == "Mat": - if self.outputarg: - self.tp = "cv::Mat&" - elif self.inputarg: - self.tp = "const cv::Mat&" - if self.tp == "vector_Mat": - if self.outputarg: - self.tp = "std::vector&" - elif self.inputarg: - self.tp = "const std::vector&" + if self.tp == "Mat" and (self.inputarg or self.outputarg): + self.tp = "cv::Mat&" + if self.inputarg and not self.outputarg: + self.const = True + if self.tp == "vector_Mat" and (self.inputarg or self.outputarg): + self.tp = "std::vector&" + if self.reference and not self.const: + self.inputarg = False + self.outputarg = True + elif self.inputarg and not self.outputarg: + self.const = True self.tp = handle_vector(self.tp).strip() if self.const: self.tp = "const " + self.tp From 9f64f021dec851a03a67a55d2e2a8493769c3042 Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Thu, 19 Dec 2024 08:17:05 +0300 Subject: [PATCH 044/102] Merge pull request #26637 from MaximSmolskiy:fix-VideoCapture-fails-to-read-single-image-with-digits-in-name Fix VideoCapture fails to read single image with digits in name #26637 ### Pull Request Readiness Checklist Fix #26457 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/videoio/src/cap_images.cpp | 13 +++---------- modules/videoio/test/test_images.cpp | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/modules/videoio/src/cap_images.cpp b/modules/videoio/src/cap_images.cpp index 6b30c5c4b6..f472ff711b 100644 --- a/modules/videoio/src/cap_images.cpp +++ b/modules/videoio/src/cap_images.cpp @@ -113,16 +113,9 @@ void CvCapture_Images::close() bool CvCapture_Images::grabFrame() { - cv::String filename; - if (length == 1) - if (currentframe < length) - filename = filename_pattern; - else - { - return false; - } - else - filename = cv::format(filename_pattern.c_str(), (int)(firstframe + currentframe)); + if (length == 1 && currentframe >= length) + return false; + const cv::String filename = cv::format(filename_pattern.c_str(), (int)(firstframe + currentframe)); CV_Assert(!filename.empty()); if (grabbedInOpen) diff --git a/modules/videoio/test/test_images.cpp b/modules/videoio/test/test_images.cpp index ccf507a50d..c3782f2c5d 100644 --- a/modules/videoio/test/test_images.cpp +++ b/modules/videoio/test/test_images.cpp @@ -285,6 +285,21 @@ TEST(videoio_images, extract_pattern) EXPECT_THROW(cv::icvExtractPattern("1.png", NULL), cv::Exception); } +TEST(videoio_images, bug_26457) +{ + ImageCollection col; + col.generate(1u); + ASSERT_EQ(col.getCount(), 1u); + + VideoCapture cap(col.getFirstFilename(), CAP_IMAGES); + ASSERT_TRUE(cap.isOpened()); + + Mat img; + const bool read_res = cap.read(img); + EXPECT_TRUE(read_res); + EXPECT_MAT_N_DIFF(img, col.getFirstFrame(), 0); +} + // TODO: should writer overwrite files? // TODO: is clamping good for seeking? // TODO: missing files? E.g. 3, 4, 6, 7, 8 (should it finish OR jump over OR return empty frame?) From 59f762b2f0baedfbc77a2a30820b82495dd30b48 Mon Sep 17 00:00:00 2001 From: adsha-quic Date: Thu, 19 Dec 2024 10:58:24 +0530 Subject: [PATCH 045/102] Merge pull request #26619 from CodeLinaro:adsha_2ndPost FastCV-based HAL for OpenCV acceleration 2ndpost-2 #26619 ### Detailed description: - Add support for multiply 8u, 16s and 32f - Add support for cv_hal_pyrdown 8u - Add support for cv_hal_cvtBGRtoHSV and cv_hal_cvtBGRtoYUVApprox 8u Requires binary from [opencv/opencv_3rdparty#90](https://github.com/opencv/opencv_3rdparty/pull/90) Depends on: [opencv/opencv#26617](https://github.com/opencv/opencv/pull/26617) ### 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/include/fastcv_hal_core.hpp | 42 +++ .../fastcv/include/fastcv_hal_imgproc.hpp | 74 ++++-- 3rdparty/fastcv/src/fastcv_hal_core.cpp | 188 +++++++++++++ 3rdparty/fastcv/src/fastcv_hal_imgproc.cpp | 249 ++++++++++++++++++ 4 files changed, 525 insertions(+), 28 deletions(-) diff --git a/3rdparty/fastcv/include/fastcv_hal_core.hpp b/3rdparty/fastcv/include/fastcv_hal_core.hpp index 47025856f7..03c17dc6b5 100644 --- a/3rdparty/fastcv/include/fastcv_hal_core.hpp +++ b/3rdparty/fastcv/include/fastcv_hal_core.hpp @@ -26,6 +26,12 @@ #define cv_hal_rotate90 fastcv_hal_rotate #undef cv_hal_addWeighted8u #define cv_hal_addWeighted8u fastcv_hal_addWeighted8u +#undef cv_hal_mul8u +#define cv_hal_mul8u fastcv_hal_mul8u +#undef cv_hal_mul16s +#define cv_hal_mul16s fastcv_hal_mul16s +#undef cv_hal_mul32f +#define cv_hal_mul32f fastcv_hal_mul32f //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// @brief look-up table transform of an array. @@ -177,4 +183,40 @@ int fastcv_hal_addWeighted8u( int height, const double scalars[3]); +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +int fastcv_hal_mul8u( + const uchar *src1_data, + size_t src1_step, + const uchar *src2_data, + size_t src2_step, + uchar *dst_data, + size_t dst_step, + int width, + int height, + double scale); + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +int fastcv_hal_mul16s( + const short *src1_data, + size_t src1_step, + const short *src2_data, + size_t src2_step, + short *dst_data, + size_t dst_step, + int width, + int height, + double scale); + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +int fastcv_hal_mul32f( + const float *src1_data, + size_t src1_step, + const float *src2_data, + size_t src2_step, + float *dst_data, + size_t dst_step, + int width, + int height, + double scale); + #endif diff --git a/3rdparty/fastcv/include/fastcv_hal_imgproc.hpp b/3rdparty/fastcv/include/fastcv_hal_imgproc.hpp index 7fd49bc62c..8b9a2ca4da 100644 --- a/3rdparty/fastcv/include/fastcv_hal_imgproc.hpp +++ b/3rdparty/fastcv/include/fastcv_hal_imgproc.hpp @@ -20,7 +20,12 @@ #define cv_hal_gaussianBlurBinomial fastcv_hal_gaussianBlurBinomial #undef cv_hal_warpPerspective #define cv_hal_warpPerspective fastcv_hal_warpPerspective - +#undef cv_hal_pyrdown +#define cv_hal_pyrdown fastcv_hal_pyrdown +#undef cv_hal_cvtBGRtoHSV +#define cv_hal_cvtBGRtoHSV fastcv_hal_cvtBGRtoHSV +#undef cv_hal_cvtBGRtoYUVApprox +#define cv_hal_cvtBGRtoYUVApprox fastcv_hal_cvtBGRtoYUVApprox //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// @brief Calculate medianBlur filter /// @param src_data Source image data @@ -88,33 +93,6 @@ int fastcv_hal_sobel( double delta, int border_type); -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -/// @brief Canny edge detector -/// @param src_data Source image data -/// @param src_step Source image step -/// @param dst_data Destination image data -/// @param dst_step Destination image step -/// @param width Source image width -/// @param height Source image height -/// @param cn Number of channels -/// @param lowThreshold low thresholds value -/// @param highThreshold high thresholds value -/// @param ksize Kernel size for Sobel operator. -/// @param L2gradient Flag, indicating use L2 or L1 norma. -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -int fastcv_hal_canny( - const uchar* src_data, - size_t src_step, - uchar* dst_data, - size_t dst_step, - int width, - int height, - int cn, - double lowThreshold, - double highThreshold, - int ksize, - bool L2gradient); - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// int fastcv_hal_boxFilter( @@ -217,4 +195,44 @@ int fastcv_hal_warpPerspective( int border_type, const double border_value[4]); +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +int fastcv_hal_pyrdown( + 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, + int depth, + int cn, + int border_type); + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +int fastcv_hal_cvtBGRtoHSV( + const uchar * src_data, + size_t src_step, + uchar * dst_data, + size_t dst_step, + int width, + int height, + int depth, + int scn, + bool swapBlue, + bool isFullRange, + bool isHSV); + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +int fastcv_hal_cvtBGRtoYUVApprox( + const uchar * src_data, + size_t src_step, + uchar * dst_data, + size_t dst_step, + int width, + int height, + int depth, + int scn, + bool swapBlue, + bool isCbCr); #endif diff --git a/3rdparty/fastcv/src/fastcv_hal_core.cpp b/3rdparty/fastcv/src/fastcv_hal_core.cpp index a219eddc8c..d46bf9a172 100644 --- a/3rdparty/fastcv/src/fastcv_hal_core.cpp +++ b/3rdparty/fastcv/src/fastcv_hal_core.cpp @@ -383,4 +383,192 @@ int fastcv_hal_addWeighted8u( } CV_HAL_RETURN(status, hal_addWeighted8u_v2); +} + +int fastcv_hal_mul8u( + const uchar *src1_data, + size_t src1_step, + const uchar *src2_data, + size_t src2_step, + uchar *dst_data, + size_t dst_step, + int width, + int height, + double scale) +{ + int8_t sF; + + if(FCV_CMP_EQ(scale,1.0)) { sF = 0; } + else if(scale > 1.0) + { + if(FCV_CMP_EQ(scale,2.0)) { sF = -1; } + else if(FCV_CMP_EQ(scale,4.0)) { sF = -2; } + else if(FCV_CMP_EQ(scale,8.0)) { sF = -3; } + else if(FCV_CMP_EQ(scale,16.0)) { sF = -4; } + else if(FCV_CMP_EQ(scale,32.0)) { sF = -5; } + else if(FCV_CMP_EQ(scale,64.0)) { sF = -6; } + else if(FCV_CMP_EQ(scale,128.0)) { sF = -7; } + else if(FCV_CMP_EQ(scale,256.0)) { sF = -8; } + else CV_HAL_RETURN_NOT_IMPLEMENTED("scale factor not supported"); + } + else if(scale > 0 && scale < 1.0) + { + if(FCV_CMP_EQ(scale,1/2.0)) { sF = 1; } + else if(FCV_CMP_EQ(scale,1/4.0)) { sF = 2; } + else if(FCV_CMP_EQ(scale,1/8.0)) { sF = 3; } + else if(FCV_CMP_EQ(scale,1/16.0)) { sF = 4; } + else if(FCV_CMP_EQ(scale,1/32.0)) { sF = 5; } + else if(FCV_CMP_EQ(scale,1/64.0)) { sF = 6; } + else if(FCV_CMP_EQ(scale,1/128.0)) { sF = 7; } + else if(FCV_CMP_EQ(scale,1/256.0)) { sF = 8; } + else CV_HAL_RETURN_NOT_IMPLEMENTED("scale factor not supported"); + } + else + CV_HAL_RETURN_NOT_IMPLEMENTED("scale factor not supported"); + + INITIALIZATION_CHECK; + + int nStripes = cv::getNumThreads(); + + if(height == 1) + { + cv::parallel_for_(cv::Range(0, width), [&](const cv::Range &range){ + int rangeWidth = range.end - range.start; + const uchar* yS1 = src1_data + static_cast(range.start); + const uchar* yS2 = src2_data + static_cast(range.start); + uchar* yD = dst_data + static_cast(range.start); + fcvElementMultiplyu8(yS1, rangeWidth, 1, 0, yS2, 0, sF, + FASTCV_CONVERT_POLICY_SATURATE, yD, 0); + }, nStripes); + } + else + { + cv::parallel_for_(cv::Range(0, height), [&](const cv::Range &range){ + int rangeHeight = range.end - range.start; + const uchar* yS1 = src1_data + static_cast(range.start)*src1_step; + const uchar* yS2 = src2_data + static_cast(range.start)*src2_step; + uchar* yD = dst_data + static_cast(range.start)*dst_step; + fcvElementMultiplyu8(yS1, width, rangeHeight, src1_step, yS2, src2_step, + sF, FASTCV_CONVERT_POLICY_SATURATE, yD, dst_step); + }, nStripes); + } + + fcvStatus status = FASTCV_SUCCESS; + CV_HAL_RETURN(status, hal_mul8u); +} + +int fastcv_hal_mul16s( + const short *src1_data, + size_t src1_step, + const short *src2_data, + size_t src2_step, + short *dst_data, + size_t dst_step, + int width, + int height, + double scale) +{ + int8_t sF; + + if(FCV_CMP_EQ(scale,1.0)) { sF = 0; } + else if(scale > 1.0) + { + if(FCV_CMP_EQ(scale,2.0)) { sF = -1; } + else if(FCV_CMP_EQ(scale,4.0)) { sF = -2; } + else if(FCV_CMP_EQ(scale,8.0)) { sF = -3; } + else if(FCV_CMP_EQ(scale,16.0)) { sF = -4; } + else if(FCV_CMP_EQ(scale,32.0)) { sF = -5; } + else if(FCV_CMP_EQ(scale,64.0)) { sF = -6; } + else if(FCV_CMP_EQ(scale,128.0)) { sF = -7; } + else if(FCV_CMP_EQ(scale,256.0)) { sF = -8; } + else CV_HAL_RETURN_NOT_IMPLEMENTED("scale factor not supported"); + } + else if(scale > 0 && scale < 1.0) + { + if(FCV_CMP_EQ(scale,1/2.0)) { sF = 1; } + else if(FCV_CMP_EQ(scale,1/4.0)) { sF = 2; } + else if(FCV_CMP_EQ(scale,1/8.0)) { sF = 3; } + else if(FCV_CMP_EQ(scale,1/16.0)) { sF = 4; } + else if(FCV_CMP_EQ(scale,1/32.0)) { sF = 5; } + else if(FCV_CMP_EQ(scale,1/64.0)) { sF = 6; } + else if(FCV_CMP_EQ(scale,1/128.0)) { sF = 7; } + else if(FCV_CMP_EQ(scale,1/256.0)) { sF = 8; } + else CV_HAL_RETURN_NOT_IMPLEMENTED("scale factor not supported"); + } + else + CV_HAL_RETURN_NOT_IMPLEMENTED("scale factor not supported"); + + INITIALIZATION_CHECK; + + int nStripes = cv::getNumThreads(); + + if(height == 1) + { + cv::parallel_for_(cv::Range(0, width), [&](const cv::Range &range){ + int rangeWidth = range.end - range.start; + const short* yS1 = src1_data + static_cast(range.start); + const short* yS2 = src2_data + static_cast(range.start); + short* yD = dst_data + static_cast(range.start); + fcvElementMultiplys16(yS1, rangeWidth, 1, 0, yS2, 0, sF, + FASTCV_CONVERT_POLICY_SATURATE, yD, 0); + }, nStripes); + } + else + { + cv::parallel_for_(cv::Range(0, height), [&](const cv::Range &range){ + int rangeHeight = range.end - range.start; + const short* yS1 = src1_data + static_cast(range.start) * (src1_step/sizeof(short)); + const short* yS2 = src2_data + static_cast(range.start) * (src2_step/sizeof(short)); + short* yD = dst_data + static_cast(range.start) * (dst_step/sizeof(short)); + fcvElementMultiplys16(yS1, width, rangeHeight, src1_step, yS2, src2_step, + sF, FASTCV_CONVERT_POLICY_SATURATE, yD, dst_step); + }, nStripes); + } + + fcvStatus status = FASTCV_SUCCESS; + CV_HAL_RETURN(status, hal_mul16s); +} + +int fastcv_hal_mul32f( + const float *src1_data, + size_t src1_step, + const float *src2_data, + size_t src2_step, + float *dst_data, + size_t dst_step, + int width, + int height, + double scale) +{ + if(!FCV_CMP_EQ(scale,1.0)) + CV_HAL_RETURN_NOT_IMPLEMENTED("scale factor not supported"); + + INITIALIZATION_CHECK; + + int nStripes = cv::getNumThreads(); + + if(height == 1) + { + cv::parallel_for_(cv::Range(0, width), [&](const cv::Range &range){ + int rangeWidth = range.end - range.start; + const float* yS1 = src1_data + static_cast(range.start); + const float* yS2 = src2_data + static_cast(range.start); + float* yD = dst_data + static_cast(range.start); + fcvElementMultiplyf32(yS1, rangeWidth, 1, 0, yS2, 0, yD, 0); + }, nStripes); + } + else + { + cv::parallel_for_(cv::Range(0, height), [&](const cv::Range &range){ + int rangeHeight = range.end - range.start; + const float* yS1 = src1_data + static_cast(range.start) * (src1_step/sizeof(float)); + const float* yS2 = src2_data + static_cast(range.start) * (src2_step/sizeof(float)); + float* yD = dst_data + static_cast(range.start) * (dst_step/sizeof(float)); + fcvElementMultiplyf32(yS1, width, rangeHeight, src1_step, + yS2, src2_step, yD, dst_step); + }, nStripes); + } + + fcvStatus status = FASTCV_SUCCESS; + CV_HAL_RETURN(status, hal_mul32f); } \ No newline at end of file diff --git a/3rdparty/fastcv/src/fastcv_hal_imgproc.cpp b/3rdparty/fastcv/src/fastcv_hal_imgproc.cpp index 7f128e7d1c..58872d8f57 100644 --- a/3rdparty/fastcv/src/fastcv_hal_imgproc.cpp +++ b/3rdparty/fastcv/src/fastcv_hal_imgproc.cpp @@ -743,4 +743,253 @@ int fastcv_hal_warpPerspective( CV_HAL_RETURN_NOT_IMPLEMENTED(cv::format("Src type:%s is not supported", cv::typeToString(src_type).c_str())); CV_HAL_RETURN(status, hal_warpPerspective); +} + +class FcvPyrLoop_Invoker : public cv::ParallelLoopBody +{ +public: + + FcvPyrLoop_Invoker(cv::Mat src_, int width_, int height_, cv::Mat dst_, int bdr_, int knl_, int stripeHeight_, int nStripes_) : + cv::ParallelLoopBody(), src(src_), width(width_), height(height_), dst(dst_), bdr(bdr_), knl(knl_), stripeHeight(stripeHeight_), nStripes(nStripes_) + { + } + + virtual void operator()(const cv::Range& range) const CV_OVERRIDE + { + int height_ = stripeHeight * (range.end - range.start); + int width_ = width; + cv::Mat src_; + int n = knl/2; + + if(range.end == nStripes) + height_ += (height - range.end * stripeHeight); + + src_ = cv::Mat(height_ + 2*n, width_ + 2*n, CV_8U); + + if(range.start == 0 && range.end == nStripes) + cv::copyMakeBorder(src(cv::Rect(0, 0, width, height)), src_, n, n, n, n, bdr); + else if(range.start == 0) + cv::copyMakeBorder(src(cv::Rect(0, 0, width_, height_ + n)), src_, n, 0, n, n, bdr); + else if(range.end == nStripes) + cv::copyMakeBorder(src(cv::Rect(0, range.start * stripeHeight - n, width_, height_ + n)), src_, 0, n, n, n, bdr); + else + cv::copyMakeBorder(src(cv::Rect(0, range.start * stripeHeight - n, width_, height_ + 2*n)), src_, 0, 0, n, n, bdr); + + int dstHeight_, dstWidth_, origDstHeight_, origDstWidth_; + dstHeight_ = (height_ + 2*n + 1)/2; + dstWidth_ = (width_ + 2*n + 1)/2; + origDstHeight_ = (height_ + 1)/2; + origDstWidth_ = (width_ + 1)/2; + + cv::Mat dst_padded = cv::Mat(dstHeight_, dstWidth_, CV_8U); + + fcvPyramidLevel_v2 framePyr[2]; + framePyr[0].ptr = NULL; + framePyr[1].ptr = dst_padded.data; + framePyr[1].stride = dstWidth_; + + fcvPyramidCreateu8_v4(src_.data, width_ + 2*n, height_ + 2*n, + width_ + 2*n, 2, FASTCV_PYRAMID_SCALE_HALF, + framePyr, FASTCV_BORDER_UNDEFINED, 0); + + int start_val = stripeHeight * range.start; + cv::Mat dst_temp1 = dst_padded(cv::Rect(n/2, n/2, origDstWidth_, origDstHeight_)); + cv::Mat dst_temp2 = dst(cv::Rect(0, start_val/2, origDstWidth_, origDstHeight_)); + dst_temp1.copyTo(dst_temp2); + } + +private: + cv::Mat src; + const int width; + const int height; + cv::Mat dst; + const int bdr; + const int knl; + const int stripeHeight; + const int nStripes; + + FcvPyrLoop_Invoker(const FcvPyrLoop_Invoker &); // = delete; + const FcvPyrLoop_Invoker& operator= (const FcvPyrLoop_Invoker &); // = delete; +}; + +int fastcv_hal_pyrdown( + 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, + int depth, + int cn, + int border_type) +{ + if(depth != CV_8U || cn!= 1) + { + CV_HAL_RETURN_NOT_IMPLEMENTED("src type not supported"); + } + + int dstW = (src_width & 1) == 1 ? ((src_width + 1) >> 1) : ((src_width) >> 1); + int dstH = (src_height & 1) == 1 ? ((src_height + 1) >> 1) : ((src_height) >> 1); + + if((dstW > dst_width) || (dstH > dst_height)) + { + CV_HAL_RETURN_NOT_IMPLEMENTED("dst size needs to be atleast half of the src size"); + } + + INITIALIZATION_CHECK; + + fcvBorderType bdr; + uint8_t bVal = 0; + int nThreads = cv::getNumThreads(); + if(nThreads <= 1) + { + switch(border_type) + { + case cv::BORDER_REPLICATE: + bdr = FASTCV_BORDER_REPLICATE; + break; + case cv::BORDER_REFLECT: + bdr = FASTCV_BORDER_REFLECT; + break; + case cv::BORDER_REFLECT101: // cv::BORDER_REFLECT_101, BORDER_DEFAULT + bdr = FASTCV_BORDER_REFLECT_V2; + break; + default: + CV_HAL_RETURN_NOT_IMPLEMENTED("border type not supported"); + } + + fcvPyramidLevel_v2 frame1Pyr[2]; + frame1Pyr[0].ptr = NULL; + frame1Pyr[1].ptr = dst_data; + frame1Pyr[1].stride = dst_step; + + fcvStatus status = fcvPyramidCreateu8_v4(src_data, src_width, src_height, + src_step, 2, FASTCV_PYRAMID_SCALE_HALF, + frame1Pyr, bdr, bVal); + + CV_HAL_RETURN(status,hal_pyrdown); + } + else + { + cv::Mat src = cv::Mat(src_height, src_width, CV_8UC1, (void*)src_data, src_step); + cv::Mat dst = cv::Mat(dst_height, dst_width, CV_8UC1, (void*)dst_data, dst_step); + + int nStripes, stripeHeight = nThreads * 10; + + if(src.rows/stripeHeight == 0) + nStripes = 1; + else + nStripes = (src.rows/stripeHeight); + + cv::parallel_for_(cv::Range(0, nStripes), + FcvPyrLoop_Invoker(src, src_width, src_height, dst, border_type, 5, stripeHeight, nStripes), nStripes); + + fcvStatus status = FASTCV_SUCCESS; + CV_HAL_RETURN(status, hal_pyrdown); + } +} + +int fastcv_hal_cvtBGRtoHSV( + const uchar * src_data, + size_t src_step, + uchar * dst_data, + size_t dst_step, + int width, + int height, + int depth, + int scn, + bool swapBlue, + bool isFullRange, + bool isHSV) +{ + if(width * height > 640 * 480) + { + CV_HAL_RETURN_NOT_IMPLEMENTED("input size not supported"); + } + if(scn != 3 || depth != CV_8U) + { + CV_HAL_RETURN_NOT_IMPLEMENTED("src type not supported"); + } + else if(!isHSV || !isFullRange) + { + CV_HAL_RETURN_NOT_IMPLEMENTED("Full range HSV supported"); + } + else if(!swapBlue) + { + CV_HAL_RETURN_NOT_IMPLEMENTED("current color code not supported, expected swapped blue channel"); + } + else if (src_data == dst_data) + { + CV_HAL_RETURN_NOT_IMPLEMENTED("In-place not supported"); + } + else if((src_step < (size_t)width*3) || + (dst_step < (size_t)width*3)) + { + CV_HAL_RETURN_NOT_IMPLEMENTED("unexpected stride values"); + } + + INITIALIZATION_CHECK; + + int nStripes = cv::getNumThreads(); + + cv::parallel_for_(cv::Range(0, height), [&](const cv::Range &range){ + int sHeight = range.end - range.start; + const uchar* yS = src_data + static_cast(range.start) * src_step; + uchar* yD = dst_data + static_cast(range.start) * dst_step; + fcvColorRGB888ToHSV888u8(yS, width, sHeight, src_step, yD, dst_step); + }, nStripes); + + fcvStatus status = FASTCV_SUCCESS; + CV_HAL_RETURN(status, hal_BGRtoHSV); +} + +int fastcv_hal_cvtBGRtoYUVApprox( + const uchar * src_data, + size_t src_step, + uchar * dst_data, + size_t dst_step, + int width, + int height, + int depth, + int scn, + bool swapBlue, + bool isCbCr) +{ + if(scn != 3 || depth != CV_8U) + { + CV_HAL_RETURN_NOT_IMPLEMENTED("src type not supported"); + } + else if(!isCbCr) + { + CV_HAL_RETURN_NOT_IMPLEMENTED("CbCr supported"); + } + else if(!swapBlue) + { + CV_HAL_RETURN_NOT_IMPLEMENTED("expected swapped blue channel"); + } + else if (src_data == dst_data) + { + CV_HAL_RETURN_NOT_IMPLEMENTED("In-place not supported"); + } + else if((src_step < (size_t)width*3) || + (dst_step < (size_t)width*3)) + { + CV_HAL_RETURN_NOT_IMPLEMENTED("unexpected stride values"); + } + + INITIALIZATION_CHECK; + + int nStripes = cv::getNumThreads(); + + cv::parallel_for_(cv::Range(0, height), [&](const cv::Range &range){ + int sHeight = range.end - range.start; + const uchar* yS = src_data + static_cast(range.start) * src_step; + uchar* yD = dst_data + static_cast(range.start) * dst_step; + fcvColorRGB888toYCrCbu8_v3(yS, width, sHeight, src_step, yD, dst_step); + }, nStripes); + + fcvStatus status = FASTCV_SUCCESS; + CV_HAL_RETURN(status, hal_BGRtoYUVApprox); } \ No newline at end of file From 60d35d1bd581c8b66a49c4f9afb65e5ce86b3579 Mon Sep 17 00:00:00 2001 From: Suleyman TURKMEN Date: Thu, 19 Dec 2024 11:57:58 +0300 Subject: [PATCH 046/102] Merge pull request #26511 from sturkmen72:proposed_fix_for_21902 * add alternative flags to cv::seamlessClone * Update photo.hpp * Update seamless_cloning.cpp * Update seamless_cloning_impl.cpp --- modules/photo/include/opencv2/photo.hpp | 79 ++++++++++++++++----- modules/photo/src/seamless_cloning.cpp | 21 ++++-- modules/photo/src/seamless_cloning_impl.cpp | 3 + modules/photo/test/test_cloning.cpp | 8 +-- 4 files changed, 82 insertions(+), 29 deletions(-) diff --git a/modules/photo/include/opencv2/photo.hpp b/modules/photo/include/opencv2/photo.hpp index 1c8a316b52..a8b7c6049a 100644 --- a/modules/photo/include/opencv2/photo.hpp +++ b/modules/photo/include/opencv2/photo.hpp @@ -708,33 +708,74 @@ CV_EXPORTS_W void decolor( InputArray src, OutputArray grayscale, OutputArray co //! @{ -//! seamlessClone algorithm flags -enum +//! Flags for the seamlessClone algorithm +enum SeamlessCloneFlags { - /** The power of the method is fully expressed when inserting objects with complex outlines into a new background*/ + /** + @brief Normal seamless cloning. + This method is ideal for inserting objects with complex outlines into a new background. + It preserves the original appearance and lighting of the inserted object, ensuring a natural blend. + */ NORMAL_CLONE = 1, - /** The classic method, color-based selection and alpha masking might be time consuming and often leaves an undesirable - halo. Seamless cloning, even averaged with the original image, is not effective. Mixed seamless cloning based on a loose selection proves effective.*/ - MIXED_CLONE = 2, - /** Monochrome transfer allows the user to easily replace certain features of one object by alternative features.*/ - MONOCHROME_TRANSFER = 3}; + + /** + @brief Mixed seamless cloning. + This method addresses cases where simple color-based selection or alpha masking is time-consuming + and may result in undesirable halos. By combining structure from the source and texture from the + destination, mixed seamless cloning is highly effective, even with loosely defined selections. + */ + MIXED_CLONE = 2, + + /** + @brief Monochrome transfer cloning. + This method allows users to replace specific features of an object, such as grayscale textures + or patterns, with alternative features. It is particularly useful for artistic effects or + targeted object modifications. + */ + MONOCHROME_TRANSFER = 3, + + /** + @brief Enhanced normal seamless cloning. + Similar to `NORMAL_CLONE`, but with an advanced approach to ROI (Region of Interest) calculation. + This mode processes a larger source region by considering the entire mask area instead of only + the bounding rectangle of non-zero pixels. + */ + NORMAL_CLONE_WIDE = 9, + + /** + @brief Enhanced mixed seamless cloning. + Similar to `MIXED_CLONE`, but with an advanced approach to ROI (Region of Interest) calculation. + This mode processes a larger source region by considering the entire mask area instead of only + the bounding rectangle of non-zero pixels. + */ + MIXED_CLONE_WIDE = 10, + + /** + @brief Enhanced monochrome transfer cloning. + Similar to `MONOCHROME_TRANSFER`, but with an advanced approach to ROI (Region of Interest) calculation. + This mode processes a larger source region by considering the entire mask area instead of only + the bounding rectangle of non-zero pixels. + */ + MONOCHROME_TRANSFER_WIDE = 11 +}; /** @example samples/cpp/tutorial_code/photo/seamless_cloning/cloning_demo.cpp An example using seamlessClone function */ -/** @brief Image editing tasks concern either global changes (color/intensity corrections, filters, -deformations) or local changes concerned to a selection. Here we are interested in achieving local -changes, ones that are restricted to a region manually selected (ROI), in a seamless and effortless -manner. The extent of the changes ranges from slight distortions to complete replacement by novel -content @cite PM03 . +/** @brief Performs seamless cloning to blend a region from a source image into a destination image. +This function is designed for local image editing, allowing changes restricted to a region +(manually selected as the ROI) to be applied effortlessly and seamlessly. These changes can +range from slight distortions to complete replacement by novel content @cite PM03. -@param src Input 8-bit 3-channel image. -@param dst Input 8-bit 3-channel image. -@param mask Input 8-bit 1 or 3-channel image. -@param p Point in dst image where object is placed. -@param blend Output image with the same size and type as dst. -@param flags Cloning method that could be cv::NORMAL_CLONE, cv::MIXED_CLONE or cv::MONOCHROME_TRANSFER +@param src The source image (8-bit 3-channel), from which a region will be blended into the destination. +@param dst The destination image (8-bit 3-channel), where the src image will be blended. +@param mask A binary mask (8-bit, 1, 3, or 4-channel) specifying the region in the source image to blend. +Non-zero pixels indicate the region to be blended. If an empty Mat is provided, a mask with +all non-zero pixels is created internally. +@param p The point where the center of the src image is placed in the dst image. +@param blend The output image that stores the result of the seamless cloning. It has the same size and type as `dst`. +@param flags Flags that control the type of cloning method, can take values of `cv::SeamlessCloneFlags`. */ CV_EXPORTS_W void seamlessClone( InputArray src, InputArray dst, InputArray mask, Point p, OutputArray blend, int flags); diff --git a/modules/photo/src/seamless_cloning.cpp b/modules/photo/src/seamless_cloning.cpp index d21a3f21fd..f9bced841c 100644 --- a/modules/photo/src/seamless_cloning.cpp +++ b/modules/photo/src/seamless_cloning.cpp @@ -47,18 +47,17 @@ using namespace std; using namespace cv; -static Mat checkMask(InputArray _mask, Size size) +static Mat checkMask(InputArray mask, Size size) { - Mat mask = _mask.getMat(); Mat gray; - if (mask.channels() > 1) + if (mask.channels() == 3 || mask.channels() == 4) cvtColor(mask, gray, COLOR_BGRA2GRAY); else { if (mask.empty()) gray = Mat(size.height, size.width, CV_8UC1, Scalar(255)); else - mask.copyTo(gray); + return mask.getMat(); } return gray; @@ -68,9 +67,11 @@ void cv::seamlessClone(InputArray _src, InputArray _dst, InputArray _mask, Point { CV_INSTRUMENT_REGION(); CV_Assert(!_src.empty()); + CV_Assert(!_dst.empty()); const Mat src = _src.getMat(); const Mat dest = _dst.getMat(); + Mat mask = checkMask(_mask, src.size()); dest.copyTo(_blend); Mat blend = _blend.getMat(); @@ -80,10 +81,18 @@ void cv::seamlessClone(InputArray _src, InputArray _dst, InputArray _mask, Point Rect roi_s = boundingRect(mask); if (roi_s.empty()) return; - Rect roi_d(p.x - roi_s.width / 2, p.y - roi_s.height / 2, roi_s.width, roi_s.height); - Mat destinationROI = dest(roi_d).clone(); + int l_from_center = p.x - roi_s.width / 2; + int t_from_center = p.y - roi_s.height / 2; + if (flags >= NORMAL_CLONE_WIDE) + { + l_from_center = p.x - (mask.cols / 2 - roi_s.x); + t_from_center = p.y - (mask.rows / 2 - roi_s.y); + } + + Rect roi_d(l_from_center, t_from_center, roi_s.width, roi_s.height); + Mat destinationROI = dest(roi_d); Mat sourceROI = Mat::zeros(roi_s.height, roi_s.width, src.type()); src(roi_s).copyTo(sourceROI,mask(roi_s)); diff --git a/modules/photo/src/seamless_cloning_impl.cpp b/modules/photo/src/seamless_cloning_impl.cpp index 4b3258a1d9..377bdb8f8d 100644 --- a/modules/photo/src/seamless_cloning_impl.cpp +++ b/modules/photo/src/seamless_cloning_impl.cpp @@ -332,11 +332,13 @@ void Cloning::normalClone(const Mat &destination, const Mat &patch, Mat &binaryM switch(flag) { case NORMAL_CLONE: + case NORMAL_CLONE_WIDE: arrayProduct(patchGradientX, binaryMaskFloat, patchGradientX); arrayProduct(patchGradientY, binaryMaskFloat, patchGradientY); break; case MIXED_CLONE: + case MIXED_CLONE_WIDE: { AutoBuffer maskIndices(n_elem_in_line); for (int i = 0; i < n_elem_in_line; ++i) @@ -373,6 +375,7 @@ void Cloning::normalClone(const Mat &destination, const Mat &patch, Mat &binaryM break; case MONOCHROME_TRANSFER: + case MONOCHROME_TRANSFER_WIDE: Mat gray; cvtColor(patch, gray, COLOR_BGR2GRAY ); diff --git a/modules/photo/test/test_cloning.cpp b/modules/photo/test/test_cloning.cpp index e4d806a7f9..fa486b23c1 100644 --- a/modules/photo/test/test_cloning.cpp +++ b/modules/photo/test/test_cloning.cpp @@ -75,7 +75,7 @@ TEST(Photo_SeamlessClone_normal, regression) Point p; p.x = destination.size().width/2; p.y = destination.size().height/2; - seamlessClone(source, destination, mask, p, result, 1); + seamlessClone(source, destination, mask, p, result, NORMAL_CLONE); Mat reference = imread(reference_path); ASSERT_FALSE(reference.empty()) << "Could not load reference image " << reference_path; @@ -88,7 +88,7 @@ TEST(Photo_SeamlessClone_normal, regression) EXPECT_LE(errorL1, reference.total() * numerical_precision) << "size=" << reference.size(); mask = Scalar(0, 0, 0); - seamlessClone(source, destination, mask, p, result, 1); + seamlessClone(source, destination, mask, p, result, NORMAL_CLONE); reference = destination; errorINF = cvtest::norm(reference, result, NORM_INF); @@ -117,7 +117,7 @@ TEST(Photo_SeamlessClone_mixed, regression) Point p; p.x = destination.size().width/2; p.y = destination.size().height/2; - seamlessClone(source, destination, mask, p, result, 2); + seamlessClone(source, destination, mask, p, result, MIXED_CLONE); SAVE(result); @@ -150,7 +150,7 @@ TEST(Photo_SeamlessClone_featureExchange, regression) Point p; p.x = destination.size().width/2; p.y = destination.size().height/2; - seamlessClone(source, destination, mask, p, result, 3); + seamlessClone(source, destination, mask, p, result, MONOCHROME_TRANSFER); SAVE(result); From d037b40faab71d90be2bfccf44bf06a20b8d3df8 Mon Sep 17 00:00:00 2001 From: quic-apreetam Date: Thu, 19 Dec 2024 16:01:26 +0530 Subject: [PATCH 047/102] Merge pull request #26621 from CodeLinaro:apreetam_2ndPost FastCV-based HAL for OpenCV acceleration 2ndpost-3 #26621 ### Detailed description: - Add cv_hal_canny for Canny API Requires binary from [opencv/opencv_3rdparty#90](https://github.com/opencv/opencv_3rdparty/pull/90) Depends on: [opencv/opencv#26617](https://github.com/opencv/opencv/pull/26617) Depends on: [opencv/opencv#26619](https://github.com/opencv/opencv/pull/26619) ### 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 --- .../fastcv/include/fastcv_hal_imgproc.hpp | 30 ++++++++++ 3rdparty/fastcv/src/fastcv_hal_imgproc.cpp | 55 +++++++++++++++++++ 2 files changed, 85 insertions(+) diff --git a/3rdparty/fastcv/include/fastcv_hal_imgproc.hpp b/3rdparty/fastcv/include/fastcv_hal_imgproc.hpp index 8b9a2ca4da..c9360f95ed 100644 --- a/3rdparty/fastcv/include/fastcv_hal_imgproc.hpp +++ b/3rdparty/fastcv/include/fastcv_hal_imgproc.hpp @@ -26,6 +26,8 @@ #define cv_hal_cvtBGRtoHSV fastcv_hal_cvtBGRtoHSV #undef cv_hal_cvtBGRtoYUVApprox #define cv_hal_cvtBGRtoYUVApprox fastcv_hal_cvtBGRtoYUVApprox +#undef cv_hal_canny +#define cv_hal_canny fastcv_hal_canny //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// @brief Calculate medianBlur filter /// @param src_data Source image data @@ -235,4 +237,32 @@ int fastcv_hal_cvtBGRtoYUVApprox( int scn, bool swapBlue, bool isCbCr); + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/// @brief Canny edge detector +/// @param src_data Source image data +/// @param src_step Source image step +/// @param dst_data Destination image data +/// @param dst_step Destination image step +/// @param width Source image width +/// @param height Source image height +/// @param cn Number of channels +/// @param lowThreshold low hresholds value +/// @param highThreshold high thresholds value +/// @param ksize Kernel size for Sobel operator. +/// @param L2gradient Flag, indicating use of L2 or L1 norma. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +int fastcv_hal_canny( + const uchar* src_data, + size_t src_step, + uchar* dst_data, + size_t dst_step, + int width, + int height, + int cn, + double lowThreshold, + double highThreshold, + int ksize, + bool L2gradient); + #endif diff --git a/3rdparty/fastcv/src/fastcv_hal_imgproc.cpp b/3rdparty/fastcv/src/fastcv_hal_imgproc.cpp index 58872d8f57..d1e972b672 100644 --- a/3rdparty/fastcv/src/fastcv_hal_imgproc.cpp +++ b/3rdparty/fastcv/src/fastcv_hal_imgproc.cpp @@ -992,4 +992,59 @@ int fastcv_hal_cvtBGRtoYUVApprox( fcvStatus status = FASTCV_SUCCESS; CV_HAL_RETURN(status, hal_BGRtoYUVApprox); +} + +int fastcv_hal_canny( + const uchar* src_data, + size_t src_step, + uchar* dst_data, + size_t dst_step, + int width, + int height, + int cn, + double lowThreshold, + double highThreshold, + int ksize, + bool L2gradient) +{ + int numThreads = cv::getNumThreads(); + + if(numThreads!=1) + CV_HAL_RETURN_NOT_IMPLEMENTED("API performs optimally in single-threaded mode"); + + if (cn != 1) + CV_HAL_RETURN_NOT_IMPLEMENTED("Multi-channel input is not supported"); + + if (lowThreshold > highThreshold) + CV_HAL_RETURN_NOT_IMPLEMENTED("lowThreshold is greater then highThreshold"); + + const double epsilon = 1e-9; + + if (std::abs(lowThreshold - std::round(lowThreshold)) > epsilon || std::abs(highThreshold - std::round(highThreshold)) > epsilon) + CV_HAL_RETURN_NOT_IMPLEMENTED("threshold with decimal values not supported"); + + INITIALIZATION_CHECK; + + fcvStatus status; + fcvNormType norm; + + if (L2gradient == 1) + norm = fcvNormType::FASTCV_NORM_L2; + else + norm = fcvNormType::FASTCV_NORM_L1; + + if ((ksize == 3) && (width > 2) && (height > 2) && (src_step >= (size_t)width) && (dst_step >= (size_t)width)) + { + int16_t* gx = (int16_t*)fcvMemAlloc(width * height * sizeof(int16_t), 16); + int16_t* gy = (int16_t*)fcvMemAlloc(width * height * sizeof(int16_t), 16); + uint32_t gstride = 2 * width; + status = fcvFilterCannyu8(src_data, width, height, src_step, ksize, static_cast(std::round(lowThreshold)), static_cast(std::round(highThreshold)), norm, dst_data, dst_step, gx, gy, gstride); + fcvMemFree(gx); + fcvMemFree(gy); + } + else + { + CV_HAL_RETURN_NOT_IMPLEMENTED(cv::format("Ksize:%d is not supported", ksize)); + } + CV_HAL_RETURN(status, hal_canny); } \ No newline at end of file From b53fa94745d2968a9720d5567af422ab43e9e01c Mon Sep 17 00:00:00 2001 From: Maksim Shabunin Date: Thu, 19 Dec 2024 22:04:24 +0300 Subject: [PATCH 048/102] videoio: fixed writer setProperty with FFmpeg plugin --- modules/videoio/src/cap_ffmpeg.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/modules/videoio/src/cap_ffmpeg.cpp b/modules/videoio/src/cap_ffmpeg.cpp index f2124ce1af..4b1f9bb857 100644 --- a/modules/videoio/src/cap_ffmpeg.cpp +++ b/modules/videoio/src/cap_ffmpeg.cpp @@ -515,8 +515,19 @@ CvResult CV_API_CALL cv_writer_get_prop(CvPluginWriter handle, int prop, CV_OUT } static -CvResult CV_API_CALL cv_writer_set_prop(CvPluginWriter /*handle*/, int /*prop*/, double /*val*/) +CvResult CV_API_CALL cv_writer_set_prop(CvPluginWriter handle, int prop, double val) { + if (!handle) + return CV_ERROR_FAIL; + try + { + CvVideoWriter_FFMPEG_proxy* instance = (CvVideoWriter_FFMPEG_proxy*)handle; + return (instance->setProperty(prop, val) ? CV_ERROR_OK : CV_ERROR_FAIL); + } + catch (...) + { + return CV_ERROR_FAIL; + } return CV_ERROR_FAIL; } From 09030615891c177d322c5a8bbcd58bc1e1384173 Mon Sep 17 00:00:00 2001 From: alex-urm <34313007+alex-urm@users.noreply.github.com> Date: Fri, 20 Dec 2024 11:00:30 +0300 Subject: [PATCH 049/102] Merge pull request #25500 from alex-urm:v4l_default_image_size V4l default image size #25500 Added ability to set default image width and height for V4L capture. This is required for cameras that does not support 640x480 resolution because otherwise V4L capture cannot be opened and failed with "Pixel format of incoming image is unsupported by OpenCV" and then with "can't open camera by index" message. Because of the videoio architecture it is not possible to insert actions between CvCaptureCAM_V4L::CvCaptureCAM_V4L and CvCaptureCAM_V4L::open so the only way I found is to use environment variables to preselect the resolution. Related bug report is [#25499](https://github.com/opencv/opencv/issues/25499) Maybe (but not confirmed) this is also related to [#24551](https://github.com/opencv/opencv/issues/24551) This fix was made and verified in my local environment: capture board AVMATRIX VC42, Ubuntu 20, NVidia Jetson Orin. ### 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/videoio/src/cap_v4l.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/videoio/src/cap_v4l.cpp b/modules/videoio/src/cap_v4l.cpp index 47b6181598..b7cc1e423d 100644 --- a/modules/videoio/src/cap_v4l.cpp +++ b/modules/videoio/src/cap_v4l.cpp @@ -919,8 +919,8 @@ bool CvCaptureCAM_V4L::open(const std::string & _deviceName) { CV_LOG_DEBUG(NULL, "VIDEOIO(V4L2:" << _deviceName << "): opening..."); FirstCapture = true; - width = DEFAULT_V4L_WIDTH; - height = DEFAULT_V4L_HEIGHT; + width = utils::getConfigurationParameterSizeT("OPENCV_VIDEOIO_V4L_DEFAULT_WIDTH", DEFAULT_V4L_WIDTH); + height = utils::getConfigurationParameterSizeT("OPENCV_VIDEOIO_V4L_DEFAULT_HEIGHT", DEFAULT_V4L_HEIGHT); width_set = height_set = 0; bufferSize = DEFAULT_V4L_BUFFERS; fps = DEFAULT_V4L_FPS; From d9a139f9e85a17b4c47dbca559ed90aef517c279 Mon Sep 17 00:00:00 2001 From: Suleyman TURKMEN Date: Fri, 20 Dec 2024 13:06:28 +0300 Subject: [PATCH 050/102] Merge pull request #25608 from sturkmen72:animated_webp_support Animated WebP Support #25608 related issues #24855 #22569 ### 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/OpenCVFindLibsGrfmt.cmake | 27 +- cmake/OpenCVFindWebP.cmake | 7 +- doc/tutorials/app/animations.markdown | 91 +++++ .../app/table_of_content_app.markdown | 1 + .../imgcodecs/include/opencv2/imgcodecs.hpp | 62 ++++ modules/imgcodecs/src/grfmt_avif.cpp | 33 +- modules/imgcodecs/src/grfmt_avif.hpp | 3 +- modules/imgcodecs/src/grfmt_base.cpp | 5 + modules/imgcodecs/src/grfmt_base.hpp | 5 + modules/imgcodecs/src/grfmt_webp.cpp | 347 ++++++++++++++---- modules/imgcodecs/src/grfmt_webp.hpp | 13 +- modules/imgcodecs/src/loadsave.cpp | 166 +++++++++ modules/imgcodecs/test/test_webp.cpp | 266 +++++++++++++- .../tutorial_code/imgcodecs/animations.cpp | 51 +++ .../tutorial_code/imgcodecs/animations.py | 52 +++ 15 files changed, 1006 insertions(+), 123 deletions(-) create mode 100644 doc/tutorials/app/animations.markdown create mode 100644 samples/cpp/tutorial_code/imgcodecs/animations.cpp create mode 100644 samples/python/tutorial_code/imgcodecs/animations.py diff --git a/cmake/OpenCVFindLibsGrfmt.cmake b/cmake/OpenCVFindLibsGrfmt.cmake index 240df36e4d..a034a37431 100644 --- a/cmake/OpenCVFindLibsGrfmt.cmake +++ b/cmake/OpenCVFindLibsGrfmt.cmake @@ -196,19 +196,34 @@ if(WITH_WEBP AND NOT WEBP_FOUND endif() if(NOT WEBP_VERSION AND WEBP_INCLUDE_DIR) - ocv_clear_vars(ENC_MAJ_VERSION ENC_MIN_VERSION ENC_REV_VERSION) - if(EXISTS "${WEBP_INCLUDE_DIR}/enc/vp8enci.h") - ocv_parse_header("${WEBP_INCLUDE_DIR}/enc/vp8enci.h" WEBP_VERSION_LINES ENC_MAJ_VERSION ENC_MIN_VERSION ENC_REV_VERSION) - set(WEBP_VERSION "${ENC_MAJ_VERSION}.${ENC_MIN_VERSION}.${ENC_REV_VERSION}") - elseif(EXISTS "${WEBP_INCLUDE_DIR}/webp/encode.h") + if(EXISTS "${WEBP_INCLUDE_DIR}/webp/encode.h") file(STRINGS "${WEBP_INCLUDE_DIR}/webp/encode.h" WEBP_ENCODER_ABI_VERSION REGEX "#define[ \t]+WEBP_ENCODER_ABI_VERSION[ \t]+([x0-9a-f]+)" ) if(WEBP_ENCODER_ABI_VERSION MATCHES "#define[ \t]+WEBP_ENCODER_ABI_VERSION[ \t]+([x0-9a-f]+)") set(WEBP_ENCODER_ABI_VERSION "${CMAKE_MATCH_1}") - set(WEBP_VERSION "encoder: ${WEBP_ENCODER_ABI_VERSION}") else() unset(WEBP_ENCODER_ABI_VERSION) endif() endif() + + if(EXISTS "${WEBP_INCLUDE_DIR}/webp/decode.h") + file(STRINGS "${WEBP_INCLUDE_DIR}/webp/decode.h" WEBP_DECODER_ABI_VERSION REGEX "#define[ \t]+WEBP_DECODER_ABI_VERSION[ \t]+([x0-9a-f]+)" ) + if(WEBP_DECODER_ABI_VERSION MATCHES "#define[ \t]+WEBP_DECODER_ABI_VERSION[ \t]+([x0-9a-f]+)") + set(WEBP_DECODER_ABI_VERSION "${CMAKE_MATCH_1}") + else() + unset(WEBP_DECODER_ABI_VERSION) + endif() + endif() + + if(EXISTS "${WEBP_INCLUDE_DIR}/webp/demux.h") + file(STRINGS "${WEBP_INCLUDE_DIR}/webp/demux.h" WEBP_DEMUX_ABI_VERSION REGEX "#define[ \t]+WEBP_DEMUX_ABI_VERSION[ \t]+([x0-9a-f]+)" ) + if(WEBP_DEMUX_ABI_VERSION MATCHES "#define[ \t]+WEBP_DEMUX_ABI_VERSION[ \t]+([x0-9a-f]+)") + set(WEBP_DEMUX_ABI_VERSION "${CMAKE_MATCH_1}") + else() + unset(WEBP_DEMUX_ABI_VERSION) + endif() + endif() + +set(WEBP_VERSION "decoder: ${WEBP_DECODER_ABI_VERSION}, encoder: ${WEBP_ENCODER_ABI_VERSION}, demux: ${WEBP_DEMUX_ABI_VERSION}") endif() # --- libopenjp2 (optional, check before libjasper) --- diff --git a/cmake/OpenCVFindWebP.cmake b/cmake/OpenCVFindWebP.cmake index 24f4f93efe..3af0908cbb 100644 --- a/cmake/OpenCVFindWebP.cmake +++ b/cmake/OpenCVFindWebP.cmake @@ -10,8 +10,6 @@ # Look for the header file. -unset(WEBP_FOUND) - FIND_PATH(WEBP_INCLUDE_DIR NAMES webp/decode.h) if(NOT WEBP_INCLUDE_DIR) @@ -21,13 +19,14 @@ else() # Look for the library. FIND_LIBRARY(WEBP_LIBRARY NAMES webp) - MARK_AS_ADVANCED(WEBP_LIBRARY) + FIND_LIBRARY(WEBP_MUX_LIBRARY NAMES webpmux) + FIND_LIBRARY(WEBP_DEMUX_LIBRARY NAMES webpdemux) # handle the QUIETLY and REQUIRED arguments and set WEBP_FOUND to TRUE if # all listed variables are TRUE INCLUDE(${CMAKE_ROOT}/Modules/FindPackageHandleStandardArgs.cmake) FIND_PACKAGE_HANDLE_STANDARD_ARGS(WebP DEFAULT_MSG WEBP_LIBRARY WEBP_INCLUDE_DIR) - SET(WEBP_LIBRARIES ${WEBP_LIBRARY}) + SET(WEBP_LIBRARIES ${WEBP_LIBRARY} ${WEBP_MUX_LIBRARY} ${WEBP_DEMUX_LIBRARY}) SET(WEBP_INCLUDE_DIRS ${WEBP_INCLUDE_DIR}) endif() diff --git a/doc/tutorials/app/animations.markdown b/doc/tutorials/app/animations.markdown new file mode 100644 index 0000000000..2f7c5e1cc8 --- /dev/null +++ b/doc/tutorials/app/animations.markdown @@ -0,0 +1,91 @@ +Handling Animated Image Files {#tutorial_animations} +=========================== + +@tableofcontents + +| | | +| -: | :- | +| Original author | Suleyman Turkmen (with help of ChatGPT) | +| Compatibility | OpenCV >= 4.11 | + +Goal +---- +In this tutorial, you will learn how to: + +- Use `cv::imreadanimation` to load frames from animated image files. +- Understand the structure and parameters of the `cv::Animation` structure. +- Display individual frames from an animation. +- Use `cv::imwriteanimation` to write `cv::Animation` to a file. + +Source Code +----------- + +@add_toggle_cpp +- **Downloadable code**: Click + [here](https://github.com/opencv/opencv/tree/4.x/samples/cpp/tutorial_code/imgcodecs/animations.cpp) + +- **Code at a glance:** + @include samples/cpp/tutorial_code/imgcodecs/animations.cpp +@end_toggle + +@add_toggle_python +- **Downloadable code**: Click + [here](https://github.com/opencv/opencv/tree/4.x/samples/python/tutorial_code/imgcodecs/animations.py) + +- **Code at a glance:** + @include samples/python/tutorial_code/imgcodecs/animations.py +@end_toggle + +Explanation +----------- + +## Initializing the Animation Structure + + Initialize a `cv::Animation` structure to hold the frames from the animated image file. + +@add_toggle_cpp +@snippet cpp/tutorial_code/imgcodecs/animations.cpp init_animation +@end_toggle + +@add_toggle_python +@snippet python/tutorial_code/imgcodecs/animations.py init_animation +@end_toggle + +## Loading Frames + + Use `cv::imreadanimation` to load frames from the specified file. Here, we load all frames from an animated WebP image. + +@add_toggle_cpp +@snippet cpp/tutorial_code/imgcodecs/animations.cpp read_animation +@end_toggle + +@add_toggle_python +@snippet python/tutorial_code/imgcodecs/animations.py read_animation +@end_toggle + +## Displaying Frames + + Each frame in the `animation.frames` vector can be displayed as a standalone image. This loop iterates through each frame, displaying it in a window with a short delay to simulate the animation. + +@add_toggle_cpp +@snippet cpp/tutorial_code/imgcodecs/animations.cpp show_animation +@end_toggle + +@add_toggle_python +@snippet python/tutorial_code/imgcodecs/animations.py show_animation +@end_toggle + +## Saving Animation + +@add_toggle_cpp +@snippet cpp/tutorial_code/imgcodecs/animations.cpp write_animation +@end_toggle + +@add_toggle_python +@snippet python/tutorial_code/imgcodecs/animations.py write_animation +@end_toggle + +## Summary + +The `cv::imreadanimation` and `cv::imwriteanimation` functions make it easy to work with animated image files by loading frames into a `cv::Animation` structure, allowing frame-by-frame processing. +With these functions, you can load, process, and save frames from animated image files like GIF, AVIF, APNG, and WebP. diff --git a/doc/tutorials/app/table_of_content_app.markdown b/doc/tutorials/app/table_of_content_app.markdown index 867f570078..714fb66b0c 100644 --- a/doc/tutorials/app/table_of_content_app.markdown +++ b/doc/tutorials/app/table_of_content_app.markdown @@ -10,3 +10,4 @@ Application utils (highgui, imgcodecs, videoio modules) {#tutorial_table_of_cont - @subpage tutorial_orbbec_uvc - @subpage tutorial_intelperc - @subpage tutorial_wayland_ubuntu +- @subpage tutorial_animations diff --git a/modules/imgcodecs/include/opencv2/imgcodecs.hpp b/modules/imgcodecs/include/opencv2/imgcodecs.hpp index cd38f22325..25273342d2 100644 --- a/modules/imgcodecs/include/opencv2/imgcodecs.hpp +++ b/modules/imgcodecs/include/opencv2/imgcodecs.hpp @@ -236,6 +236,36 @@ enum ImwriteGIFCompressionFlags { //! @} imgcodecs_flags +/** @brief Represents an animation with multiple frames. +The `Animation` struct is designed to store and manage data for animated sequences such as those from animated formats (e.g., GIF, AVIF, APNG, WebP). +It provides support for looping, background color settings, frame timing, and frame storage. +*/ +struct CV_EXPORTS_W_SIMPLE Animation +{ + //! Number of times the animation should loop. 0 means infinite looping. + CV_PROP_RW int loop_count; + //! Background color of the animation in BGRA format. + CV_PROP_RW Scalar bgcolor; + //! Duration for each frame in milliseconds. + CV_PROP_RW std::vector durations; + //! Vector of frames, where each Mat represents a single frame. + CV_PROP_RW std::vector frames; + + /** @brief Constructs an Animation object with optional loop count and background color. + + @param loopCount An integer representing the number of times the animation should loop: + - `0` (default) indicates infinite looping, meaning the animation will replay continuously. + - Positive values denote finite repeat counts, allowing the animation to play a limited number of times. + - If a negative value or a value beyond the maximum of `0xffff` (65535) is provided, it is reset to `0` + (infinite looping) to maintain valid bounds. + + @param bgColor A `Scalar` object representing the background color in BGRA format: + - Defaults to `Scalar()`, indicating an empty color (usually transparent if supported). + - This background color provides a solid fill behind frames that have transparency, ensuring a consistent display appearance. + */ + Animation(int loopCount = 0, Scalar bgColor = Scalar()); +}; + /** @brief Loads an image from a file. @anchor imread @@ -323,6 +353,38 @@ The function imreadmulti loads a specified range from a multi-page image from th */ CV_EXPORTS_W bool imreadmulti(const String& filename, CV_OUT std::vector& mats, int start, int count, int flags = IMREAD_ANYCOLOR); +/** @example samples/cpp/tutorial_code/imgcodecs/animations.cpp +An example to show usage of cv::imreadanimation and cv::imwriteanimation functions. +Check @ref tutorial_animations "the corresponding tutorial" for more details +*/ + +/** @brief Loads frames from an animated image file into an Animation structure. + +The function imreadanimation loads frames from an animated image file (e.g., GIF, AVIF, APNG, WEBP) into the provided Animation struct. + +@param filename A string containing the path to the file. +@param animation A reference to an Animation structure where the loaded frames will be stored. It should be initialized before the function is called. +@param start The index of the first frame to load. This is optional and defaults to 0. +@param count The number of frames to load. This is optional and defaults to 32767. + +@return Returns true if the file was successfully loaded and frames were extracted; returns false otherwise. +*/ +CV_EXPORTS_W bool imreadanimation(const String& filename, CV_OUT Animation& animation, int start = 0, int count = INT16_MAX); + +/** @brief Saves an Animation to a specified file. + +The function imwriteanimation saves the provided Animation data to the specified file in an animated format. +Supported formats depend on the implementation and may include formats like GIF, AVIF, APNG, or WEBP. + +@param filename The name of the file where the animation will be saved. The file extension determines the format. +@param animation A constant reference to an Animation struct containing the frames and metadata to be saved. +@param params Optional format-specific parameters encoded as pairs (paramId_1, paramValue_1, paramId_2, paramValue_2, ...). +These parameters are used to specify additional options for the encoding process. Refer to `cv::ImwriteFlags` for details on possible parameters. + +@return Returns true if the animation was successfully saved; returns false otherwise. +*/ +CV_EXPORTS_W bool imwriteanimation(const String& filename, const Animation& animation, const std::vector& params = std::vector()); + /** @brief Returns the number of images inside the given file The function imcount returns the number of pages in a multi-page image (e.g. TIFF), the number of frames in an animation (e.g. AVIF), and 1 otherwise. diff --git a/modules/imgcodecs/src/grfmt_avif.cpp b/modules/imgcodecs/src/grfmt_avif.cpp index 4752c6ece4..23cdbb1baa 100644 --- a/modules/imgcodecs/src/grfmt_avif.cpp +++ b/modules/imgcodecs/src/grfmt_avif.cpp @@ -11,6 +11,7 @@ #include #include +#include #include "opencv2/imgproc.hpp" #include "grfmt_avif.hpp" @@ -242,6 +243,8 @@ bool AvifDecoder::readData(Mat &img) { return false; } + m_animation.durations.push_back(decoder_->imageTiming.durationInTimescales); + if (decoder_->image->exif.size > 0) { m_exif.parseExif(decoder_->image->exif.data, decoder_->image->exif.size); } @@ -297,16 +300,26 @@ bool AvifEncoder::isFormatSupported(int depth) const { bool AvifEncoder::write(const Mat &img, const std::vector ¶ms) { std::vector img_vec(1, img); - return writeToOutput(img_vec, params); + return writemulti(img_vec, params); } bool AvifEncoder::writemulti(const std::vector &img_vec, const std::vector ¶ms) { - return writeToOutput(img_vec, params); + + CV_LOG_INFO(NULL, "Multi page image will be written as animation with 1 second frame duration."); + + Animation animation; + animation.frames = img_vec; + + for (size_t i = 0; i < animation.frames.size(); i++) + { + animation.durations.push_back(1000); + } + return writeanimation(animation, params); } -bool AvifEncoder::writeToOutput(const std::vector &img_vec, - const std::vector ¶ms) { +bool AvifEncoder::writeanimation(const Animation& animation, + const std::vector ¶ms) { int bit_depth = 8; int speed = AVIF_SPEED_FASTEST; for (size_t i = 0; i < params.size(); i += 2) { @@ -340,12 +353,12 @@ bool AvifEncoder::writeToOutput(const std::vector &img_vec, #endif encoder_->speed = speed; - const avifAddImageFlags flag = (img_vec.size() == 1) + const avifAddImageFlags flag = (animation.frames.size() == 1) ? AVIF_ADD_IMAGE_FLAG_SINGLE : AVIF_ADD_IMAGE_FLAG_NONE; std::vector images; std::vector imgs_scaled; - for (const cv::Mat &img : img_vec) { + for (const cv::Mat &img : animation.frames) { CV_CheckType( img.type(), (bit_depth == 8 && img.depth() == CV_8U) || @@ -358,13 +371,15 @@ bool AvifEncoder::writeToOutput(const std::vector &img_vec, images.emplace_back(ConvertToAvif(img, do_lossless, bit_depth)); } - for (const AvifImageUniquePtr &image : images) { + + for (size_t i = 0; i < images.size(); i++) + { OPENCV_AVIF_CHECK_STATUS( - avifEncoderAddImage(encoder_, image.get(), /*durationInTimescale=*/1, - flag), + avifEncoderAddImage(encoder_, images[i].get(), animation.durations[i], flag), encoder_); } + encoder_->timescale = 1000; OPENCV_AVIF_CHECK_STATUS(avifEncoderFinish(encoder_, output.get()), encoder_); if (m_buf) { diff --git a/modules/imgcodecs/src/grfmt_avif.hpp b/modules/imgcodecs/src/grfmt_avif.hpp index e64357366a..31f9b00a41 100644 --- a/modules/imgcodecs/src/grfmt_avif.hpp +++ b/modules/imgcodecs/src/grfmt_avif.hpp @@ -46,12 +46,11 @@ class AvifEncoder CV_FINAL : public BaseImageEncoder { bool writemulti(const std::vector& img_vec, const std::vector& params) CV_OVERRIDE; + bool writeanimation(const Animation& animation, const std::vector& params) CV_OVERRIDE; ImageEncoder newEncoder() const CV_OVERRIDE; private: - bool writeToOutput(const std::vector& img_vec, - const std::vector& params); avifEncoder* encoder_; }; diff --git a/modules/imgcodecs/src/grfmt_base.cpp b/modules/imgcodecs/src/grfmt_base.cpp index 6ae9d98f24..d01899fb7e 100644 --- a/modules/imgcodecs/src/grfmt_base.cpp +++ b/modules/imgcodecs/src/grfmt_base.cpp @@ -144,6 +144,11 @@ bool BaseImageEncoder::writemulti(const std::vector&, const std::vector& ) +{ + return false; +} + ImageEncoder BaseImageEncoder::newEncoder() const { return ImageEncoder(); diff --git a/modules/imgcodecs/src/grfmt_base.hpp b/modules/imgcodecs/src/grfmt_base.hpp index f6b5ba1b27..a90bd8a3de 100644 --- a/modules/imgcodecs/src/grfmt_base.hpp +++ b/modules/imgcodecs/src/grfmt_base.hpp @@ -133,6 +133,8 @@ public: */ virtual bool checkSignature(const String& signature) const; + const Animation& animation() const { return m_animation; }; + /** * @brief Create and return a new instance of the derived image decoder. * @return A new ImageDecoder object. @@ -151,6 +153,7 @@ protected: bool m_use_rgb; ///< Flag indicating whether to decode the image in RGB order. ExifReader m_exif; ///< Object for reading EXIF metadata from the image. size_t m_frame_count; ///< Number of frames in the image (for animations and multi-page images). + Animation m_animation; }; @@ -215,6 +218,8 @@ public: */ virtual bool writemulti(const std::vector& img_vec, const std::vector& params); + virtual bool writeanimation(const Animation& animation, const std::vector& params); + /** * @brief Get a description of the image encoder (e.g., the format it supports). * @return A string describing the encoder. diff --git a/modules/imgcodecs/src/grfmt_webp.cpp b/modules/imgcodecs/src/grfmt_webp.cpp index ca54effe86..a0597068ac 100644 --- a/modules/imgcodecs/src/grfmt_webp.cpp +++ b/modules/imgcodecs/src/grfmt_webp.cpp @@ -44,17 +44,15 @@ #include "precomp.hpp" -#include -#include - #include #include - #include "grfmt_webp.hpp" - -#include "opencv2/imgproc.hpp" - +#include #include +#include +#include +#include +#include namespace cv { @@ -67,12 +65,18 @@ static const size_t WEBP_HEADER_SIZE = 32; WebPDecoder::WebPDecoder() { m_buf_supported = true; - channels = 0; fs_size = 0; + m_has_animation = false; + m_previous_timestamp = 0; } WebPDecoder::~WebPDecoder() {} +void WebPDecoder::UniquePtrDeleter::operator()(WebPAnimDecoder* decoder) const +{ + WebPAnimDecoderDelete(decoder); +} + size_t WebPDecoder::signatureLength() const { return WEBP_HEADER_SIZE; @@ -102,6 +106,11 @@ ImageDecoder WebPDecoder::newDecoder() const bool WebPDecoder::readHeader() { + if (m_has_animation) + { + return true; + } + uint8_t header[WEBP_HEADER_SIZE] = { 0 }; if (m_buf.empty()) { @@ -124,28 +133,49 @@ bool WebPDecoder::readHeader() } WebPBitstreamFeatures features; - if (VP8_STATUS_OK == WebPGetFeatures(header, sizeof(header), &features)) + if (VP8_STATUS_OK < WebPGetFeatures(header, sizeof(header), &features)) return false; + + m_has_animation = features.has_animation == 1; + + if (m_has_animation) { - CV_CheckEQ(features.has_animation, 0, "WebP backend does not support animated webp images"); - - m_width = features.width; - m_height = features.height; - - if (features.has_alpha) + if (m_buf.empty()) { - m_type = CV_8UC4; - channels = 4; - } - else - { - m_type = CV_8UC3; - channels = 3; + fs.seekg(0, std::ios::beg); CV_Assert(fs && "File stream error"); + data.create(1, validateToInt(fs_size), CV_8UC1); + fs.read((char*)data.ptr(), fs_size); + CV_Assert(fs && "Can't read file data"); + fs.close(); } - return true; + CV_Assert(data.type() == CV_8UC1); CV_Assert(data.rows == 1); + + WebPData webp_data; + webp_data.bytes = (const uint8_t*)data.ptr(); + webp_data.size = data.total(); + + WebPAnimDecoderOptions dec_options; + WebPAnimDecoderOptionsInit(&dec_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); + m_animation.loop_count = anim_info.loop_count; + + m_animation.bgcolor[0] = (anim_info.bgcolor >> 24) & 0xFF; + m_animation.bgcolor[1] = (anim_info.bgcolor >> 16) & 0xFF; + m_animation.bgcolor[2] = (anim_info.bgcolor >> 8) & 0xFF; + m_animation.bgcolor[3] = anim_info.bgcolor & 0xFF; + m_frame_count = anim_info.frame_count; } + m_width = features.width; + m_height = features.height; + m_type = features.has_alpha ? CV_8UC4 : CV_8UC3; - return false; + return true; } bool WebPDecoder::readData(Mat &img) @@ -155,7 +185,7 @@ bool WebPDecoder::readData(Mat &img) CV_CheckEQ(img.cols, m_width, ""); CV_CheckEQ(img.rows, m_height, ""); - if (m_buf.empty()) + if (data.empty()) { fs.seekg(0, std::ios::beg); CV_Assert(fs && "File stream error"); data.create(1, validateToInt(fs_size), CV_8UC1); @@ -165,70 +195,96 @@ bool WebPDecoder::readData(Mat &img) } CV_Assert(data.type() == CV_8UC1); CV_Assert(data.rows == 1); + Mat read_img; + CV_CheckType(img.type(), img.type() == CV_8UC1 || img.type() == CV_8UC3 || img.type() == CV_8UC4, ""); + if (img.type() != m_type || img.cols != m_width || img.rows != m_height) { - Mat read_img; - CV_CheckType(img.type(), img.type() == CV_8UC1 || img.type() == CV_8UC3 || img.type() == CV_8UC4, ""); - if (img.type() != m_type) + read_img.create(m_height, m_width, m_type); + } + else + { + read_img = img; // copy header + } + + uchar* out_data = read_img.ptr(); + size_t out_data_size = read_img.dataend - out_data; + + uchar* res_ptr = NULL; + + if (m_has_animation) + { + uint8_t* buf; + int timestamp; + + WebPAnimDecoderGetNext(anim_decoder.get(), &buf, ×tamp); + Mat tmp(Size(m_width, m_height), CV_8UC4, buf); + + if (img.type() == CV_8UC1) { - read_img.create(m_height, m_width, m_type); + cvtColor(tmp, img, COLOR_BGR2GRAY); } else + if (img.type() == CV_8UC3) { - read_img = img; // copy header - } - - uchar* out_data = read_img.ptr(); - size_t out_data_size = read_img.dataend - out_data; - - uchar *res_ptr = NULL; - if (channels == 3) - { - CV_CheckTypeEQ(read_img.type(), CV_8UC3, ""); - if (m_use_rgb) - res_ptr = WebPDecodeRGBInto(data.ptr(), data.total(), out_data, - (int)out_data_size, (int)read_img.step); - else - res_ptr = WebPDecodeBGRInto(data.ptr(), data.total(), out_data, - (int)out_data_size, (int)read_img.step); - } - else if (channels == 4) - { - CV_CheckTypeEQ(read_img.type(), CV_8UC4, ""); - if (m_use_rgb) - res_ptr = WebPDecodeRGBAInto(data.ptr(), data.total(), out_data, - (int)out_data_size, (int)read_img.step); - else - res_ptr = WebPDecodeBGRAInto(data.ptr(), data.total(), out_data, - (int)out_data_size, (int)read_img.step); - } - - if (res_ptr != out_data) - return false; - - if (read_img.data == img.data && img.type() == m_type) - { - // nothing - } - else if (img.type() == CV_8UC1) - { - cvtColor(read_img, img, COLOR_BGR2GRAY); - } - else if (img.type() == CV_8UC3 && m_type == CV_8UC4) - { - cvtColor(read_img, img, COLOR_BGRA2BGR); - } - else if (img.type() == CV_8UC4 && m_type == CV_8UC3) - { - cvtColor(read_img, img, COLOR_BGR2BGRA); + cvtColor(tmp, img, COLOR_BGRA2BGR); } else - { - CV_Error(Error::StsInternal, ""); - } + tmp.copyTo(img); + + m_animation.durations.push_back(timestamp - m_previous_timestamp); + m_previous_timestamp = timestamp; + return true; + } + + if (m_type == CV_8UC3) + { + CV_CheckTypeEQ(read_img.type(), CV_8UC3, ""); + if (m_use_rgb) + res_ptr = WebPDecodeRGBInto(data.ptr(), data.total(), out_data, + (int)out_data_size, (int)read_img.step); + else + res_ptr = WebPDecodeBGRInto(data.ptr(), data.total(), out_data, + (int)out_data_size, (int)read_img.step); + } + else if (m_type == CV_8UC4) + { + CV_CheckTypeEQ(read_img.type(), CV_8UC4, ""); + if (m_use_rgb) + res_ptr = WebPDecodeRGBAInto(data.ptr(), data.total(), out_data, + (int)out_data_size, (int)read_img.step); + else + res_ptr = WebPDecodeBGRAInto(data.ptr(), data.total(), out_data, + (int)out_data_size, (int)read_img.step); + } + + if (res_ptr != out_data) + return false; + + if (read_img.data == img.data && img.type() == m_type) + { + // nothing + } + else if (img.type() == CV_8UC1) + { + cvtColor(read_img, img, COLOR_BGR2GRAY); + } + else if (img.type() == CV_8UC3 && m_type == CV_8UC4) + { + cvtColor(read_img, img, COLOR_BGRA2BGR); + } + else + { + CV_Error(Error::StsInternal, ""); } return true; } +bool WebPDecoder::nextPage() +{ + // Prepare the next page, if any. + return WebPAnimDecoderHasMoreFrames(anim_decoder.get()) > 0; +} + WebPEncoder::WebPEncoder() { m_description = "WebP files (*.webp)"; @@ -312,23 +368,152 @@ bool WebPEncoder::write(const Mat& img, const std::vector& params) #endif CV_Assert(size > 0); - + size_t bytes_written = 0; if (m_buf) { m_buf->resize(size); memcpy(&(*m_buf)[0], out, size); + bytes_written = size; } else { FILE *fd = fopen(m_filename.c_str(), "wb"); if (fd != NULL) { - fwrite(out, size, sizeof(uint8_t), fd); + bytes_written = fwrite(out, sizeof(uint8_t), size, fd); + if (size != bytes_written) + { + CV_LOG_ERROR(NULL, cv::format("Only %zu or %zu bytes are written\n",bytes_written, size)); + } fclose(fd); fd = NULL; } } - return size > 0; + return (size > 0) && (bytes_written == size); +} + +bool WebPEncoder::writemulti(const std::vector& img_vec, const std::vector& params) +{ + CV_LOG_INFO(NULL, "Multi page image will be written as animation with 1 second frame duration."); + + Animation animation; + animation.frames = img_vec; + + for (size_t i = 0; i < animation.frames.size(); i++) + { + animation.durations.push_back(1000); + } + return writeanimation(animation, params); +} + +bool WebPEncoder::writeanimation(const Animation& animation, const std::vector& params) +{ + CV_CheckDepthEQ(animation.frames[0].depth(), CV_8U, "WebP codec supports only 8-bit unsigned images"); + int ok = 0; + int timestamp = 0; + const int width = animation.frames[0].cols, height = animation.frames[0].rows; + + WebPAnimEncoderOptions anim_config; + WebPConfig config; + WebPPicture pic; + WebPData webp_data; + + WebPDataInit(&webp_data); + if (!WebPAnimEncoderOptionsInit(&anim_config) || + !WebPConfigInit(&config) || + !WebPPictureInit(&pic)) { + CV_LOG_ERROR(NULL, "Library version mismatch!\n"); + WebPDataClear(&webp_data); + return false; + } + + int bgvalue = (static_cast(animation.bgcolor[0]) & 0xFF) << 24 | + (static_cast(animation.bgcolor[1]) & 0xFF) << 16 | + (static_cast(animation.bgcolor[2]) & 0xFF) << 8 | + (static_cast(animation.bgcolor[3]) & 0xFF); + + anim_config.anim_params.bgcolor = bgvalue; + anim_config.anim_params.loop_count = animation.loop_count; + + if (params.size() > 1) + { + if (params[0] == IMWRITE_WEBP_QUALITY) + { + config.lossless = 0; + config.quality = static_cast(params[1]); + if (config.quality < 1.0f) + { + config.quality = 1.0f; + } + if (config.quality >= 100.0f) + { + config.lossless = 1; + } + } + anim_config.minimize_size = 0; + } + + std::unique_ptr anim_encoder( + WebPAnimEncoderNew(width, height, &anim_config), WebPAnimEncoderDelete); + + pic.width = width; + pic.height = height; + pic.use_argb = 1; + pic.argb_stride = width; + WebPEncode(&config, &pic); + + bool is_input_rgba = animation.frames[0].channels() == 4; + Size canvas_size = Size(animation.frames[0].cols,animation.frames[0].rows); + + for (size_t i = 0; i < animation.frames.size(); i++) + { + Mat argb; + CV_Assert(canvas_size == Size(animation.frames[i].cols,animation.frames[i].rows)); + if (is_input_rgba) + pic.argb = (uint32_t*)animation.frames[i].data; + else + { + cvtColor(animation.frames[i], argb, COLOR_BGR2BGRA); + pic.argb = (uint32_t*)argb.data; + } + ok = WebPAnimEncoderAdd(anim_encoder.get(), &pic, timestamp, &config); + timestamp += animation.durations[i]; + } + + // add a last fake frame to signal the last duration + ok = ok & WebPAnimEncoderAdd(anim_encoder.get(), NULL, timestamp, NULL); + ok = ok & WebPAnimEncoderAssemble(anim_encoder.get(), &webp_data); + + size_t bytes_written = 0; + if (ok) + { + if (m_buf) + { + m_buf->resize(webp_data.size); + memcpy(&(*m_buf)[0], webp_data.bytes, webp_data.size); + bytes_written = webp_data.size; + } + else + { + FILE* fd = fopen(m_filename.c_str(), "wb"); + if (fd != NULL) + { + bytes_written = fwrite(webp_data.bytes, sizeof(uint8_t), webp_data.size, fd); + if (webp_data.size != bytes_written) + { + CV_LOG_ERROR(NULL, cv::format("Only %zu or %zu bytes are written\n",bytes_written, webp_data.size)); + } + fclose(fd); fd = NULL; + } + } + } + + bool status = (ok > 0) && (webp_data.size == bytes_written); + + // free resources + WebPDataClear(&webp_data); + + return status; } } diff --git a/modules/imgcodecs/src/grfmt_webp.hpp b/modules/imgcodecs/src/grfmt_webp.hpp index 6d833e0db6..3aba83d4c9 100644 --- a/modules/imgcodecs/src/grfmt_webp.hpp +++ b/modules/imgcodecs/src/grfmt_webp.hpp @@ -49,6 +49,8 @@ #include +struct WebPAnimDecoder; + namespace cv { @@ -61,6 +63,7 @@ public: bool readData( Mat& img ) CV_OVERRIDE; bool readHeader() CV_OVERRIDE; + bool nextPage() CV_OVERRIDE; size_t signatureLength() const CV_OVERRIDE; bool checkSignature( const String& signature) const CV_OVERRIDE; @@ -68,10 +71,16 @@ public: ImageDecoder newDecoder() const CV_OVERRIDE; protected: + struct UniquePtrDeleter { + void operator()(WebPAnimDecoder* decoder) const; + }; + std::ifstream fs; size_t fs_size; Mat data; - int channels; + std::unique_ptr anim_decoder; + bool m_has_animation; + int m_previous_timestamp; }; class WebPEncoder CV_FINAL : public BaseImageEncoder @@ -81,6 +90,8 @@ public: ~WebPEncoder() CV_OVERRIDE; bool write(const Mat& img, const std::vector& params) CV_OVERRIDE; + bool writemulti(const std::vector& img_vec, const std::vector& params) CV_OVERRIDE; + bool writeanimation(const Animation& animation, const std::vector& params) CV_OVERRIDE; ImageEncoder newEncoder() const CV_OVERRIDE; }; diff --git a/modules/imgcodecs/src/loadsave.cpp b/modules/imgcodecs/src/loadsave.cpp index 25f32a789c..14c8b2c81a 100644 --- a/modules/imgcodecs/src/loadsave.cpp +++ b/modules/imgcodecs/src/loadsave.cpp @@ -689,6 +689,116 @@ bool imreadmulti(const String& filename, std::vector& mats, int start, int return imreadmulti_(filename, flags, mats, start, count); } +static bool +imreadanimation_(const String& filename, int flags, int start, int count, Animation& animation) +{ + bool success = false; + if (start < 0) { + start = 0; + } + if (count < 0) { + count = INT16_MAX; + } + + /// Search for the relevant decoder to handle the imagery + ImageDecoder decoder; + decoder = findDecoder(filename); + + /// if no decoder was found, return false. + if (!decoder) { + CV_LOG_WARNING(NULL, "Decoder for " << filename << " not found!\n"); + return false; + } + + /// set the filename in the driver + decoder->setSource(filename); + // read the header to make sure it succeeds + try + { + // read the header to make sure it succeeds + if (!decoder->readHeader()) + return false; + } + catch (const cv::Exception& e) + { + CV_LOG_ERROR(NULL, "imreadanimation_('" << filename << "'): can't read header: " << e.what()); + return false; + } + catch (...) + { + CV_LOG_ERROR(NULL, "imreadanimation_('" << filename << "'): can't read header: unknown exception"); + return false; + } + + int current = 0; + int frame_count = (int)decoder->getFrameCount(); + count = count + start > frame_count ? frame_count - start : count; + + uint64 pixels = (uint64)decoder->width() * (uint64)decoder->height() * (uint64)(count + 4); + if (pixels > CV_IO_MAX_IMAGE_PIXELS) { + CV_LOG_WARNING(NULL, "\nyou are trying to read " << pixels << + " bytes that exceed CV_IO_MAX_IMAGE_PIXELS.\n"); + return false; + } + + while (current < start + count) + { + // grab the decoded type + const int type = calcType(decoder->type(), flags); + + // established the required input image size + Size size = validateInputImageSize(Size(decoder->width(), decoder->height())); + + // read the image data + Mat mat(size.height, size.width, type); + success = false; + try + { + if (decoder->readData(mat)) + success = true; + } + catch (const cv::Exception& e) + { + CV_LOG_ERROR(NULL, "imreadanimation_('" << filename << "'): can't read data: " << e.what()); + } + catch (...) + { + CV_LOG_ERROR(NULL, "imreadanimation_('" << filename << "'): can't read data: unknown exception"); + } + if (!success) + break; + + // optionally rotate the data if EXIF' orientation flag says so + if ((flags & IMREAD_IGNORE_ORIENTATION) == 0 && flags != IMREAD_UNCHANGED) + { + ApplyExifOrientation(decoder->getExifTag(ORIENTATION), mat); + } + + if (current >= start) + { + animation.durations.push_back(decoder->animation().durations[decoder->animation().durations.size() - 1]); + animation.frames.push_back(mat); + } + + if (!decoder->nextPage()) + { + break; + } + ++current; + } + animation.bgcolor = decoder->animation().bgcolor; + animation.loop_count = decoder->animation().loop_count; + + return success; +} + +bool imreadanimation(const String& filename, CV_OUT Animation& animation, int start, int count) +{ + CV_TRACE_FUNCTION(); + + return imreadanimation_(filename, IMREAD_UNCHANGED, start, count, animation); +} + static size_t imcount_(const String& filename, int flags) { @@ -823,6 +933,55 @@ bool imwrite( const String& filename, InputArray _img, return imwrite_(filename, img_vec, params, false); } +static bool imwriteanimation_(const String& filename, const Animation& animation, const std::vector& params) +{ + ImageEncoder encoder = findEncoder(filename); + if (!encoder) + CV_Error(Error::StsError, "could not find a writer for the specified extension"); + + encoder->setDestination(filename); + + bool code = false; + try + { + code = encoder->writeanimation(animation, params); + + if (!code) + { + FILE* f = fopen(filename.c_str(), "wb"); + if (!f) + { + if (errno == EACCES) + { + CV_LOG_ERROR(NULL, "imwriteanimation_('" << filename << "'): can't open file for writing: permission denied"); + } + } + else + { + fclose(f); + remove(filename.c_str()); + } + } + } + catch (const cv::Exception& e) + { + CV_LOG_ERROR(NULL, "imwriteanimation_('" << filename << "'): can't write data: " << e.what()); + } + catch (...) + { + CV_LOG_ERROR(NULL, "imwriteanimation_('" << filename << "'): can't write data: unknown exception"); + } + + return code; +} + +bool imwriteanimation(const String& filename, const Animation& animation, const std::vector& params) +{ + CV_Assert(!animation.frames.empty()); + CV_Assert(animation.frames.size() == animation.durations.size()); + return imwriteanimation_(filename, animation, params); +} + static bool imdecode_( const Mat& buf, int flags, Mat& mat ) { @@ -1468,6 +1627,13 @@ ImageCollection::iterator ImageCollection::iterator::operator++(int) { return tmp; } +Animation::Animation(int loopCount, Scalar bgColor) + : loop_count(loopCount), bgcolor(bgColor) +{ + if (loopCount < 0 || loopCount > 0xffff) + this->loop_count = 0; // loop_count should be non-negative +} + } /* End of file. */ diff --git a/modules/imgcodecs/test/test_webp.cpp b/modules/imgcodecs/test/test_webp.cpp index 3647714039..0e8280c73b 100644 --- a/modules/imgcodecs/test/test_webp.cpp +++ b/modules/imgcodecs/test/test_webp.cpp @@ -7,23 +7,9 @@ namespace opencv_test { namespace { #ifdef HAVE_WEBP -TEST(Imgcodecs_WebP, encode_decode_lossless_webp) +static void readFileBytes(const std::string& fname, std::vector& buf) { - const string root = cvtest::TS::ptr()->get_data_path(); - string filename = root + "../cv/shared/lena.png"; - cv::Mat img = cv::imread(filename); - ASSERT_FALSE(img.empty()); - - string output = cv::tempfile(".webp"); - EXPECT_NO_THROW(cv::imwrite(output, img)); // lossless - - cv::Mat img_webp = cv::imread(output); - - std::vector buf; - - FILE * wfile = NULL; - - wfile = fopen(output.c_str(), "rb"); + FILE * wfile = fopen(fname.c_str(), "rb"); if (wfile != NULL) { fseek(wfile, 0, SEEK_END); @@ -39,12 +25,24 @@ TEST(Imgcodecs_WebP, encode_decode_lossless_webp) fclose(wfile); } - if (data_size != wfile_size) - { - EXPECT_TRUE(false); - } + EXPECT_EQ(data_size, wfile_size); } +} +TEST(Imgcodecs_WebP, encode_decode_lossless_webp) +{ + const string root = cvtest::TS::ptr()->get_data_path(); + string filename = root + "../cv/shared/lena.png"; + cv::Mat img = cv::imread(filename); + ASSERT_FALSE(img.empty()); + + string output = cv::tempfile(".webp"); + EXPECT_NO_THROW(cv::imwrite(output, img)); // lossless + + cv::Mat img_webp = cv::imread(output); + + std::vector buf; + readFileBytes(output, buf); EXPECT_EQ(0, remove(output.c_str())); cv::Mat decode = cv::imdecode(buf, IMREAD_COLOR); @@ -115,6 +113,234 @@ TEST(Imgcodecs_WebP, encode_decode_with_alpha_webp) EXPECT_EQ(512, img_webp_bgr.rows); } +TEST(Imgcodecs_WebP, load_save_animation_rgba) +{ + RNG rng = theRNG(); + + // 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 + "pngsuite/tp1n3p08.png"; + + // Create an Animation object using the default constructor. + // This initializes the loop count to 0 (infinite looping), background color to 0 (transparent) + Animation l_animation; + + // Create an Animation object with custom parameters. + int loop_count = 0xffff; // 0xffff is the maximum value to set. + Scalar bgcolor(125, 126, 127, 128); // different values for test purpose. + Animation s_animation(loop_count, bgcolor); + + // Load the image file with alpha channel (IMREAD_UNCHANGED). + Mat image = imread(filename, IMREAD_UNCHANGED); + ASSERT_FALSE(image.empty()) << "Failed to load image: " << filename; + + // Add the first frame with a duration value of 500 milliseconds. + int duration = 100; + s_animation.durations.push_back(duration * 5); + s_animation.frames.push_back(image.clone()); // Store the first frame. + putText(s_animation.frames[0], "0", Point(5, 28), FONT_HERSHEY_SIMPLEX, .5, Scalar(100, 255, 0, 255), 2); + + // Define a region of interest (ROI) in the loaded image for manipulation. + Mat roi = image(Rect(0, 16, 32, 16)); // Select a subregion of the image. + + // Modify the ROI in 13 iterations to simulate slight changes in animation frames. + for (int i = 1; i < 14; i++) + { + for (int x = 0; x < roi.rows; x++) + for (int y = 0; y < roi.cols; y++) + { + // Apply random changes to pixel values to create animation variations. + Vec4b& pixel = roi.at(x, y); + if (pixel[3] > 0) + { + if (pixel[0] > 10) pixel[0] -= (uchar)rng.uniform(3, 10); // Reduce blue channel. + if (pixel[1] > 10) pixel[1] -= (uchar)rng.uniform(3, 10); // Reduce green channel. + if (pixel[2] > 10) pixel[2] -= (uchar)rng.uniform(3, 10); // Reduce red channel. + pixel[3] -= (uchar)rng.uniform(2, 5); // Reduce alpha channel. + } + } + + // Update the duration and add the modified frame to the animation. + duration += rng.uniform(2, 10); // Increase duration with random value (to be sure different duration values saved correctly). + s_animation.frames.push_back(image.clone()); + putText(s_animation.frames[i], format("%d", i), Point(5, 28), FONT_HERSHEY_SIMPLEX, .5, Scalar(100, 255, 0, 255), 2); + s_animation.durations.push_back(duration); + } + + // Add two identical frames with the same duration. + s_animation.durations.push_back(duration); + s_animation.frames.push_back(s_animation.frames[13].clone()); + s_animation.durations.push_back(duration); + s_animation.frames.push_back(s_animation.frames[13].clone()); + + // Create a temporary output filename for saving the animation. + string output = cv::tempfile(".webp"); + + // Write the animation to a .webp file and verify success. + EXPECT_TRUE(imwriteanimation(output, s_animation)); + imwriteanimation("output.webp", s_animation); + + // Read the animation back and compare with the original. + EXPECT_TRUE(imreadanimation(output, l_animation)); + + // Since the last frames are identical, WebP optimizes by storing only one of them, + // and the duration value for the last frame is handled by libwebp. + size_t expected_frame_count = s_animation.frames.size() - 2; + + // Verify that the number of frames matches the expected count. + EXPECT_EQ(imcount(output), expected_frame_count); + EXPECT_EQ(l_animation.frames.size(), expected_frame_count); + + // Check that the background color and loop count match between saved and loaded animations. + EXPECT_EQ(l_animation.bgcolor, s_animation.bgcolor); // written as BGRA order + EXPECT_EQ(l_animation.loop_count, s_animation.loop_count); + + // Verify that the durations of frames match. + for (size_t i = 0; i < l_animation.frames.size() - 1; i++) + EXPECT_EQ(s_animation.durations[i], l_animation.durations[i]); + + EXPECT_TRUE(imreadanimation(output, l_animation, 5, 3)); + EXPECT_EQ(l_animation.frames.size(), expected_frame_count + 3); + EXPECT_EQ(l_animation.frames.size(), l_animation.durations.size()); + EXPECT_EQ(0, cvtest::norm(l_animation.frames[5], l_animation.frames[14], NORM_INF)); + EXPECT_EQ(0, cvtest::norm(l_animation.frames[6], l_animation.frames[15], NORM_INF)); + EXPECT_EQ(0, cvtest::norm(l_animation.frames[7], l_animation.frames[16], NORM_INF)); + + // Verify whether the imread function successfully loads the first frame + Mat frame = imread(output, IMREAD_UNCHANGED); + EXPECT_EQ(0, cvtest::norm(l_animation.frames[0], frame, NORM_INF)); + + std::vector buf; + readFileBytes(output, buf); + vector webp_frames; + + EXPECT_TRUE(imdecodemulti(buf, IMREAD_UNCHANGED, webp_frames)); + EXPECT_EQ(expected_frame_count, webp_frames.size()); + + webp_frames.clear(); + // Test saving the animation frames as individual still images. + EXPECT_TRUE(imwrite(output, s_animation.frames)); + + // Read back the still images into a vector of Mats. + EXPECT_TRUE(imreadmulti(output, webp_frames)); + + // Expect all frames written as multi-page image + expected_frame_count = 14; + EXPECT_EQ(expected_frame_count, webp_frames.size()); + + // Test encoding and decoding the images in memory (without saving to disk). + webp_frames.clear(); + EXPECT_TRUE(imencode(".webp", s_animation.frames, buf)); + EXPECT_TRUE(imdecodemulti(buf, IMREAD_UNCHANGED, webp_frames)); + EXPECT_EQ(expected_frame_count, webp_frames.size()); + + // Clean up by removing the temporary file. + EXPECT_EQ(0, remove(output.c_str())); +} + +TEST(Imgcodecs_WebP, load_save_animation_rgb) +{ + RNG rng = theRNG(); + + // 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 + "pngsuite/tp1n3p08.png"; + + // Create an Animation object using the default constructor. + // This initializes the loop count to 0 (infinite looping), background color to 0 (transparent) + Animation l_animation; + + // Create an Animation object with custom parameters. + int loop_count = 0xffff; // 0xffff is the maximum value to set. + Scalar bgcolor(125, 126, 127, 128); // different values for test purpose. + Animation s_animation(loop_count, bgcolor); + + // Load the image file without alpha channel + Mat image = imread(filename); + ASSERT_FALSE(image.empty()) << "Failed to load image: " << filename; + + // Add the first frame with a duration value of 500 milliseconds. + int duration = 100; + s_animation.durations.push_back(duration * 5); + s_animation.frames.push_back(image.clone()); // Store the first frame. + putText(s_animation.frames[0], "0", Point(5, 28), FONT_HERSHEY_SIMPLEX, .5, Scalar(100, 255, 0, 255), 2); + + // Define a region of interest (ROI) in the loaded image for manipulation. + Mat roi = image(Rect(0, 16, 32, 16)); // Select a subregion of the image. + + // Modify the ROI in 13 iterations to simulate slight changes in animation frames. + for (int i = 1; i < 14; i++) + { + for (int x = 0; x < roi.rows; x++) + for (int y = 0; y < roi.cols; y++) + { + // Apply random changes to pixel values to create animation variations. + Vec3b& pixel = roi.at(x, y); + if (pixel[0] > 50) pixel[0] -= (uchar)rng.uniform(3, 10); // Reduce blue channel. + if (pixel[1] > 50) pixel[1] -= (uchar)rng.uniform(3, 10); // Reduce green channel. + if (pixel[2] > 50) pixel[2] -= (uchar)rng.uniform(3, 10); // Reduce red channel. + } + + // Update the duration and add the modified frame to the animation. + duration += rng.uniform(2, 10); // Increase duration with random value (to be sure different duration values saved correctly). + s_animation.frames.push_back(image.clone()); + putText(s_animation.frames[i], format("%d", i), Point(5, 28), FONT_HERSHEY_SIMPLEX, .5, Scalar(100, 255, 0, 255), 2); + s_animation.durations.push_back(duration); + } + + // Add two identical frames with the same duration. + s_animation.durations.push_back(duration); + s_animation.frames.push_back(s_animation.frames[13].clone()); + s_animation.durations.push_back(duration); + s_animation.frames.push_back(s_animation.frames[13].clone()); + + // Create a temporary output filename for saving the animation. + string output = cv::tempfile(".webp"); + + // Write the animation to a .webp file and verify success. + EXPECT_EQ(true, imwriteanimation(output, s_animation)); + + // Read the animation back and compare with the original. + EXPECT_EQ(true, imreadanimation(output, l_animation)); + + // Since the last frames are identical, WebP optimizes by storing only one of them, + // and the duration value for the last frame is handled by libwebp. + size_t expected_frame_count = s_animation.frames.size() - 2; + + // Verify that the number of frames matches the expected count. + EXPECT_EQ(imcount(output), expected_frame_count); + EXPECT_EQ(l_animation.frames.size(), expected_frame_count); + + // Check that the background color and loop count match between saved and loaded animations. + EXPECT_EQ(l_animation.bgcolor, s_animation.bgcolor); // written as BGRA order + EXPECT_EQ(l_animation.loop_count, s_animation.loop_count); + + // Verify that the durations of frames match. + for (size_t i = 0; i < l_animation.frames.size() - 1; i++) + EXPECT_EQ(s_animation.durations[i], l_animation.durations[i]); + + EXPECT_EQ(true, imreadanimation(output, l_animation, 5, 3)); + EXPECT_EQ(l_animation.frames.size(), expected_frame_count + 3); + EXPECT_EQ(l_animation.frames.size(), l_animation.durations.size()); + EXPECT_TRUE(cvtest::norm(l_animation.frames[5], l_animation.frames[14], NORM_INF) == 0); + EXPECT_TRUE(cvtest::norm(l_animation.frames[6], l_animation.frames[15], NORM_INF) == 0); + EXPECT_TRUE(cvtest::norm(l_animation.frames[7], l_animation.frames[16], NORM_INF) == 0); + + // Verify whether the imread function successfully loads the first frame + Mat frame = imread(output, IMREAD_COLOR); + EXPECT_TRUE(cvtest::norm(l_animation.frames[0], frame, NORM_INF) == 0); + + std::vector buf; + readFileBytes(output, buf); + + vector webp_frames; + EXPECT_TRUE(imdecodemulti(buf, IMREAD_UNCHANGED, webp_frames)); + EXPECT_EQ(webp_frames.size(), expected_frame_count); + + // Clean up by removing the temporary file. + EXPECT_EQ(0, remove(output.c_str())); +} + #endif // HAVE_WEBP }} // namespace diff --git a/samples/cpp/tutorial_code/imgcodecs/animations.cpp b/samples/cpp/tutorial_code/imgcodecs/animations.cpp new file mode 100644 index 0000000000..2f56a9f6ea --- /dev/null +++ b/samples/cpp/tutorial_code/imgcodecs/animations.cpp @@ -0,0 +1,51 @@ +#include +#include +#include +#include + +using namespace cv; + +int main( int argc, const char** argv ) +{ + std::string filename = argc > 1 ? argv[1] : "animated_image.webp"; + + //! [write_animation] + if (argc == 1) + { + Animation animation_to_save; + Mat image(128, 256, CV_8UC4, Scalar(150, 150, 150, 255)); + int duration = 200; + + for (int i = 0; i < 10; ++i) { + animation_to_save.frames.push_back(image.clone()); + putText(animation_to_save.frames[i], format("Frame %d", i), Point(30, 80), FONT_HERSHEY_SIMPLEX, 1.5, Scalar(255, 100, 0, 255), 2); + animation_to_save.durations.push_back(duration); + } + imwriteanimation("animated_image.webp", animation_to_save, { IMWRITE_WEBP_QUALITY, 100 }); + } + //! [write_animation] + + //! [init_animation] + Animation animation; + //! [init_animation] + + //! [read_animation] + bool success = imreadanimation(filename, animation); + if (!success) { + std::cerr << "Failed to load animation frames\n"; + return -1; + } + //! [read_animation] + + //! [show_animation] + while (true) + for (size_t i = 0; i < animation.frames.size(); ++i) { + imshow("Animation", animation.frames[i]); + int key_code = waitKey(animation.durations[i]); // Delay between frames + if (key_code == 27) + exit(0); + } + //! [show_animation] + + return 0; +} diff --git a/samples/python/tutorial_code/imgcodecs/animations.py b/samples/python/tutorial_code/imgcodecs/animations.py new file mode 100644 index 0000000000..162450eb12 --- /dev/null +++ b/samples/python/tutorial_code/imgcodecs/animations.py @@ -0,0 +1,52 @@ +import cv2 as cv +import numpy as np + +def main(filename): + ## [write_animation] + if filename == "animated_image.webp": + # Create an Animation instance to save + animation_to_save = cv.Animation() + + # Generate a base image with a specific color + image = np.full((128, 256, 4), (150, 150, 150, 255), dtype=np.uint8) + duration = 200 + frames = [] + durations = [] + + # Populate frames and durations in the Animation object + for i in range(10): + frame = image.copy() + cv.putText(frame, f"Frame {i}", (30, 80), cv.FONT_HERSHEY_SIMPLEX, 1.5, (255, 100, 0, 255), 2) + frames.append(frame) + durations.append(duration) + + animation_to_save.frames = frames + animation_to_save.durations = durations + + # Write the animation to file + cv.imwriteanimation(filename, animation_to_save, [cv.IMWRITE_WEBP_QUALITY, 100]) + ## [write_animation] + + ## [init_animation] + animation = cv.Animation() + ## [init_animation] + + ## [read_animation] + success, animation = cv.imreadanimation(filename) + if not success: + print("Failed to load animation frames") + return + ## [read_animation] + + ## [show_animation] + while True: + for i, frame in enumerate(animation.frames): + cv.imshow("Animation", frame) + key_code = cv.waitKey(animation.durations[i]) + if key_code == 27: # Exit if 'Esc' key is pressed + return + ## [show_animation] + +if __name__ == "__main__": + import sys + main(sys.argv[1] if len(sys.argv) > 1 else "animated_image.webp") From aa52dafc90723c996fd6cff6b6c1a9b12d25f58d Mon Sep 17 00:00:00 2001 From: alexlyulkov Date: Mon, 23 Dec 2024 10:04:34 +0300 Subject: [PATCH 051/102] Merge pull request #26127 from alexlyulkov:al/blob-from-images Faster implementation of blobFromImages for cpu nchw output #26127 Faster implementation of blobFromImage and blobFromImages for HWC cv::Mat images -> NCHW cv::Mat case Running time on my pc in ms: **blobFromImage** ``` image size old new speed-up 32x32x3 0.008 0.002 4.0x 64x64x3 0.021 0.009 2.3x 128x128x3 0.164 0.037 4.4x 256x256x3 0.728 0.158 4.6x 512x512x3 3.310 0.628 5.2x 1024x1024x3 14.503 3.124 4.6x 2048x2048x3 61.647 28.049 2.2x ``` **blobFromImages** ``` image size old new speed-up 16x32x32x3 0.122 0.041 3.0x 16x64x64x3 0.790 0.165 4.8x 16x128x128x3 3.313 0.652 5.1x 16x256x256x3 13.495 3.127 4.3x 16x512x512x3 58.795 28.127 2.1x 16x1024x1024x3 251.135 121.955 2.1x 16x2048x2048x3 1023.570 487.188 2.1x ``` ### 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/dnn/perf/perf_utils.cpp | 66 ++++++++++++ modules/dnn/src/dnn_utils.cpp | 174 ++++++++++++++++++++++++++++---- 2 files changed, 218 insertions(+), 22 deletions(-) create mode 100644 modules/dnn/perf/perf_utils.cpp diff --git a/modules/dnn/perf/perf_utils.cpp b/modules/dnn/perf/perf_utils.cpp new file mode 100644 index 0000000000..1ab544b3eb --- /dev/null +++ b/modules/dnn/perf/perf_utils.cpp @@ -0,0 +1,66 @@ +// 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. +// +// Copyright (C) 2017, Intel Corporation, all rights reserved. +// Third party copyrights are property of their respective owners. + +#include "perf_precomp.hpp" + +namespace opencv_test { + +using Utils_blobFromImage = TestBaseWithParam>; +PERF_TEST_P_(Utils_blobFromImage, HWC_TO_NCHW) { + std::vector input_shape = GetParam(); + + Mat input(input_shape, CV_32FC3); + randu(input, -10.0f, 10.f); + + TEST_CYCLE() { + Mat blob = blobFromImage(input); + } + + SANITY_CHECK_NOTHING(); +} + +INSTANTIATE_TEST_CASE_P(/**/, Utils_blobFromImage, + Values(std::vector{ 32, 32}, + std::vector{ 64, 64}, + std::vector{ 128, 128}, + std::vector{ 256, 256}, + std::vector{ 512, 512}, + std::vector{1024, 1024}, + std::vector{2048, 2048}) +); + +using Utils_blobFromImages = TestBaseWithParam>; +PERF_TEST_P_(Utils_blobFromImages, HWC_TO_NCHW) { + std::vector input_shape = GetParam(); + int batch = input_shape.front(); + std::vector input_shape_no_batch(input_shape.begin()+1, input_shape.end()); + + std::vector inputs; + for (int i = 0; i < batch; i++) { + Mat input(input_shape_no_batch, CV_32FC3); + randu(input, -10.0f, 10.f); + inputs.push_back(input); + } + + TEST_CYCLE() { + Mat blobs = blobFromImages(inputs); + } + + SANITY_CHECK_NOTHING(); +} + +INSTANTIATE_TEST_CASE_P(/**/, Utils_blobFromImages, + Values(std::vector{16, 32, 32}, + std::vector{16, 64, 64}, + std::vector{16, 128, 128}, + std::vector{16, 256, 256}, + std::vector{16, 512, 512}, + std::vector{16, 1024, 1024}, + std::vector{16, 2048, 2048}) +); + +} diff --git a/modules/dnn/src/dnn_utils.cpp b/modules/dnn/src/dnn_utils.cpp index 3ef75fad36..87561d0e50 100644 --- a/modules/dnn/src/dnn_utils.cpp +++ b/modules/dnn/src/dnn_utils.cpp @@ -126,6 +126,111 @@ Mat blobFromImagesWithParams(InputArrayOfArrays images, const Image2BlobParams& return blob; } +template +void blobFromImagesNCHWImpl(const std::vector& images, Mat& blob_, const Image2BlobParams& param) +{ + int w = images[0].cols; + int h = images[0].rows; + int wh = w * h; + int nch = images[0].channels(); + CV_Assert(nch == 1 || nch == 3 || nch == 4); + int sz[] = { (int)images.size(), nch, h, w}; + blob_.create(4, sz, param.ddepth); + + for (size_t k = 0; k < images.size(); ++k) + { + CV_Assert(images[k].depth() == images[0].depth()); + CV_Assert(images[k].channels() == images[0].channels()); + CV_Assert(images[k].size() == images[0].size()); + + Tout* p_blob = blob_.ptr() + k * nch * wh; + Tout* p_blob_r = p_blob; + Tout* p_blob_g = p_blob + wh; + Tout* p_blob_b = p_blob + 2 * wh; + Tout* p_blob_a = p_blob + 3 * wh; + + if (param.swapRB) + std::swap(p_blob_r, p_blob_b); + + for (size_t i = 0; i < h; ++i) + { + const Tinp* p_img_row = images[k].ptr(i); + + if (nch == 1) + { + for (size_t j = 0; j < w; ++j) + { + p_blob[i * w + j] = p_img_row[j]; + } + } + else if (nch == 3) + { + for (size_t j = 0; j < w; ++j) + { + p_blob_r[i * w + j] = p_img_row[j * 3 ]; + p_blob_g[i * w + j] = p_img_row[j * 3 + 1]; + p_blob_b[i * w + j] = p_img_row[j * 3 + 2]; + } + } + else // if (nch == 4) + { + for (size_t j = 0; j < w; ++j) + { + p_blob_r[i * w + j] = p_img_row[j * 4 ]; + p_blob_g[i * w + j] = p_img_row[j * 4 + 1]; + p_blob_b[i * w + j] = p_img_row[j * 4 + 2]; + p_blob_a[i * w + j] = p_img_row[j * 4 + 3]; + } + } + } + } + + if (param.mean == Scalar() && param.scalefactor == Scalar::all(1.0)) + return; + CV_CheckTypeEQ(param.ddepth, CV_32F, "Scaling and mean substraction is supported only for CV_32F blob depth"); + + for (size_t k = 0; k < images.size(); ++k) + { + for (size_t ch = 0; ch < nch; ++ch) + { + float cur_mean = param.mean[ch]; + float cur_scale = param.scalefactor[ch]; + Tout* p_blob = blob_.ptr() + k * nch * wh + ch * wh; + for (size_t i = 0; i < wh; ++i) + { + p_blob[i] = (p_blob[i] - cur_mean) * cur_scale; + } + } + } +} + +template +void blobFromImagesNCHW(const std::vector& images, Mat& blob_, const Image2BlobParams& param) +{ + if (images[0].depth() == CV_8U) + blobFromImagesNCHWImpl(images, blob_, param); + else if (images[0].depth() == CV_8S) + blobFromImagesNCHWImpl(images, blob_, param); + else if (images[0].depth() == CV_16U) + blobFromImagesNCHWImpl(images, blob_, param); + else if (images[0].depth() == CV_16S) + blobFromImagesNCHWImpl(images, blob_, param); + else if (images[0].depth() == CV_32S) + blobFromImagesNCHWImpl(images, blob_, param); + else if (images[0].depth() == CV_32F) + blobFromImagesNCHWImpl(images, blob_, param); + else if (images[0].depth() == CV_64F) + blobFromImagesNCHWImpl(images, blob_, param); + else + CV_Error(Error::BadDepth, "Unsupported input image depth for blobFromImagesNCHW"); +} + +template +void blobFromImagesNCHW(const std::vector& images, UMat& blob_, const Image2BlobParams& param) +{ + CV_Error(Error::StsNotImplemented, ""); +} + template void blobFromImagesWithParamsImpl(InputArrayOfArrays images_, Tmat& blob_, const Image2BlobParams& param) { @@ -154,19 +259,6 @@ void blobFromImagesWithParamsImpl(InputArrayOfArrays images_, Tmat& blob_, const Scalar scalefactor = param.scalefactor; Scalar mean = param.mean; - if (param.swapRB) - { - if (nch > 2) - { - std::swap(mean[0], mean[2]); - std::swap(scalefactor[0], scalefactor[2]); - } - else - { - CV_LOG_WARNING(NULL, "Red/blue color swapping requires at least three image channels."); - } - } - for (size_t i = 0; i < images.size(); i++) { Size imgSize = images[i].size(); @@ -203,18 +295,35 @@ void blobFromImagesWithParamsImpl(InputArrayOfArrays images_, Tmat& blob_, const resize(images[i], images[i], size, 0, 0, INTER_LINEAR); } } - - if (images[i].depth() == CV_8U && param.ddepth == CV_32F) - images[i].convertTo(images[i], CV_32F); - - subtract(images[i], mean, images[i]); - multiply(images[i], scalefactor, images[i]); } size_t nimages = images.size(); Tmat image0 = images[0]; CV_Assert(image0.dims == 2); + if (std::is_same::value && param.datalayout == DNN_LAYOUT_NCHW) + { + // Fast implementation for HWC cv::Mat images -> NCHW cv::Mat blob + if (param.ddepth == CV_8U) + blobFromImagesNCHW(images, blob_, param); + else + blobFromImagesNCHW(images, blob_, param); + return; + } + + if (param.swapRB) + { + if (nch > 2) + { + std::swap(mean[0], mean[2]); + std::swap(scalefactor[0], scalefactor[2]); + } + else + { + CV_LOG_WARNING(NULL, "Red/blue color swapping requires at least three image channels."); + } + } + if (param.datalayout == DNN_LAYOUT_NCHW) { if (nch == 3 || nch == 4) @@ -225,7 +334,14 @@ void blobFromImagesWithParamsImpl(InputArrayOfArrays images_, Tmat& blob_, const for (size_t i = 0; i < nimages; i++) { - const Tmat& image = images[i]; + Tmat& image = images[i]; + if (image.depth() == CV_8U && param.ddepth == CV_32F) + image.convertTo(image, CV_32F); + if (mean != Scalar()) + subtract(image, mean, image); + if (scalefactor != Scalar::all(1.0)) + multiply(image, scalefactor, image); + CV_Assert(image.depth() == blob_.depth()); nch = image.channels(); CV_Assert(image.dims == 2 && (nch == 3 || nch == 4)); @@ -250,7 +366,14 @@ void blobFromImagesWithParamsImpl(InputArrayOfArrays images_, Tmat& blob_, const for (size_t i = 0; i < nimages; i++) { - const Tmat& image = images[i]; + Tmat& image = images[i]; + if (image.depth() == CV_8U && param.ddepth == CV_32F) + image.convertTo(image, CV_32F); + if (mean != Scalar()) + subtract(image, mean, image); + if (scalefactor != Scalar::all(1.0)) + multiply(image, scalefactor, image); + CV_Assert(image.depth() == blob_.depth()); nch = image.channels(); CV_Assert(image.dims == 2 && (nch == 1)); @@ -269,7 +392,14 @@ void blobFromImagesWithParamsImpl(InputArrayOfArrays images_, Tmat& blob_, const int subMatType = CV_MAKETYPE(param.ddepth, nch); for (size_t i = 0; i < nimages; i++) { - const Tmat& image = images[i]; + Tmat& image = images[i]; + if (image.depth() == CV_8U && param.ddepth == CV_32F) + image.convertTo(image, CV_32F); + if (mean != Scalar()) + subtract(image, mean, image); + if (scalefactor != Scalar::all(1.0)) + multiply(image, scalefactor, image); + CV_Assert(image.depth() == blob_.depth()); CV_Assert(image.channels() == image0.channels()); CV_Assert(image.size() == image0.size()); From 4efd52f6763a70e2417795dbbff5fe5613f8d7d0 Mon Sep 17 00:00:00 2001 From: FantasqueX Date: Mon, 23 Dec 2024 16:36:39 +0800 Subject: [PATCH 052/102] Merge pull request #26650 from FantasqueX:fix-26642 Use size_t when calculating size of all_points #26650 Closes: #26642 Asan log ``` ================================================================= ==41401==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7fc55a02a3fc at pc 0x7fc58e304131 bp 0x7ffd54787b00 sp 0x7ffd54787af8 WRITE of size 4 at 0x7fc55a02a3fc thread T0 #0 0x7fc58e304130 in cv::QRDetectMulti::checkSets(std::vector, std::allocator > >, std::allocator, std::allocator > > > >&, std::vector, std::allocator > >, std::allocator, std::allocator > > > >&, std::vector, std::allocator > >&) /home/fanta/source/opencv/modules/objdetect/src/qrcode.cpp:3726 #1 0x7fc58e3054b0 in cv::QRDetectMulti::localization() /home/fanta/source/opencv/modules/objdetect/src/qrcode.cpp:3829 #2 0x7fc58e308020 in cv::ImplContour::detectMulti(cv::_InputArray const&, cv::_OutputArray const&) const /home/fanta/source/opencv/modules/objdetect/src/qrcode.cpp:3987 #3 0x7fc58e30b5b1 in cv::ImplContour::detectAndDecodeMulti(cv::_InputArray const&, std::vector, std::allocator >, std::allocator, std::allocator > > >&, cv::_OutputArray const&, cv::_OutputArray const&) const /home/fanta/source/opencv/modules/objdetect/src/qrcode.cpp:4176 #4 0x7fc58e28922f in cv::GraphicalCodeDetector::detectAndDecodeMulti(cv::_InputArray const&, std::vector, std::allocator >, std::allocator, std::allocator > > >&, cv::_OutputArray const&, cv::_OutputArray const&) const /home/fanta/source/opencv/modules/objdetect/src/graphical_code_detector.cpp:42 #5 0x5954e8 in Body /home/fanta/source/opencv/modules/objdetect/test/test_qrcode.cpp:48 #6 0x594fc0 in TestBody /home/fanta/source/opencv/modules/objdetect/test/test_qrcode.cpp:42 #7 0x67ee6a in void testing::internal::HandleSehExceptionsInMethodIfSupported(testing::Test*, void (testing::Test::*)(), char const*) /home/fanta/source/opencv/modules/ts/src/ts_gtest.cpp:3919 #8 0x6734a4 in void testing::internal::HandleExceptionsInMethodIfSupported(testing::Test*, void (testing::Test::*)(), char const*) /home/fanta/source/opencv/modules/ts/src/ts_gtest.cpp:3955 #9 0x641fe8 in testing::Test::Run() /home/fanta/source/opencv/modules/ts/src/ts_gtest.cpp:3993 #10 0x6431ac in testing::TestInfo::Run() /home/fanta/source/opencv/modules/ts/src/ts_gtest.cpp:4169 #11 0x643d15 in testing::TestCase::Run() /home/fanta/source/opencv/modules/ts/src/ts_gtest.cpp:4287 #12 0x659ff3 in testing::internal::UnitTestImpl::RunAllTests() /home/fanta/source/opencv/modules/ts/src/ts_gtest.cpp:6662 #13 0x681205 in bool testing::internal::HandleSehExceptionsInMethodIfSupported(testing::internal::UnitTestImpl*, bool (testing::internal::UnitTestImpl::*)(), char const*) /home/fanta/source/opencv/modules/ts/src/ts_gtest.cpp:3919 #14 0x675127 in bool testing::internal::HandleExceptionsInMethodIfSupported(testing::internal::UnitTestImpl*, bool (testing::internal::UnitTestImpl::*)(), char const*) /home/fanta/source/opencv/modules/ts/src/ts_gtest.cpp:3955 #15 0x65734c in testing::UnitTest::Run() /home/fanta/source/opencv/modules/ts/src/ts_gtest.cpp:6271 #16 0x5907f0 in RUN_ALL_TESTS() /home/fanta/source/opencv/modules/ts/include/opencv2/ts/ts_gtest.h:22240 #17 0x590cdd in main (/home/fanta/source/opencv-build-4.x-clang/bin/opencv_test_objdetect+0x590cdd) (BuildId: a9363fc788d57c48225fc0559ac9199d07d415db) #18 0x7fc58ab242ad in __libc_start_call_main (/lib64/libc.so.6+0x2a2ad) (BuildId: 03f1631dc9760d3e30311fe62e15cc4baaa89db7) #19 0x7fc58ab24378 in __libc_start_main@@GLIBC_2.34 (/lib64/libc.so.6+0x2a378) (BuildId: 03f1631dc9760d3e30311fe62e15cc4baaa89db7) #20 0x417014 in _start ../sysdeps/x86_64/start.S:115 0x7fc55a02a3fc is located 0 bytes after 2938510332-byte region [0x7fc4aadc8800,0x7fc55a02a3fc) allocated by thread T0 here: #0 0x7fc58e590298 in operator new(unsigned long) (/lib64/libasan.so.8+0xfd298) (BuildId: da72ee674d801ced58193987786b90646d94ff8d) #1 0x7fc58e34d010 in std::__new_allocator >::allocate(unsigned long, void const*) /usr/include/c++/14/bits/new_allocator.h:151 SUMMARY: AddressSanitizer: heap-buffer-overflow /home/fanta/source/opencv/modules/objdetect/src/qrcode.cpp:3726 in cv::QRDetectMulti::checkSets(std::vector, std::allocator > >, std::allocator, std::allocator > > > >&, std::vector, std::allocator > >, std::allocator, std::allocator > > > >&, std::vector, std::allocator > >&) Shadow bytes around the buggy address: 0x7fc55a02a100: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x7fc55a02a180: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x7fc55a02a200: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x7fc55a02a280: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x7fc55a02a300: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 =>0x7fc55a02a380: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00[04] 0x7fc55a02a400: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x7fc55a02a480: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x7fc55a02a500: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x7fc55a02a580: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x7fc55a02a600: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASan internal: fe Left alloca redzone: ca Right alloca redzone: cb ==41401==ABORTING ``` `(true_points_group[i].size()` is 1794 and `(true_points_group[i].size() - 2 ) * (true_points_group[i].size() - 1) * true_points_group[i].size())` is 5764222464 which overflows `int` ### 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/objdetect/src/qrcode.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/objdetect/src/qrcode.cpp b/modules/objdetect/src/qrcode.cpp index b6350b83e6..ac1e7fcadc 100644 --- a/modules/objdetect/src/qrcode.cpp +++ b/modules/objdetect/src/qrcode.cpp @@ -19,7 +19,6 @@ #include #include #include -#include #include namespace cv @@ -3709,7 +3708,11 @@ bool QRDetectMulti::checkSets(vector >& true_points_group, vecto vector set_size(true_points_group.size()); for (size_t i = 0; i < true_points_group.size(); i++) { - set_size[i] = int( (true_points_group[i].size() - 2 ) * (true_points_group[i].size() - 1) * true_points_group[i].size()) / 6; + const std::uint64_t true_points_group_size = true_points_group[i].size(); + // ensure set_size[i] doesn't overflow + CV_Assert(true_points_group_size <= 2345); + set_size[i] = static_cast((true_points_group_size - 2) * (true_points_group_size - 1) * + true_points_group_size / 6); } vector< vector< Vec3i > > all_points(true_points_group.size()); From ec2208f5f74c72c8823b61c58a5003f274660fa4 Mon Sep 17 00:00:00 2001 From: Maksim Shabunin Date: Mon, 23 Dec 2024 12:54:57 +0300 Subject: [PATCH 053/102] dnn: remove obsolete OV models tests --- modules/dnn/test/test_ie_models.cpp | 49 +++++------------------------ 1 file changed, 7 insertions(+), 42 deletions(-) diff --git a/modules/dnn/test/test_ie_models.cpp b/modules/dnn/test/test_ie_models.cpp index 647ee907bc..9ad3c80355 100644 --- a/modules/dnn/test/test_ie_models.cpp +++ b/modules/dnn/test/test_ie_models.cpp @@ -103,25 +103,6 @@ static const std::map& getOpenVINOTestMo "intel/age-gender-recognition-retail-0013/FP16/age-gender-recognition-retail-0013", "intel/age-gender-recognition-retail-0013/FP32/age-gender-recognition-retail-0013" }}, -#endif -#if INF_ENGINE_RELEASE >= 2021020000 - // OMZ: 2020.2 - { "face-detection-0105", { - "intel/face-detection-0105/FP32/face-detection-0105", - "intel/face-detection-0105/FP16/face-detection-0105" - }}, - { "face-detection-0106", { - "intel/face-detection-0106/FP32/face-detection-0106", - "intel/face-detection-0106/FP16/face-detection-0106" - }}, -#endif -#if INF_ENGINE_RELEASE >= 2021040000 - // OMZ: 2021.4 - { "person-vehicle-bike-detection-2004", { - "intel/person-vehicle-bike-detection-2004/FP32/person-vehicle-bike-detection-2004", - "intel/person-vehicle-bike-detection-2004/FP16/person-vehicle-bike-detection-2004" - //"intel/person-vehicle-bike-detection-2004/FP16-INT8/person-vehicle-bike-detection-2004" - }}, #endif }; @@ -228,7 +209,12 @@ void runIE(Target target, const std::string& xmlPath, const std::string& binPath for (auto&& it : model->inputs()) { auto type = it.get_element_type(); - auto shape = it.get_shape(); + auto shape_ = it.get_partial_shape(); + if (shape_.is_dynamic()) + { + FAIL() << "Model should not have dynamic shapes (" << it.get_any_name() << " => " << shape_ << ")"; + } + auto shape = shape_.to_shape(); auto& m = inputsMap[it.get_any_name()]; auto tensor = ov::Tensor(type, shape); @@ -265,10 +251,10 @@ void runIE(Target target, const std::string& xmlPath, const std::string& binPath for (const auto& it : model->outputs()) { auto type = it.get_element_type(); - auto shape = it.get_shape(); auto& m = outputsMap[it.get_any_name()]; auto tensor = infRequest.get_tensor(it); + auto shape = tensor.get_shape(); if (type == ov::element::f32) { m.create(std::vector(shape.begin(), shape.end()), CV_32F); @@ -341,22 +327,9 @@ TEST_P(DNNTestOpenVINO, models) if (targetId == DNN_TARGET_MYRIAD && (false || modelName == "person-detection-retail-0013" // ncDeviceOpen:1013 Failed to find booted device after boot || modelName == "age-gender-recognition-retail-0013" // ncDeviceOpen:1013 Failed to find booted device after boot - || modelName == "face-detection-0105" // get_element_type() must be called on a node with exactly one output - || modelName == "face-detection-0106" // get_element_type() must be called on a node with exactly one output - || modelName == "person-vehicle-bike-detection-2004" // 2021.4+: ncDeviceOpen:1013 Failed to find booted device after boot ) ) applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_MYRIAD, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); - if (targetId == DNN_TARGET_OPENCL && (false - || modelName == "face-detection-0106" // Operation: 2278 of type ExperimentalDetectronPriorGridGenerator(op::v6) is not supported - ) - ) - applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_OPENCL, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); - if (targetId == DNN_TARGET_OPENCL_FP16 && (false - || modelName == "face-detection-0106" // Operation: 2278 of type ExperimentalDetectronPriorGridGenerator(op::v6) is not supported - ) - ) - applyTestTag(CV_TEST_TAG_DNN_SKIP_IE_OPENCL_FP16, CV_TEST_TAG_DNN_SKIP_IE_NGRAPH, CV_TEST_TAG_DNN_SKIP_IE_VERSION); #endif #if INF_ENGINE_VER_MAJOR_GE(2020020000) @@ -399,14 +372,6 @@ TEST_P(DNNTestOpenVINO, models) if (targetId == DNN_TARGET_CPU && checkHardwareSupport(CV_CPU_AVX_512F)) eps = 1e-5; #endif -#if INF_ENGINE_VER_MAJOR_GE(2021030000) - if (targetId == DNN_TARGET_CPU && modelName == "face-detection-0105") - eps = 2e-4; -#endif -#if INF_ENGINE_VER_MAJOR_GE(2021040000) - if (targetId == DNN_TARGET_CPU && modelName == "person-vehicle-bike-detection-2004") - eps = 1e-6; -#endif EXPECT_EQ(ieOutputsMap.size(), cvOutputsMap.size()); for (auto& srcIt : ieOutputsMap) From a2ce9e1bac1629773a65d9a7ba4d29439bcc3574 Mon Sep 17 00:00:00 2001 From: Alexander Smorkalov Date: Mon, 23 Dec 2024 13:58:08 +0300 Subject: [PATCH 054/102] pre: OpenCV 4.11.0 (version++) --- .../cross_referencing/tutorial_cross_referencing.markdown | 4 ++-- modules/core/include/opencv2/core/version.hpp | 4 ++-- 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, 10 insertions(+), 9 deletions(-) diff --git a/doc/tutorials/introduction/cross_referencing/tutorial_cross_referencing.markdown b/doc/tutorials/introduction/cross_referencing/tutorial_cross_referencing.markdown index 6d43761e14..af809751f7 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.10.0 +TAGFILES = ./docs/doxygen-tags/opencv.tag=http://docs.opencv.org/4.11.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.10.0 + ./docs/doxygen-tags/opencv.tag=http://docs.opencv.org/4.11.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 39c8f33aef..c0c7717e1f 100644 --- a/modules/core/include/opencv2/core/version.hpp +++ b/modules/core/include/opencv2/core/version.hpp @@ -6,9 +6,9 @@ #define OPENCV_VERSION_HPP #define CV_VERSION_MAJOR 4 -#define CV_VERSION_MINOR 10 +#define CV_VERSION_MINOR 11 #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 f83d90dab4..5cf0870b44 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 20240521 +#define OPENCV_DNN_API_VERSION 20241223 #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 068f6180cb..76702ce1ca 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.10.0') # TODO + package_version = os.environ.get('OPENCV_VERSION', '4.11.0') # TODO long_description = 'Open Source Computer Vision Library Python bindings' # TODO @@ -66,6 +66,7 @@ def main(): "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "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 6b0e0f54d3..5355f0e67e 100644 --- a/platforms/maven/opencv-it/pom.xml +++ b/platforms/maven/opencv-it/pom.xml @@ -4,7 +4,7 @@ org.opencv opencv-parent - 4.10.0 + 4.11.0 org.opencv opencv-it diff --git a/platforms/maven/opencv/pom.xml b/platforms/maven/opencv/pom.xml index 8e5221f0a9..4ecd738b40 100644 --- a/platforms/maven/opencv/pom.xml +++ b/platforms/maven/opencv/pom.xml @@ -4,7 +4,7 @@ org.opencv opencv-parent - 4.10.0 + 4.11.0 org.opencv opencv diff --git a/platforms/maven/pom.xml b/platforms/maven/pom.xml index c0b4f77697..af9f969c65 100644 --- a/platforms/maven/pom.xml +++ b/platforms/maven/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.opencv opencv-parent - 4.10.0 + 4.11.0 pom OpenCV Parent POM From 0756dbfe3d8307e86597e203f13213388078c2d3 Mon Sep 17 00:00:00 2001 From: Maksim Shabunin Date: Mon, 23 Dec 2024 23:58:37 +0300 Subject: [PATCH 055/102] RISC-V: enabled intrinsics in dotProd, relaxed test thresholds --- modules/core/src/matmul.simd.hpp | 3 +-- modules/core/test/test_math.cpp | 14 ++++++++++++-- modules/imgproc/test/test_histograms.cpp | 3 ++- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/modules/core/src/matmul.simd.hpp b/modules/core/src/matmul.simd.hpp index 6f77adc68b..a64cd7ba6a 100644 --- a/modules/core/src/matmul.simd.hpp +++ b/modules/core/src/matmul.simd.hpp @@ -2536,8 +2536,7 @@ double dotProd_16s(const short* src1, const short* src2, int len) double dotProd_32s(const int* src1, const int* src2, int len) { -#if CV_SIMD_64F // TODO: enable for CV_SIMD_SCALABLE_64F -// Test failed on RVV(QEMU): Too big difference (=1.20209e-08 > 1.11022e-12) +#if CV_SIMD_64F || CV_SIMD_SCALABLE_64F double r = .0; int i = 0; const int step = VTraits::vlanes(); diff --git a/modules/core/test/test_math.cpp b/modules/core/test/test_math.cpp index 024177f1a1..dc9153680c 100644 --- a/modules/core/test/test_math.cpp +++ b/modules/core/test/test_math.cpp @@ -477,8 +477,9 @@ class Core_DotProductTest : public Core_MatrixTest public: Core_DotProductTest(); protected: - void run_func(); - void prepare_to_validation( int test_case_idx ); + void run_func() CV_OVERRIDE; + void prepare_to_validation( int test_case_idx ) CV_OVERRIDE; + double get_success_error_level( int test_case_idx, int i, int j ) CV_OVERRIDE; }; @@ -498,6 +499,15 @@ void Core_DotProductTest::prepare_to_validation( int ) test_mat[REF_OUTPUT][0].at(0,0) = Scalar(cvtest::crossCorr( test_mat[INPUT][0], test_mat[INPUT][1] )); } +double Core_DotProductTest::get_success_error_level( int test_case_idx, int i, int j ) +{ +#ifdef __riscv + const int depth = test_mat[i][j].depth(); + if (depth == CV_64F) + return 1.7e-5; +#endif + return Core_MatrixTest::get_success_error_level( test_case_idx, i, j ); +} ///////// crossproduct ////////// diff --git a/modules/imgproc/test/test_histograms.cpp b/modules/imgproc/test/test_histograms.cpp index b6a97ef395..2460fd92f0 100644 --- a/modules/imgproc/test/test_histograms.cpp +++ b/modules/imgproc/test/test_histograms.cpp @@ -1078,6 +1078,7 @@ int CV_CompareHistTest::validate_test_results( int /*test_case_idx*/ ) i == CV_COMP_BHATTACHARYYA ? "Bhattacharyya" : i == CV_COMP_KL_DIV ? "Kullback-Leibler" : "Unknown"; + const auto thresh = FLT_EPSILON*14*MAX(fabs(v0),0.17); if( cvIsNaN(v) || cvIsInf(v) ) { ts->printf( cvtest::TS::LOG, "The comparison result using the method #%d (%s) is invalid (=%g)\n", @@ -1085,7 +1086,7 @@ int CV_CompareHistTest::validate_test_results( int /*test_case_idx*/ ) code = cvtest::TS::FAIL_INVALID_OUTPUT; break; } - else if( fabs(v0 - v) > FLT_EPSILON*14*MAX(fabs(v0),0.1) ) + else if( fabs(v0 - v) > thresh ) { ts->printf( cvtest::TS::LOG, "The comparison result using the method #%d (%s)\n\tis inaccurate (=%g, should be =%g)\n", i, method_name, v, v0 ); From fe649f4adb5b0d3ca18bb3fd782de61bef7a126d Mon Sep 17 00:00:00 2001 From: WangWeiLin-MV <156736127+WangWeiLin-MV@users.noreply.github.com> Date: Mon, 16 Dec 2024 06:31:22 +0000 Subject: [PATCH 056/102] Add include chrono gstreamersource.cpp --- modules/gapi/src/streaming/gstreamer/gstreamersource.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/gapi/src/streaming/gstreamer/gstreamersource.cpp b/modules/gapi/src/streaming/gstreamer/gstreamersource.cpp index 9fb15729b4..0d6d7dd5b2 100644 --- a/modules/gapi/src/streaming/gstreamer/gstreamersource.cpp +++ b/modules/gapi/src/streaming/gstreamer/gstreamersource.cpp @@ -17,6 +17,8 @@ #include +#include + #ifdef HAVE_GSTREAMER #include #include From 745a12c03b1c3f954db2cba0f4b86acfacb507de Mon Sep 17 00:00:00 2001 From: Alexander Smorkalov Date: Thu, 26 Dec 2024 09:54:09 +0300 Subject: [PATCH 057/102] Sevral fixes for FastCV handling. --- 3rdparty/fastcv/CMakeLists.txt | 2 -- cmake/OpenCVFindLibsPerf.cmake | 14 +++++++++----- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/3rdparty/fastcv/CMakeLists.txt b/3rdparty/fastcv/CMakeLists.txt index 268d261a98..ab17375902 100644 --- a/3rdparty/fastcv/CMakeLists.txt +++ b/3rdparty/fastcv/CMakeLists.txt @@ -24,8 +24,6 @@ if(HAVE_FASTCV) ocv_install_target(fastcv_hal EXPORT OpenCVModules ARCHIVE DESTINATION ${OPENCV_3P_LIB_INSTALL_PATH} COMPONENT dev) endif() - ocv_install_3rdparty_licenses(FastCV "${OpenCV_BINARY_DIR}/3rdparty/fastcv/LICENSE") - if(ENABLE_SOLUTION_FOLDERS) set_target_properties(fastcv_hal PROPERTIES FOLDER "3rdparty") endif() diff --git a/cmake/OpenCVFindLibsPerf.cmake b/cmake/OpenCVFindLibsPerf.cmake index 4459e2e2e5..c2380e6ce5 100644 --- a/cmake/OpenCVFindLibsPerf.cmake +++ b/cmake/OpenCVFindLibsPerf.cmake @@ -180,19 +180,23 @@ endif(WITH_KLEIDICV) # --- FastCV --- if(WITH_FASTCV) if((EXISTS ${FastCV_INCLUDE_PATH}) AND (EXISTS ${FastCV_LIB_PATH})) + message(STATUS "Use external FastCV ${FastCV_INCLUDE_PATH}, ${FastCV_LIB_PATH}") set(HAVE_FASTCV TRUE CACHE BOOL "FastCV status") else() include("${OpenCV_SOURCE_DIR}/3rdparty/fastcv/fastcv.cmake") set(FCV_ROOT_DIR "${OpenCV_BINARY_DIR}/3rdparty/fastcv") download_fastcv(${FCV_ROOT_DIR}) - if (HAVE_FASTCV) + if(HAVE_FASTCV) set(FastCV_INCLUDE_PATH "${FCV_ROOT_DIR}/inc" CACHE PATH "FastCV includes directory") set(FastCV_LIB_PATH "${FCV_ROOT_DIR}/libs" CACHE PATH "FastCV library directory") + ocv_install_3rdparty_licenses(FastCV "${OpenCV_BINARY_DIR}/3rdparty/fastcv/LICENSE") + install(FILES "${FastCV_LIB_PATH}/libfastcvopt.so" + DESTINATION "${OPENCV_LIB_INSTALL_PATH}" COMPONENT "bin") else() set(HAVE_FASTCV FALSE CACHE BOOL "FastCV status") endif() - if (HAVE_FASTCV) - set(FASTCV_LIBRARY "${FastCV_LIB_PATH}/libfastcvopt.so" CACHE PATH "FastCV library") - endif() -endif() + endif() + if(HAVE_FASTCV) + set(FASTCV_LIBRARY "${FastCV_LIB_PATH}/libfastcvopt.so" CACHE PATH "FastCV library") + endif() endif(WITH_FASTCV) From e9982e856f2aab28d427d7fc9e04281ae60b63ee Mon Sep 17 00:00:00 2001 From: Dmitry Kurtaev Date: Thu, 26 Dec 2024 12:48:49 +0300 Subject: [PATCH 058/102] Merge pull request #25584 from dkurt:videocapture_from_buffer Open VideoCapture from data stream #25584 ### Pull Request Readiness Checklist Add VideoCapture option to read a raw binary video data from `std::streambuf`. There are multiple motivations: 1. Avoid disk file creation in case of video already in memory (received by network or from database). 2. Streaming mode. Frames decoding starts during sequential file transfer by chunks. Suppoted backends: * FFmpeg * MSMF (no streaming mode) Supporter interfaces: * C++ (std::streambuf) * Python (io.BufferedIOBase) resolves https://github.com/opencv/opencv/issues/24400 - [x] test h264 - [x] test IP camera like approach with no metadata but key frame only? - [x] C API plugin 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/include/opencv2/videoio.hpp | 40 +++++ .../include/opencv2/videoio/registry.hpp | 10 ++ .../include/opencv2/videoio/utils.private.hpp | 31 ++++ modules/videoio/misc/objc/gen_dict.json | 3 +- .../videoio/misc/python/pyopencv_videoio.hpp | 110 ++++++++++++++ .../videoio/misc/python/test/test_videoio.py | 66 +++++++++ modules/videoio/src/backend.hpp | 5 + modules/videoio/src/backend_plugin.cpp | 66 ++++++++- modules/videoio/src/backend_static.cpp | 43 ++++-- modules/videoio/src/cap.cpp | 140 +++++++++++++++++- modules/videoio/src/cap_ffmpeg.cpp | 67 ++++++++- modules/videoio/src/cap_ffmpeg_impl.hpp | 100 +++++++++++-- modules/videoio/src/cap_interface.hpp | 3 + modules/videoio/src/cap_msmf.cpp | 105 +++++++++++-- modules/videoio/src/plugin_capture_api.hpp | 37 ++++- modules/videoio/src/videoio_registry.cpp | 56 ++++++- modules/videoio/src/videoio_registry.hpp | 2 + modules/videoio/test/test_video_io.cpp | 130 ++++++++++++++++ 18 files changed, 966 insertions(+), 48 deletions(-) diff --git a/modules/videoio/include/opencv2/videoio.hpp b/modules/videoio/include/opencv2/videoio.hpp index 92fa5d39b7..6e50f87e44 100644 --- a/modules/videoio/include/opencv2/videoio.hpp +++ b/modules/videoio/include/opencv2/videoio.hpp @@ -719,6 +719,25 @@ enum VideoCaptureOBSensorProperties{ //! @} videoio_flags_others +/** @brief Read data stream interface + */ +class CV_EXPORTS_W IStreamReader +{ +public: + virtual ~IStreamReader(); + + /** @brief Read bytes from stream */ + virtual long long read(char* buffer, long long size) = 0; + + /** @brief Sets the stream position + * + * @param offset Seek offset + * @param origin SEEK_SET / SEEK_END / SEEK_CUR + * + * @see fseek + */ + virtual long long seek(long long offset, int origin) = 0; +}; class IVideoCapture; //! @cond IGNORED @@ -798,6 +817,14 @@ public: */ CV_WRAP explicit VideoCapture(int index, int apiPreference, const std::vector& params); + /** @overload + @brief Opens a video using data stream. + + The `params` parameter allows to specify extra parameters encoded as pairs `(paramId_1, paramValue_1, paramId_2, paramValue_2, ...)`. + See cv::VideoCaptureProperties + */ + CV_WRAP VideoCapture(const Ptr& source, int apiPreference, const std::vector& params); + /** @brief Default destructor The method first calls VideoCapture::release to close the already opened file or camera. @@ -852,6 +879,19 @@ public: */ CV_WRAP virtual bool open(int index, int apiPreference, const std::vector& params); + /** @brief Opens a video using data stream. + + @overload + + The `params` parameter allows to specify extra parameters encoded as pairs `(paramId_1, paramValue_1, paramId_2, paramValue_2, ...)`. + See cv::VideoCaptureProperties + + @return `true` if the file has been successfully opened + + The method first calls VideoCapture::release to close the already opened file or camera. + */ + CV_WRAP virtual bool open(const Ptr& source, int apiPreference, const std::vector& params); + /** @brief Returns true if video capturing has been initialized already. If the previous call to VideoCapture constructor or VideoCapture::open() succeeded, the method returns diff --git a/modules/videoio/include/opencv2/videoio/registry.hpp b/modules/videoio/include/opencv2/videoio/registry.hpp index cf72247b3f..47e94b1909 100644 --- a/modules/videoio/include/opencv2/videoio/registry.hpp +++ b/modules/videoio/include/opencv2/videoio/registry.hpp @@ -35,6 +35,9 @@ CV_EXPORTS_W std::vector getCameraBackends(); /** @brief Returns list of available backends which works via `cv::VideoCapture(filename)` */ CV_EXPORTS_W std::vector getStreamBackends(); +/** @brief Returns list of available backends which works via `cv::VideoCapture(buffer)` */ +CV_EXPORTS_W std::vector getStreamBufferedBackends(); + /** @brief Returns list of available backends which works via `cv::VideoWriter()` */ CV_EXPORTS_W std::vector getWriterBackends(); @@ -58,6 +61,13 @@ CV_EXPORTS_W std::string getStreamBackendPluginVersion( CV_OUT int& version_API ); +/** @brief Returns description and ABI/API version of videoio plugin's buffer capture interface */ +CV_EXPORTS_W std::string getStreamBufferedBackendPluginVersion( + VideoCaptureAPIs api, + CV_OUT int& version_ABI, + CV_OUT int& version_API +); + /** @brief Returns description and ABI/API version of videoio plugin's writer interface */ CV_EXPORTS_W std::string getWriterBackendPluginVersion( VideoCaptureAPIs api, diff --git a/modules/videoio/include/opencv2/videoio/utils.private.hpp b/modules/videoio/include/opencv2/videoio/utils.private.hpp index e331aaf2ac..0fa6d285b0 100644 --- a/modules/videoio/include/opencv2/videoio/utils.private.hpp +++ b/modules/videoio/include/opencv2/videoio/utils.private.hpp @@ -10,6 +10,37 @@ namespace cv { CV_EXPORTS std::string icvExtractPattern(const std::string& filename, unsigned *offset); + +class PluginStreamReader : public IStreamReader +{ +public: + PluginStreamReader(void* _opaque, + long long (*_read)(void* opaque, char* buffer, long long size), + long long (*_seek)(void* opaque, long long offset, int way)) + { + opaque = _opaque; + readCallback = _read; + seekCallback = _seek; + } + + virtual ~PluginStreamReader() {} + + long long read(char* buffer, long long size) override + { + return readCallback(opaque, buffer, size); + } + + long long seek(long long offset, int way) override + { + return seekCallback(opaque, offset, way); + } + +private: + void* opaque; + long long (*readCallback)(void* opaque, char* buffer, long long size); + long long (*seekCallback)(void* opaque, long long offset, int way); +}; + } #endif // OPENCV_VIDEOIO_UTILS_PRIVATE_HPP diff --git a/modules/videoio/misc/objc/gen_dict.json b/modules/videoio/misc/objc/gen_dict.json index 70dc844f0c..f42211a949 100644 --- a/modules/videoio/misc/objc/gen_dict.json +++ b/modules/videoio/misc/objc/gen_dict.json @@ -14,7 +14,8 @@ "func_arg_fix" : { "VideoCapture" : { "(BOOL)open:(int)index apiPreference:(int)apiPreference" : { "open" : {"name" : "openWithIndex"} }, - "(BOOL)open:(int)index apiPreference:(int)apiPreference params:(IntVector*)params" : { "open" : {"name" : "openWithIndexAndParameters"} } + "(BOOL)open:(int)index apiPreference:(int)apiPreference params:(IntVector*)params" : { "open" : {"name" : "openWithIndexAndParameters"} }, + "(BOOL)open:(IStreamReader*)source apiPreference:(int)apiPreference params:(IntVector*)params" : { "open" : {"name" : "openWithStreamReader"} } } } } diff --git a/modules/videoio/misc/python/pyopencv_videoio.hpp b/modules/videoio/misc/python/pyopencv_videoio.hpp index e729c8631f..ab26e00c82 100644 --- a/modules/videoio/misc/python/pyopencv_videoio.hpp +++ b/modules/videoio/misc/python/pyopencv_videoio.hpp @@ -31,4 +31,114 @@ template<> bool pyopencv_to(PyObject* obj, cv::VideoCapture& stream, const ArgIn return true; } +class PythonStreamReader : public cv::IStreamReader +{ +public: + PythonStreamReader(PyObject* _obj = nullptr) : obj(_obj) + { + if (obj) + Py_INCREF(obj); + } + + ~PythonStreamReader() + { + if (obj) + Py_DECREF(obj); + } + + long long read(char* buffer, long long size) CV_OVERRIDE + { + if (!obj) + return 0; + + PyObject* ioBase = reinterpret_cast(obj); + + PyGILState_STATE gstate; + gstate = PyGILState_Ensure(); + + PyObject* py_size = pyopencv_from(static_cast(size)); + + PyObject* res = PyObject_CallMethodObjArgs(ioBase, PyString_FromString("read"), py_size, NULL); + bool hasPyReadError = PyErr_Occurred() != nullptr; + char* src = PyBytes_AsString(res); + size_t len = static_cast(PyBytes_Size(res)); + bool hasPyBytesError = PyErr_Occurred() != nullptr; + if (src && len <= static_cast(size)) + { + std::memcpy(buffer, src, len); + } + Py_DECREF(res); + Py_DECREF(py_size); + + PyGILState_Release(gstate); + + if (hasPyReadError) + CV_Error(cv::Error::StsError, "Python .read() call error"); + if (hasPyBytesError) + CV_Error(cv::Error::StsError, "Python buffer access error"); + + CV_CheckLE(len, static_cast(size), "Stream chunk size should be less or equal than requested size"); + + return len; + } + + long long seek(long long offset, int way) CV_OVERRIDE + { + if (!obj) + return 0; + + PyObject* ioBase = reinterpret_cast(obj); + + PyGILState_STATE gstate; + gstate = PyGILState_Ensure(); + + PyObject* py_offset = pyopencv_from(static_cast(offset)); + PyObject* py_whence = pyopencv_from(way); + + PyObject* res = PyObject_CallMethodObjArgs(ioBase, PyString_FromString("seek"), py_offset, py_whence, NULL); + bool hasPySeekError = PyErr_Occurred() != nullptr; + long long pos = PyLong_AsLongLong(res); + bool hasPyConvertError = PyErr_Occurred() != nullptr; + Py_DECREF(res); + Py_DECREF(py_offset); + Py_DECREF(py_whence); + + PyGILState_Release(gstate); + + if (hasPySeekError) + CV_Error(cv::Error::StsError, "Python .seek() call error"); + if (hasPyConvertError) + CV_Error(cv::Error::StsError, "Python .seek() result => long long conversion error"); + return pos; + } + +private: + PyObject* obj; +}; + +template<> +bool pyopencv_to(PyObject* obj, Ptr& p, const ArgInfo&) +{ + if (!obj) + return false; + + PyObject* ioModule = PyImport_ImportModule("io"); + PyObject* type = PyObject_GetAttrString(ioModule, "BufferedIOBase"); + Py_DECREF(ioModule); + bool isValidPyType = PyObject_IsInstance(obj, type) == 1; + Py_DECREF(type); + + if (!isValidPyType) + { + PyErr_SetString(PyExc_TypeError, "Input stream should be derived from io.BufferedIOBase"); + return false; + } + + if (!PyErr_Occurred()) { + p = makePtr(obj); + return true; + } + return false; +} + #endif // HAVE_OPENCV_VIDEOIO diff --git a/modules/videoio/misc/python/test/test_videoio.py b/modules/videoio/misc/python/test/test_videoio.py index 2bbfeecda0..181ade769d 100644 --- a/modules/videoio/misc/python/test/test_videoio.py +++ b/modules/videoio/misc/python/test/test_videoio.py @@ -3,6 +3,8 @@ from __future__ import print_function import numpy as np import cv2 as cv +import io +import sys from tests_common import NewOpenCVTests @@ -21,5 +23,69 @@ class Bindings(NewOpenCVTests): for backend in backends: self.check_name(cv.videoio_registry.getBackendName(backend)) + def test_capture_stream_file(self): + if sys.version_info[0] < 3: + raise self.skipTest('Python 3.x required') + + api_pref = None + for backend in cv.videoio_registry.getStreamBufferedBackends(): + if not cv.videoio_registry.hasBackend(backend): + continue + if not cv.videoio_registry.isBackendBuiltIn(backend): + _, abi, api = cv.videoio_registry.getStreamBufferedBackendPluginVersion(backend) + if (abi < 1 or (abi == 1 and api < 2)): + continue + api_pref = backend + break + + if not api_pref: + raise self.skipTest("No available backends") + + with open(self.find_file("cv/video/768x576.avi"), "rb") as f: + cap = cv.VideoCapture(f, api_pref, []) + self.assertTrue(cap.isOpened()) + hasFrame, frame = cap.read() + self.assertTrue(hasFrame) + self.assertEqual(frame.shape, (576, 768, 3)) + + def test_capture_stream_buffer(self): + if sys.version_info[0] < 3: + raise self.skipTest('Python 3.x required') + + api_pref = None + for backend in cv.videoio_registry.getStreamBufferedBackends(): + if not cv.videoio_registry.hasBackend(backend): + continue + if not cv.videoio_registry.isBackendBuiltIn(backend): + _, abi, api = cv.videoio_registry.getStreamBufferedBackendPluginVersion(backend) + if (abi < 1 or (abi == 1 and api < 2)): + continue + api_pref = backend + break + + if not api_pref: + raise self.skipTest("No available backends") + + class BufferStream(io.BufferedIOBase): + def __init__(self, filepath): + self.f = open(filepath, "rb") + + def read(self, size=-1): + return self.f.read(size) + + def seek(self, offset, whence): + return self.f.seek(offset, whence) + + def __del__(self): + self.f.close() + + stream = BufferStream(self.find_file("cv/video/768x576.avi")) + + cap = cv.VideoCapture(stream, api_pref, []) + self.assertTrue(cap.isOpened()) + hasFrame, frame = cap.read() + self.assertTrue(hasFrame) + self.assertEqual(frame.shape, (576, 768, 3)) + if __name__ == '__main__': NewOpenCVTests.bootstrap() diff --git a/modules/videoio/src/backend.hpp b/modules/videoio/src/backend.hpp index 2a95ec05aa..b75cc51ea8 100644 --- a/modules/videoio/src/backend.hpp +++ b/modules/videoio/src/backend.hpp @@ -18,6 +18,7 @@ public: virtual ~IBackend() {} virtual Ptr createCapture(int camera, const VideoCaptureParameters& params) const = 0; virtual Ptr createCapture(const std::string &filename, const VideoCaptureParameters& params) const = 0; + virtual Ptr createCapture(const Ptr&stream, const VideoCaptureParameters& params) const = 0; virtual Ptr createWriter(const std::string& filename, int fourcc, double fps, const cv::Size& sz, const VideoWriterParameters& params) const = 0; }; @@ -34,15 +35,19 @@ public: typedef Ptr (*FN_createCaptureFile)(const std::string & filename); typedef Ptr (*FN_createCaptureCamera)(int camera); +typedef Ptr (*FN_createCaptureStream)(const Ptr& stream); typedef Ptr (*FN_createCaptureFileWithParams)(const std::string & filename, const VideoCaptureParameters& params); typedef Ptr (*FN_createCaptureCameraWithParams)(int camera, const VideoCaptureParameters& params); +typedef Ptr (*FN_createCaptureStreamWithParams)(const Ptr& stream, const VideoCaptureParameters& params); typedef Ptr (*FN_createWriter)(const std::string& filename, int fourcc, double fps, const Size& sz, const VideoWriterParameters& params); Ptr createBackendFactory(FN_createCaptureFile createCaptureFile, FN_createCaptureCamera createCaptureCamera, + FN_createCaptureStream createCaptureStream, FN_createWriter createWriter); Ptr createBackendFactory(FN_createCaptureFileWithParams createCaptureFile, FN_createCaptureCameraWithParams createCaptureCamera, + FN_createCaptureStreamWithParams createCaptureStream, FN_createWriter createWriter); Ptr createPluginBackendFactory(VideoCaptureAPIs id, const char* baseName); diff --git a/modules/videoio/src/backend_plugin.cpp b/modules/videoio/src/backend_plugin.cpp index cc9b33b80c..a38537c778 100644 --- a/modules/videoio/src/backend_plugin.cpp +++ b/modules/videoio/src/backend_plugin.cpp @@ -208,6 +208,7 @@ public: Ptr createCapture(int camera, const VideoCaptureParameters& params) const CV_OVERRIDE; Ptr createCapture(const std::string &filename) const; Ptr createCapture(const std::string &filename, const VideoCaptureParameters& params) const CV_OVERRIDE; + Ptr createCapture(const Ptr& stream, const VideoCaptureParameters& params) const CV_OVERRIDE; Ptr createWriter(const std::string& filename, int fourcc, double fps, const cv::Size& sz, const VideoWriterParameters& params) const CV_OVERRIDE; @@ -447,16 +448,52 @@ class PluginCapture : public cv::IVideoCapture { const OpenCV_VideoIO_Capture_Plugin_API* plugin_api_; CvPluginCapture capture_; + Ptr readStream_; public: static Ptr create(const OpenCV_VideoIO_Capture_Plugin_API* plugin_api, - const std::string &filename, int camera, const VideoCaptureParameters& params) + const std::string &filename, const Ptr& stream, int camera, const VideoCaptureParameters& params) { CV_Assert(plugin_api); CV_Assert(plugin_api->v0.Capture_release); CvPluginCapture capture = NULL; + if (stream && plugin_api->api_header.api_version >= 2 && plugin_api->v2.Capture_open_stream) + { + std::vector vint_params = params.getIntVector(); + int* c_params = vint_params.data(); + unsigned n_params = (unsigned)(vint_params.size() / 2); + + if (CV_ERROR_OK == plugin_api->v2.Capture_open_stream( + stream.get(), + [](void* opaque, char* buffer, long long size) -> long long { + CV_LOG_VERBOSE(NULL, 0, "IStreamReader::read(" << size << ")..."); + auto is = reinterpret_cast(opaque); + try { + return is->read(buffer, size); + } catch (...) { + CV_LOG_WARNING(NULL, "IStreamReader::read(" << size << ") failed"); + return 0; + } + }, + [](void* opaque, long long offset, int way) -> long long { + CV_LOG_VERBOSE(NULL, 0, "IStreamReader::seek(" << offset << ", way=" << way << ")..."); + auto is = reinterpret_cast(opaque); + try { + return is->seek(offset, way); + } catch (...) { + CV_LOG_WARNING(NULL, "IStreamReader::seek(" << offset << ", way=" << way << ") failed"); + return -1; + } + }, c_params, n_params, &capture)) + { + CV_Assert(capture); + return makePtr(plugin_api, capture, stream); + } + } + else if (stream) + return Ptr(); if (plugin_api->api_header.api_version >= 1 && plugin_api->v1.Capture_open_with_params) { @@ -488,8 +525,8 @@ public: return Ptr(); } - PluginCapture(const OpenCV_VideoIO_Capture_Plugin_API* plugin_api, CvPluginCapture capture) - : plugin_api_(plugin_api), capture_(capture) + PluginCapture(const OpenCV_VideoIO_Capture_Plugin_API* plugin_api, CvPluginCapture capture, const Ptr& readStream = Ptr()) + : plugin_api_(plugin_api), capture_(capture), readStream_(readStream) { CV_Assert(plugin_api_); CV_Assert(capture_); } @@ -661,7 +698,7 @@ Ptr PluginBackend::createCapture(int camera, const VideoCapturePa try { if (capture_api_) - return PluginCapture::create(capture_api_, std::string(), camera, params); //.staticCast(); + return PluginCapture::create(capture_api_, std::string(), nullptr, camera, params); //.staticCast(); if (plugin_api_) { Ptr cap = legacy::PluginCapture::create(plugin_api_, std::string(), camera); //.staticCast(); @@ -685,7 +722,7 @@ Ptr PluginBackend::createCapture(const std::string &filename, con try { if (capture_api_) - return PluginCapture::create(capture_api_, filename, 0, params); //.staticCast(); + return PluginCapture::create(capture_api_, filename, nullptr, 0, params); //.staticCast(); if (plugin_api_) { Ptr cap = legacy::PluginCapture::create(plugin_api_, filename, 0); //.staticCast(); @@ -704,6 +741,25 @@ Ptr PluginBackend::createCapture(const std::string &filename, con return Ptr(); } +Ptr PluginBackend::createCapture(const Ptr& stream, const VideoCaptureParameters& params) const +{ + try + { + if (capture_api_) + return PluginCapture::create(capture_api_, std::string(), stream, 0, params); //.staticCast(); + if (plugin_api_) + { + CV_Error(Error::StsNotImplemented, "Legacy plugin API for stream capture"); + } + } + catch (...) + { + CV_LOG_DEBUG(NULL, "Video I/O: can't open stream capture"); + throw; + } + return Ptr(); +} + Ptr PluginBackend::createWriter(const std::string& filename, int fourcc, double fps, const cv::Size& sz, const VideoWriterParameters& params) const { diff --git a/modules/videoio/src/backend_static.cpp b/modules/videoio/src/backend_static.cpp index 3001906acf..511aff9ffa 100644 --- a/modules/videoio/src/backend_static.cpp +++ b/modules/videoio/src/backend_static.cpp @@ -36,10 +36,11 @@ class StaticBackend: public IBackend public: FN_createCaptureFile fn_createCaptureFile_; FN_createCaptureCamera fn_createCaptureCamera_; + FN_createCaptureStream fn_createCaptureStream_; FN_createWriter fn_createWriter_; - StaticBackend(FN_createCaptureFile fn_createCaptureFile, FN_createCaptureCamera fn_createCaptureCamera, FN_createWriter fn_createWriter) - : fn_createCaptureFile_(fn_createCaptureFile), fn_createCaptureCamera_(fn_createCaptureCamera), fn_createWriter_(fn_createWriter) + StaticBackend(FN_createCaptureFile fn_createCaptureFile, FN_createCaptureCamera fn_createCaptureCamera, FN_createCaptureStream fn_createCaptureStream, FN_createWriter fn_createWriter) + : fn_createCaptureFile_(fn_createCaptureFile), fn_createCaptureCamera_(fn_createCaptureCamera), fn_createCaptureStream_(fn_createCaptureStream), fn_createWriter_(fn_createWriter) { // nothing } @@ -72,6 +73,19 @@ public: } return Ptr(); } + Ptr createCapture(const Ptr& stream, const VideoCaptureParameters& params) const CV_OVERRIDE + { + if (fn_createCaptureStream_) + { + Ptr cap = fn_createCaptureStream_(stream); + if (cap && !params.empty()) + { + applyParametersFallback(cap, params); + } + return cap; + } + return Ptr(); + } Ptr createWriter(const std::string& filename, int fourcc, double fps, const cv::Size& sz, const VideoWriterParameters& params) const CV_OVERRIDE { @@ -87,8 +101,8 @@ protected: Ptr backend; public: - StaticBackendFactory(FN_createCaptureFile createCaptureFile, FN_createCaptureCamera createCaptureCamera, FN_createWriter createWriter) - : backend(makePtr(createCaptureFile, createCaptureCamera, createWriter)) + StaticBackendFactory(FN_createCaptureFile createCaptureFile, FN_createCaptureCamera createCaptureCamera, FN_createCaptureStream createCaptureStream, FN_createWriter createWriter) + : backend(makePtr(createCaptureFile, createCaptureCamera, createCaptureStream, createWriter)) { // nothing } @@ -106,9 +120,10 @@ public: Ptr createBackendFactory(FN_createCaptureFile createCaptureFile, FN_createCaptureCamera createCaptureCamera, + FN_createCaptureStream createCaptureStream, FN_createWriter createWriter) { - return makePtr(createCaptureFile, createCaptureCamera, createWriter).staticCast(); + return makePtr(createCaptureFile, createCaptureCamera, createCaptureStream, createWriter).staticCast(); } @@ -118,10 +133,11 @@ class StaticBackendWithParams: public IBackend public: FN_createCaptureFileWithParams fn_createCaptureFile_; FN_createCaptureCameraWithParams fn_createCaptureCamera_; + FN_createCaptureStreamWithParams fn_createCaptureStream_; FN_createWriter fn_createWriter_; - StaticBackendWithParams(FN_createCaptureFileWithParams fn_createCaptureFile, FN_createCaptureCameraWithParams fn_createCaptureCamera, FN_createWriter fn_createWriter) - : fn_createCaptureFile_(fn_createCaptureFile), fn_createCaptureCamera_(fn_createCaptureCamera), fn_createWriter_(fn_createWriter) + StaticBackendWithParams(FN_createCaptureFileWithParams fn_createCaptureFile, FN_createCaptureCameraWithParams fn_createCaptureCamera, FN_createCaptureStreamWithParams fn_createCaptureStream, FN_createWriter fn_createWriter) + : fn_createCaptureFile_(fn_createCaptureFile), fn_createCaptureCamera_(fn_createCaptureCamera), fn_createCaptureStream_(fn_createCaptureStream), fn_createWriter_(fn_createWriter) { // nothing } @@ -140,6 +156,12 @@ public: return fn_createCaptureFile_(filename, params); return Ptr(); } + Ptr createCapture(const Ptr& stream, const VideoCaptureParameters& params) const CV_OVERRIDE + { + if (fn_createCaptureStream_) + return fn_createCaptureStream_(stream, params); + return Ptr(); + } Ptr createWriter(const std::string& filename, int fourcc, double fps, const cv::Size& sz, const VideoWriterParameters& params) const CV_OVERRIDE { @@ -155,8 +177,8 @@ protected: Ptr backend; public: - StaticBackendWithParamsFactory(FN_createCaptureFileWithParams createCaptureFile, FN_createCaptureCameraWithParams createCaptureCamera, FN_createWriter createWriter) - : backend(makePtr(createCaptureFile, createCaptureCamera, createWriter)) + StaticBackendWithParamsFactory(FN_createCaptureFileWithParams createCaptureFile, FN_createCaptureCameraWithParams createCaptureCamera, FN_createCaptureStreamWithParams createCaptureStream, FN_createWriter createWriter) + : backend(makePtr(createCaptureFile, createCaptureCamera, createCaptureStream, createWriter)) { // nothing } @@ -174,9 +196,10 @@ public: Ptr createBackendFactory(FN_createCaptureFileWithParams createCaptureFile, FN_createCaptureCameraWithParams createCaptureCamera, + FN_createCaptureStreamWithParams createCaptureStream, FN_createWriter createWriter) { - return makePtr(createCaptureFile, createCaptureCamera, createWriter).staticCast(); + return makePtr(createCaptureFile, createCaptureCamera, createCaptureStream, createWriter).staticCast(); } diff --git a/modules/videoio/src/cap.cpp b/modules/videoio/src/cap.cpp index f8572aad08..97f5736052 100644 --- a/modules/videoio/src/cap.cpp +++ b/modules/videoio/src/cap.cpp @@ -65,6 +65,10 @@ static bool param_VIDEOWRITER_DEBUG = utils::getConfigurationParameterBool("OPEN void DefaultDeleter::operator ()(CvCapture* obj) const { cvReleaseCapture(&obj); } void DefaultDeleter::operator ()(CvVideoWriter* obj) const { cvReleaseVideoWriter(&obj); } +IStreamReader::~IStreamReader() +{ + // nothing +} VideoCapture::VideoCapture() : throwOnFail(false) {} @@ -82,6 +86,13 @@ VideoCapture::VideoCapture(const String& filename, int apiPreference, const std: open(filename, apiPreference, params); } +VideoCapture::VideoCapture(const Ptr& source, int apiPreference, const std::vector& params) + : throwOnFail(false) +{ + CV_TRACE_FUNCTION(); + open(source, apiPreference, params); +} + VideoCapture::VideoCapture(int index, int apiPreference) : throwOnFail(false) { CV_TRACE_FUNCTION(); @@ -188,7 +199,7 @@ bool VideoCapture::open(const String& filename, int apiPreference, const std::ve else { CV_CAPTURE_LOG_DEBUG(NULL, - cv::format("VIDEOIO(%s): backend is not available " + cv::format("VIDEOIO(%s): backend is not available " "(plugin is missing, or can't be loaded due " "dependencies or it is not compatible)", info.name)); @@ -228,6 +239,131 @@ bool VideoCapture::open(const String& filename, int apiPreference, const std::ve return false; } +bool VideoCapture::open(const Ptr& stream, int apiPreference, const std::vector& params) + +{ + CV_INSTRUMENT_REGION(); + + if (apiPreference == CAP_ANY) + { + CV_Error_(Error::StsBadArg, ("Avoid CAP_ANY - explicit backend expected to avoid read data stream reset")); + } + + if (isOpened()) + { + release(); + } + + const VideoCaptureParameters parameters(params); + const std::vector backends = cv::videoio_registry::getAvailableBackends_CaptureByStream(); + for (size_t i = 0; i < backends.size(); i++) + { + const VideoBackendInfo& info = backends[i]; + if (apiPreference != info.id) + continue; + + if (!info.backendFactory) + { + CV_LOG_DEBUG(NULL, "VIDEOIO(" << info.name << "): factory is not available (plugins require filesystem support)"); + continue; + } + CV_CAPTURE_LOG_DEBUG(NULL, + cv::format("VIDEOIO(%s): trying capture buffer ...", + info.name)); + CV_Assert(!info.backendFactory.empty()); + const Ptr backend = info.backendFactory->getBackend(); + if (!backend.empty()) + { + try + { + icap = backend->createCapture(stream, parameters); + if (!icap.empty()) + { + CV_CAPTURE_LOG_DEBUG(NULL, + cv::format("VIDEOIO(%s): created, isOpened=%d", + info.name, icap->isOpened())); + if (icap->isOpened()) + { + return true; + } + icap.release(); + } + else + { + CV_CAPTURE_LOG_DEBUG(NULL, + cv::format("VIDEOIO(%s): can't create capture", + info.name)); + } + } + catch (const cv::Exception& e) + { + if (throwOnFail) + { + throw; + } + CV_LOG_WARNING(NULL, + cv::format("VIDEOIO(%s): raised OpenCV exception:\n\n%s\n", + info.name, e.what())); + } + catch (const std::exception& e) + { + if (throwOnFail) + { + throw; + } + CV_LOG_WARNING(NULL, cv::format("VIDEOIO(%s): raised C++ exception:\n\n%s\n", + info.name, e.what())); + } + catch (...) + { + if (throwOnFail) + { + throw; + } + CV_LOG_WARNING(NULL, + cv::format("VIDEOIO(%s): raised unknown C++ exception!\n\n", + info.name)); + } + } + else + { + CV_CAPTURE_LOG_DEBUG(NULL, + cv::format("VIDEOIO(%s): backend is not available " + "(plugin is missing, or can't be loaded due " + "dependencies or it is not compatible)", + info.name)); + } + } + + bool found = cv::videoio_registry::isBackendBuiltIn(static_cast(apiPreference)); + if (found) + { + CV_LOG_WARNING(NULL, cv::format("VIDEOIO(%s): backend is generally available " + "but can't be used to capture by read data stream", + cv::videoio_registry::getBackendName(static_cast(apiPreference)).c_str())); + } + + if (throwOnFail) + { + CV_Error_(Error::StsError, ("could not open read data stream")); + } + + if (cv::videoio_registry::checkDeprecatedBackend(apiPreference)) + { + CV_LOG_DEBUG(NULL, + cv::format("VIDEOIO(%s): backend is removed from OpenCV", + cv::videoio_registry::getBackendName((VideoCaptureAPIs) apiPreference).c_str())); + } + else + { + CV_LOG_DEBUG(NULL, "VIDEOIO: choosen backend does not work or wrong. " + "Please make sure that your computer support chosen backend and OpenCV built " + "with right flags."); + } + + return false; +} + bool VideoCapture::open(int cameraNum, int apiPreference) { return open(cameraNum, apiPreference, std::vector()); @@ -326,7 +462,7 @@ bool VideoCapture::open(int cameraNum, int apiPreference, const std::vector else { CV_CAPTURE_LOG_DEBUG(NULL, - cv::format("VIDEOIO(%s): backend is not available " + cv::format("VIDEOIO(%s): backend is not available " "(plugin is missing, or can't be loaded due " "dependencies or it is not compatible)", info.name)); diff --git a/modules/videoio/src/cap_ffmpeg.cpp b/modules/videoio/src/cap_ffmpeg.cpp index 4b1f9bb857..62f64cb0f7 100644 --- a/modules/videoio/src/cap_ffmpeg.cpp +++ b/modules/videoio/src/cap_ffmpeg.cpp @@ -74,6 +74,11 @@ public: { open(filename, params); } + CvCapture_FFMPEG_proxy(const Ptr& stream, const cv::VideoCaptureParameters& params) + : ffmpegCapture(NULL) + { + open(stream, params); + } virtual ~CvCapture_FFMPEG_proxy() { close(); } virtual double getProperty_(int propId) const CV_OVERRIDE @@ -122,6 +127,14 @@ public: ffmpegCapture = cvCreateFileCaptureWithParams_FFMPEG(filename.c_str(), params); return ffmpegCapture != 0; } + bool open(const Ptr& stream, const cv::VideoCaptureParameters& params) + { + close(); + + readStream = stream; // Increase counter + ffmpegCapture = cvCreateStreamCaptureWithParams_FFMPEG(stream, params); + return ffmpegCapture != 0; + } void close() { if (ffmpegCapture) @@ -135,6 +148,7 @@ public: protected: CvCapture_FFMPEG* ffmpegCapture; + Ptr readStream; }; } // namespace @@ -147,6 +161,14 @@ cv::Ptr cvCreateFileCapture_FFMPEG_proxy(const std::string &f return cv::Ptr(); } +cv::Ptr cvCreateStreamCapture_FFMPEG_proxy(const Ptr& stream, const cv::VideoCaptureParameters& params) +{ + cv::Ptr capture = std::make_shared(stream, params); + if (capture && capture->isOpened()) + return capture; + return cv::Ptr(); +} + namespace { class CvVideoWriter_FFMPEG_proxy CV_FINAL : @@ -234,7 +256,7 @@ cv::Ptr cvCreateVideoWriter_FFMPEG_proxy(const std::string& fi #include "plugin_api.hpp" #else #define CAPTURE_ABI_VERSION 1 -#define CAPTURE_API_VERSION 1 +#define CAPTURE_API_VERSION 2 #include "plugin_capture_api.hpp" #define WRITER_ABI_VERSION 1 #define WRITER_API_VERSION 1 @@ -255,7 +277,7 @@ CvResult CV_API_CALL cv_capture_open(const char* filename, int camera_index, CV_ CvCapture_FFMPEG_proxy *cap = 0; try { - cap = new CvCapture_FFMPEG_proxy(filename, cv::VideoCaptureParameters()); + cap = new CvCapture_FFMPEG_proxy(String(filename), cv::VideoCaptureParameters()); if (cap->isOpened()) { *handle = (CvPluginCapture)cap; @@ -292,7 +314,43 @@ CvResult CV_API_CALL cv_capture_open_with_params( try { cv::VideoCaptureParameters parameters(params, n_params); - cap = new CvCapture_FFMPEG_proxy(filename, parameters); + cap = new CvCapture_FFMPEG_proxy(String(filename), parameters); + if (cap->isOpened()) + { + *handle = (CvPluginCapture)cap; + return CV_ERROR_OK; + } + } + catch (const std::exception& e) + { + CV_LOG_WARNING(NULL, "FFmpeg: Exception is raised: " << e.what()); + } + catch (...) + { + CV_LOG_WARNING(NULL, "FFmpeg: Unknown C++ exception is raised"); + } + if (cap) + delete cap; + return CV_ERROR_FAIL; +} + +static +CvResult CV_API_CALL cv_capture_open_buffer( + void* opaque, + long long(*read)(void* opaque, char* buffer, long long size), + long long(*seek)(void* opaque, long long offset, int way), + int* params, unsigned n_params, + CV_OUT CvPluginCapture* handle +) +{ + if (!handle) + return CV_ERROR_FAIL; + *handle = NULL; + CvCapture_FFMPEG_proxy *cap = 0; + try + { + cv::VideoCaptureParameters parameters(params, n_params); + cap = new CvCapture_FFMPEG_proxy(makePtr(opaque, read, seek), parameters); if (cap->isOpened()) { *handle = (CvPluginCapture)cap; @@ -609,6 +667,9 @@ static const OpenCV_VideoIO_Capture_Plugin_API capture_plugin_api = }, { /* 8*/cv_capture_open_with_params, + }, + { + /* 9*/cv_capture_open_buffer, } }; diff --git a/modules/videoio/src/cap_ffmpeg_impl.hpp b/modules/videoio/src/cap_ffmpeg_impl.hpp index f13cb8462b..d2359b066e 100644 --- a/modules/videoio/src/cap_ffmpeg_impl.hpp +++ b/modules/videoio/src/cap_ffmpeg_impl.hpp @@ -526,7 +526,7 @@ inline static std::string _opencv_ffmpeg_get_error_string(int error_code) struct CvCapture_FFMPEG { - bool open(const char* filename, const VideoCaptureParameters& params); + bool open(const char* filename, const Ptr& stream, const VideoCaptureParameters& params); void close(); double getProperty(int) const; @@ -563,6 +563,8 @@ struct CvCapture_FFMPEG int64_t pts_in_fps_time_base; int64_t dts_delay_in_fps_time_base; + AVIOContext * avio_context; + AVPacket packet; Image_FFMPEG frame; struct SwsContext *img_convert_ctx; @@ -580,6 +582,8 @@ struct CvCapture_FFMPEG */ char * filename; + Ptr readStream; + AVDictionary *dict; #if USE_AV_INTERRUPT_CALLBACK int open_timeout; @@ -628,11 +632,14 @@ void CvCapture_FFMPEG::init() avcodec = 0; context = 0; + avio_context = 0; frame_number = 0; eps_zero = 0.000025; rotation_angle = 0; + readStream.reset(); + dict = NULL; #if USE_AV_INTERRUPT_CALLBACK @@ -730,6 +737,13 @@ void CvCapture_FFMPEG::close() #endif } + if (avio_context) + { + av_free(avio_context->buffer); + av_freep(&avio_context); + } + readStream.reset(); + init(); } @@ -1019,7 +1033,7 @@ static bool isThreadSafe() { return threadSafe; } -bool CvCapture_FFMPEG::open(const char* _filename, const VideoCaptureParameters& params) +bool CvCapture_FFMPEG::open(const char* _filename, const Ptr& stream, const VideoCaptureParameters& params) { const bool threadSafe = isThreadSafe(); InternalFFMpegRegister::init(threadSafe); @@ -1034,6 +1048,8 @@ bool CvCapture_FFMPEG::open(const char* _filename, const VideoCaptureParameters& close(); + readStream = stream; + if (!params.empty()) { convertRGB = params.get(CAP_PROP_CONVERT_RGB, true); @@ -1145,6 +1161,56 @@ bool CvCapture_FFMPEG::open(const char* _filename, const VideoCaptureParameters& input_format = av_find_input_format(entry->value); } + if (!_filename) + { + size_t avio_ctx_buffer_size = 4096; + uint8_t* avio_ctx_buffer = (uint8_t*)av_malloc(avio_ctx_buffer_size); + CV_Assert(avio_ctx_buffer); + avio_context = avio_alloc_context(avio_ctx_buffer, avio_ctx_buffer_size, 0, this, + [](void *opaque, uint8_t *buf, int buf_size) -> int { + try { + auto capture = reinterpret_cast(opaque); + auto is = capture->readStream; + int result = (int)is->read(reinterpret_cast(buf), buf_size); + + // https://github.com/FFmpeg/FFmpeg/commit/858db4b01fa2b55ee55056c033054ca54ac9b0fd#diff-863c87afc9bb02fe42d071015fc8218972c80b146d603239f20b483ad0988ae9R394 + // https://github.com/FFmpeg/FFmpeg/commit/a606f27f4c610708fa96e35eed7b7537d3d8f712 + // https://github.com/FFmpeg/FFmpeg/blob/n4.0/libavformat/version.h#L83C41-L83C73 +#if (LIBAVFORMAT_VERSION_MAJOR >= 58) && (LIBAVFORMAT_VERSION_MICRO >= 100) // FFmpeg n4.0+ + if (result == 0 && buf_size > 0) + { + result = AVERROR_EOF; + } +#endif + + CV_LOG_VERBOSE(NULL, 0, "FFMPEG: IStreamReader::read(" << buf_size << ") = " << result); + return result; + } catch (...) { + CV_LOG_WARNING(NULL, "FFMPEG: IStreamReader::read(" << buf_size << ") failed"); + return 0; + } + }, + NULL, + [](void *opaque, int64_t offset, int whence) -> int64_t { + try { + int64_t result = -1; + auto capture = reinterpret_cast(opaque); + auto is = capture->readStream; + int origin = whence & (~AVSEEK_FORCE); + if (origin == SEEK_SET || origin == SEEK_CUR || origin == SEEK_END) + { + result = is->seek(offset, origin); + } + CV_LOG_VERBOSE(NULL, 0, "FFMPEG: IStreamReader::seek(" << offset << ", whence=" << whence << ") = " << result); + return result; + } catch (...) { + CV_LOG_WARNING(NULL, "FFMPEG: IStreamReader::seek(" << offset << ", whence=" << whence << ") failed"); + return -1; + } + }); + CV_Assert(avio_context); + ic->pb = avio_context; + } int err = avformat_open_input(&ic, _filename, input_format, &dict); if (err < 0) @@ -3292,16 +3358,30 @@ bool CvVideoWriter_FFMPEG::open( const char * filename, int fourcc, static CvCapture_FFMPEG* cvCreateFileCaptureWithParams_FFMPEG(const char* filename, const VideoCaptureParameters& params) { - // FIXIT: remove unsafe malloc() approach - CvCapture_FFMPEG* capture = (CvCapture_FFMPEG*)malloc(sizeof(*capture)); + CvCapture_FFMPEG* capture = new CvCapture_FFMPEG(); if (!capture) return 0; capture->init(); - if (capture->open(filename, params)) + if (capture->open(filename, nullptr, params)) return capture; capture->close(); - free(capture); + delete capture; + return 0; +} + +static +CvCapture_FFMPEG* cvCreateStreamCaptureWithParams_FFMPEG(const Ptr& stream, const VideoCaptureParameters& params) +{ + CvCapture_FFMPEG* capture = new CvCapture_FFMPEG(); + if (!capture) + return 0; + capture->init(); + if (capture->open(nullptr, stream, params)) + return capture; + + capture->close(); + delete capture; return 0; } @@ -3310,7 +3390,7 @@ void cvReleaseCapture_FFMPEG(CvCapture_FFMPEG** capture) if( capture && *capture ) { (*capture)->close(); - free(*capture); + delete *capture; *capture = 0; } } @@ -3344,14 +3424,14 @@ int cvRetrieveFrame2_FFMPEG(CvCapture_FFMPEG* capture, unsigned char** data, int static CvVideoWriter_FFMPEG* cvCreateVideoWriterWithParams_FFMPEG( const char* filename, int fourcc, double fps, int width, int height, const VideoWriterParameters& params ) { - CvVideoWriter_FFMPEG* writer = (CvVideoWriter_FFMPEG*)malloc(sizeof(*writer)); + CvVideoWriter_FFMPEG* writer = new CvVideoWriter_FFMPEG(); if (!writer) return 0; writer->init(); if( writer->open( filename, fourcc, fps, width, height, params )) return writer; writer->close(); - free(writer); + delete writer; return 0; } @@ -3368,7 +3448,7 @@ void cvReleaseVideoWriter_FFMPEG( CvVideoWriter_FFMPEG** writer ) if( writer && *writer ) { (*writer)->close(); - free(*writer); + delete *writer; *writer = 0; } } diff --git a/modules/videoio/src/cap_interface.hpp b/modules/videoio/src/cap_interface.hpp index a8e3e49dd0..624da9358a 100644 --- a/modules/videoio/src/cap_interface.hpp +++ b/modules/videoio/src/cap_interface.hpp @@ -9,6 +9,7 @@ #include "opencv2/core/core_c.h" #include "opencv2/videoio.hpp" #include "opencv2/videoio/videoio_c.h" +#include "opencv2/videoio/utils.private.hpp" //=================================================== @@ -326,6 +327,7 @@ protected: //================================================================================================== Ptr cvCreateFileCapture_FFMPEG_proxy(const std::string &filename, const VideoCaptureParameters& params); +Ptr cvCreateStreamCapture_FFMPEG_proxy(const Ptr& stream, const VideoCaptureParameters& params); Ptr cvCreateVideoWriter_FFMPEG_proxy(const std::string& filename, int fourcc, double fps, const Size& frameSize, const VideoWriterParameters& params); @@ -351,6 +353,7 @@ Ptr create_WRT_capture(int device); Ptr cvCreateCapture_MSMF(int index, const VideoCaptureParameters& params); Ptr cvCreateCapture_MSMF(const std::string& filename, const VideoCaptureParameters& params); +Ptr cvCreateCapture_MSMF(const Ptr& stream, const VideoCaptureParameters& params); Ptr cvCreateVideoWriter_MSMF(const std::string& filename, int fourcc, double fps, const Size& frameSize, const VideoWriterParameters& params); diff --git a/modules/videoio/src/cap_msmf.cpp b/modules/videoio/src/cap_msmf.cpp index 138e26e87a..5c2121498d 100644 --- a/modules/videoio/src/cap_msmf.cpp +++ b/modules/videoio/src/cap_msmf.cpp @@ -746,7 +746,7 @@ public: virtual ~CvCapture_MSMF(); bool configureHW(const cv::VideoCaptureParameters& params); virtual bool open(int, const cv::VideoCaptureParameters* params); - virtual bool open(const cv::String&, const cv::VideoCaptureParameters* params); + virtual bool open(const cv::String&, const Ptr&, const cv::VideoCaptureParameters* params); virtual void close(); virtual double getProperty(int) const CV_OVERRIDE; virtual bool setProperty(int, double) CV_OVERRIDE; @@ -789,6 +789,7 @@ protected: _ComPtr D3DDev; _ComPtr D3DMgr; #endif + _ComPtr byteStream; _ComPtr videoFileSource; _ComPtr readCallback; // non-NULL for "live" streams (camera capture) std::vector dwStreamIndices; @@ -1034,7 +1035,7 @@ bool CvCapture_MSMF::configureHW(bool enable) } } // Reopen if needed - return reopen ? (prevcam >= 0 ? open(prevcam, NULL) : open(prevfile.c_str(), NULL)) : true; + return reopen ? (prevcam >= 0 ? open(prevcam, NULL) : open(prevfile.c_str(), nullptr, NULL)) : true; } D3DMgr.Release(); } @@ -1050,7 +1051,7 @@ bool CvCapture_MSMF::configureHW(bool enable) if (D3DDev) D3DDev.Release(); captureMode = MODE_SW; - return reopen ? (prevcam >= 0 ? open(prevcam, NULL) : open(prevfile.c_str(), NULL)) : true; + return reopen ? (prevcam >= 0 ? open(prevcam, NULL) : open(prevfile.c_str(), nullptr, NULL)) : true; } #else return !enable; @@ -1249,10 +1250,10 @@ bool CvCapture_MSMF::open(int index, const cv::VideoCaptureParameters* params) return isOpen; } -bool CvCapture_MSMF::open(const cv::String& _filename, const cv::VideoCaptureParameters* params) +bool CvCapture_MSMF::open(const cv::String& _filename, const Ptr& stream, const cv::VideoCaptureParameters* params) { close(); - if (_filename.empty()) + if (_filename.empty() && !stream) return false; if (params) @@ -1263,9 +1264,34 @@ bool CvCapture_MSMF::open(const cv::String& _filename, const cv::VideoCapturePar } // Set source reader parameters _ComPtr attr = getDefaultSourceConfig(); - cv::AutoBuffer unicodeFileName(_filename.length() + 1); - MultiByteToWideChar(CP_ACP, 0, _filename.c_str(), -1, unicodeFileName.data(), (int)_filename.length() + 1); - if (SUCCEEDED(MFCreateSourceReaderFromURL(unicodeFileName.data(), attr.Get(), &videoFileSource))) + bool succeeded = false; + if (!_filename.empty()) + { + cv::AutoBuffer unicodeFileName(_filename.length() + 1); + MultiByteToWideChar(CP_ACP, 0, _filename.c_str(), -1, unicodeFileName.data(), (int)_filename.length() + 1); + succeeded = SUCCEEDED(MFCreateSourceReaderFromURL(unicodeFileName.data(), attr.Get(), &videoFileSource)); + } + else if (stream) + { + // TODO: implement read by chunks + // FIXIT: save stream in field + std::vector data; + data.resize((size_t)stream->seek(0, SEEK_END)); + stream->seek(0, SEEK_SET); + stream->read(data.data(), data.size()); + IStream* s = SHCreateMemStream(reinterpret_cast(data.data()), static_cast(data.size())); + if (!s) + return false; + + succeeded = SUCCEEDED(MFCreateMFByteStreamOnStream(s, &byteStream)); + if (!succeeded) + return false; + if (!SUCCEEDED(MFStartup(MF_VERSION))) + return false; + succeeded = SUCCEEDED(MFCreateSourceReaderFromByteStream(byteStream.Get(), attr.Get(), &videoFileSource)); + } + + if (succeeded) { isOpen = true; usedVideoSampleTime = 0; @@ -2375,12 +2401,24 @@ cv::Ptr cv::cvCreateCapture_MSMF( int index, const cv::VideoC return cv::Ptr(); } -cv::Ptr cv::cvCreateCapture_MSMF (const cv::String& filename, const cv::VideoCaptureParameters& params) +cv::Ptr cv::cvCreateCapture_MSMF(const cv::String& filename, const cv::VideoCaptureParameters& params) { cv::Ptr capture = cv::makePtr(); if (capture) { - capture->open(filename, ¶ms); + capture->open(filename, nullptr, ¶ms); + if (capture->isOpened()) + return capture; + } + return cv::Ptr(); +} + +cv::Ptr cv::cvCreateCapture_MSMF(const Ptr& stream, const cv::VideoCaptureParameters& params) +{ + cv::Ptr capture = cv::makePtr(); + if (capture) + { + capture->open(std::string(), stream, ¶ms); if (capture->isOpened()) return capture; } @@ -2707,7 +2745,7 @@ cv::Ptr cv::cvCreateVideoWriter_MSMF( const std::string& filen #include "plugin_api.hpp" #else #define CAPTURE_ABI_VERSION 1 -#define CAPTURE_API_VERSION 1 +#define CAPTURE_API_VERSION 2 #include "plugin_capture_api.hpp" #define WRITER_ABI_VERSION 1 #define WRITER_API_VERSION 1 @@ -2736,7 +2774,9 @@ CvResult CV_API_CALL cv_capture_open_with_params( cap = new CaptureT(); bool res; if (filename) - res = cap->open(std::string(filename), ¶meters); + { + res = cap->open(std::string(filename), nullptr, ¶meters); + } else res = cap->open(camera_index, ¶meters); if (res) @@ -2758,6 +2798,44 @@ CvResult CV_API_CALL cv_capture_open_with_params( return CV_ERROR_FAIL; } +static +CvResult CV_API_CALL cv_capture_open_buffer( + void* opaque, + long long(*read)(void* opaque, char* buffer, long long size), + long long(*seek)(void* opaque, long long offset, int way), + int* params, unsigned n_params, + CV_OUT CvPluginCapture* handle +) +{ + if (!handle) + return CV_ERROR_FAIL; + + *handle = NULL; + CaptureT* cap = 0; + try + { + cv::VideoCaptureParameters parameters(params, n_params); + cap = new CaptureT(); + bool res = cap->open(std::string(), makePtr(opaque, read, seek), ¶meters); + if (res) + { + *handle = (CvPluginCapture)cap; + return CV_ERROR_OK; + } + } + catch (const std::exception& e) + { + CV_LOG_WARNING(NULL, "MSMF: Exception is raised: " << e.what()); + } + catch (...) + { + CV_LOG_WARNING(NULL, "MSMF: Unknown C++ exception is raised"); + } + if (cap) + delete cap; + return CV_ERROR_FAIL; +} + static CvResult CV_API_CALL cv_capture_open(const char* filename, int camera_index, CV_OUT CvPluginCapture* handle) { @@ -3027,6 +3105,9 @@ static const OpenCV_VideoIO_Capture_Plugin_API capture_plugin_api = }, { /* 8*/cv::cv_capture_open_with_params, + }, + { + /* 9*/cv::cv_capture_open_buffer, } }; diff --git a/modules/videoio/src/plugin_capture_api.hpp b/modules/videoio/src/plugin_capture_api.hpp index 8f33d40219..c616bbc928 100644 --- a/modules/videoio/src/plugin_capture_api.hpp +++ b/modules/videoio/src/plugin_capture_api.hpp @@ -13,7 +13,7 @@ /// increased for backward-compatible changes, e.g. add new function /// Caller API <= Plugin API -> plugin is fully compatible /// Caller API > Plugin API -> plugin is not fully compatible, caller should use extra checks to use plugins with older API -#define CAPTURE_API_VERSION 1 +#define CAPTURE_API_VERSION 2 /// increased for incompatible changes, e.g. remove function argument /// Caller ABI == Plugin ABI -> plugin is compatible @@ -121,6 +121,29 @@ struct OpenCV_VideoIO_Capture_Plugin_API_v1_1_api_entries CV_OUT CvPluginCapture* handle); }; // OpenCV_VideoIO_Capture_Plugin_API_v1_1_api_entries +struct OpenCV_VideoIO_Capture_Plugin_API_v1_2_api_entries +{ + /** @brief Open video capture from buffer with parameters + + @param opaque A pointer to user data + @param read A pointer to a function that is called to reads @p size bytes to allocated @p buffer. Returns a number of bytes that were actually read + @param seek A pointer to a function that is called to move starting position inside the stream buffer. + @p offset is a number of bytes and @p way is one of the markers SEEK_SET, SEEK_CUR, SEEK_END. + Function returns an absolute current position in bytes. + @param params pointer on 2*n_params array of 'key,value' pairs + @param n_params number of passed parameters + @param[out] handle pointer on Capture handle + + @note API-CALL 9, API-Version == 2 + */ + CvResult (CV_API_CALL *Capture_open_stream)( + void* opaque, + long long(*read)(void* opaque, char* buffer, long long size), + long long(*seek)(void* opaque, long long offset, int way), + int* params, unsigned n_params, + CV_OUT CvPluginCapture* handle); +}; // OpenCV_VideoIO_Capture_Plugin_API_v1_2_api_entries + typedef struct OpenCV_VideoIO_Capture_Plugin_API_v1_0 { OpenCV_API_Header api_header; @@ -134,7 +157,17 @@ typedef struct OpenCV_VideoIO_Capture_Plugin_API_v1_1 struct OpenCV_VideoIO_Capture_Plugin_API_v1_1_api_entries v1; } OpenCV_VideoIO_Capture_Plugin_API_v1_1; -#if CAPTURE_ABI_VERSION == 1 && CAPTURE_API_VERSION == 1 +typedef struct OpenCV_VideoIO_Capture_Plugin_API_v1_2 +{ + OpenCV_API_Header api_header; + struct OpenCV_VideoIO_Capture_Plugin_API_v1_0_api_entries v0; + struct OpenCV_VideoIO_Capture_Plugin_API_v1_1_api_entries v1; + struct OpenCV_VideoIO_Capture_Plugin_API_v1_2_api_entries v2; +} OpenCV_VideoIO_Capture_Plugin_API_v1_2; + +#if CAPTURE_ABI_VERSION == 1 && CAPTURE_API_VERSION == 2 +typedef struct OpenCV_VideoIO_Capture_Plugin_API_v1_2 OpenCV_VideoIO_Capture_Plugin_API; +#elif CAPTURE_ABI_VERSION == 1 && CAPTURE_API_VERSION == 1 typedef struct OpenCV_VideoIO_Capture_Plugin_API_v1_1 OpenCV_VideoIO_Capture_Plugin_API; #elif CAPTURE_ABI_VERSION == 1 && CAPTURE_API_VERSION == 0 typedef struct OpenCV_VideoIO_Capture_Plugin_API_v1_0 OpenCV_VideoIO_Capture_Plugin_API; diff --git a/modules/videoio/src/videoio_registry.cpp b/modules/videoio/src/videoio_registry.cpp index a3aee5abfe..a84258ad90 100644 --- a/modules/videoio/src/videoio_registry.cpp +++ b/modules/videoio/src/videoio_registry.cpp @@ -47,7 +47,12 @@ namespace { #define DECLARE_STATIC_BACKEND(cap, name, mode, createCaptureFile, createCaptureCamera, createWriter) \ { \ - cap, (BackendMode)(mode), 1000, name, createBackendFactory(createCaptureFile, createCaptureCamera, createWriter) \ + cap, (BackendMode)(mode), 1000, name, createBackendFactory(createCaptureFile, createCaptureCamera, 0, createWriter) \ +}, + +#define DECLARE_STATIC_BACKEND_WITH_STREAM_SUPPORT(cap, name, mode, createCaptureStream) \ +{ \ + cap, (BackendMode)(mode), 1000, name, createBackendFactory(0, 0, createCaptureStream, 0) \ }, /** Ordering guidelines: @@ -62,8 +67,9 @@ static const struct VideoBackendInfo builtin_backends[] = { #ifdef HAVE_FFMPEG DECLARE_STATIC_BACKEND(CAP_FFMPEG, "FFMPEG", MODE_CAPTURE_BY_FILENAME | MODE_WRITER, cvCreateFileCapture_FFMPEG_proxy, 0, cvCreateVideoWriter_FFMPEG_proxy) + DECLARE_STATIC_BACKEND_WITH_STREAM_SUPPORT(CAP_FFMPEG, "FFMPEG", MODE_CAPTURE_BY_STREAM, cvCreateStreamCapture_FFMPEG_proxy) #elif defined(ENABLE_PLUGINS) || defined(HAVE_FFMPEG_WRAPPER) - DECLARE_DYNAMIC_BACKEND(CAP_FFMPEG, "FFMPEG", MODE_CAPTURE_BY_FILENAME | MODE_WRITER) + DECLARE_DYNAMIC_BACKEND(CAP_FFMPEG, "FFMPEG", MODE_CAPTURE_BY_FILENAME | MODE_CAPTURE_BY_STREAM | MODE_WRITER) #endif #ifdef HAVE_GSTREAMER @@ -90,8 +96,9 @@ static const struct VideoBackendInfo builtin_backends[] = #ifdef HAVE_MSMF DECLARE_STATIC_BACKEND(CAP_MSMF, "MSMF", MODE_CAPTURE_ALL | MODE_WRITER, cvCreateCapture_MSMF, cvCreateCapture_MSMF, cvCreateVideoWriter_MSMF) + DECLARE_STATIC_BACKEND_WITH_STREAM_SUPPORT(CAP_MSMF, "MSMF", MODE_CAPTURE_BY_STREAM, cvCreateCapture_MSMF) #elif defined(ENABLE_PLUGINS) && defined(_WIN32) - DECLARE_DYNAMIC_BACKEND(CAP_MSMF, "MSMF", MODE_CAPTURE_ALL | MODE_WRITER) + DECLARE_DYNAMIC_BACKEND(CAP_MSMF, "MSMF", MODE_CAPTURE_ALL | MODE_CAPTURE_BY_STREAM | MODE_WRITER) #endif #ifdef HAVE_DSHOW @@ -330,6 +337,17 @@ public: } return result; } + inline std::vector getAvailableBackends_CaptureByStream() const + { + std::vector result; + for (size_t i = 0; i < enabledBackends.size(); i++) + { + const VideoBackendInfo& info = enabledBackends[i]; + if (info.mode & MODE_CAPTURE_BY_STREAM) + result.push_back(info); + } + return result; + } inline std::vector getAvailableBackends_Writer() const { std::vector result; @@ -357,6 +375,11 @@ std::vector getAvailableBackends_CaptureByFilename() const std::vector result = VideoBackendRegistry::getInstance().getAvailableBackends_CaptureByFilename(); return result; } +std::vector getAvailableBackends_CaptureByStream() +{ + const std::vector result = VideoBackendRegistry::getInstance().getAvailableBackends_CaptureByStream(); + return result; +} std::vector getAvailableBackends_Writer() { const std::vector result = VideoBackendRegistry::getInstance().getAvailableBackends_Writer(); @@ -424,6 +447,15 @@ std::vector getStreamBackends() } +std::vector getStreamBufferedBackends() +{ + const std::vector backends = VideoBackendRegistry::getInstance().getAvailableBackends_CaptureByStream(); + std::vector result; + for (size_t i = 0; i < backends.size(); i++) + result.push_back((VideoCaptureAPIs)backends[i].id); + return result; +} + std::vector getWriterBackends() { const std::vector backends = VideoBackendRegistry::getInstance().getAvailableBackends_Writer(); @@ -501,6 +533,24 @@ std::string getStreamBackendPluginVersion(VideoCaptureAPIs api, CV_Error(Error::StsError, "Unknown or wrong backend ID"); } +std::string getStreamBufferedBackendPluginVersion(VideoCaptureAPIs api, + CV_OUT int& version_ABI, + CV_OUT int& version_API +) +{ + const std::vector backends = VideoBackendRegistry::getInstance().getAvailableBackends_CaptureByStream(); + for (size_t i = 0; i < backends.size(); i++) + { + const VideoBackendInfo& info = backends[i]; + if (api == info.id) + { + CV_Assert(!info.backendFactory.empty()); + CV_Assert(!info.backendFactory->isBuiltIn()); + return getCapturePluginVersion(info.backendFactory, version_ABI, version_API); + } + } + CV_Error(Error::StsError, "Unknown or wrong backend ID"); +} /** @brief Returns description and ABI/API version of videoio plugin's writer interface */ std::string getWriterBackendPluginVersion(VideoCaptureAPIs api, diff --git a/modules/videoio/src/videoio_registry.hpp b/modules/videoio/src/videoio_registry.hpp index 3a81df84b5..e2fe579a2f 100644 --- a/modules/videoio/src/videoio_registry.hpp +++ b/modules/videoio/src/videoio_registry.hpp @@ -14,6 +14,7 @@ namespace cv enum BackendMode { MODE_CAPTURE_BY_INDEX = 1 << 0, //!< device index MODE_CAPTURE_BY_FILENAME = 1 << 1, //!< filename or device path (v4l2) + MODE_CAPTURE_BY_STREAM = 1 << 2, //!< data stream MODE_WRITER = 1 << 4, //!< writer MODE_CAPTURE_ALL = MODE_CAPTURE_BY_INDEX + MODE_CAPTURE_BY_FILENAME, @@ -38,6 +39,7 @@ namespace videoio_registry { std::vector getAvailableBackends_CaptureByIndex(); std::vector getAvailableBackends_CaptureByFilename(); +std::vector getAvailableBackends_CaptureByStream(); std::vector getAvailableBackends_Writer(); bool checkDeprecatedBackend(int api); diff --git a/modules/videoio/test/test_video_io.cpp b/modules/videoio/test/test_video_io.cpp index 001035f35a..f3c0862999 100644 --- a/modules/videoio/test/test_video_io.cpp +++ b/modules/videoio/test/test_video_io.cpp @@ -3,6 +3,7 @@ // of this distribution and at http://opencv.org/license.html. #include "test_precomp.hpp" +#include "opencv2/core/utils/filesystem.hpp" namespace opencv_test { @@ -1024,4 +1025,133 @@ INSTANTIATE_TEST_CASE_P(videoio, videowriter_acceleration, testing::Combine( testing::ValuesIn(hw_use_umat) )); +class BufferStream : public cv::IStreamReader +{ +public: + BufferStream(const std::string& filename) + { + Ptr file = makePtr(); + file->open(filename.c_str(), std::ios::in | std::ios::binary); + stream = file; + } + + BufferStream(const Ptr& _stream) : stream(_stream) {} + + long long read(char* buffer, long long size) CV_OVERRIDE + { + auto result = stream->sgetn(buffer, size); + return result; + } + + long long seek(long long offset, int way) CV_OVERRIDE + { + auto result = stream->pubseekoff(offset, way == SEEK_SET ? std::ios_base::beg : (way == SEEK_END ? std::ios_base::end : std::ios_base::cur)); + return result; + } + +private: + Ptr stream; +}; + +typedef testing::TestWithParam> stream_capture; +TEST_P(stream_capture, read) +{ + std::string ext = get<0>(GetParam()); + VideoCaptureAPIs apiPref = get<1>(GetParam()); + std::vector supportedAPIs = videoio_registry::getStreamBufferedBackends(); + if (!videoio_registry::hasBackend(apiPref)) + throw SkipTestException(cv::String("Backend is not available/disabled: ") + cv::videoio_registry::getBackendName(apiPref)); + if (std::find(supportedAPIs.begin(), supportedAPIs.end(), apiPref) == supportedAPIs.end()) + throw SkipTestException(cv::String("Backend is not supported: ") + cv::videoio_registry::getBackendName(apiPref)); + if (cvtest::skipUnstableTests && apiPref == CAP_MSMF && (ext == "h264" || ext == "h265" || ext == "mpg")) + throw SkipTestException("Unstable MSMF test"); + + if (!videoio_registry::isBackendBuiltIn(apiPref)) + { + int pluginABI, pluginAPI; + videoio_registry::getStreamBufferedBackendPluginVersion(apiPref, pluginABI, pluginAPI); + if (pluginABI < 1 || (pluginABI == 1 && pluginAPI < 2)) + throw SkipTestException(format("Buffer capture supported since ABI/API = 1/2. %s plugin is %d/%d", + cv::videoio_registry::getBackendName(apiPref).c_str(), pluginABI, pluginAPI)); + } + + VideoCapture cap; + String video_file = BunnyParameters::getFilename(String(".") + ext); + ASSERT_TRUE(utils::fs::exists(video_file)); + EXPECT_NO_THROW(cap.open(makePtr(video_file), apiPref, {})); + ASSERT_TRUE(cap.isOpened()); + + const int numFrames = 10; + Mat frames[numFrames]; + Mat hardCopies[numFrames]; + for(int i = 0; i < numFrames; i++) + { + ASSERT_NO_THROW(cap >> frames[i]); + EXPECT_FALSE(frames[i].empty()); + hardCopies[i] = frames[i].clone(); + } + + for(int i = 0; i < numFrames; i++) + EXPECT_EQ(0, cv::norm(frames[i], hardCopies[i], NORM_INF)) << i; +} +INSTANTIATE_TEST_CASE_P(videoio, stream_capture, + testing::Combine( + testing::ValuesIn(bunny_params), + testing::ValuesIn(backend_params))); + +// This test for stream input for container format (See test_ffmpeg/videoio_container.read test) +typedef testing::TestWithParam stream_capture_ffmpeg; +TEST_P(stream_capture_ffmpeg, raw) +{ + std::string ext = GetParam(); + VideoCaptureAPIs apiPref = CAP_FFMPEG; + std::vector supportedAPIs = videoio_registry::getStreamBufferedBackends(); + if (!videoio_registry::hasBackend(apiPref)) + throw SkipTestException(cv::String("Backend is not available/disabled: ") + cv::videoio_registry::getBackendName(apiPref)); + if (std::find(supportedAPIs.begin(), supportedAPIs.end(), apiPref) == supportedAPIs.end()) + throw SkipTestException(cv::String("Backend is not supported: ") + cv::videoio_registry::getBackendName(apiPref)); + + if (!videoio_registry::isBackendBuiltIn(apiPref)) + { + int pluginABI, pluginAPI; + videoio_registry::getStreamBufferedBackendPluginVersion(apiPref, pluginABI, pluginAPI); + if (pluginABI < 1 || (pluginABI == 1 && pluginAPI < 2)) + throw SkipTestException(format("Buffer capture supported since ABI/API = 1/2. %s plugin is %d/%d", + cv::videoio_registry::getBackendName(apiPref).c_str(), pluginABI, pluginAPI)); + } + + VideoCapture container; + String video_file = BunnyParameters::getFilename(String(".") + ext); + ASSERT_TRUE(utils::fs::exists(video_file)); + EXPECT_NO_THROW(container.open(video_file, apiPref, {CAP_PROP_FORMAT, -1})); + ASSERT_TRUE(container.isOpened()); + ASSERT_EQ(-1.f, container.get(CAP_PROP_FORMAT)); + + auto stream = std::make_shared(); + Mat keyFrame; + while (true) + { + container >> keyFrame; + if (keyFrame.empty()) + break; + stream->sputn(keyFrame.ptr(), keyFrame.total()); + } + + VideoCapture capRef(video_file); + VideoCapture capStream; + EXPECT_NO_THROW(capStream.open(makePtr(stream), apiPref, {})); + ASSERT_TRUE(capStream.isOpened()); + + const int numFrames = 10; + Mat frameRef, frame; + for (int i = 0; i < numFrames; ++i) + { + capRef >> frameRef; + ASSERT_NO_THROW(capStream >> frame); + EXPECT_FALSE(frame.empty()); + EXPECT_EQ(0, cv::norm(frame, frameRef, NORM_INF)) << i; + } +} +INSTANTIATE_TEST_CASE_P(videoio, stream_capture_ffmpeg, testing::Values("h264", "h265", "mjpg.avi")); + } // namespace From 09892c9d1706f40342bda0bc404580f63492d9f8 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Wed, 25 Dec 2024 11:42:16 +0000 Subject: [PATCH 059/102] fix FFmpeg wrapper build --- modules/imgproc/CMakeLists.txt | 4 ++++ modules/imgproc/src/color.cpp | 3 +++ modules/imgproc/src/resize.cpp | 2 ++ 3 files changed, 9 insertions(+) diff --git a/modules/imgproc/CMakeLists.txt b/modules/imgproc/CMakeLists.txt index 10aed6bedd..7bf6087a9a 100644 --- a/modules/imgproc/CMakeLists.txt +++ b/modules/imgproc/CMakeLists.txt @@ -12,6 +12,10 @@ ocv_add_dispatched_file(smooth SSE2 SSE4_1 AVX2) ocv_add_dispatched_file(sumpixels SSE2 AVX2 AVX512_SKX) ocv_define_module(imgproc opencv_core WRAP java objc python js) +if(OPENCV_CORE_EXCLUDE_C_API) + ocv_target_compile_definitions(${the_module} PRIVATE "OPENCV_EXCLUDE_C_API=1") +endif() + if(HAVE_IPP) # OPENCV_IPP_ENABLE_ALL is defined in modules/core/CMakeList.txt OCV_OPTION(OPENCV_IPP_GAUSSIAN_BLUR "Enable IPP optimizations for GaussianBlur (+8Mb in binary size)" OPENCV_IPP_ENABLE_ALL) diff --git a/modules/imgproc/src/color.cpp b/modules/imgproc/src/color.cpp index 703511b9cf..0d89b571ab 100644 --- a/modules/imgproc/src/color.cpp +++ b/modules/imgproc/src/color.cpp @@ -387,6 +387,7 @@ void cvtColor( InputArray _src, OutputArray _dst, int code, int dcn, AlgorithmHi } } //namespace cv +#ifndef OPENCV_EXCLUDE_C_API CV_IMPL void cvCvtColor( const CvArr* srcarr, CvArr* dstarr, int code ) @@ -397,3 +398,5 @@ cvCvtColor( const CvArr* srcarr, CvArr* dstarr, int code ) cv::cvtColor(src, dst, code, dst.channels()); CV_Assert( dst.data == dst0.data ); } + +#endif diff --git a/modules/imgproc/src/resize.cpp b/modules/imgproc/src/resize.cpp index 3750690632..4b317c6a5a 100644 --- a/modules/imgproc/src/resize.cpp +++ b/modules/imgproc/src/resize.cpp @@ -4245,6 +4245,7 @@ void cv::resize( InputArray _src, OutputArray _dst, Size dsize, hal::resize(src.type(), src.data, src.step, src.cols, src.rows, dst.data, dst.step, dst.cols, dst.rows, inv_scale_x, inv_scale_y, interpolation); } +#ifndef OPENCV_EXCLUDE_C_API CV_IMPL void cvResize( const CvArr* srcarr, CvArr* dstarr, int method ) @@ -4255,4 +4256,5 @@ cvResize( const CvArr* srcarr, CvArr* dstarr, int method ) (double)dst.rows/src.rows, method ); } +#endif /* End of file. */ From 4c7ea700512ab6cd68b163dd7e66da4d6f1fbc71 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Wed, 20 Nov 2024 11:57:35 +0000 Subject: [PATCH 060/102] videoio(test): re-enable FFmpeg tests on WIN32 - related PR25874 --- modules/videoio/test/test_ffmpeg.cpp | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/modules/videoio/test/test_ffmpeg.cpp b/modules/videoio/test/test_ffmpeg.cpp index daf1736e62..588b286ec1 100644 --- a/modules/videoio/test/test_ffmpeg.cpp +++ b/modules/videoio/test/test_ffmpeg.cpp @@ -296,10 +296,6 @@ INSTANTIATE_TEST_CASE_P(/**/, videoio_container_get, testing::ValuesIn(videoio_c typedef tuple videoio_encapsulate_params_t; typedef testing::TestWithParam< videoio_encapsulate_params_t > videoio_encapsulate; -#if defined(WIN32) // remove when FFmpeg wrapper includes PR25874 -#define WIN32_WAIT_FOR_FFMPEG_WRAPPER_UPDATE -#endif - TEST_P(videoio_encapsulate, write) { const VideoCaptureAPIs api = CAP_FFMPEG; @@ -331,11 +327,10 @@ TEST_P(videoio_encapsulate, write) Mat rawFrame; for (int i = 0; i < nFrames; i++) { ASSERT_TRUE(capRaw.read(rawFrame)); -#if !defined(WIN32_WAIT_FOR_FFMPEG_WRAPPER_UPDATE) if (setPts && i == 0) { - ASSERT_TRUE(container.set(VIDEOWRITER_PROP_DTS_DELAY, capRaw.get(CAP_PROP_DTS_DELAY))); + double dts = capRaw.get(CAP_PROP_DTS_DELAY); + ASSERT_TRUE(container.set(VIDEOWRITER_PROP_DTS_DELAY, dts)) << "dts=" << dts; } -#endif ASSERT_FALSE(rawFrame.empty()); if (i == 0 && mpeg4) { Mat tmp = rawFrame.clone(); @@ -346,11 +341,10 @@ TEST_P(videoio_encapsulate, write) memcpy(rawFrame.data, extraData.data, extraData.total()); memcpy(rawFrame.data + extraData.total(), tmp.data, tmp.total()); } -#if !defined(WIN32_WAIT_FOR_FFMPEG_WRAPPER_UPDATE) if (setPts) { - ASSERT_TRUE(container.set(VIDEOWRITER_PROP_PTS, capRaw.get(CAP_PROP_PTS))); + double pts = capRaw.get(CAP_PROP_PTS); + ASSERT_TRUE(container.set(VIDEOWRITER_PROP_PTS, pts)) << "pts=" << pts; } -#endif container.write(rawFrame); } container.release(); @@ -381,11 +375,9 @@ TEST_P(videoio_encapsulate, write) const bool keyFrameActual = capActualRaw.get(CAP_PROP_LRF_HAS_KEY_FRAME) == 1.; const bool keyFrameReference = idrPeriod ? i % idrPeriod == 0 : 1; ASSERT_EQ(keyFrameReference, keyFrameActual); -#if !defined(WIN32_WAIT_FOR_FFMPEG_WRAPPER_UPDATE) if (tsWorking) { ASSERT_EQ(round(capReference.get(CAP_PROP_POS_MSEC)), round(capActual.get(CAP_PROP_POS_MSEC))); } -#endif } } From c64fe91ff41fdbc426a2753b5a9bf2c60f659525 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Thu, 26 Dec 2024 12:30:48 +0000 Subject: [PATCH 061/102] ffmpeg/4.x: update FFmpeg wrapper 2024.12 --- 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 f55882e59f..718461ed6c 100644 --- a/3rdparty/ffmpeg/ffmpeg.cmake +++ b/3rdparty/ffmpeg/ffmpeg.cmake @@ -1,8 +1,8 @@ -# Binaries branch name: ffmpeg/4.x_20240522 -# Binaries were created for OpenCV: 8393885a39dac1e650bf5d0aaff84c04ad8bcdd3 -ocv_update(FFMPEG_BINARIES_COMMIT "394dca6ceb3085c979415e6385996b6570e94153") -ocv_update(FFMPEG_FILE_HASH_BIN32 "bdfbd1efb295f3e54c07d2cb7a843bf9") -ocv_update(FFMPEG_FILE_HASH_BIN64 "bfef029900f788480a363d6dc05c4f0e") +# 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") ocv_update(FFMPEG_FILE_HASH_CMAKE "8862c87496e2e8c375965e1277dee1c7") function(download_win_ffmpeg script_var) From b31f7694c5ffb17f4e19d6ca549765d307c43294 Mon Sep 17 00:00:00 2001 From: Liutong HAN Date: Fri, 27 Dec 2024 08:39:52 +0000 Subject: [PATCH 062/102] Add test cases and fix bugs in the RVV HAL. --- 3rdparty/hal_rvv/hal_rvv_1p0/mean.hpp | 2 +- 3rdparty/hal_rvv/hal_rvv_1p0/merge.hpp | 95 +++++++++++++++++--------- modules/core/test/test_mat.cpp | 23 +++++-- 3 files changed, 83 insertions(+), 37 deletions(-) diff --git a/3rdparty/hal_rvv/hal_rvv_1p0/mean.hpp b/3rdparty/hal_rvv/hal_rvv_1p0/mean.hpp index 52f99bbe0b..4a9ffec500 100644 --- a/3rdparty/hal_rvv/hal_rvv_1p0/mean.hpp +++ b/3rdparty/hal_rvv/hal_rvv_1p0/mean.hpp @@ -119,8 +119,8 @@ inline int meanStdDev_8UC4(const uchar* src_data, size_t src_step, int width, in vec_sqsum = __riscv_vwmaccu_vv_u64m8_tumu(vmask, vec_sqsum, vec_pixel, vec_pixel, vl); nz += __riscv_vcpop_m_b8(vmask, vl); } - nz /= 4; } + nz /= 4; } else { for (int i = 0; i < height; i++) { const uchar* src_row = src_data + i * src_step; diff --git a/3rdparty/hal_rvv/hal_rvv_1p0/merge.hpp b/3rdparty/hal_rvv/hal_rvv_1p0/merge.hpp index 97c71ad0f3..353885b709 100644 --- a/3rdparty/hal_rvv/hal_rvv_1p0/merge.hpp +++ b/3rdparty/hal_rvv/hal_rvv_1p0/merge.hpp @@ -20,9 +20,9 @@ namespace cv { namespace cv_hal_rvv { #if defined __GNUC__ __attribute__((optimize("no-tree-vectorize"))) #endif -static int merge8u(const uchar** src, uchar* dst, int len, int cn ) { +inline int merge8u(const uchar** src, uchar* dst, int len, int cn ) { int k = cn % 4 ? cn % 4 : 4; - int i = 0, j; + int i = 0; int vl = __riscv_vsetvlmax_e8m1(); if( k == 1 ) { @@ -30,7 +30,7 @@ static int merge8u(const uchar** src, uchar* dst, int len, int cn ) { for( ; i <= len - vl; i += vl) { auto a = __riscv_vle8_v_u8m1(src0 + i, vl); - __riscv_vsse8_v_u8m1(dst + i*cn, sizeof(uchar)*2, a, vl); + __riscv_vsse8_v_u8m1(dst + i*cn, sizeof(uchar)*cn, a, vl); } #if defined(__clang__) #pragma clang loop vectorize(disable) @@ -45,8 +45,8 @@ static int merge8u(const uchar** src, uchar* dst, int len, int cn ) { { auto a = __riscv_vle8_v_u8m1(src0 + i, vl); auto b = __riscv_vle8_v_u8m1(src1 + i, vl); - __riscv_vsse8_v_u8m1(dst + i*cn, sizeof(uchar)*2, a, vl); - __riscv_vsse8_v_u8m1(dst + i*cn + 1, sizeof(uchar)*2, b, vl); + __riscv_vsse8_v_u8m1(dst + i*cn, sizeof(uchar)*cn, a, vl); + __riscv_vsse8_v_u8m1(dst + i*cn + 1, sizeof(uchar)*cn, b, vl); } #if defined(__clang__) #pragma clang loop vectorize(disable) @@ -65,9 +65,9 @@ static int merge8u(const uchar** src, uchar* dst, int len, int cn ) { auto a = __riscv_vle8_v_u8m1(src0 + i, vl); auto b = __riscv_vle8_v_u8m1(src1 + i, vl); auto c = __riscv_vle8_v_u8m1(src2 + i, vl); - __riscv_vsse8_v_u8m1(dst + i*cn, sizeof(uchar)*3, a, vl); - __riscv_vsse8_v_u8m1(dst + i*cn + 1, sizeof(uchar)*3, b, vl); - __riscv_vsse8_v_u8m1(dst + i*cn + 2, sizeof(uchar)*3, c, vl); + __riscv_vsse8_v_u8m1(dst + i*cn, sizeof(uchar)*cn, a, vl); + __riscv_vsse8_v_u8m1(dst + i*cn + 1, sizeof(uchar)*cn, b, vl); + __riscv_vsse8_v_u8m1(dst + i*cn + 2, sizeof(uchar)*cn, c, vl); } #if defined(__clang__) #pragma clang loop vectorize(disable) @@ -88,10 +88,10 @@ static int merge8u(const uchar** src, uchar* dst, int len, int cn ) { auto b = __riscv_vle8_v_u8m1(src1 + i, vl); auto c = __riscv_vle8_v_u8m1(src2 + i, vl); auto d = __riscv_vle8_v_u8m1(src3 + i, vl); - __riscv_vsse8_v_u8m1(dst + i*cn, sizeof(uchar)*4, a, vl); - __riscv_vsse8_v_u8m1(dst + i*cn + 1, sizeof(uchar)*4, b, vl); - __riscv_vsse8_v_u8m1(dst + i*cn + 2, sizeof(uchar)*4, c, vl); - __riscv_vsse8_v_u8m1(dst + i*cn + 3, sizeof(uchar)*4, d, vl); + __riscv_vsse8_v_u8m1(dst + i*cn, sizeof(uchar)*cn, a, vl); + __riscv_vsse8_v_u8m1(dst + i*cn + 1, sizeof(uchar)*cn, b, vl); + __riscv_vsse8_v_u8m1(dst + i*cn + 2, sizeof(uchar)*cn, c, vl); + __riscv_vsse8_v_u8m1(dst + i*cn + 3, sizeof(uchar)*cn, d, vl); } #if defined(__clang__) #pragma clang loop vectorize(disable) @@ -110,10 +110,27 @@ static int merge8u(const uchar** src, uchar* dst, int len, int cn ) { for( ; k < cn; k += 4 ) { const uchar *src0 = src[k], *src1 = src[k+1], *src2 = src[k+2], *src3 = src[k+3]; - for( i = 0, j = k; i < len; i++, j += cn ) + i = 0; + for( ; i <= len - vl; i += vl) { - dst[j] = src0[i]; dst[j+1] = src1[i]; - dst[j+2] = src2[i]; dst[j+3] = src3[i]; + auto a = __riscv_vle8_v_u8m1(src0 + i, vl); + auto b = __riscv_vle8_v_u8m1(src1 + i, vl); + auto c = __riscv_vle8_v_u8m1(src2 + i, vl); + auto d = __riscv_vle8_v_u8m1(src3 + i, vl); + __riscv_vsse8_v_u8m1(dst + k+i*cn, sizeof(uchar)*cn, a, vl); + __riscv_vsse8_v_u8m1(dst + k+i*cn + 1, sizeof(uchar)*cn, b, vl); + __riscv_vsse8_v_u8m1(dst + k+i*cn + 2, sizeof(uchar)*cn, c, vl); + __riscv_vsse8_v_u8m1(dst + k+i*cn + 3, sizeof(uchar)*cn, d, vl); + } + #if defined(__clang__) + #pragma clang loop vectorize(disable) + #endif + for( ; i < len; i++ ) + { + dst[k+i*cn] = src0[i]; + dst[k+i*cn+1] = src1[i]; + dst[k+i*cn+2] = src2[i]; + dst[k+i*cn+3] = src3[i]; } } return CV_HAL_ERROR_OK; @@ -122,9 +139,9 @@ static int merge8u(const uchar** src, uchar* dst, int len, int cn ) { #if defined __GNUC__ __attribute__((optimize("no-tree-vectorize"))) #endif -static int merge16u(const ushort** src, ushort* dst, int len, int cn ) { +inline int merge16u(const ushort** src, ushort* dst, int len, int cn ) { int k = cn % 4 ? cn % 4 : 4; - int i = 0, j; + int i = 0; int vl = __riscv_vsetvlmax_e16m1(); if( k == 1 ) { @@ -132,7 +149,7 @@ static int merge16u(const ushort** src, ushort* dst, int len, int cn ) { for( ; i <= len - vl; i += vl) { auto a = __riscv_vle16_v_u16m1(src0 + i, vl); - __riscv_vsse16_v_u16m1(dst + i*cn, sizeof(ushort)*2, a, vl); + __riscv_vsse16_v_u16m1(dst + i*cn, sizeof(ushort)*cn, a, vl); } #if defined(__clang__) #pragma clang loop vectorize(disable) @@ -147,8 +164,8 @@ static int merge16u(const ushort** src, ushort* dst, int len, int cn ) { { auto a = __riscv_vle16_v_u16m1(src0 + i, vl); auto b = __riscv_vle16_v_u16m1(src1 + i, vl); - __riscv_vsse16_v_u16m1(dst + i*cn, sizeof(ushort)*2, a, vl); - __riscv_vsse16_v_u16m1(dst + i*cn + 1, sizeof(ushort)*2, b, vl); + __riscv_vsse16_v_u16m1(dst + i*cn, sizeof(ushort)*cn, a, vl); + __riscv_vsse16_v_u16m1(dst + i*cn + 1, sizeof(ushort)*cn, b, vl); } #if defined(__clang__) #pragma clang loop vectorize(disable) @@ -167,9 +184,9 @@ static int merge16u(const ushort** src, ushort* dst, int len, int cn ) { auto a = __riscv_vle16_v_u16m1(src0 + i, vl); auto b = __riscv_vle16_v_u16m1(src1 + i, vl); auto c = __riscv_vle16_v_u16m1(src2 + i, vl); - __riscv_vsse16_v_u16m1(dst + i*cn, sizeof(ushort)*3, a, vl); - __riscv_vsse16_v_u16m1(dst + i*cn + 1, sizeof(ushort)*3, b, vl); - __riscv_vsse16_v_u16m1(dst + i*cn + 2, sizeof(ushort)*3, c, vl); + __riscv_vsse16_v_u16m1(dst + i*cn, sizeof(ushort)*cn, a, vl); + __riscv_vsse16_v_u16m1(dst + i*cn + 1, sizeof(ushort)*cn, b, vl); + __riscv_vsse16_v_u16m1(dst + i*cn + 2, sizeof(ushort)*cn, c, vl); } #if defined(__clang__) #pragma clang loop vectorize(disable) @@ -190,10 +207,10 @@ static int merge16u(const ushort** src, ushort* dst, int len, int cn ) { auto b = __riscv_vle16_v_u16m1(src1 + i, vl); auto c = __riscv_vle16_v_u16m1(src2 + i, vl); auto d = __riscv_vle16_v_u16m1(src3 + i, vl); - __riscv_vsse16_v_u16m1(dst + i*cn, sizeof(ushort)*4, a, vl); - __riscv_vsse16_v_u16m1(dst + i*cn + 1, sizeof(ushort)*4, b, vl); - __riscv_vsse16_v_u16m1(dst + i*cn + 2, sizeof(ushort)*4, c, vl); - __riscv_vsse16_v_u16m1(dst + i*cn + 3, sizeof(ushort)*4, d, vl); + __riscv_vsse16_v_u16m1(dst + i*cn, sizeof(ushort)*cn, a, vl); + __riscv_vsse16_v_u16m1(dst + i*cn + 1, sizeof(ushort)*cn, b, vl); + __riscv_vsse16_v_u16m1(dst + i*cn + 2, sizeof(ushort)*cn, c, vl); + __riscv_vsse16_v_u16m1(dst + i*cn + 3, sizeof(ushort)*cn, d, vl); } #if defined(__clang__) #pragma clang loop vectorize(disable) @@ -212,10 +229,24 @@ static int merge16u(const ushort** src, ushort* dst, int len, int cn ) { for( ; k < cn; k += 4 ) { const uint16_t *src0 = src[k], *src1 = src[k+1], *src2 = src[k+2], *src3 = src[k+3]; - for( i = 0, j = k; i < len; i++, j += cn ) + i = 0; + for( ; i <= len - vl; i += vl) { - dst[j] = src0[i]; dst[j+1] = src1[i]; - dst[j+2] = src2[i]; dst[j+3] = src3[i]; + auto a = __riscv_vle16_v_u16m1(src0 + i, vl); + auto b = __riscv_vle16_v_u16m1(src1 + i, vl); + auto c = __riscv_vle16_v_u16m1(src2 + i, vl); + auto d = __riscv_vle16_v_u16m1(src3 + i, vl); + __riscv_vsse16_v_u16m1(dst + k+i*cn, sizeof(ushort)*cn, a, vl); + __riscv_vsse16_v_u16m1(dst + k+i*cn + 1, sizeof(ushort)*cn, b, vl); + __riscv_vsse16_v_u16m1(dst + k+i*cn + 2, sizeof(ushort)*cn, c, vl); + __riscv_vsse16_v_u16m1(dst + k+i*cn + 3, sizeof(ushort)*cn, d, vl); + } + for( ; i < len; i++ ) + { + dst[k+i*cn] = src0[i]; + dst[k+i*cn+1] = src1[i]; + dst[k+i*cn+2] = src2[i]; + dst[k+i*cn+3] = src3[i]; } } return CV_HAL_ERROR_OK; @@ -224,7 +255,7 @@ static int merge16u(const ushort** src, ushort* dst, int len, int cn ) { #if defined __GNUC__ __attribute__((optimize("no-tree-vectorize"))) #endif -static int merge32s(const int** src, int* dst, int len, int cn ) { +inline int merge32s(const int** src, int* dst, int len, int cn ) { int k = cn % 4 ? cn % 4 : 4; int i, j; if( k == 1 ) @@ -294,7 +325,7 @@ static int merge32s(const int** src, int* dst, int len, int cn ) { #if defined __GNUC__ __attribute__((optimize("no-tree-vectorize"))) #endif -static int merge64s(const int64** src, int64* dst, int len, int cn ) { +inline int merge64s(const int64** src, int64* dst, int len, int cn ) { int k = cn % 4 ? cn % 4 : 4; int i, j; if( k == 1 ) diff --git a/modules/core/test/test_mat.cpp b/modules/core/test/test_mat.cpp index 9349e2b95e..d77aca06cb 100644 --- a/modules/core/test/test_mat.cpp +++ b/modules/core/test/test_mat.cpp @@ -1749,18 +1749,22 @@ TEST(Core_Mat_array, copyTo_roi_row) EXPECT_EQ(5, (int)dst2[4]); } -TEST(Core_Mat_array, SplitMerge) +typedef testing::TestWithParam< tuple > Core_Mat_arrays; + +TEST_P(Core_Mat_arrays, SplitMerge) { - std::array src; + int cn = get<0>(GetParam()); + int type = get<1>(GetParam()); + std::vector src(cn); for (size_t i = 0; i < src.size(); ++i) { - src[i] = Mat(10, 10, CV_8U, Scalar((double)(16 * (i + 1)))); + src[i] = Mat(10, 10, type, Scalar((double)(16 * (i + 1)))); } Mat merged; merge(src, merged); - std::array dst; + std::vector dst(cn); split(merged, dst); for (size_t i = 0; i < dst.size(); ++i) @@ -1769,6 +1773,17 @@ TEST(Core_Mat_array, SplitMerge) } } +INSTANTIATE_TEST_CASE_P(/*nothing*/, Core_Mat_arrays, testing::Combine( + testing::Range(1, 9), + testing::Values( + perf::MatType(CV_8U), + perf::MatType(CV_16U), + perf::MatType(CV_32S), + perf::MatType(CV_64F) + ) +) +); + TEST(Mat, regression_8680) { Mat_ mat(3,1); From d39aae6bdfc1a45553e1426334a9c72848354261 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Ihle?= Date: Mon, 30 Dec 2024 09:09:11 +0100 Subject: [PATCH 063/102] Merge pull request #26656 from warped-rudi:mediandk AndroidMediaNdkCapture pixel format enhancement #26656 ### 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 - [ 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 --- .../org/opencv/android/NativeCameraView.java | 21 ++--- modules/videoio/src/cap_android_mediandk.cpp | 92 ++++++++++++++++--- .../samples/recorder/RecorderActivity.java | 22 +++-- 3 files changed, 101 insertions(+), 34 deletions(-) diff --git a/modules/java/generator/android-24/java/org/opencv/android/NativeCameraView.java b/modules/java/generator/android-24/java/org/opencv/android/NativeCameraView.java index b28c2121cd..0053a25a07 100644 --- a/modules/java/generator/android-24/java/org/opencv/android/NativeCameraView.java +++ b/modules/java/generator/android-24/java/org/opencv/android/NativeCameraView.java @@ -2,6 +2,7 @@ package org.opencv.android; import org.opencv.core.Mat; import org.opencv.core.Size; +import org.opencv.core.MatOfInt; import org.opencv.imgproc.Imgproc; @@ -123,8 +124,11 @@ public class NativeCameraView extends CameraBridgeViewBase { return false; } + MatOfInt params = new MatOfInt(Videoio.CAP_PROP_FRAME_WIDTH, width, + Videoio.CAP_PROP_FRAME_HEIGHT, height); + Log.d(TAG, "Try to open camera with index " + localCameraIndex); - mCamera = new VideoCapture(localCameraIndex, Videoio.CAP_ANDROID); + mCamera = new VideoCapture(localCameraIndex, Videoio.CAP_ANDROID, params); if (mCamera == null) return false; @@ -139,9 +143,6 @@ public class NativeCameraView extends CameraBridgeViewBase { mFrame = new RotatedCameraFrame(new NativeCameraFrame(mCamera), frameRotation); - mCamera.set(Videoio.CAP_PROP_FRAME_WIDTH, width); - mCamera.set(Videoio.CAP_PROP_FRAME_HEIGHT, height); - if (frameRotation % 180 == 0) { mFrameWidth = (int) mCamera.get(Videoio.CAP_PROP_FRAME_WIDTH); mFrameHeight = (int) mCamera.get(Videoio.CAP_PROP_FRAME_HEIGHT); @@ -181,10 +182,9 @@ public class NativeCameraView extends CameraBridgeViewBase { @Override public Mat rgba() { - mCapture.set(Videoio.CAP_PROP_FOURCC, VideoWriter.fourcc('R','G','B','3')); - mCapture.retrieve(mBgr); - Log.d(TAG, "Retrived frame with size " + mBgr.cols() + "x" + mBgr.rows() + " and channels: " + mBgr.channels()); - Imgproc.cvtColor(mBgr, mRgba, Imgproc.COLOR_RGB2RGBA); + mCapture.set(Videoio.CAP_PROP_FOURCC, VideoWriter.fourcc('R','G','B','4')); + mCapture.retrieve(mRgba); + Log.d(TAG, "Retrieved frame with size " + mRgba.cols() + "x" + mRgba.rows() + " and channels: " + mRgba.channels()); return mRgba; } @@ -192,7 +192,7 @@ public class NativeCameraView extends CameraBridgeViewBase { public Mat gray() { mCapture.set(Videoio.CAP_PROP_FOURCC, VideoWriter.fourcc('G','R','E','Y')); mCapture.retrieve(mGray); - Log.d(TAG, "Retrived frame with size " + mGray.cols() + "x" + mGray.rows() + " and channels: " + mGray.channels()); + Log.d(TAG, "Retrieved frame with size " + mGray.cols() + "x" + mGray.rows() + " and channels: " + mGray.channels()); return mGray; } @@ -200,20 +200,17 @@ public class NativeCameraView extends CameraBridgeViewBase { mCapture = capture; mGray = new Mat(); mRgba = new Mat(); - mBgr = new Mat(); } @Override public void release() { if (mGray != null) mGray.release(); if (mRgba != null) mRgba.release(); - if (mBgr != null) mBgr.release(); } private VideoCapture mCapture; private Mat mRgba; private Mat mGray; - private Mat mBgr; }; private class CameraWorker implements Runnable { diff --git a/modules/videoio/src/cap_android_mediandk.cpp b/modules/videoio/src/cap_android_mediandk.cpp index 98f9eea3f9..5242a17d25 100644 --- a/modules/videoio/src/cap_android_mediandk.cpp +++ b/modules/videoio/src/cap_android_mediandk.cpp @@ -21,10 +21,20 @@ #define INPUT_TIMEOUT_MS 2000 +#define COLOR_FormatUnknown -1 #define COLOR_FormatYUV420Planar 19 #define COLOR_FormatYUV420SemiPlanar 21 #define COLOR_FormatSurface 0x7f000789 //See https://developer.android.com/reference/android/media/MediaCodecInfo.CodecCapabilities for codes +#define FOURCC_BGR CV_FOURCC_MACRO('B','G','R','3') +#define FOURCC_RGB CV_FOURCC_MACRO('R','G','B','3') +#define FOURCC_BGRA CV_FOURCC_MACRO('B','G','R','4') +#define FOURCC_RGBA CV_FOURCC_MACRO('R','G','B','4') +#define FOURCC_GRAY CV_FOURCC_MACRO('G','R','E','Y') +#define FOURCC_NV12 CV_FOURCC_MACRO('N','V','1','2') +#define FOURCC_YV12 CV_FOURCC_MACRO('Y','V','1','2') +#define FOURCC_UNKNOWN 0xFFFFFFFF + using namespace cv; #define TAG "NativeCodec" @@ -51,9 +61,9 @@ class AndroidMediaNdkCapture : public IVideoCapture public: AndroidMediaNdkCapture(): sawInputEOS(false), sawOutputEOS(false), - frameStride(0), frameWidth(0), frameHeight(0), colorFormat(0), - videoWidth(0), videoHeight(0), - videoFrameCount(0), + frameStride(0), frameWidth(0), frameHeight(0), + colorFormat(COLOR_FormatUnknown), fourCC(FOURCC_BGR), + videoWidth(0), videoHeight(0), videoFrameCount(0), videoRotation(0), videoRotationCode(-1), videoOrientationAuto(false) {} @@ -65,6 +75,7 @@ public: int32_t frameWidth; int32_t frameHeight; int32_t colorFormat; + uint32_t fourCC; int32_t videoWidth; int32_t videoHeight; float videoFrameRate; @@ -73,7 +84,6 @@ public: int32_t videoRotationCode; bool videoOrientationAuto; std::vector buffer; - Mat frame; ~AndroidMediaNdkCapture() { cleanUp(); } @@ -157,23 +167,51 @@ public: return false; } - Mat yuv(frameHeight + frameHeight/2, frameStride, CV_8UC1, buffer.data()); + ColorConversionCodes ccCode; + const Mat yuv(frameHeight + frameHeight/2, + frameWidth, CV_8UC1, buffer.data(), frameStride); if (colorFormat == COLOR_FormatYUV420Planar) { - cv::cvtColor(yuv, frame, cv::COLOR_YUV2BGR_YV12); + switch(fourCC) + { + case FOURCC_BGR: ccCode = COLOR_YUV2BGR_YV12; break; + case FOURCC_RGB: ccCode = COLOR_YUV2RGB_YV12; break; + case FOURCC_BGRA: ccCode = COLOR_YUV2BGRA_YV12; break; + case FOURCC_RGBA: ccCode = COLOR_YUV2RGBA_YV12; break; + case FOURCC_GRAY: ccCode = COLOR_YUV2GRAY_YV12; break; + case FOURCC_YV12: break; + case FOURCC_UNKNOWN: fourCC = FOURCC_YV12; break; + default: LOGE("Unexpected FOURCC value: %d", fourCC); + return false; + } } else if (colorFormat == COLOR_FormatYUV420SemiPlanar) { - cv::cvtColor(yuv, frame, cv::COLOR_YUV2BGR_NV21); + // Attention: COLOR_FormatYUV420SemiPlanar seems to correspond to NV12. + // This is different from the Camera2 interface, where NV21 + // is used in this situation. + switch(fourCC) + { + case FOURCC_BGR: ccCode = COLOR_YUV2BGR_NV12; break; + case FOURCC_RGB: ccCode = COLOR_YUV2RGB_NV12; break; + case FOURCC_BGRA: ccCode = COLOR_YUV2BGRA_NV12; break; + case FOURCC_RGBA: ccCode = COLOR_YUV2RGBA_NV12; break; + case FOURCC_GRAY: ccCode = COLOR_YUV2GRAY_NV12; break; + case FOURCC_NV12: break; + case FOURCC_UNKNOWN: fourCC = FOURCC_NV12; break; + default: LOGE("Unexpected FOURCC value: %d", fourCC); + return false; + } } else { LOGE("Unsupported video format: %d", colorFormat); return false; } - Mat croppedFrame = frame(Rect(0, 0, videoWidth, videoHeight)); - out.assign(croppedFrame); + if (fourCC == FOURCC_YV12 || fourCC == FOURCC_NV12) + yuv.copyTo(out); + else + cvtColor(yuv, out, ccCode); - if (videoOrientationAuto && -1 != videoRotationCode) { - cv::rotate(out, out, videoRotationCode); - } + if (videoOrientationAuto && -1 != videoRotationCode) + rotate(out, out, videoRotationCode); return true; } @@ -194,8 +232,11 @@ public: case CAP_PROP_FRAME_COUNT: return videoFrameCount; case CAP_PROP_ORIENTATION_META: return videoRotation; case CAP_PROP_ORIENTATION_AUTO: return videoOrientationAuto ? 1 : 0; + case CAP_PROP_FOURCC: return fourCC; } - return 0; + + // unknown parameter or value not available + return -1; } bool setProperty(int property_id, double value) CV_OVERRIDE @@ -206,6 +247,31 @@ public: videoOrientationAuto = value != 0 ? true : false; return true; } + case CAP_PROP_FOURCC: { + uint32_t newFourCC = cvRound(value); + switch (newFourCC) + { + case FOURCC_BGR: + case FOURCC_RGB: + case FOURCC_BGRA: + case FOURCC_RGBA: + case FOURCC_GRAY: + fourCC = newFourCC; + return true; + case FOURCC_YV12: + if (colorFormat != COLOR_FormatYUV420SemiPlanar) { + fourCC = (colorFormat == COLOR_FormatUnknown) ? FOURCC_UNKNOWN : FOURCC_YV12; + return true; + } + break; + case FOURCC_NV12: + if (colorFormat != COLOR_FormatYUV420Planar) { + fourCC = (colorFormat == COLOR_FormatUnknown) ? FOURCC_UNKNOWN : FOURCC_NV12; + return true; + } + break; + } + } } return false; diff --git a/samples/android/video-recorder/src/org/opencv/samples/recorder/RecorderActivity.java b/samples/android/video-recorder/src/org/opencv/samples/recorder/RecorderActivity.java index d4faea51b2..f7f1af77cf 100644 --- a/samples/android/video-recorder/src/org/opencv/samples/recorder/RecorderActivity.java +++ b/samples/android/video-recorder/src/org/opencv/samples/recorder/RecorderActivity.java @@ -57,7 +57,6 @@ public class RecorderActivity extends CameraActivity implements CvCameraViewList private VideoWriter mVideoWriter = null; private VideoCapture mVideoCapture = null; private Mat mVideoFrame; - private Mat mRenderFrame; public RecorderActivity() { Log.i(TAG, "Instantiated new " + this.getClass()); @@ -122,7 +121,6 @@ public class RecorderActivity extends CameraActivity implements CvCameraViewList mTriggerButton.setText("Start Camera"); mVideoFrame.release(); - mRenderFrame.release(); } @Override @@ -132,7 +130,6 @@ public class RecorderActivity extends CameraActivity implements CvCameraViewList super.onResume(); mVideoFrame = new Mat(); - mRenderFrame = new Mat(); changeStatus(); } @@ -294,12 +291,16 @@ public class RecorderActivity extends CameraActivity implements CvCameraViewList mVideoCapture = new VideoCapture(mVideoFilename, Videoio.CAP_OPENCV_MJPEG); } - if (!mVideoCapture.isOpened()) { + if (mVideoCapture == null || !mVideoCapture.isOpened()) { Log.e(TAG, "Can't open video"); Toast.makeText(this, "Can't open file " + mVideoFilename, Toast.LENGTH_SHORT).show(); return false; } + if (!mUseBuiltInMJPG){ + mVideoCapture.set(Videoio.CAP_PROP_FOURCC, VideoWriter.fourcc('R','G','B','4')); + } + Toast.makeText(this, "Starting playback from file " + mVideoFilename, Toast.LENGTH_SHORT).show(); mPlayerThread = new Runnable() { @@ -315,11 +316,14 @@ public class RecorderActivity extends CameraActivity implements CvCameraViewList } return; } - // VideoCapture with CAP_ANDROID generates RGB frames instead of BGR - // https://github.com/opencv/opencv/issues/24687 - Imgproc.cvtColor(mVideoFrame, mRenderFrame, mUseBuiltInMJPG ? Imgproc.COLOR_BGR2RGBA: Imgproc.COLOR_RGB2RGBA); - Bitmap bmp = Bitmap.createBitmap(mRenderFrame.cols(), mRenderFrame.rows(), Bitmap.Config.ARGB_8888); - Utils.matToBitmap(mRenderFrame, bmp); + + // MJPEG codec will output BGR only. So we need to convert to RGBA. + if (mUseBuiltInMJPG) { + Imgproc.cvtColor(mVideoFrame, mVideoFrame, Imgproc.COLOR_BGR2RGBA); + } + + Bitmap bmp = Bitmap.createBitmap(mVideoFrame.cols(), mVideoFrame.rows(), Bitmap.Config.ARGB_8888); + Utils.matToBitmap(mVideoFrame, bmp); mImageView.setImageBitmap(bmp); Handler h = new Handler(); h.postDelayed(this, 33); From 8bc65a1d136c2ac4c31910ee271b5f3c9d14317e Mon Sep 17 00:00:00 2001 From: Suleyman TURKMEN Date: Mon, 30 Dec 2024 11:32:31 +0300 Subject: [PATCH 064/102] Merge pull request #25715 from sturkmen72:apng_support Animated PNG Support #25715 Continues https://github.com/opencv/opencv/pull/25608 ### 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/imgcodecs/src/grfmt_avif.cpp | 2 +- modules/imgcodecs/src/grfmt_png.cpp | 1346 ++++++++++++++++++++- modules/imgcodecs/src/grfmt_png.hpp | 128 +- modules/imgcodecs/src/loadsave.cpp | 3 +- modules/imgcodecs/test/test_animation.cpp | 436 +++++++ modules/imgcodecs/test/test_webp.cpp | 231 +--- 6 files changed, 1847 insertions(+), 299 deletions(-) create mode 100644 modules/imgcodecs/test/test_animation.cpp diff --git a/modules/imgcodecs/src/grfmt_avif.cpp b/modules/imgcodecs/src/grfmt_avif.cpp index 23cdbb1baa..58f6ce91c1 100644 --- a/modules/imgcodecs/src/grfmt_avif.cpp +++ b/modules/imgcodecs/src/grfmt_avif.cpp @@ -243,7 +243,7 @@ bool AvifDecoder::readData(Mat &img) { return false; } - m_animation.durations.push_back(decoder_->imageTiming.durationInTimescales); + m_animation.durations.push_back(decoder_->imageTiming.duration * 1000); if (decoder_->image->exif.size > 0) { m_exif.parseExif(decoder_->image->exif.data, decoder_->image->exif.size); diff --git a/modules/imgcodecs/src/grfmt_png.cpp b/modules/imgcodecs/src/grfmt_png.cpp index 726c8b90b7..32620fdbc0 100644 --- a/modules/imgcodecs/src/grfmt_png.cpp +++ b/modules/imgcodecs/src/grfmt_png.cpp @@ -51,6 +51,52 @@ and png2bmp sample from libpng distribution (Copyright (C) 1999-2001 MIYASAKA Masaru) \****************************************************************************************/ +/****************************************************************************\ + * + * this file includes some modified part of apngasm and APNG Optimizer 1.4 + * both have zlib license. + * + ****************************************************************************/ + + + /* apngasm + * + * The next generation of apngasm, the APNG Assembler. + * The apngasm CLI tool and library can assemble and disassemble APNG image files. + * + * https://github.com/apngasm/apngasm + + + /* APNG Optimizer 1.4 + * + * Makes APNG files smaller. + * + * http://sourceforge.net/projects/apng/files + * + * Copyright (c) 2011-2015 Max Stepin + * maxst at users.sourceforge.net + * + * zlib license + * ------------ + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + * + */ + #ifndef _LFS64_LARGEFILE # define _LFS64_LARGEFILE 0 #endif @@ -58,14 +104,13 @@ # define _FILE_OFFSET_BITS 0 #endif -#include -#include - #include "grfmt_png.hpp" +#include #if defined _MSC_VER && _MSC_VER >= 1200 // interaction between '_setjmp' and C++ object destruction is non-portable #pragma warning( disable: 4611 ) + #pragma warning( disable: 4244 ) #endif // the following defines are a hack to avoid multiple problems with frame pointer handling and setjmp @@ -76,7 +121,64 @@ namespace cv { -/////////////////////// PngDecoder /////////////////// +const uint32_t id_IHDR = 0x52444849; // PNG header +const uint32_t id_acTL = 0x4C546361; // Animation control chunk +const uint32_t id_fcTL = 0x4C546366; // Frame control chunk +const uint32_t id_IDAT = 0x54414449; // first frame and/or default image +const uint32_t id_fdAT = 0x54416466; // Frame data chunk +const uint32_t id_PLTE = 0x45544C50; +const uint32_t id_bKGD = 0x44474B62; +const uint32_t id_tRNS = 0x534E5274; +const uint32_t id_IEND = 0x444E4549; // end/footer chunk + +APNGFrame::APNGFrame() +{ + _pixels = NULL; + _width = 0; + _height = 0; + _colorType = 0; + _paletteSize = 0; + _transparencySize = 0; + _delayNum = 1; + _delayDen = 1000; + _rows = NULL; +} + +APNGFrame::~APNGFrame() {} + +bool APNGFrame::setMat(const cv::Mat& src, unsigned delayNum, unsigned delayDen) +{ + _delayNum = delayNum; + _delayDen = delayDen; + + if (!src.empty()) + { + png_uint_32 rowbytes = src.cols * src.channels(); + + _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; + _pixels = src.data; + _rows = new png_bytep[_height * sizeof(png_bytep)]; + + for (unsigned int i = 0; i < _height; ++i) + _rows[i] = _pixels + i * rowbytes; + return true; + } + return false; +} + +void APNGFrame::setWidth(unsigned int width) { _width = width; } +void APNGFrame::setHeight(unsigned int height) { _height = height;} +void APNGFrame::setColorType(unsigned char colorType) { _colorType = colorType; } +void APNGFrame::setPalette(const rgb* palette) { std::copy(palette, palette + 256, _palette); } +void APNGFrame::setTransparency(const unsigned char* transparency) { std::copy(transparency, transparency + 256, _transparency); } +void APNGFrame::setPaletteSize(int paletteSize) { _paletteSize = paletteSize; } +void APNGFrame::setTransparencySize(int transparencySize) { _transparencySize = transparencySize; } +void APNGFrame::setDelayNum(unsigned int delayNum) { _delayNum = delayNum; } +void APNGFrame::setDelayDen(unsigned int delayDen) { _delayDen = delayDen; } +void APNGFrame::setPixels(unsigned char* pixels) { _pixels = pixels; } +void APNGFrame::setRows(unsigned char** rows) { _rows = rows; } PngDecoder::PngDecoder() { @@ -88,20 +190,18 @@ PngDecoder::PngDecoder() m_buf_supported = true; m_buf_pos = 0; m_bit_depth = 0; + m_frame_no = 0; + w0 = 0; + h0 = 0; + x0 = 0; + y0 = 0; + delay_num = 0; + delay_den = 0; + dop = 0; + bop = 0; } - PngDecoder::~PngDecoder() -{ - close(); -} - -ImageDecoder PngDecoder::newDecoder() const -{ - return makePtr(); -} - -void PngDecoder::close() { if( m_f ) { @@ -119,8 +219,12 @@ void PngDecoder::close() } } +ImageDecoder PngDecoder::newDecoder() const +{ + return makePtr(); +} -void PngDecoder::readDataFromBuf( void* _png_ptr, uchar* dst, size_t size ) +void PngDecoder::readDataFromBuf( void* _png_ptr, unsigned char* dst, size_t size ) { png_structp png_ptr = (png_structp)_png_ptr; PngDecoder* decoder = (PngDecoder*)(png_get_io_ptr(png_ptr)); @@ -138,7 +242,6 @@ void PngDecoder::readDataFromBuf( void* _png_ptr, uchar* dst, size_t size ) bool PngDecoder::readHeader() { volatile bool result = false; - close(); png_structp png_ptr = png_create_read_struct( PNG_LIBPNG_VER_STRING, 0, 0, 0 ); @@ -156,55 +259,126 @@ bool PngDecoder::readHeader() { if( setjmp( png_jmpbuf( png_ptr ) ) == 0 ) { + unsigned char sig[8]; + uint32_t id = 0; + Chunk chunk; + if( !m_buf.empty() ) png_set_read_fn(png_ptr, this, (png_rw_ptr)readDataFromBuf ); else { - m_f = fopen( m_filename.c_str(), "rb" ); - if( m_f ) - png_init_io( png_ptr, m_f ); + m_f = fopen(m_filename.c_str(), "rb"); + if (!m_f) + return false; + png_init_io(png_ptr, m_f); + + if (fread(sig, 1, 8, m_f)) + id = read_chunk(m_chunkIHDR); } - if( !m_buf.empty() || m_f ) + if (id != id_IHDR) { - png_uint_32 wdth, hght; - int bit_depth, color_type, num_trans=0; - png_bytep trans; - png_color_16p trans_values; + read_from_io(&sig, 8, 1); + id = read_chunk(m_chunkIHDR); + } - png_read_info( png_ptr, info_ptr ); + if (!(id == id_IHDR && m_chunkIHDR.size == 25)) + return false; - png_get_IHDR( png_ptr, info_ptr, &wdth, &hght, - &bit_depth, &color_type, 0, 0, 0 ); + while (true) + { + m_is_fcTL_loaded = false; + id = read_chunk(chunk); - m_width = (int)wdth; - m_height = (int)hght; - m_color_type = color_type; - m_bit_depth = bit_depth; + if ((m_f && feof(m_f)) || (!m_buf.empty() && m_buf_pos > m_buf.total())) + return false; - if( bit_depth <= 8 || bit_depth == 16 ) + if (id == id_IDAT) { - switch(color_type) - { - case PNG_COLOR_TYPE_RGB: - case PNG_COLOR_TYPE_PALETTE: - png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, &trans_values); - if( num_trans > 0 ) - m_type = CV_8UC4; - else - m_type = CV_8UC3; - break; - case PNG_COLOR_TYPE_GRAY_ALPHA: - case PNG_COLOR_TYPE_RGB_ALPHA: - m_type = CV_8UC4; - break; - default: - m_type = CV_8UC1; - } - if( bit_depth == 16 ) - m_type = CV_MAKETYPE(CV_16U, CV_MAT_CN(m_type)); - result = true; + if (m_f) + fseek(m_f, 0, SEEK_SET); + else + m_buf_pos = 0; + break; } + + if (id == id_acTL && chunk.size == 20) + { + m_animation.loop_count = png_get_uint_32(chunk.p + 12); + + if (chunk.p[8] > 0) + { + chunk.p[8] = 0; + chunk.p[9] = 0; + m_frame_count = png_get_uint_32(chunk.p + 8); + m_frame_count++; + } + else + m_frame_count = png_get_uint_32(chunk.p + 8); + } + + if (id == id_fcTL) + { + m_is_fcTL_loaded = true; + w0 = png_get_uint_32(chunk.p + 12); + h0 = png_get_uint_32(chunk.p + 16); + x0 = png_get_uint_32(chunk.p + 20); + y0 = png_get_uint_32(chunk.p + 24); + delay_num = png_get_uint_16(chunk.p + 28); + delay_den = png_get_uint_16(chunk.p + 30); + dop = chunk.p[32]; + bop = chunk.p[33]; + } + + if (id == id_bKGD) + { + int bgcolor = png_get_uint_32(chunk.p + 8); + m_animation.bgcolor[3] = (bgcolor >> 24) & 0xFF; + m_animation.bgcolor[2] = (bgcolor >> 16) & 0xFF; + m_animation.bgcolor[1] = (bgcolor >> 8) & 0xFF; + m_animation.bgcolor[0] = bgcolor & 0xFF; + } + + if (id == id_PLTE || id == id_tRNS) + m_chunksInfo.push_back(chunk); + } + + png_uint_32 wdth, hght; + int bit_depth, color_type, num_trans=0; + png_bytep trans; + png_color_16p trans_values; + + png_read_info( png_ptr, info_ptr ); + png_get_IHDR(png_ptr, info_ptr, &wdth, &hght, + &bit_depth, &color_type, 0, 0, 0); + + m_width = (int)wdth; + m_height = (int)hght; + m_color_type = color_type; + m_bit_depth = bit_depth; + + if (bit_depth <= 8 || bit_depth == 16) + { + switch (color_type) + { + case PNG_COLOR_TYPE_RGB: + case PNG_COLOR_TYPE_PALETTE: + png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, &trans_values); + if (num_trans > 0) + m_type = CV_8UC4; + else + m_type = CV_8UC3; + break; + case PNG_COLOR_TYPE_GRAY_ALPHA: + case PNG_COLOR_TYPE_RGB_ALPHA: + m_type = CV_8UC4; + break; + default: + m_type = CV_8UC1; + } + if (bit_depth == 16) + m_type = CV_MAKETYPE(CV_16U, CV_MAT_CN(m_type)); + result = true; } } } @@ -213,12 +387,147 @@ bool PngDecoder::readHeader() return result; } - bool PngDecoder::readData( Mat& img ) { + if (m_frame_count > 1) + { + Mat mat_cur = Mat(img.rows, img.cols, m_type); + uint32_t id = 0; + uint32_t j = 0; + uint32_t imagesize = m_width * m_height * mat_cur.channels(); + m_is_IDAT_loaded = false; + + if (m_frame_no == 0) + { + 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); + + frameCur.setMat(mat_cur); + + processing_start((void*)&frameRaw, mat_cur); + png_structp png_ptr = (png_structp)m_png_ptr; + png_infop info_ptr = (png_infop)m_info_ptr; + + while (true) + { + Chunk chunk; + id = read_chunk(chunk); + if (!id) + return false; + + if (id == id_fcTL && m_is_IDAT_loaded) + { + 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.channels()); + if (delay_den < 1000) + delay_num = cvRound(1000.0 / delay_den); + m_animation.durations.push_back(delay_num); + + if (mat_cur.channels() == img.channels()) + mat_cur.copyTo(img); + else if (img.channels() == 1) + cvtColor(mat_cur, img, COLOR_BGRA2GRAY); + else if (img.channels() == 3) + cvtColor(mat_cur, 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 + { + delete[] chunk.p; + return false; + } + + w0 = png_get_uint_32(chunk.p + 12); + h0 = png_get_uint_32(chunk.p + 16); + x0 = png_get_uint_32(chunk.p + 20); + y0 = png_get_uint_32(chunk.p + 24); + delay_num = png_get_uint_16(chunk.p + 28); + delay_den = png_get_uint_16(chunk.p + 30); + dop = chunk.p[32]; + bop = chunk.p[33]; + + if (int(x0 + w0) > img.cols || int(y0 + h0) > img.rows || dop > 2 || bop > 1) + { + delete[] chunk.p; + return false; + } + + memcpy(m_chunkIHDR.p + 8, chunk.p + 12, 8); + return true; + } + else if (id == id_IDAT) + { + m_is_IDAT_loaded = true; + png_process_data(png_ptr, info_ptr, chunk.p, chunk.size); + } + else if (id == id_fdAT && m_is_fcTL_loaded) + { + m_is_IDAT_loaded = true; + png_save_uint_32(chunk.p + 4, chunk.size - 16); + memcpy(chunk.p + 8, "IDAT", 4); + png_process_data(png_ptr, info_ptr, chunk.p + 4, chunk.size - 4); + } + else if (id == id_IEND) + { + if (processing_finish()) + { + compose_frame(frameCur.getRows(), frameRaw.getRows(), bop, x0, y0, w0, h0, mat_cur.channels()); + if (delay_den < 1000) + delay_num = cvRound(1000.0 / delay_den); + m_animation.durations.push_back(delay_num); + + if (mat_cur.channels() == img.channels()) + mat_cur.copyTo(img); + else if (img.channels() == 1) + cvtColor(mat_cur, img, COLOR_BGRA2GRAY); + else if (img.channels() == 3) + cvtColor(mat_cur, img, COLOR_BGRA2BGR); + } + else + return false; + + delete[] chunk.p; + return true; + } + else + png_process_data(png_ptr, info_ptr, chunk.p, chunk.size); + + delete[] chunk.p; + } + return false; + } + volatile bool result = false; - AutoBuffer _buffer(m_height); - uchar** buffer = _buffer.data(); + AutoBuffer _buffer(m_height); + unsigned char** buffer = _buffer.data(); bool color = img.channels() > 1; png_structp png_ptr = (png_structp)m_png_ptr; @@ -300,22 +609,178 @@ bool PngDecoder::readData( Mat& img ) return result; } +bool PngDecoder::nextPage() { + return ++m_frame_no < (int)m_frame_count; +} + +void PngDecoder::compose_frame(unsigned char** rows_dst, unsigned char** rows_src, unsigned char _bop, uint32_t x, uint32_t y, uint32_t w, uint32_t h, int channels) +{ + uint32_t i, j; + int u, v, al; + + for (j = 0; j < h; j++) + { + unsigned char* sp = rows_src[j]; + unsigned char* dp = rows_dst[j + y] + x * channels; + + if (_bop == 0) + memcpy(dp, sp, w * channels); + else + for (i = 0; i < w; i++, sp += 4, dp += 4) + { + if (sp[3] == 255) + memcpy(dp, sp, 4); + else + if (sp[3] != 0) + { + if (dp[3] != 0) + { + u = sp[3] * 255; + v = (255 - sp[3]) * dp[3]; + al = u + v; + dp[0] = (sp[0] * u + dp[0] * v) / al; + dp[1] = (sp[1] * u + dp[1] * v) / al; + dp[2] = (sp[2] * u + dp[2] * v) / al; + dp[3] = al / 255; + } + else + memcpy(dp, sp, 4); + } + } + } +} + +size_t PngDecoder::read_from_io(void* _Buffer, size_t _ElementSize, size_t _ElementCount) +{ + if (m_f) + return fread(_Buffer, _ElementSize, _ElementCount, m_f); + + if (m_buf_pos > m_buf.cols * m_buf.rows * m_buf.elemSize()) + CV_Error(Error::StsInternal, "PNG input buffer is incomplete"); + + memcpy( _Buffer, m_buf.ptr() + m_buf_pos, _ElementSize ); + m_buf_pos += _ElementSize; + return 1; +} + +uint32_t PngDecoder::read_chunk(Chunk& chunk) +{ + unsigned char len[4]; + if (read_from_io(&len, 4, 1) == 1) + { + chunk.size = png_get_uint_32(len) + 12; + if (chunk.size > PNG_USER_CHUNK_MALLOC_MAX) + { + CV_LOG_WARNING(NULL, "chunk data is too large"); + } + chunk.p = new unsigned char[chunk.size]; + memcpy(chunk.p, len, 4); + if (read_from_io(chunk.p + 4, chunk.size - 4, 1) == 1) + return *(uint32_t*)(chunk.p + 4); + } + return 0; +} + +bool PngDecoder::processing_start(void* frame_ptr, const Mat& img) +{ + static uint8_t header[8] = { 137, 80, 78, 71, 13, 10, 26, 10 }; + + png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + png_infop info_ptr = png_create_info_struct(png_ptr); + + m_png_ptr = png_ptr; + m_info_ptr = info_ptr; + + if (!png_ptr || !info_ptr) + return false; + + if (setjmp(png_jmpbuf(png_ptr))) + { + png_destroy_read_struct(&png_ptr, &info_ptr, 0); + return false; + } + + png_set_crc_action(png_ptr, PNG_CRC_QUIET_USE, PNG_CRC_QUIET_USE); + png_set_progressive_read_fn(png_ptr, frame_ptr, (png_progressive_info_ptr)info_fn, row_fn, NULL); + + if (img.channels() < 4) + png_set_strip_alpha(png_ptr); + else + png_set_tRNS_to_alpha(png_ptr); + + png_process_data(png_ptr, info_ptr, header, 8); + png_process_data(png_ptr, info_ptr, m_chunkIHDR.p, m_chunkIHDR.size); + + if ((m_color_type & PNG_COLOR_MASK_COLOR) && img.channels() > 1 && !m_use_rgb) + png_set_bgr(png_ptr); // convert RGB to BGR + else if (img.channels() > 1) + png_set_gray_to_rgb(png_ptr); // Gray->RGB + else + png_set_rgb_to_gray(png_ptr, 1, 0.299, 0.587); // RGB->Gray + + for (size_t i = 0; i < m_chunksInfo.size(); i++) + png_process_data(png_ptr, info_ptr, m_chunksInfo[i].p, m_chunksInfo[i].size); + + return true; +} + +bool PngDecoder::processing_finish() +{ + static uint8_t footer[12] = { 0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130 }; + + png_structp png_ptr = (png_structp)m_png_ptr; + png_infop info_ptr = (png_infop)m_info_ptr; + + if (!png_ptr || !info_ptr) + return false; + + if (setjmp(png_jmpbuf(png_ptr))) + { + png_destroy_read_struct(&png_ptr, &info_ptr, 0); + return false; + } + + png_process_data(png_ptr, info_ptr, footer, 12); + png_destroy_read_struct(&png_ptr, &info_ptr, 0); + m_png_ptr = 0; + return true; +} + +void PngDecoder::info_fn(png_structp png_ptr, png_infop info_ptr) +{ + png_set_expand(png_ptr); + png_set_strip_16(png_ptr); + (void)png_set_interlace_handling(png_ptr); + png_read_update_info(png_ptr, info_ptr); +} + +void PngDecoder::row_fn(png_structp png_ptr, png_bytep new_row, png_uint_32 row_num, int pass) +{ + CV_UNUSED(pass); + APNGFrame* frame = (APNGFrame*)png_get_progressive_ptr(png_ptr); + png_progressive_combine_row(png_ptr, frame->getRows()[row_num], new_row); +} /////////////////////// PngEncoder /////////////////// - PngEncoder::PngEncoder() { m_description = "Portable Network Graphics files (*.png)"; m_buf_supported = true; + op_zstream1.zalloc = NULL; + op_zstream2.zalloc = NULL; + next_seq_num = 0; + trnssize = 0; + palsize = 0; + memset(palette, 0, sizeof(palette)); + memset(trns, 0, sizeof(trns)); + memset(op, 0, sizeof(op)); } - PngEncoder::~PngEncoder() { } - bool PngEncoder::isFormatSupported( int depth ) const { return depth == CV_8U || depth == CV_16U; @@ -326,8 +791,7 @@ ImageEncoder PngEncoder::newEncoder() const return makePtr(); } - -void PngEncoder::writeDataToBuf(void* _png_ptr, uchar* src, size_t size) +void PngEncoder::writeDataToBuf(void* _png_ptr, unsigned char* src, size_t size) { if( size == 0 ) return; @@ -339,7 +803,6 @@ void PngEncoder::writeDataToBuf(void* _png_ptr, uchar* src, size_t size) memcpy( &(*encoder->m_buf)[cursz], src, size ); } - void PngEncoder::flushBuf(void*) { } @@ -449,6 +912,765 @@ bool PngEncoder::write( const Mat& img, const std::vector& params ) return result; } +size_t PngEncoder::write_to_io(void const* _Buffer, size_t _ElementSize, size_t _ElementCount, FILE * _Stream) +{ + if (_Stream) + return fwrite(_Buffer, _ElementSize, _ElementCount, _Stream); + + size_t cursz = m_buf->size(); + m_buf->resize(cursz + _ElementCount); + memcpy( &(*m_buf)[cursz], _Buffer, _ElementCount ); + return _ElementCount; +} + +void PngEncoder::writeChunk(FILE* f, const char* name, unsigned char* data, uint32_t length) +{ + unsigned char buf[4]; + uint32_t crc = crc32(0, Z_NULL, 0); + + png_save_uint_32(buf, length); + write_to_io(buf, 1, 4, f); + write_to_io(name, 1, 4, f); + crc = crc32(crc, (const Bytef*)name, 4); + + if (memcmp(name, "fdAT", 4) == 0) + { + png_save_uint_32(buf, next_seq_num++); + write_to_io(buf, 1, 4, f); + crc = crc32(crc, buf, 4); + length -= 4; + } + + if (data != NULL && length > 0) + { + write_to_io(data, 1, length, f); + crc = crc32(crc, data, length); + } + + png_save_uint_32(buf, crc); + write_to_io(buf, 1, 4, f); +} + +void PngEncoder::writeIDATs(FILE* f, int frame, unsigned char* data, uint32_t length, uint32_t idat_size) +{ + uint32_t z_cmf = data[0]; + if ((z_cmf & 0x0f) == 8 && (z_cmf & 0xf0) <= 0x70) + { + if (length >= 2) + { + uint32_t z_cinfo = z_cmf >> 4; + uint32_t half_z_window_size = 1 << (z_cinfo + 7); + while (idat_size <= half_z_window_size && half_z_window_size >= 256) + { + z_cinfo--; + half_z_window_size >>= 1; + } + z_cmf = (z_cmf & 0x0f) | (z_cinfo << 4); + if (data[0] != (unsigned char)z_cmf) + { + data[0] = (unsigned char)z_cmf; + data[1] &= 0xe0; + data[1] += (unsigned char)(0x1f - ((z_cmf << 8) + data[1]) % 0x1f); + } + } + } + + while (length > 0) + { + uint32_t ds = length; + if (ds > 32768) + ds = 32768; + + if (frame == 0) + writeChunk(f, "IDAT", data, ds); + else + writeChunk(f, "fdAT", data, ds + 4); + + data += ds; + length -= ds; + } +} + +void PngEncoder::processRect(unsigned char* row, int rowbytes, int bpp, int stride, int h, unsigned char* rows) +{ + int i, j, v; + int a, b, c, pa, pb, pc, p; + unsigned char* prev = NULL; + unsigned char* dp = rows; + unsigned char* out; + + for (j = 0; j < h; j++) + { + uint32_t sum = 0; + unsigned char* best_row = row_buf.data(); + uint32_t mins = ((uint32_t)(-1)) >> 1; + + out = row_buf.data() + 1; + for (i = 0; i < rowbytes; i++) + { + v = out[i] = row[i]; + sum += (v < 128) ? v : 256 - v; + } + mins = sum; + + sum = 0; + out = sub_row.data() + 1; + for (i = 0; i < bpp; i++) + { + v = out[i] = row[i]; + sum += (v < 128) ? v : 256 - v; + } + for (i = bpp; i < rowbytes; i++) + { + v = out[i] = row[i] - row[i - bpp]; + sum += (v < 128) ? v : 256 - v; + if (sum > mins) + break; + } + if (sum < mins) + { + mins = sum; + best_row = sub_row.data(); + } + + if (prev) + { + sum = 0; + out = up_row.data() + 1; + for (i = 0; i < rowbytes; i++) + { + v = out[i] = row[i] - prev[i]; + sum += (v < 128) ? v : 256 - v; + if (sum > mins) + break; + } + if (sum < mins) + { + mins = sum; + best_row = up_row.data(); + } + + sum = 0; + out = avg_row.data() + 1; + for (i = 0; i < bpp; i++) + { + v = out[i] = row[i] - prev[i] / 2; + sum += (v < 128) ? v : 256 - v; + } + for (i = bpp; i < rowbytes; i++) + { + v = out[i] = row[i] - (prev[i] + row[i - bpp]) / 2; + sum += (v < 128) ? v : 256 - v; + if (sum > mins) + break; + } + if (sum < mins) + { + mins = sum; + best_row = avg_row.data(); + } + + sum = 0; + out = paeth_row.data() + 1; + for (i = 0; i < bpp; i++) + { + v = out[i] = row[i] - prev[i]; + sum += (v < 128) ? v : 256 - v; + } + for (i = bpp; i < rowbytes; i++) + { + a = row[i - bpp]; + b = prev[i]; + c = prev[i - bpp]; + p = b - c; + pc = a - c; + pa = abs(p); + pb = abs(pc); + pc = abs(p + pc); + p = (pa <= pb && pa <= pc) ? a : (pb <= pc) ? b + : c; + v = out[i] = row[i] - p; + sum += (v < 128) ? v : 256 - v; + if (sum > mins) + break; + } + if (sum < mins) + { + best_row = paeth_row.data(); + } + } + + if (rows == NULL) + { + // deflate_rect_op() + op_zstream1.next_in = row_buf.data(); + op_zstream1.avail_in = rowbytes + 1; + deflate(&op_zstream1, Z_NO_FLUSH); + + op_zstream2.next_in = best_row; + op_zstream2.avail_in = rowbytes + 1; + deflate(&op_zstream2, Z_NO_FLUSH); + } + else + { + // deflate_rect_fin() + memcpy(dp, best_row, rowbytes + 1); + dp += rowbytes + 1; + } + + prev = row; + row += stride; + } +} + +void PngEncoder::deflateRectOp(unsigned char* pdata, int x, int y, int w, int h, int bpp, int stride, int zbuf_size, int n) +{ + unsigned char* row = pdata + y * stride + x * bpp; + int rowbytes = w * bpp; + + op_zstream1.data_type = Z_BINARY; + op_zstream1.next_out = op_zbuf1.data(); + op_zstream1.avail_out = zbuf_size; + + op_zstream2.data_type = Z_BINARY; + op_zstream2.next_out = op_zbuf2.data(); + op_zstream2.avail_out = zbuf_size; + + processRect(row, rowbytes, bpp, stride, h, NULL); + + deflate(&op_zstream1, Z_FINISH); + deflate(&op_zstream2, Z_FINISH); + op[n].p = pdata; + + if (op_zstream1.total_out < op_zstream2.total_out) + { + op[n].size = op_zstream1.total_out; + op[n].filters = 0; + } + else + { + op[n].size = op_zstream2.total_out; + op[n].filters = 1; + } + op[n].x = x; + op[n].y = y; + op[n].w = w; + op[n].h = h; + op[n].valid = 1; + deflateReset(&op_zstream1); + deflateReset(&op_zstream2); +} + +bool PngEncoder::getRect(uint32_t w, uint32_t h, unsigned char* pimage1, unsigned char* pimage2, unsigned char* ptemp, uint32_t bpp, uint32_t stride, int zbuf_size, uint32_t has_tcolor, uint32_t tcolor, int n) +{ + uint32_t i, j, x0, y0, w0, h0; + uint32_t x_min = w - 1; + uint32_t y_min = h - 1; + uint32_t x_max = 0; + uint32_t y_max = 0; + uint32_t diffnum = 0; + uint32_t over_is_possible = 1; + + if (!has_tcolor) + over_is_possible = 0; + + if (bpp == 1) + { + unsigned char* pa = pimage1; + unsigned char* pb = pimage2; + unsigned char* pc = ptemp; + + for (j = 0; j < h; j++) + for (i = 0; i < w; i++) + { + unsigned char c = *pb++; + if (*pa++ != c) + { + diffnum++; + if (has_tcolor && c == tcolor) + over_is_possible = 0; + if (i < x_min) + x_min = i; + if (i > x_max) + x_max = i; + if (j < y_min) + y_min = j; + if (j > y_max) + y_max = j; + } + else + c = tcolor; + + *pc++ = c; + } + } + else if (bpp == 2) + { + unsigned short* pa = (unsigned short*)pimage1; + unsigned short* pb = (unsigned short*)pimage2; + unsigned short* pc = (unsigned short*)ptemp; + + for (j = 0; j < h; j++) + for (i = 0; i < w; i++) + { + uint32_t c1 = *pa++; + uint32_t c2 = *pb++; + if ((c1 != c2) && ((c1 >> 8) || (c2 >> 8))) + { + diffnum++; + if ((c2 >> 8) != 0xFF) + over_is_possible = 0; + if (i < x_min) + x_min = i; + if (i > x_max) + x_max = i; + if (j < y_min) + y_min = j; + if (j > y_max) + y_max = j; + } + else + c2 = 0; + + *pc++ = c2; + } + } + else if (bpp == 3) + { + unsigned char* pa = pimage1; + unsigned char* pb = pimage2; + unsigned char* pc = ptemp; + + for (j = 0; j < h; j++) + for (i = 0; i < w; i++) + { + uint32_t c1 = (pa[2] << 16) + (pa[1] << 8) + pa[0]; + uint32_t c2 = (pb[2] << 16) + (pb[1] << 8) + pb[0]; + if (c1 != c2) + { + diffnum++; + if (has_tcolor && c2 == tcolor) + over_is_possible = 0; + if (i < x_min) + x_min = i; + if (i > x_max) + x_max = i; + if (j < y_min) + y_min = j; + if (j > y_max) + y_max = j; + } + else + c2 = tcolor; + + memcpy(pc, &c2, 3); + pa += 3; + pb += 3; + pc += 3; + } + } + else if (bpp == 4) + { + uint32_t* pa = (uint32_t*)pimage1; + uint32_t* pb = (uint32_t*)pimage2; + uint32_t* pc = (uint32_t*)ptemp; + + for (j = 0; j < h; j++) + for (i = 0; i < w; i++) + { + uint32_t c1 = *pa++; + uint32_t c2 = *pb++; + if ((c1 != c2) && ((c1 >> 24) || (c2 >> 24))) + { + diffnum++; + if ((c2 >> 24) != 0xFF) + over_is_possible = 0; + if (i < x_min) + x_min = i; + if (i > x_max) + x_max = i; + if (j < y_min) + y_min = j; + if (j > y_max) + y_max = j; + } + else + c2 = 0; + + *pc++ = c2; + } + } + + if (diffnum == 0) + { + return false; + } + else + { + x0 = x_min; + y0 = y_min; + w0 = x_max - x_min + 1; + h0 = y_max - y_min + 1; + } + + if (n < 3) + { + deflateRectOp(pimage2, x0, y0, w0, h0, bpp, stride, zbuf_size, n * 2); + + if (over_is_possible) + deflateRectOp(ptemp, x0, y0, w0, h0, bpp, stride, zbuf_size, n * 2 + 1); + } + + return true; +} + +void PngEncoder::deflateRectFin(unsigned char* zbuf, uint32_t* zsize, int bpp, int stride, unsigned char* rows, int zbuf_size, int n) +{ + unsigned char* row = op[n].p + op[n].y * stride + op[n].x * bpp; + int rowbytes = op[n].w * bpp; + + if (op[n].filters == 0) + { + unsigned char* dp = rows; + for (int j = 0; j < op[n].h; j++) + { + *dp++ = 0; + memcpy(dp, row, rowbytes); + dp += rowbytes; + row += stride; + } + } + else + processRect(row, rowbytes, bpp, stride, op[n].h, rows); + + z_stream fin_zstream; + fin_zstream.data_type = Z_BINARY; + fin_zstream.zalloc = Z_NULL; + fin_zstream.zfree = Z_NULL; + fin_zstream.opaque = Z_NULL; + deflateInit2(&fin_zstream, Z_BEST_COMPRESSION, 8, 15, 8, op[n].filters ? Z_FILTERED : Z_DEFAULT_STRATEGY); + + fin_zstream.next_out = zbuf; + fin_zstream.avail_out = zbuf_size; + fin_zstream.next_in = rows; + fin_zstream.avail_in = op[n].h * (rowbytes + 1); + deflate(&fin_zstream, Z_FINISH); + *zsize = fin_zstream.total_out; + deflateEnd(&fin_zstream); +} + +bool PngEncoder::writemulti(const std::vector& img_vec, const std::vector& params) +{ + CV_Assert(img_vec[0].depth() == CV_8U); + CV_LOG_INFO(NULL, "Multi page image will be written as animation with 1 second frame duration."); + + Animation animation; + animation.frames = img_vec; + + for (size_t i = 0; i < animation.frames.size(); i++) + { + animation.durations.push_back(1000); + } + return writeanimation(animation, params); +} + +bool PngEncoder::writeanimation(const Animation& animation, const std::vector& params) +{ + int compression_level = 6; + int compression_strategy = IMWRITE_PNG_STRATEGY_RLE; // Default strategy + bool isBilevel = false; + + for (size_t i = 0; i < params.size(); i += 2) + { + if (params[i] == IMWRITE_PNG_COMPRESSION) + { + compression_strategy = IMWRITE_PNG_STRATEGY_DEFAULT; // Default strategy + compression_level = params[i + 1]; + compression_level = MIN(MAX(compression_level, 0), Z_BEST_COMPRESSION); + } + if (params[i] == IMWRITE_PNG_STRATEGY) + { + compression_strategy = params[i + 1]; + compression_strategy = MIN(MAX(compression_strategy, 0), Z_FIXED); + } + if (params[i] == IMWRITE_PNG_BILEVEL) + { + isBilevel = params[i + 1] != 0; + } + } + + CV_UNUSED(isBilevel); + uint32_t first =0; + uint32_t loops= animation.loop_count; + uint32_t coltype= animation.frames[0].channels() == 1 ? PNG_COLOR_TYPE_GRAY : animation.frames[0].channels() == 3 ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA; + + FILE* m_f = NULL; + uint32_t i, j, k; + uint32_t x0, y0, w0, h0, dop, bop; + uint32_t idat_size, zbuf_size, zsize; + unsigned char header[8] = { 137, 80, 78, 71, 13, 10, 26, 10 }; + uint32_t num_frames = (int)animation.frames.size(); + uint32_t width = animation.frames[0].cols; + uint32_t height = animation.frames[0].rows; + uint32_t bpp = (coltype == 6) ? 4 : (coltype == 2) ? 3 + : (coltype == 4) ? 2 + : 1; + uint32_t has_tcolor = (coltype >= 4 || (coltype <= 2 && trnssize)) ? 1 : 0; + uint32_t tcolor = 0; + uint32_t rowbytes = width * bpp; + uint32_t imagesize = rowbytes * height; + + AutoBuffer temp(imagesize); + AutoBuffer over1(imagesize); + AutoBuffer over2(imagesize); + AutoBuffer over3(imagesize); + AutoBuffer rest(imagesize); + AutoBuffer rows((rowbytes + 1) * height); + + std::vector frames; + std::vector tmpframes; + + for (i = 0; i < (uint32_t)animation.frames.size(); i++) + { + APNGFrame apngFrame; + tmpframes.push_back(animation.frames[i].clone()); + // TO DO optimize BGR RGB conversations + if (animation.frames[i].channels() == 4) + cvtColor(animation.frames[i], tmpframes[i], COLOR_BGRA2RGBA); + if (animation.frames[i].channels() == 3) + cvtColor(animation.frames[i], tmpframes[i], COLOR_BGR2RGB); + + apngFrame.setMat(tmpframes[i], animation.durations[i]); + + if (i > 0 && !getRect(width, height, frames.back().getPixels(), apngFrame.getPixels(), over1.data(), bpp, rowbytes, 0, 0, 0, 3)) + { + frames[i - 1].setDelayNum(frames.back().getDelayNum() + apngFrame.getDelayNum()); + num_frames--; + } + else + frames.push_back(apngFrame); + } + + if (trnssize) + { + if (coltype == 0) + tcolor = trns[1]; + else if (coltype == 2) + tcolor = (((trns[5] << 8) + trns[3]) << 8) + trns[1]; + else if (coltype == 3) + { + for (i = 0; i < trnssize; i++) + if (trns[i] == 0) + { + has_tcolor = 1; + tcolor = i; + break; + } + } + } + + if (m_buf || (m_f = fopen(m_filename.c_str(), "wb")) != 0) + { + unsigned char buf_IHDR[13]; + unsigned char buf_acTL[8]; + unsigned char buf_fcTL[26]; + + png_save_uint_32(buf_IHDR, width); + png_save_uint_32(buf_IHDR + 4, height); + buf_IHDR[8] = 8; + buf_IHDR[9] = coltype; + buf_IHDR[10] = 0; + buf_IHDR[11] = 0; + buf_IHDR[12] = 0; + + png_save_uint_32(buf_acTL, num_frames - first); + png_save_uint_32(buf_acTL + 4, loops); + + write_to_io(header, 1, 8, m_f); + + writeChunk(m_f, "IHDR", buf_IHDR, 13); + + if (num_frames > 1) + writeChunk(m_f, "acTL", buf_acTL, 8); + else + first = 0; + + if (palsize > 0) + writeChunk(m_f, "PLTE", (unsigned char*)(&palette), palsize * 3); + + if ((animation.bgcolor != Scalar()) && (animation.frames.size() > 1)) + { + uint64_t bgvalue = (static_cast(animation.bgcolor[0]) & 0xFF) << 24 | + (static_cast(animation.bgcolor[1]) & 0xFF) << 16 | + (static_cast(animation.bgcolor[2]) & 0xFF) << 8 | + (static_cast(animation.bgcolor[3]) & 0xFF); + writeChunk(m_f, "bKGD", (unsigned char*)(&bgvalue), 6); //the bKGD chunk must precede the first IDAT chunk, and must follow the PLTE chunk. + } + + if (trnssize > 0) + writeChunk(m_f, "tRNS", trns, trnssize); + + op_zstream1.data_type = Z_BINARY; + op_zstream1.zalloc = Z_NULL; + op_zstream1.zfree = Z_NULL; + op_zstream1.opaque = Z_NULL; + deflateInit2(&op_zstream1, compression_level, 8, 15, 8, compression_strategy); + + op_zstream2.data_type = Z_BINARY; + op_zstream2.zalloc = Z_NULL; + op_zstream2.zfree = Z_NULL; + op_zstream2.opaque = Z_NULL; + deflateInit2(&op_zstream2, compression_level, 8, 15, 8, Z_FILTERED); + + idat_size = (rowbytes + 1) * height; + zbuf_size = idat_size + ((idat_size + 7) >> 3) + ((idat_size + 63) >> 6) + 11; + + AutoBuffer zbuf(zbuf_size); + op_zbuf1.allocate(zbuf_size); + op_zbuf2.allocate(zbuf_size); + row_buf.allocate(rowbytes + 1); + sub_row.allocate(rowbytes + 1); + up_row.allocate(rowbytes + 1); + avg_row.allocate(rowbytes + 1); + paeth_row.allocate(rowbytes + 1); + + row_buf[0] = 0; + sub_row[0] = 1; + up_row[0] = 2; + avg_row[0] = 3; + paeth_row[0] = 4; + + x0 = 0; + y0 = 0; + w0 = width; + h0 = height; + bop = 0; + next_seq_num = 0; + + for (j = 0; j < 6; j++) + op[j].valid = 0; + 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++) + { + uint32_t op_min; + int op_best; + + for (j = 0; j < 6; j++) + op[j].valid = 0; + + /* dispose = none */ + getRect(width, height, frames[i].getPixels(), frames[i + 1].getPixels(), over1.data(), bpp, rowbytes, zbuf_size, has_tcolor, tcolor, 0); + + /* dispose = background */ + if (has_tcolor) + { + memcpy(temp.data(), frames[i].getPixels(), imagesize); + if (coltype == 2) + for (j = 0; j < h0; j++) + for (k = 0; k < w0; k++) + memcpy(temp.data() + ((j + y0) * width + (k + x0)) * 3, &tcolor, 3); + else + for (j = 0; j < h0; j++) + memset(temp.data() + ((j + y0) * width + x0) * bpp, tcolor, w0 * bpp); + + getRect(width, height, temp.data(), frames[i + 1].getPixels(), over2.data(), bpp, rowbytes, zbuf_size, has_tcolor, tcolor, 1); + } + + /* dispose = previous */ + if (i > first) + getRect(width, height, rest.data(), frames[i + 1].getPixels(), over3.data(), bpp, rowbytes, zbuf_size, has_tcolor, tcolor, 2); + + op_min = op[0].size; + op_best = 0; + for (j = 1; j < 6; j++) + if (op[j].valid) + { + if (op[j].size < op_min) + { + op_min = op[j].size; + op_best = j; + } + } + + dop = op_best >> 1; + + png_save_uint_32(buf_fcTL, next_seq_num++); + png_save_uint_32(buf_fcTL + 4, w0); + png_save_uint_32(buf_fcTL + 8, h0); + png_save_uint_32(buf_fcTL + 12, x0); + png_save_uint_32(buf_fcTL + 16, y0); + png_save_uint_16(buf_fcTL + 20, frames[i].getDelayNum()); + png_save_uint_16(buf_fcTL + 22, frames[i].getDelayDen()); + buf_fcTL[24] = dop; + buf_fcTL[25] = bop; + writeChunk(m_f, "fcTL", buf_fcTL, 26); + + writeIDATs(m_f, i, zbuf.data(), zsize, idat_size); + + /* process apng dispose - begin */ + if (dop != 2) + memcpy(rest.data(), frames[i].getPixels(), imagesize); + + if (dop == 1) + { + if (coltype == 2) + for (j = 0; j < h0; j++) + for (k = 0; k < w0; k++) + memcpy(rest.data() + ((j + y0) * width + (k + x0)) * 3, &tcolor, 3); + else + for (j = 0; j < h0; j++) + memset(rest.data() + ((j + y0) * width + x0) * bpp, tcolor, w0 * bpp); + } + /* process apng dispose - end */ + + x0 = op[op_best].x; + y0 = op[op_best].y; + w0 = op[op_best].w; + h0 = op[op_best].h; + bop = op_best & 1; + + deflateRectFin(zbuf.data(), &zsize, bpp, rowbytes, rows.data(), zbuf_size, op_best); + } + + if (num_frames > 1) + { + png_save_uint_32(buf_fcTL, next_seq_num++); + png_save_uint_32(buf_fcTL + 4, w0); + png_save_uint_32(buf_fcTL + 8, h0); + png_save_uint_32(buf_fcTL + 12, x0); + png_save_uint_32(buf_fcTL + 16, y0); + png_save_uint_16(buf_fcTL + 20, frames[i].getDelayNum()); + png_save_uint_16(buf_fcTL + 22, frames[i].getDelayDen()); + buf_fcTL[24] = 0; + buf_fcTL[25] = bop; + writeChunk(m_f, "fcTL", buf_fcTL, 26); + } + + writeIDATs(m_f, num_frames - 1, zbuf.data(), zsize, idat_size); + + writeChunk(m_f, "IEND", 0, 0); + + if (m_f) + fclose(m_f); + + deflateEnd(&op_zstream1); + deflateEnd(&op_zstream2); + } + + return true; +} + } #endif diff --git a/modules/imgcodecs/src/grfmt_png.hpp b/modules/imgcodecs/src/grfmt_png.hpp index 3d8d1a764a..c23fb8ab54 100644 --- a/modules/imgcodecs/src/grfmt_png.hpp +++ b/modules/imgcodecs/src/grfmt_png.hpp @@ -47,26 +47,98 @@ #include "grfmt_base.hpp" #include "bitstrm.hpp" +#include +#include namespace cv { +struct Chunk { unsigned char* p; uint32_t size; }; +struct OP { unsigned char* p; uint32_t size; int x, y, w, h, valid, filters; }; + +typedef struct { + unsigned char r, g, b; +} rgb; + +class APNGFrame { +public: + + APNGFrame(); + + // Destructor + ~APNGFrame(); + + bool setMat(const cv::Mat& src, unsigned delayNum = 1, unsigned delayDen = 1000); + + // Getters and Setters + unsigned char* getPixels() const { return _pixels; } + void setPixels(unsigned char* pixels); + + unsigned int getWidth() const { return _width; } + void setWidth(unsigned int width); + + unsigned int getHeight() const { return _height; } + void setHeight(unsigned int height); + + unsigned char getColorType() const { return _colorType; } + void setColorType(unsigned char colorType); + + rgb* getPalette() { return _palette; } + void setPalette(const rgb* palette); + + unsigned char* getTransparency() { return _transparency; } + void setTransparency(const unsigned char* transparency); + + int getPaletteSize() const { return _paletteSize; } + void setPaletteSize(int paletteSize); + + int getTransparencySize() const { return _transparencySize; } + void setTransparencySize(int transparencySize); + + unsigned int getDelayNum() const { return _delayNum; } + void setDelayNum(unsigned int delayNum); + + unsigned int getDelayDen() const { return _delayDen; } + void setDelayDen(unsigned int delayDen); + + unsigned char** getRows() const { return _rows; } + void setRows(unsigned char** rows); + +private: + unsigned char* _pixels; + unsigned int _width; + unsigned int _height; + unsigned char _colorType; + rgb _palette[256]; + unsigned char _transparency[256]; + int _paletteSize; + int _transparencySize; + unsigned int _delayNum; + unsigned int _delayDen; + unsigned char** _rows; +}; + class PngDecoder CV_FINAL : public BaseImageDecoder { public: - PngDecoder(); virtual ~PngDecoder(); bool readData( Mat& img ) CV_OVERRIDE; bool readHeader() CV_OVERRIDE; - void close(); + bool nextPage() CV_OVERRIDE; ImageDecoder newDecoder() const CV_OVERRIDE; protected: - static void readDataFromBuf(void* png_ptr, uchar* dst, size_t size); + static void info_fn(png_structp png_ptr, png_infop info_ptr); + static void row_fn(png_structp png_ptr, png_bytep new_row, png_uint_32 row_num, int pass); + bool processing_start(void* frame_ptr, const Mat& img); + bool processing_finish(); + void compose_frame(unsigned char** rows_dst, unsigned char** rows_src, unsigned char bop, uint32_t x, uint32_t y, uint32_t w, uint32_t h, int channels); + size_t read_from_io(void* _Buffer, size_t _ElementSize, size_t _ElementCount); + uint32_t read_chunk(Chunk& chunk); int m_bit_depth; void* m_png_ptr; // pointer to decompression structure @@ -74,7 +146,25 @@ protected: void* m_end_info; // pointer to one more image information structure FILE* m_f; int m_color_type; + Chunk m_chunkIHDR; + int m_frame_no; size_t m_buf_pos; + std::vector m_chunksInfo; + APNGFrame frameRaw; + APNGFrame frameNext; + APNGFrame frameCur; + Mat m_mat_raw; + Mat m_mat_next; + uint32_t w0; + uint32_t h0; + uint32_t x0; + uint32_t y0; + uint32_t delay_num; + uint32_t delay_den; + uint32_t dop; + uint32_t bop; + bool m_is_fcTL_loaded; + bool m_is_IDAT_loaded; }; @@ -84,14 +174,40 @@ public: PngEncoder(); virtual ~PngEncoder(); - bool isFormatSupported( int depth ) const CV_OVERRIDE; - bool write( const Mat& img, const std::vector& params ) CV_OVERRIDE; + bool isFormatSupported( int depth ) const CV_OVERRIDE; + bool write( const Mat& img, const std::vector& params ) CV_OVERRIDE; + bool writemulti(const std::vector& img_vec, const std::vector& params) CV_OVERRIDE; + bool writeanimation(const Animation& animinfo, const std::vector& params) CV_OVERRIDE; ImageEncoder newEncoder() const CV_OVERRIDE; protected: - static void writeDataToBuf(void* png_ptr, uchar* src, size_t size); + static void writeDataToBuf(void* png_ptr, unsigned char* src, size_t size); static void flushBuf(void* png_ptr); + size_t write_to_io(void const* _Buffer, size_t _ElementSize, size_t _ElementCount, FILE* _Stream); + +private: + void writeChunk(FILE* f, const char* name, unsigned char* data, uint32_t length); + void writeIDATs(FILE* f, int frame, unsigned char* data, uint32_t length, uint32_t idat_size); + void processRect(unsigned char* row, int rowbytes, int bpp, int stride, int h, unsigned char* rows); + void deflateRectFin(unsigned char* zbuf, uint32_t* zsize, int bpp, int stride, unsigned char* rows, int zbuf_size, int n); + void deflateRectOp(unsigned char* pdata, int x, int y, int w, int h, int bpp, int stride, int zbuf_size, int n); + bool getRect(uint32_t w, uint32_t h, unsigned char* pimage1, unsigned char* pimage2, unsigned char* ptemp, uint32_t bpp, uint32_t stride, int zbuf_size, uint32_t has_tcolor, uint32_t tcolor, int n); + + AutoBuffer op_zbuf1; + AutoBuffer op_zbuf2; + AutoBuffer row_buf; + AutoBuffer sub_row; + AutoBuffer up_row; + AutoBuffer avg_row; + AutoBuffer paeth_row; + z_stream op_zstream1; + z_stream op_zstream2; + OP op[6]; + rgb palette[256]; + unsigned char trns[256]; + uint32_t palsize, trnssize; + uint32_t next_seq_num; }; } diff --git a/modules/imgcodecs/src/loadsave.cpp b/modules/imgcodecs/src/loadsave.cpp index 14c8b2c81a..e044554350 100644 --- a/modules/imgcodecs/src/loadsave.cpp +++ b/modules/imgcodecs/src/loadsave.cpp @@ -776,7 +776,8 @@ imreadanimation_(const String& filename, int flags, int start, int count, Animat if (current >= start) { - animation.durations.push_back(decoder->animation().durations[decoder->animation().durations.size() - 1]); + int duration = decoder->animation().durations.size() > 0 ? decoder->animation().durations.back() : 1000; + animation.durations.push_back(duration); animation.frames.push_back(mat); } diff --git a/modules/imgcodecs/test/test_animation.cpp b/modules/imgcodecs/test/test_animation.cpp new file mode 100644 index 0000000000..fcfce4b6ab --- /dev/null +++ b/modules/imgcodecs/test/test_animation.cpp @@ -0,0 +1,436 @@ +// 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" + +namespace opencv_test { namespace { + +static void readFileBytes(const std::string& fname, std::vector& buf) +{ + FILE * wfile = fopen(fname.c_str(), "rb"); + if (wfile != NULL) + { + fseek(wfile, 0, SEEK_END); + size_t wfile_size = ftell(wfile); + fseek(wfile, 0, SEEK_SET); + + buf.resize(wfile_size); + + size_t data_size = fread(&buf[0], 1, wfile_size, wfile); + + if(wfile) + { + fclose(wfile); + } + + EXPECT_EQ(data_size, wfile_size); + } +} + +static bool fillFrames(Animation& animation, bool hasAlpha, int n = 14) +{ + // 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 + "pngsuite/tp1n3p08.png"; + + EXPECT_TRUE(imreadanimation(filename, animation)); + EXPECT_EQ(1000, animation.durations.back()); + + if (!hasAlpha) + cvtColor(animation.frames[0], animation.frames[0], COLOR_BGRA2BGR); + + animation.loop_count = 0xffff; // 0xffff is the maximum value to set. + + // Add the first frame with a duration value of 400 milliseconds. + int duration = 80; + animation.durations[0] = duration * 5; + Mat image = animation.frames[0].clone(); + putText(animation.frames[0], "0", Point(5, 28), FONT_HERSHEY_SIMPLEX, .5, Scalar(100, 255, 0, 255), 2); + + // Define a region of interest (ROI) + Rect roi(2, 16, 26, 16); + + // Modify the ROI in n iterations to simulate slight changes in animation frames. + for (int i = 1; i < n; i++) + { + roi.x++; + roi.width -= 2; + RNG rng = theRNG(); + for (int x = roi.x; x < roi.x + roi.width; x++) + for (int y = roi.y; y < roi.y + roi.height; y++) + { + if (hasAlpha) + { + Vec4b& pixel = image.at(y, x); + if (pixel[3] > 0) + { + if (pixel[0] > 10) pixel[0] -= (uchar)rng.uniform(2, 5); + if (pixel[1] > 10) pixel[1] -= (uchar)rng.uniform(2, 5); + if (pixel[2] > 10) pixel[2] -= (uchar)rng.uniform(2, 5); + pixel[3] -= (uchar)rng.uniform(2, 5); + } + } + else + { + Vec3b& pixel = image.at(y, x); + if (pixel[0] > 50) pixel[0] -= (uchar)rng.uniform(2, 5); + if (pixel[1] > 50) pixel[1] -= (uchar)rng.uniform(2, 5); + if (pixel[2] > 50) pixel[2] -= (uchar)rng.uniform(2, 5); + } + } + + // Update the duration and add the modified frame to the animation. + duration += rng.uniform(2, 10); // Increase duration with random value (to be sure different duration values saved correctly). + animation.frames.push_back(image.clone()); + putText(animation.frames[i], format("%d", i), Point(5, 28), FONT_HERSHEY_SIMPLEX, .5, Scalar(100, 255, 0, 255), 2); + animation.durations.push_back(duration); + } + + // Add two identical frames with the same duration. + if (animation.frames.size() > 1 && animation.frames.size() < 20) + { + animation.durations.push_back(++duration); + animation.frames.push_back(animation.frames.back()); + animation.durations.push_back(++duration); + animation.frames.push_back(animation.frames.back()); + } + + return true; +} + +#ifdef HAVE_WEBP + +TEST(Imgcodecs_WebP, imwriteanimation_rgba) +{ + Animation s_animation, l_animation; + EXPECT_TRUE(fillFrames(s_animation, true)); + s_animation.bgcolor = Scalar(50, 100, 150, 128); // different values for test purpose. + + // Create a temporary output filename for saving the animation. + string output = cv::tempfile(".webp"); + + // Write the animation to a .webp file and verify success. + EXPECT_TRUE(imwriteanimation(output, s_animation)); + + // Read the animation back and compare with the original. + EXPECT_TRUE(imreadanimation(output, l_animation)); + + // Since the last frames are identical, WebP optimizes by storing only one of them, + // and the duration value for the last frame is handled by libwebp. + size_t expected_frame_count = s_animation.frames.size() - 2; + + // Verify that the number of frames matches the expected count. + EXPECT_EQ(expected_frame_count, imcount(output)); + EXPECT_EQ(expected_frame_count, l_animation.frames.size()); + + // Check that the background color and loop count match between saved and loaded animations. + EXPECT_EQ(l_animation.bgcolor, s_animation.bgcolor); // written as BGRA order + EXPECT_EQ(l_animation.loop_count, s_animation.loop_count); + + // Verify that the durations of frames match. + for (size_t i = 0; i < l_animation.frames.size() - 1; i++) + EXPECT_EQ(s_animation.durations[i], l_animation.durations[i]); + + EXPECT_TRUE(imreadanimation(output, l_animation, 5, 3)); + EXPECT_EQ(expected_frame_count + 3, l_animation.frames.size()); + EXPECT_EQ(l_animation.frames.size(), l_animation.durations.size()); + EXPECT_EQ(0, cvtest::norm(l_animation.frames[5], l_animation.frames[14], NORM_INF)); + EXPECT_EQ(0, cvtest::norm(l_animation.frames[6], l_animation.frames[15], NORM_INF)); + EXPECT_EQ(0, cvtest::norm(l_animation.frames[7], l_animation.frames[16], NORM_INF)); + + // Verify whether the imread function successfully loads the first frame + Mat frame = imread(output, IMREAD_UNCHANGED); + EXPECT_EQ(0, cvtest::norm(l_animation.frames[0], frame, NORM_INF)); + + std::vector buf; + readFileBytes(output, buf); + vector webp_frames; + + EXPECT_TRUE(imdecodemulti(buf, IMREAD_UNCHANGED, webp_frames)); + EXPECT_EQ(expected_frame_count, webp_frames.size()); + + // Clean up by removing the temporary file. + EXPECT_EQ(0, remove(output.c_str())); +} + +TEST(Imgcodecs_WebP, imwriteanimation_rgb) +{ + Animation s_animation, l_animation; + EXPECT_TRUE(fillFrames(s_animation, false)); + + // Create a temporary output filename for saving the animation. + string output = cv::tempfile(".webp"); + + // Write the animation to a .webp file and verify success. + EXPECT_TRUE(imwriteanimation(output, s_animation)); + + // Read the animation back and compare with the original. + EXPECT_TRUE(imreadanimation(output, l_animation)); + + // Since the last frames are identical, WebP optimizes by storing only one of them, + // and the duration value for the last frame is handled by libwebp. + size_t expected_frame_count = s_animation.frames.size() - 2; + + // Verify that the number of frames matches the expected count. + EXPECT_EQ(expected_frame_count, imcount(output)); + EXPECT_EQ(expected_frame_count, l_animation.frames.size()); + + // Verify that the durations of frames match. + for (size_t i = 0; i < l_animation.frames.size() - 1; i++) + EXPECT_EQ(s_animation.durations[i], l_animation.durations[i]); + + EXPECT_TRUE(imreadanimation(output, l_animation, 5, 3)); + EXPECT_EQ(expected_frame_count + 3, l_animation.frames.size()); + EXPECT_EQ(l_animation.frames.size(), l_animation.durations.size()); + EXPECT_TRUE(cvtest::norm(l_animation.frames[5], l_animation.frames[14], NORM_INF) == 0); + EXPECT_TRUE(cvtest::norm(l_animation.frames[6], l_animation.frames[15], NORM_INF) == 0); + EXPECT_TRUE(cvtest::norm(l_animation.frames[7], l_animation.frames[16], NORM_INF) == 0); + + // Verify whether the imread function successfully loads the first frame + Mat frame = imread(output, IMREAD_COLOR); + EXPECT_TRUE(cvtest::norm(l_animation.frames[0], frame, NORM_INF) == 0); + + std::vector buf; + readFileBytes(output, buf); + + vector webp_frames; + EXPECT_TRUE(imdecodemulti(buf, IMREAD_UNCHANGED, webp_frames)); + EXPECT_EQ(expected_frame_count,webp_frames.size()); + + // Clean up by removing the temporary file. + EXPECT_EQ(0, remove(output.c_str())); +} + +TEST(Imgcodecs_WebP, imwritemulti_rgba) +{ + Animation s_animation; + EXPECT_TRUE(fillFrames(s_animation, true)); + + string output = cv::tempfile(".webp"); + ASSERT_TRUE(imwrite(output, s_animation.frames)); + vector read_frames; + ASSERT_TRUE(imreadmulti(output, read_frames, IMREAD_UNCHANGED)); + EXPECT_EQ(s_animation.frames.size() - 2, read_frames.size()); + EXPECT_EQ(4, s_animation.frames[0].channels()); + EXPECT_EQ(0, remove(output.c_str())); +} + +TEST(Imgcodecs_WebP, imwritemulti_rgb) +{ + Animation s_animation; + EXPECT_TRUE(fillFrames(s_animation, false)); + + string output = cv::tempfile(".webp"); + ASSERT_TRUE(imwrite(output, s_animation.frames)); + vector read_frames; + ASSERT_TRUE(imreadmulti(output, read_frames)); + EXPECT_EQ(s_animation.frames.size() - 2, read_frames.size()); + EXPECT_EQ(0, remove(output.c_str())); +} + +TEST(Imgcodecs_WebP, imencode_rgba) +{ + Animation s_animation; + EXPECT_TRUE(fillFrames(s_animation, true, 3)); + + std::vector buf; + vector apng_frames; + + // Test encoding and decoding the images in memory (without saving to disk). + EXPECT_TRUE(imencode(".webp", s_animation.frames, buf)); + EXPECT_TRUE(imdecodemulti(buf, IMREAD_UNCHANGED, apng_frames)); + EXPECT_EQ(s_animation.frames.size() - 2, apng_frames.size()); +} + +#endif // HAVE_WEBP + +#ifdef HAVE_PNG + +TEST(Imgcodecs_APNG, imwriteanimation_rgba) +{ + Animation s_animation, l_animation; + EXPECT_TRUE(fillFrames(s_animation, true)); + + // Create a temporary output filename for saving the animation. + string output = cv::tempfile(".png"); + + // Write the animation to a .png file and verify success. + EXPECT_TRUE(imwriteanimation(output, s_animation)); + + // Read the animation back and compare with the original. + EXPECT_TRUE(imreadanimation(output, l_animation)); + + size_t expected_frame_count = s_animation.frames.size() - 2; + + // Verify that the number of frames matches the expected count. + EXPECT_EQ(expected_frame_count, imcount(output)); + EXPECT_EQ(expected_frame_count, l_animation.frames.size()); + + for (size_t i = 0; i < l_animation.frames.size() - 1; i++) + { + EXPECT_EQ(s_animation.durations[i], l_animation.durations[i]); + EXPECT_EQ(0, cvtest::norm(s_animation.frames[i], l_animation.frames[i], NORM_INF)); + } + + EXPECT_TRUE(imreadanimation(output, l_animation, 5, 3)); + EXPECT_EQ(expected_frame_count + 3, l_animation.frames.size()); + EXPECT_EQ(l_animation.frames.size(), l_animation.durations.size()); + EXPECT_EQ(0, cvtest::norm(l_animation.frames[5], l_animation.frames[14], NORM_INF)); + EXPECT_EQ(0, cvtest::norm(l_animation.frames[6], l_animation.frames[15], NORM_INF)); + EXPECT_EQ(0, cvtest::norm(l_animation.frames[7], l_animation.frames[16], NORM_INF)); + + // Verify whether the imread function successfully loads the first frame + Mat frame = imread(output, IMREAD_UNCHANGED); + EXPECT_EQ(0, cvtest::norm(l_animation.frames[0], frame, NORM_INF)); + + std::vector buf; + readFileBytes(output, buf); + vector apng_frames; + + EXPECT_TRUE(imdecodemulti(buf, IMREAD_UNCHANGED, apng_frames)); + EXPECT_EQ(expected_frame_count, apng_frames.size()); + + apng_frames.clear(); + // Test saving the animation frames as individual still images. + EXPECT_TRUE(imwrite(output, s_animation.frames)); + + // Read back the still images into a vector of Mats. + EXPECT_TRUE(imreadmulti(output, apng_frames)); + + // Expect all frames written as multi-page image + EXPECT_EQ(expected_frame_count, apng_frames.size()); + + // Clean up by removing the temporary file. + EXPECT_EQ(0, remove(output.c_str())); +} + +TEST(Imgcodecs_APNG, imwriteanimation_rgb) +{ + Animation s_animation, l_animation; + EXPECT_TRUE(fillFrames(s_animation, false)); + + string output = cv::tempfile(".png"); + + // Write the animation to a .png file and verify success. + EXPECT_TRUE(imwriteanimation(output, s_animation)); + + // Read the animation back and compare with the original. + EXPECT_TRUE(imreadanimation(output, l_animation)); + EXPECT_EQ(l_animation.frames.size(), s_animation.frames.size() - 2); + for (size_t i = 0; i < l_animation.frames.size() - 1; i++) + { + EXPECT_EQ(0, cvtest::norm(s_animation.frames[i], l_animation.frames[i], NORM_INF)); + } + EXPECT_EQ(0, remove(output.c_str())); +} + +TEST(Imgcodecs_APNG, imwritemulti_rgba) +{ + Animation s_animation; + EXPECT_TRUE(fillFrames(s_animation, true)); + + string output = cv::tempfile(".png"); + EXPECT_EQ(true, imwrite(output, s_animation.frames)); + vector read_frames; + EXPECT_EQ(true, imreadmulti(output, read_frames, IMREAD_UNCHANGED)); + EXPECT_EQ(read_frames.size(), s_animation.frames.size() - 2); + EXPECT_EQ(imcount(output), read_frames.size()); + EXPECT_EQ(0, remove(output.c_str())); +} + +TEST(Imgcodecs_APNG, imwritemulti_rgb) +{ + Animation s_animation; + EXPECT_TRUE(fillFrames(s_animation, false)); + + string output = cv::tempfile(".png"); + ASSERT_TRUE(imwrite(output, s_animation.frames)); + vector read_frames; + ASSERT_TRUE(imreadmulti(output, read_frames)); + EXPECT_EQ(read_frames.size(), s_animation.frames.size() - 2); + EXPECT_EQ(0, remove(output.c_str())); + + for (size_t i = 0; i < read_frames.size(); i++) + { + EXPECT_EQ(0, cvtest::norm(s_animation.frames[i], read_frames[i], NORM_INF)); + } +} + +TEST(Imgcodecs_APNG, imwritemulti_gray) +{ + Animation s_animation; + EXPECT_TRUE(fillFrames(s_animation, false)); + + for (size_t i = 0; i < s_animation.frames.size(); i++) + { + cvtColor(s_animation.frames[i], s_animation.frames[i], COLOR_BGR2GRAY); + } + + string output = cv::tempfile(".png"); + EXPECT_TRUE(imwrite(output, s_animation.frames)); + vector read_frames; + EXPECT_TRUE(imreadmulti(output, read_frames)); + EXPECT_EQ(1, read_frames[0].channels()); + read_frames.clear(); + EXPECT_TRUE(imreadmulti(output, read_frames, IMREAD_UNCHANGED)); + EXPECT_EQ(1, read_frames[0].channels()); + read_frames.clear(); + EXPECT_TRUE(imreadmulti(output, read_frames, IMREAD_COLOR)); + EXPECT_EQ(3, read_frames[0].channels()); + read_frames.clear(); + EXPECT_TRUE(imreadmulti(output, read_frames, IMREAD_GRAYSCALE)); + EXPECT_EQ(0, remove(output.c_str())); + + for (size_t i = 0; i < read_frames.size(); i++) + { + EXPECT_EQ(0, cvtest::norm(s_animation.frames[i], read_frames[i], NORM_INF)); + } +} + +TEST(Imgcodecs_APNG, imwriteanimation_bgcolor) +{ + Animation s_animation, l_animation; + EXPECT_TRUE(fillFrames(s_animation, true, 2)); + s_animation.bgcolor = Scalar(50, 100, 150, 128); // different values for test purpose. + + // Create a temporary output filename for saving the animation. + string output = cv::tempfile(".png"); + + // Write the animation to a .png file and verify success. + EXPECT_TRUE(imwriteanimation(output, s_animation)); + + // Read the animation back and compare with the original. + EXPECT_TRUE(imreadanimation(output, l_animation)); + + // Check that the background color match between saved and loaded animations. + EXPECT_EQ(l_animation.bgcolor, s_animation.bgcolor); + EXPECT_EQ(0, remove(output.c_str())); + + EXPECT_TRUE(fillFrames(s_animation, true, 2)); + s_animation.bgcolor = Scalar(); + + output = cv::tempfile(".png"); + EXPECT_TRUE(imwriteanimation(output, s_animation)); + EXPECT_TRUE(imreadanimation(output, l_animation)); + EXPECT_EQ(l_animation.bgcolor, s_animation.bgcolor); + + EXPECT_EQ(0, remove(output.c_str())); +} + +TEST(Imgcodecs_APNG, imencode_rgba) +{ + Animation s_animation; + EXPECT_TRUE(fillFrames(s_animation, true, 3)); + + std::vector buf; + vector read_frames; + // Test encoding and decoding the images in memory (without saving to disk). + EXPECT_TRUE(imencode(".png", s_animation.frames, buf)); + EXPECT_TRUE(imdecodemulti(buf, IMREAD_UNCHANGED, read_frames)); + EXPECT_EQ(read_frames.size(), s_animation.frames.size() - 2); +} + +#endif // HAVE_PNG + +}} // namespace diff --git a/modules/imgcodecs/test/test_webp.cpp b/modules/imgcodecs/test/test_webp.cpp index 0e8280c73b..6414e60469 100644 --- a/modules/imgcodecs/test/test_webp.cpp +++ b/modules/imgcodecs/test/test_webp.cpp @@ -1,6 +1,7 @@ // 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 +// of this distribution and at http://opencv.org/license.html. + #include "test_precomp.hpp" namespace opencv_test { namespace { @@ -113,234 +114,6 @@ TEST(Imgcodecs_WebP, encode_decode_with_alpha_webp) EXPECT_EQ(512, img_webp_bgr.rows); } -TEST(Imgcodecs_WebP, load_save_animation_rgba) -{ - RNG rng = theRNG(); - - // 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 + "pngsuite/tp1n3p08.png"; - - // Create an Animation object using the default constructor. - // This initializes the loop count to 0 (infinite looping), background color to 0 (transparent) - Animation l_animation; - - // Create an Animation object with custom parameters. - int loop_count = 0xffff; // 0xffff is the maximum value to set. - Scalar bgcolor(125, 126, 127, 128); // different values for test purpose. - Animation s_animation(loop_count, bgcolor); - - // Load the image file with alpha channel (IMREAD_UNCHANGED). - Mat image = imread(filename, IMREAD_UNCHANGED); - ASSERT_FALSE(image.empty()) << "Failed to load image: " << filename; - - // Add the first frame with a duration value of 500 milliseconds. - int duration = 100; - s_animation.durations.push_back(duration * 5); - s_animation.frames.push_back(image.clone()); // Store the first frame. - putText(s_animation.frames[0], "0", Point(5, 28), FONT_HERSHEY_SIMPLEX, .5, Scalar(100, 255, 0, 255), 2); - - // Define a region of interest (ROI) in the loaded image for manipulation. - Mat roi = image(Rect(0, 16, 32, 16)); // Select a subregion of the image. - - // Modify the ROI in 13 iterations to simulate slight changes in animation frames. - for (int i = 1; i < 14; i++) - { - for (int x = 0; x < roi.rows; x++) - for (int y = 0; y < roi.cols; y++) - { - // Apply random changes to pixel values to create animation variations. - Vec4b& pixel = roi.at(x, y); - if (pixel[3] > 0) - { - if (pixel[0] > 10) pixel[0] -= (uchar)rng.uniform(3, 10); // Reduce blue channel. - if (pixel[1] > 10) pixel[1] -= (uchar)rng.uniform(3, 10); // Reduce green channel. - if (pixel[2] > 10) pixel[2] -= (uchar)rng.uniform(3, 10); // Reduce red channel. - pixel[3] -= (uchar)rng.uniform(2, 5); // Reduce alpha channel. - } - } - - // Update the duration and add the modified frame to the animation. - duration += rng.uniform(2, 10); // Increase duration with random value (to be sure different duration values saved correctly). - s_animation.frames.push_back(image.clone()); - putText(s_animation.frames[i], format("%d", i), Point(5, 28), FONT_HERSHEY_SIMPLEX, .5, Scalar(100, 255, 0, 255), 2); - s_animation.durations.push_back(duration); - } - - // Add two identical frames with the same duration. - s_animation.durations.push_back(duration); - s_animation.frames.push_back(s_animation.frames[13].clone()); - s_animation.durations.push_back(duration); - s_animation.frames.push_back(s_animation.frames[13].clone()); - - // Create a temporary output filename for saving the animation. - string output = cv::tempfile(".webp"); - - // Write the animation to a .webp file and verify success. - EXPECT_TRUE(imwriteanimation(output, s_animation)); - imwriteanimation("output.webp", s_animation); - - // Read the animation back and compare with the original. - EXPECT_TRUE(imreadanimation(output, l_animation)); - - // Since the last frames are identical, WebP optimizes by storing only one of them, - // and the duration value for the last frame is handled by libwebp. - size_t expected_frame_count = s_animation.frames.size() - 2; - - // Verify that the number of frames matches the expected count. - EXPECT_EQ(imcount(output), expected_frame_count); - EXPECT_EQ(l_animation.frames.size(), expected_frame_count); - - // Check that the background color and loop count match between saved and loaded animations. - EXPECT_EQ(l_animation.bgcolor, s_animation.bgcolor); // written as BGRA order - EXPECT_EQ(l_animation.loop_count, s_animation.loop_count); - - // Verify that the durations of frames match. - for (size_t i = 0; i < l_animation.frames.size() - 1; i++) - EXPECT_EQ(s_animation.durations[i], l_animation.durations[i]); - - EXPECT_TRUE(imreadanimation(output, l_animation, 5, 3)); - EXPECT_EQ(l_animation.frames.size(), expected_frame_count + 3); - EXPECT_EQ(l_animation.frames.size(), l_animation.durations.size()); - EXPECT_EQ(0, cvtest::norm(l_animation.frames[5], l_animation.frames[14], NORM_INF)); - EXPECT_EQ(0, cvtest::norm(l_animation.frames[6], l_animation.frames[15], NORM_INF)); - EXPECT_EQ(0, cvtest::norm(l_animation.frames[7], l_animation.frames[16], NORM_INF)); - - // Verify whether the imread function successfully loads the first frame - Mat frame = imread(output, IMREAD_UNCHANGED); - EXPECT_EQ(0, cvtest::norm(l_animation.frames[0], frame, NORM_INF)); - - std::vector buf; - readFileBytes(output, buf); - vector webp_frames; - - EXPECT_TRUE(imdecodemulti(buf, IMREAD_UNCHANGED, webp_frames)); - EXPECT_EQ(expected_frame_count, webp_frames.size()); - - webp_frames.clear(); - // Test saving the animation frames as individual still images. - EXPECT_TRUE(imwrite(output, s_animation.frames)); - - // Read back the still images into a vector of Mats. - EXPECT_TRUE(imreadmulti(output, webp_frames)); - - // Expect all frames written as multi-page image - expected_frame_count = 14; - EXPECT_EQ(expected_frame_count, webp_frames.size()); - - // Test encoding and decoding the images in memory (without saving to disk). - webp_frames.clear(); - EXPECT_TRUE(imencode(".webp", s_animation.frames, buf)); - EXPECT_TRUE(imdecodemulti(buf, IMREAD_UNCHANGED, webp_frames)); - EXPECT_EQ(expected_frame_count, webp_frames.size()); - - // Clean up by removing the temporary file. - EXPECT_EQ(0, remove(output.c_str())); -} - -TEST(Imgcodecs_WebP, load_save_animation_rgb) -{ - RNG rng = theRNG(); - - // 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 + "pngsuite/tp1n3p08.png"; - - // Create an Animation object using the default constructor. - // This initializes the loop count to 0 (infinite looping), background color to 0 (transparent) - Animation l_animation; - - // Create an Animation object with custom parameters. - int loop_count = 0xffff; // 0xffff is the maximum value to set. - Scalar bgcolor(125, 126, 127, 128); // different values for test purpose. - Animation s_animation(loop_count, bgcolor); - - // Load the image file without alpha channel - Mat image = imread(filename); - ASSERT_FALSE(image.empty()) << "Failed to load image: " << filename; - - // Add the first frame with a duration value of 500 milliseconds. - int duration = 100; - s_animation.durations.push_back(duration * 5); - s_animation.frames.push_back(image.clone()); // Store the first frame. - putText(s_animation.frames[0], "0", Point(5, 28), FONT_HERSHEY_SIMPLEX, .5, Scalar(100, 255, 0, 255), 2); - - // Define a region of interest (ROI) in the loaded image for manipulation. - Mat roi = image(Rect(0, 16, 32, 16)); // Select a subregion of the image. - - // Modify the ROI in 13 iterations to simulate slight changes in animation frames. - for (int i = 1; i < 14; i++) - { - for (int x = 0; x < roi.rows; x++) - for (int y = 0; y < roi.cols; y++) - { - // Apply random changes to pixel values to create animation variations. - Vec3b& pixel = roi.at(x, y); - if (pixel[0] > 50) pixel[0] -= (uchar)rng.uniform(3, 10); // Reduce blue channel. - if (pixel[1] > 50) pixel[1] -= (uchar)rng.uniform(3, 10); // Reduce green channel. - if (pixel[2] > 50) pixel[2] -= (uchar)rng.uniform(3, 10); // Reduce red channel. - } - - // Update the duration and add the modified frame to the animation. - duration += rng.uniform(2, 10); // Increase duration with random value (to be sure different duration values saved correctly). - s_animation.frames.push_back(image.clone()); - putText(s_animation.frames[i], format("%d", i), Point(5, 28), FONT_HERSHEY_SIMPLEX, .5, Scalar(100, 255, 0, 255), 2); - s_animation.durations.push_back(duration); - } - - // Add two identical frames with the same duration. - s_animation.durations.push_back(duration); - s_animation.frames.push_back(s_animation.frames[13].clone()); - s_animation.durations.push_back(duration); - s_animation.frames.push_back(s_animation.frames[13].clone()); - - // Create a temporary output filename for saving the animation. - string output = cv::tempfile(".webp"); - - // Write the animation to a .webp file and verify success. - EXPECT_EQ(true, imwriteanimation(output, s_animation)); - - // Read the animation back and compare with the original. - EXPECT_EQ(true, imreadanimation(output, l_animation)); - - // Since the last frames are identical, WebP optimizes by storing only one of them, - // and the duration value for the last frame is handled by libwebp. - size_t expected_frame_count = s_animation.frames.size() - 2; - - // Verify that the number of frames matches the expected count. - EXPECT_EQ(imcount(output), expected_frame_count); - EXPECT_EQ(l_animation.frames.size(), expected_frame_count); - - // Check that the background color and loop count match between saved and loaded animations. - EXPECT_EQ(l_animation.bgcolor, s_animation.bgcolor); // written as BGRA order - EXPECT_EQ(l_animation.loop_count, s_animation.loop_count); - - // Verify that the durations of frames match. - for (size_t i = 0; i < l_animation.frames.size() - 1; i++) - EXPECT_EQ(s_animation.durations[i], l_animation.durations[i]); - - EXPECT_EQ(true, imreadanimation(output, l_animation, 5, 3)); - EXPECT_EQ(l_animation.frames.size(), expected_frame_count + 3); - EXPECT_EQ(l_animation.frames.size(), l_animation.durations.size()); - EXPECT_TRUE(cvtest::norm(l_animation.frames[5], l_animation.frames[14], NORM_INF) == 0); - EXPECT_TRUE(cvtest::norm(l_animation.frames[6], l_animation.frames[15], NORM_INF) == 0); - EXPECT_TRUE(cvtest::norm(l_animation.frames[7], l_animation.frames[16], NORM_INF) == 0); - - // Verify whether the imread function successfully loads the first frame - Mat frame = imread(output, IMREAD_COLOR); - EXPECT_TRUE(cvtest::norm(l_animation.frames[0], frame, NORM_INF) == 0); - - std::vector buf; - readFileBytes(output, buf); - - vector webp_frames; - EXPECT_TRUE(imdecodemulti(buf, IMREAD_UNCHANGED, webp_frames)); - EXPECT_EQ(webp_frames.size(), expected_frame_count); - - // Clean up by removing the temporary file. - EXPECT_EQ(0, remove(output.c_str())); -} - #endif // HAVE_WEBP }} // namespace From f15fa21c6b1292590046e0e7425c719cddeefbaf Mon Sep 17 00:00:00 2001 From: MaximSmolskiy Date: Tue, 31 Dec 2024 02:49:15 +0300 Subject: [PATCH 065/102] Speed up and reduce memory consumption for findContours --- modules/imgproc/src/contours_new.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/modules/imgproc/src/contours_new.cpp b/modules/imgproc/src/contours_new.cpp index 0b82496aaa..9f8c716fbb 100644 --- a/modules/imgproc/src/contours_new.cpp +++ b/modules/imgproc/src/contours_new.cpp @@ -368,10 +368,6 @@ CNode& ContourScanner_::makeContour(schar& nbd_, const bool is_hole, const int x const Point start_pt(x - (is_hole ? 1 : 0), y); CNode& res = tree.newElem(); - if (isChain) - res.body.codes.reserve(200); - else - res.body.pts.reserve(200); res.body.isHole = is_hole; res.body.isChain = isChain; res.body.origin = start_pt + offset; From d2264d5868b94abc88f3a3793a108f6063af1e79 Mon Sep 17 00:00:00 2001 From: Alexander Smorkalov Date: Tue, 31 Dec 2024 10:23:13 +0300 Subject: [PATCH 066/102] Tune threshold to stabinlize test with Vulkan backend. --- modules/dnn/test/test_tflite_importer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/dnn/test/test_tflite_importer.cpp b/modules/dnn/test/test_tflite_importer.cpp index 9773943fba..94cc16ca06 100644 --- a/modules/dnn/test/test_tflite_importer.cpp +++ b/modules/dnn/test/test_tflite_importer.cpp @@ -93,7 +93,7 @@ TEST_P(Test_TFLite, face_landmark) { if (backend == DNN_BACKEND_CUDA && target == DNN_TARGET_CUDA_FP16) applyTestTag(CV_TEST_TAG_DNN_SKIP_CUDA_FP16); - double l1 = 2e-5, lInf = 2e-4; + double l1 = 2.2e-5, lInf = 2e-4; if (target == DNN_TARGET_CPU_FP16 || target == DNN_TARGET_CUDA_FP16 || target == DNN_TARGET_OPENCL_FP16 || target == DNN_TARGET_MYRIAD || (backend == DNN_BACKEND_INFERENCE_ENGINE_NGRAPH && target == DNN_TARGET_OPENCL)) { From c803aa2dddb536533cccb25b87528e2c9caccdaf Mon Sep 17 00:00:00 2001 From: Alexander Smorkalov <2536374+asmorkalov@users.noreply.github.com> Date: Tue, 31 Dec 2024 11:53:04 +0300 Subject: [PATCH 067/102] Merge pull request #26057 from asmorkalov:as/android_16k_pages Android builds update #26057 Fixes https://github.com/opencv/opencv/issues/26027 Should also address https://github.com/opencv/opencv/issues/26542 Changes: - Switched to Android build tools 34, NDK 26d, target API level 34 (required by Google Play). - Use flexible page size on Android by default to support Android 15+. - Dummy stub for R and BuildConfig classes for javadoc. - Java 17 everywhere. - Strict ndkVersion and ABI list in release package. Related: - Docker: https://github.com/opencv-infrastructure/opencv-gha-dockerfile/pull/41 - Pipeline: https://github.com/opencv/ci-gha-workflow/pull/183 Related IPP issue with NDK 27+: https://github.com/opencv/opencv/issues/26072 Google documentation for 16kb pages support : https://developer.android.com/guide/practices/page-sizes?hl=en ### 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/OpenCVCompilerOptions.cmake | 8 +++++++ cmake/android/android_gradle_projects.cmake | 22 ++++++++++--------- modules/java/android_sdk/CMakeLists.txt | 6 ++++- .../aar-template/OpenCV/build.gradle.template | 1 + platforms/android/aar-template/build.gradle | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- platforms/android/build_java_shared_aar.py | 17 +++++++++++--- platforms/android/build_sdk.py | 18 +++++++++++++-- platforms/android/build_static_aar.py | 9 +++++--- platforms/android/default.config.py | 6 +++++ samples/android/build.gradle.in | 2 +- .../build.gradle.in | 3 ++- .../jni/CMakeLists.txt | 8 +++++++ .../android/tutorial-4-opencl/build.gradle.in | 2 ++ .../tutorial-4-opencl/jni/CMakeLists.txt | 8 +++++++ 15 files changed, 91 insertions(+), 23 deletions(-) create mode 100644 platforms/android/default.config.py diff --git a/cmake/OpenCVCompilerOptions.cmake b/cmake/OpenCVCompilerOptions.cmake index a664e417c3..512e6d1abd 100644 --- a/cmake/OpenCVCompilerOptions.cmake +++ b/cmake/OpenCVCompilerOptions.cmake @@ -404,6 +404,14 @@ if(NOT OPENCV_SKIP_LINK_NO_UNDEFINED) endif() endif() +# For 16k pages support with NDK prior 27 +# Details: https://developer.android.com/guide/practices/page-sizes?hl=en +if(ANDROID AND ANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES AND (ANDROID_ABI STREQUAL arm64-v8a OR ANDROID_ABI STREQUAL x86_64)) + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,max-page-size=16384") + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,max-page-size=16384") + set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,-z,max-page-size=16384") +endif() + # combine all "extra" options if(NOT OPENCV_SKIP_EXTRA_COMPILER_FLAGS) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OPENCV_EXTRA_FLAGS} ${OPENCV_EXTRA_C_FLAGS}") diff --git a/cmake/android/android_gradle_projects.cmake b/cmake/android/android_gradle_projects.cmake index 4278b10f8d..d8e65a036a 100644 --- a/cmake/android/android_gradle_projects.cmake +++ b/cmake/android/android_gradle_projects.cmake @@ -1,5 +1,5 @@ # https://developer.android.com/studio/releases/gradle-plugin -set(ANDROID_GRADLE_PLUGIN_VERSION "7.3.1" CACHE STRING "Android Gradle Plugin version") +set(ANDROID_GRADLE_PLUGIN_VERSION "8.6.0" CACHE STRING "Android Gradle Plugin version") message(STATUS "Android Gradle Plugin version: ${ANDROID_GRADLE_PLUGIN_VERSION}") set(KOTLIN_PLUGIN_VERSION "1.8.20" CACHE STRING "Kotlin Plugin version") @@ -13,16 +13,16 @@ else() set(KOTLIN_STD_LIB "" CACHE STRING "Kotlin Standard Library dependency") endif() -set(GRADLE_VERSION "7.6.3" CACHE STRING "Gradle version") +set(GRADLE_VERSION "8.11.1" CACHE STRING "Gradle version") message(STATUS "Gradle version: ${GRADLE_VERSION}") -set(ANDROID_COMPILE_SDK_VERSION "31" CACHE STRING "Android compileSdkVersion") +set(ANDROID_COMPILE_SDK_VERSION "34" CACHE STRING "Android compileSdkVersion") if(ANDROID_NATIVE_API_LEVEL GREATER 21) set(ANDROID_MIN_SDK_VERSION "${ANDROID_NATIVE_API_LEVEL}" CACHE STRING "Android minSdkVersion") else() set(ANDROID_MIN_SDK_VERSION "21" CACHE STRING "Android minSdkVersion") endif() -set(ANDROID_TARGET_SDK_VERSION "31" CACHE STRING "Android minSdkVersion") +set(ANDROID_TARGET_SDK_VERSION "34" CACHE STRING "Android targetSdkVersion") set(ANDROID_BUILD_BASE_DIR "${OpenCV_BINARY_DIR}/opencv_android" CACHE INTERNAL "") set(ANDROID_TMP_INSTALL_BASE_DIR "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/install/opencv_android") @@ -35,11 +35,11 @@ include '${ANDROID_ABI}' ") set(ANDROID_INSTALL_ABI_FILTER " -//reset() -//include 'armeabi-v7a' -//include 'arm64-v8a' -//include 'x86' -//include 'x86_64' + reset() + include 'armeabi-v7a' + include 'arm64-v8a' + include 'x86' + include 'x86_64' ") if(NOT INSTALL_CREATE_DISTRIB) set(ANDROID_INSTALL_ABI_FILTER "${ANDROID_BUILD_ABI_FILTER}") @@ -54,7 +54,9 @@ set(ANDROID_STRICT_BUILD_CONFIGURATION "true") configure_file("${OpenCV_SOURCE_DIR}/samples/android/build.gradle.in" "${ANDROID_BUILD_BASE_DIR}/build.gradle" @ONLY) set(ANDROID_ABI_FILTER "${ANDROID_INSTALL_ABI_FILTER}") -set(ANDROID_STRICT_BUILD_CONFIGURATION "false") +# CI uses NDK 26d to overcome https://github.com/opencv/opencv/issues/26072 +# It's ahead of default configuration and we have to force version to get non-controversial parts of the package +#set(ANDROID_STRICT_BUILD_CONFIGURATION "false") configure_file("${OpenCV_SOURCE_DIR}/samples/android/build.gradle.in" "${ANDROID_TMP_INSTALL_BASE_DIR}/${ANDROID_INSTALL_SAMPLES_DIR}/build.gradle" @ONLY) install(FILES "${ANDROID_TMP_INSTALL_BASE_DIR}/${ANDROID_INSTALL_SAMPLES_DIR}/build.gradle" DESTINATION "${ANDROID_INSTALL_SAMPLES_DIR}" COMPONENT samples) diff --git a/modules/java/android_sdk/CMakeLists.txt b/modules/java/android_sdk/CMakeLists.txt index a9e6ff52af..42fd08e15b 100644 --- a/modules/java/android_sdk/CMakeLists.txt +++ b/modules/java/android_sdk/CMakeLists.txt @@ -20,6 +20,11 @@ set(SOURSE_SETS_JAVA_SRC_DIRS "'java/src'") set(SOURSE_SETS_RES_SRC_DIRS "'java/res'") set(SOURSE_SETS_MANIFEST_SRC_FILE "'java/AndroidManifest.xml'") set(BUILD_GRADLE_COMPILE_OPTIONS " + android { + buildFeatures { + buildConfig true + } + } compileOptions { sourceCompatibility JavaVersion.VERSION_${ANDROID_GRADLE_JAVA_VERSION_INIT} targetCompatibility JavaVersion.VERSION_${ANDROID_GRADLE_JAVA_VERSION_INIT} @@ -183,7 +188,6 @@ set(SOURSE_SETS_JNI_LIBS_SRC_DIRS "'../../jni'") set(SOURSE_SETS_JAVA_SRC_DIRS "'src'") set(SOURSE_SETS_RES_SRC_DIRS "'${OpenCV_SOURCE_DIR}/modules/java/android_sdk/android_gradle_lib/res'") set(SOURSE_SETS_MANIFEST_SRC_FILE "'AndroidManifest.xml'") -set(BUILD_GRADLE_COMPILE_OPTIONS "") set(MAVEN_PUBLISH_PLUGIN_DECLARATION "") set(BUILD_GRADLE_ANDROID_PUBLISHING_CONFIG "") set(BUILD_GRADLE_PUBLISHING_CONFIG "") diff --git a/platforms/android/aar-template/OpenCV/build.gradle.template b/platforms/android/aar-template/OpenCV/build.gradle.template index b12802063b..192a46877a 100644 --- a/platforms/android/aar-template/OpenCV/build.gradle.template +++ b/platforms/android/aar-template/OpenCV/build.gradle.template @@ -7,6 +7,7 @@ plugins { android { namespace 'org.opencv' compileSdk ${COMPILE_SDK} + ndkVersion "${NDK_VERSION}" defaultConfig { minSdk ${MIN_SDK} diff --git a/platforms/android/aar-template/build.gradle b/platforms/android/aar-template/build.gradle index 280dab5da7..538635ace5 100644 --- a/platforms/android/aar-template/build.gradle +++ b/platforms/android/aar-template/build.gradle @@ -1,5 +1,5 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id 'com.android.library' version '7.3.0' apply false + id 'com.android.library' version '8.6.0' apply false id 'org.jetbrains.kotlin.android' version '1.8.20' apply false } diff --git a/platforms/android/aar-template/gradle/wrapper/gradle-wrapper.properties b/platforms/android/aar-template/gradle/wrapper/gradle-wrapper.properties index c4f47b9a2e..9300a54eba 100644 --- a/platforms/android/aar-template/gradle/wrapper/gradle-wrapper.properties +++ b/platforms/android/aar-template/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Mon Jul 10 11:57:38 SGT 2023 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/platforms/android/build_java_shared_aar.py b/platforms/android/build_java_shared_aar.py index 8e17a7a4d6..5452ac5d2c 100755 --- a/platforms/android/build_java_shared_aar.py +++ b/platforms/android/build_java_shared_aar.py @@ -47,6 +47,14 @@ def get_opencv_version(opencv_sdk_path): revision = re.search(r'^#define\W+CV_VERSION_REVISION\W+(\d+)$', data, re.MULTILINE).group(1) return "%(major)s.%(minor)s.%(revision)s" % locals() +def get_ndk_version(ndk_path): + props_path = path.join(ndk_path, "source.properties") + with open(props_path, "rt") as f: + data = f.read() + version = re.search(r'Pkg\.Revision\W+=\W+(\d+\.\d+\.\d+)', data).group(1) + return version.strip() + + def get_compiled_aar_path(path1, path2): if path.exists(path1): return path1 @@ -70,6 +78,8 @@ def cleanup(paths_to_remove): def main(args): opencv_version = get_opencv_version(args.opencv_sdk_path) + ndk_version = get_ndk_version(args.ndk_location) + print("Detected ndk_version:", ndk_version) abis = os.listdir(path.join(args.opencv_sdk_path, "sdk/native/libs")) lib_name = "opencv_java" + opencv_version.split(".")[0] final_aar_path = FINAL_AAR_PATH_TEMPLATE.replace("", opencv_version) @@ -90,6 +100,7 @@ def main(args): "LIB_TYPE": "c++_shared", "PACKAGE_NAME": MAVEN_PACKAGE_NAME, "OPENCV_VERSION": opencv_version, + "NDK_VERSION": ndk_version, "COMPILE_SDK": args.android_compile_sdk, "MIN_SDK": args.android_min_sdk, "TARGET_SDK": args.android_target_sdk, @@ -170,10 +181,10 @@ def main(args): if __name__ == "__main__": parser = argparse.ArgumentParser(description="Builds AAR with Java and shared C++ libs from OpenCV SDK") parser.add_argument('opencv_sdk_path') - parser.add_argument('--android_compile_sdk', default="31") + parser.add_argument('--android_compile_sdk', default="34") parser.add_argument('--android_min_sdk', default="21") - parser.add_argument('--android_target_sdk', default="31") - parser.add_argument('--java_version', default="1_8") + parser.add_argument('--android_target_sdk', default="34") + parser.add_argument('--java_version', default="17") parser.add_argument('--ndk_location', default="") parser.add_argument('--cmake_location', default="") parser.add_argument('--offline', action="store_true", help="Force Gradle use offline mode") diff --git a/platforms/android/build_sdk.py b/platforms/android/build_sdk.py index 83f40904d5..3a920c78f1 100755 --- a/platforms/android/build_sdk.py +++ b/platforms/android/build_sdk.py @@ -285,7 +285,10 @@ class Builder: cmd.append(self.opencvdir) execute(cmd) # full parallelism for C++ compilation tasks - execute([self.ninja_path, "opencv_modules"]) + build_targets = ["opencv_modules"] + if do_install: + build_targets.append("opencv_tests") + execute([self.ninja_path, *build_targets]) # limit parallelism for building samples (avoid huge memory consumption) if self.no_samples_build: execute([self.ninja_path, "install" if (self.debug_info or self.debug) else "install/strip"]) @@ -300,6 +303,14 @@ class Builder: classpaths.append(os.path.join(dir, f)) srcdir = os.path.join(self.resultdest, 'sdk', 'java', 'src') dstdir = self.docdest + # HACK: create stubs for auto-generated files to satisfy imports + with open(os.path.join(srcdir, 'org', 'opencv', 'BuildConfig.java'), 'wt') as fs: + fs.write("package org.opencv;\n public class BuildConfig {\n}") + fs.close() + with open(os.path.join(srcdir, 'org', 'opencv', 'R.java'), 'wt') as fs: + fs.write("package org.opencv;\n public class R {\n}") + fs.close() + # synchronize with modules/java/jar/build.xml.in shutil.copy2(os.path.join(SCRIPT_DIR, '../../doc/mymath.js'), dstdir) cmd = [ @@ -327,9 +338,12 @@ class Builder: '-bottom', 'Generated on %s / OpenCV %s' % (time.strftime("%Y-%m-%d %H:%M:%S"), self.opencv_version), "-d", dstdir, "-classpath", ":".join(classpaths), - '-subpackages', 'org.opencv', + '-subpackages', 'org.opencv' ] execute(cmd) + # HACK: remove temporary files needed to satisfy javadoc imports + os.remove(os.path.join(srcdir, 'org', 'opencv', 'BuildConfig.java')) + os.remove(os.path.join(srcdir, 'org', 'opencv', 'R.java')) def gather_results(self): # Copy all files diff --git a/platforms/android/build_static_aar.py b/platforms/android/build_static_aar.py index 56cfbcbc1e..1f817818c3 100755 --- a/platforms/android/build_static_aar.py +++ b/platforms/android/build_static_aar.py @@ -7,7 +7,7 @@ import os import shutil import subprocess -from build_java_shared_aar import cleanup, fill_template, get_compiled_aar_path, get_opencv_version +from build_java_shared_aar import cleanup, fill_template, get_compiled_aar_path, get_opencv_version, get_ndk_version ANDROID_PROJECT_TEMPLATE_DIR = path.join(path.dirname(__file__), "aar-template") @@ -103,6 +103,8 @@ def convert_deps_list_to_prefab(linked_libs, opencv_libs, external_libs): def main(args): opencv_version = get_opencv_version(args.opencv_sdk_path) + ndk_version = get_ndk_version(args.ndk_location) + print("Detected ndk_version:", ndk_version) abis = os.listdir(path.join(args.opencv_sdk_path, "sdk/native/libs")) final_aar_path = FINAL_AAR_PATH_TEMPLATE.replace("", opencv_version) sdk_dir = args.opencv_sdk_path @@ -121,6 +123,7 @@ def main(args): "LIB_TYPE": "c++_static", "PACKAGE_NAME": MAVEN_PACKAGE_NAME, "OPENCV_VERSION": opencv_version, + "NDK_VERSION": ndk_version, "COMPILE_SDK": args.android_compile_sdk, "MIN_SDK": args.android_min_sdk, "TARGET_SDK": args.android_target_sdk, @@ -243,9 +246,9 @@ def main(args): if __name__ == "__main__": parser = argparse.ArgumentParser(description="Builds AAR with static C++ libs from OpenCV SDK") parser.add_argument('opencv_sdk_path') - parser.add_argument('--android_compile_sdk', default="31") + parser.add_argument('--android_compile_sdk', default="34") parser.add_argument('--android_min_sdk', default="21") - parser.add_argument('--android_target_sdk', default="31") + parser.add_argument('--android_target_sdk', default="34") parser.add_argument('--java_version', default="1_8") parser.add_argument('--ndk_location', default="") parser.add_argument('--cmake_location', default="") diff --git a/platforms/android/default.config.py b/platforms/android/default.config.py new file mode 100644 index 0000000000..9c7b9ad0ef --- /dev/null +++ b/platforms/android/default.config.py @@ -0,0 +1,6 @@ +ABIs = [ + ABI("2", "armeabi-v7a", None, 21, cmake_vars=dict(ANDROID_ABI='armeabi-v7a with NEON')), + ABI("3", "arm64-v8a", None, 21, cmake_vars=dict(ANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES='ON')), + ABI("5", "x86_64", None, 21, cmake_vars=dict(ANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES='ON')), + ABI("4", "x86", None, 21), +] diff --git a/samples/android/build.gradle.in b/samples/android/build.gradle.in index d159899132..a06c42187d 100644 --- a/samples/android/build.gradle.in +++ b/samples/android/build.gradle.in @@ -99,7 +99,7 @@ gradle.afterProject { project -> // Android Gradle Plugin (AGP) 3.5+ is required // https://github.com/android/ndk-samples/wiki/Configure-NDK-Path def isNdkVersionSupported = project.android.metaClass.getProperties().find { it.name == 'ndkVersion' } != null - if ((false || opencv_strict_build_configuration) && isNdkVersionSupported) { + if (opencv_strict_build_configuration && isNdkVersionSupported) { gradle.println("Override ndkVersion for the project ${project.name}") project.android { ndkVersion '@ANDROID_NDK_REVISION@' diff --git a/samples/android/tutorial-2-mixedprocessing/build.gradle.in b/samples/android/tutorial-2-mixedprocessing/build.gradle.in index a156f42240..0e8f5b2a9f 100644 --- a/samples/android/tutorial-2-mixedprocessing/build.gradle.in +++ b/samples/android/tutorial-2-mixedprocessing/build.gradle.in @@ -14,10 +14,11 @@ android { cmake { if (gradle.opencv_source == "sdk_path") { arguments "-DOpenCV_DIR=" + project(':opencv').projectDir + "/@ANDROID_PROJECT_JNI_PATH@", + "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON", "-DOPENCV_FROM_SDK=TRUE"@OPENCV_ANDROID_CMAKE_EXTRA_ARGS@ - } else { arguments "-DOPENCV_VERSION_MAJOR=@OPENCV_VERSION_MAJOR@", + "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON", "-DOPENCV_FROM_SDK=FALSE"@OPENCV_ANDROID_CMAKE_EXTRA_ARGS@ } targets "mixed_sample" diff --git a/samples/android/tutorial-2-mixedprocessing/jni/CMakeLists.txt b/samples/android/tutorial-2-mixedprocessing/jni/CMakeLists.txt index 37e9a698bb..fd7b1389d3 100644 --- a/samples/android/tutorial-2-mixedprocessing/jni/CMakeLists.txt +++ b/samples/android/tutorial-2-mixedprocessing/jni/CMakeLists.txt @@ -14,6 +14,14 @@ endif() message(STATUS "ANDROID_ABI=${ANDROID_ABI}") find_package(OpenCV REQUIRED COMPONENTS ${ANDROID_OPENCV_COMPONENTS}) +# For 16k pages support with NDK prior 27 +# Details: https://developer.android.com/guide/practices/page-sizes?hl=en +if(ANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES) + if(ANDROID_ABI STREQUAL arm64-v8a OR ANDROID_ABI STREQUAL x86_64) + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,max-page-size=16384") + endif() +endif() + file(GLOB srcs *.cpp *.c) file(GLOB hdrs *.hpp *.h) diff --git a/samples/android/tutorial-4-opencl/build.gradle.in b/samples/android/tutorial-4-opencl/build.gradle.in index 8eeb12b17d..8118bd1d19 100644 --- a/samples/android/tutorial-4-opencl/build.gradle.in +++ b/samples/android/tutorial-4-opencl/build.gradle.in @@ -15,11 +15,13 @@ android { if (gradle.opencv_source == "sdk_path") { arguments "-DOpenCV_DIR=" + project(':opencv').projectDir + "/@ANDROID_PROJECT_JNI_PATH@", "-DOPENCV_FROM_SDK=TRUE", + "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON", "-DANDROID_OPENCL_SDK=@ANDROID_OPENCL_SDK@" @OPENCV_ANDROID_CMAKE_EXTRA_ARGS@ } else { arguments "-DOPENCV_VERSION_MAJOR=@OPENCV_VERSION_MAJOR@", "-DOPENCV_FROM_SDK=FALSE", + "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON", "-DANDROID_OPENCL_SDK=@ANDROID_OPENCL_SDK@" @OPENCV_ANDROID_CMAKE_EXTRA_ARGS@ } targets "JNIpart" diff --git a/samples/android/tutorial-4-opencl/jni/CMakeLists.txt b/samples/android/tutorial-4-opencl/jni/CMakeLists.txt index d1a1dcf234..df38f0ac83 100644 --- a/samples/android/tutorial-4-opencl/jni/CMakeLists.txt +++ b/samples/android/tutorial-4-opencl/jni/CMakeLists.txt @@ -18,6 +18,14 @@ find_package(OpenCL QUIET) file(GLOB srcs *.cpp *.c) file(GLOB hdrs *.hpp *.h) +# For 16k pages support with NDK prior 27 +# Details: https://developer.android.com/guide/practices/page-sizes?hl=en +if(ANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES) + if(ANDROID_ABI STREQUAL arm64-v8a OR ANDROID_ABI STREQUAL x86_64) + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,max-page-size=16384") + endif() +endif() + include_directories("${CMAKE_CURRENT_LIST_DIR}") add_library(${target} SHARED ${srcs} ${hdrs}) From 1db982780fa7789b9437f39f523f22f358af694a Mon Sep 17 00:00:00 2001 From: cDc Date: Tue, 31 Dec 2024 10:56:35 +0200 Subject: [PATCH 068/102] Merge pull request #26379 from cdcseacave:jxl_codec Add jxl (JPEG XL) codec support #26379 ### Pull Request Readiness Checklist Related CI and Docker changes: - https://github.com/opencv/ci-gha-workflow/pull/190 - https://github.com/opencv-infrastructure/opencv-gha-dockerfile/pull/44 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 https://github.com/opencv/opencv/issues/20178 - [ ] 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 --- CMakeLists.txt | 7 + cmake/OpenCVFindJPEGXL.cmake | 148 ++++++ cmake/OpenCVFindLibsGrfmt.cmake | 11 + cmake/templates/cvconfig.h.in | 3 + .../config_reference.markdown | 3 +- modules/highgui/src/window_w32.cpp | 3 + modules/imgcodecs/CMakeLists.txt | 13 +- .../imgcodecs/include/opencv2/imgcodecs.hpp | 10 +- modules/imgcodecs/src/grfmt_jpegxl.cpp | 420 ++++++++++++++++++ modules/imgcodecs/src/grfmt_jpegxl.hpp | 68 +++ modules/imgcodecs/src/grfmts.hpp | 1 + modules/imgcodecs/src/loadsave.cpp | 4 + modules/imgcodecs/test/test_jpegxl.cpp | 186 ++++++++ modules/imgcodecs/test/test_read_write.cpp | 7 + 14 files changed, 881 insertions(+), 3 deletions(-) create mode 100644 cmake/OpenCVFindJPEGXL.cmake create mode 100644 modules/imgcodecs/src/grfmt_jpegxl.cpp create mode 100644 modules/imgcodecs/src/grfmt_jpegxl.hpp create mode 100644 modules/imgcodecs/test/test_jpegxl.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 7af059f8dd..e60406fbe2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -306,6 +306,9 @@ OCV_OPTION(WITH_OPENJPEG "Include JPEG2K support (OpenJPEG)" ON OCV_OPTION(WITH_JPEG "Include JPEG support" ON VISIBLE_IF TRUE VERIFY HAVE_JPEG) +OCV_OPTION(WITH_JPEGXL "Include JPEG XL support" OFF + VISIBLE_IF TRUE + VERIFY HAVE_JPEGXL) OCV_OPTION(WITH_WEBP "Include WebP support" ON VISIBLE_IF NOT WINRT VERIFY HAVE_WEBP) @@ -1533,6 +1536,10 @@ if(WITH_TIFF OR HAVE_TIFF) status(" TIFF:" TIFF_FOUND THEN "${TIFF_LIBRARY} (ver ${TIFF_VERSION} / ${TIFF_VERSION_STRING})" ELSE "build (ver ${TIFF_VERSION} - ${TIFF_VERSION_STRING})") endif() +if(WITH_JPEGXL OR HAVE_JPEGXL) + status(" JPEG XL:" JPEGXL_FOUND THEN "${JPEGXL_LIBRARY} (ver ${JPEGXL_VERSION})" ELSE "NO") +endif() + if(HAVE_OPENJPEG) status(" JPEG 2000:" OpenJPEG_FOUND THEN "OpenJPEG (ver ${OPENJPEG_VERSION})" diff --git a/cmake/OpenCVFindJPEGXL.cmake b/cmake/OpenCVFindJPEGXL.cmake new file mode 100644 index 0000000000..6eb5abb9c4 --- /dev/null +++ b/cmake/OpenCVFindJPEGXL.cmake @@ -0,0 +1,148 @@ +# The script is taken from https://webkit.googlesource.com/WebKit/+/master/Source/cmake/FindJPEGXL.cmake + +# Copyright (C) 2021 Sony Interactive Entertainment Inc. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS +# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +# THE POSSIBILITY OF SUCH DAMAGE. +#[=======================================================================[.rst: +FindJPEGXL +--------- +Find JPEGXL headers and libraries. +Imported Targets +^^^^^^^^^^^^^^^^ +``JPEGXL::jxl`` + The JPEGXL library, if found. +Result Variables +^^^^^^^^^^^^^^^^ +This will define the following variables in your project: +``JPEGXL_FOUND`` + true if (the requested version of) JPEGXL is available. +``JPEGXL_VERSION`` + the version of JPEGXL. +``JPEGXL_LIBRARIES`` + the libraries to link against to use JPEGXL. +``JPEGXL_INCLUDE_DIRS`` + where to find the JPEGXL headers. +``JPEGXL_COMPILE_OPTIONS`` + this should be passed to target_compile_options(), if the + target is not used for linking +#]=======================================================================] + +if(NOT OPENCV_SKIP_JPEGXL_FIND_PACKAGE) +find_package(PkgConfig QUIET) +if (PkgConfig_FOUND) + pkg_check_modules(PC_JPEGXL QUIET jxl) + set(JPEGXL_COMPILE_OPTIONS ${PC_JPEGXL_CFLAGS_OTHER}) + set(JPEGXL_VERSION ${PC_JPEGXL_VERSION}) +endif () +find_path(JPEGXL_INCLUDE_DIR + NAMES jxl/decode.h + HINTS ${PC_JPEGXL_INCLUDEDIR} ${PC_JPEGXL_INCLUDE_DIRS} ${JPEGXL_INCLUDE_DIR} +) +find_library(JPEGXL_LIBRARY + NAMES ${JPEGXL_NAMES} jxl + HINTS ${PC_JPEGXL_LIBDIR} ${PC_JPEGXL_LIBRARY_DIRS} +) +find_library(JPEGXL_CMS_LIBRARY + NAMES ${JPEGXL_NAMES} jxl_cms + HINTS ${PC_JPEGXL_LIBDIR} ${PC_JPEGXL_LIBRARY_DIRS} +) +if (JPEGXL_LIBRARY AND JPEGXL_CMS_LIBRARY) + set(JPEGXL_LIBRARY ${JPEGXL_LIBRARY} ${JPEGXL_CMS_LIBRARY}) +endif () +find_library(JPEGXL_CMS2_LIBRARY + NAMES ${JPEGXL_NAMES} lcms2 + HINTS ${PC_JPEGXL_LIBDIR} ${PC_JPEGXL_LIBRARY_DIRS} +) +if (JPEGXL_LIBRARY AND JPEGXL_CMS2_LIBRARY) + set(JPEGXL_LIBRARY ${JPEGXL_LIBRARY} ${JPEGXL_CMS2_LIBRARY}) +endif () +find_library(JPEGXL_THREADS_LIBRARY + NAMES ${JPEGXL_NAMES} jxl_threads + HINTS ${PC_JPEGXL_LIBDIR} ${PC_JPEGXL_LIBRARY_DIRS} +) +if (JPEGXL_LIBRARY AND JPEGXL_THREADS_LIBRARY) + set(JPEGXL_LIBRARY ${JPEGXL_LIBRARY} ${JPEGXL_THREADS_LIBRARY}) +endif () +find_library(JPEGXL_BROTLICOMMON_LIBRARY + NAMES ${JPEGXL_NAMES} brotlicommon + HINTS ${PC_JPEGXL_LIBDIR} ${PC_JPEGXL_LIBRARY_DIRS} +) +find_library(JPEGXL_BROTLIDEC_LIBRARY + NAMES ${JPEGXL_NAMES} brotlidec + HINTS ${PC_JPEGXL_LIBDIR} ${PC_JPEGXL_LIBRARY_DIRS} +) +if (JPEGXL_LIBRARY AND JPEGXL_BROTLIDEC_LIBRARY) + set(JPEGXL_LIBRARY ${JPEGXL_LIBRARY} ${JPEGXL_BROTLIDEC_LIBRARY}) +endif () +find_library(JPEGXL_BROTLIENC_LIBRARY + NAMES ${JPEGXL_NAMES} brotlienc + HINTS ${PC_JPEGXL_LIBDIR} ${PC_JPEGXL_LIBRARY_DIRS} +) +if (JPEGXL_LIBRARY AND JPEGXL_BROTLIENC_LIBRARY) + set(JPEGXL_LIBRARY ${JPEGXL_LIBRARY} ${JPEGXL_BROTLIENC_LIBRARY}) +endif () +if (JPEGXL_LIBRARY AND JPEGXL_BROTLICOMMON_LIBRARY) + set(JPEGXL_LIBRARY ${JPEGXL_LIBRARY} ${JPEGXL_BROTLICOMMON_LIBRARY}) +endif () +find_library(JPEGXL_HWY_LIBRARY + NAMES ${JPEGXL_NAMES} hwy + HINTS ${PC_JPEGXL_LIBDIR} ${PC_JPEGXL_LIBRARY_DIRS} +) +if (JPEGXL_LIBRARY AND JPEGXL_HWY_LIBRARY) + set(JPEGXL_LIBRARY ${JPEGXL_LIBRARY} ${JPEGXL_HWY_LIBRARY}) +endif () +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(JPEGXL + FOUND_VAR JPEGXL_FOUND + REQUIRED_VARS JPEGXL_LIBRARY JPEGXL_INCLUDE_DIR + VERSION_VAR JPEGXL_VERSION +) +if (NOT EXISTS "${JPEGXL_INCLUDE_DIR}/jxl/version.h") + # the library version older 0.6 is not supported (no version.h file there) + set(JPEGXL_FOUND FALSE CACHE BOOL "libjxl found" FORCE) + message(STATUS "Ignored incompatible version of libjxl") +endif () + +if (JPEGXL_LIBRARY AND NOT TARGET JPEGXL::jxl) + add_library(JPEGXL::jxl UNKNOWN IMPORTED GLOBAL) + set_target_properties(JPEGXL::jxl PROPERTIES + IMPORTED_LOCATION "${JPEGXL_LIBRARY}" + INTERFACE_COMPILE_OPTIONS "${JPEGXL_COMPILE_OPTIONS}" + INTERFACE_INCLUDE_DIRECTORIES "${JPEGXL_INCLUDE_DIR}" + ) +endif () +mark_as_advanced(JPEGXL_INCLUDE_DIR JPEGXL_LIBRARY) +if (JPEGXL_FOUND) + set(JPEGXL_LIBRARIES ${JPEGXL_LIBRARY}) + set(JPEGXL_INCLUDE_DIRS ${JPEGXL_INCLUDE_DIR}) + if (NOT JPEGXL_VERSION) + file(READ "${JPEGXL_INCLUDE_DIR}/jxl/version.h" VERSION_HEADER_CONTENTS) + string(REGEX MATCH "#define JPEGXL_MAJOR_VERSION ([0-9]+)" _ ${VERSION_HEADER_CONTENTS}) + set(JXL_VERSION_MAJOR ${CMAKE_MATCH_1}) + string(REGEX MATCH "#define JPEGXL_MINOR_VERSION ([0-9]+)" _ ${VERSION_HEADER_CONTENTS}) + set(JXL_VERSION_MINOR ${CMAKE_MATCH_1}) + string(REGEX MATCH "#define JPEGXL_PATCH_VERSION ([0-9]+)" _ ${VERSION_HEADER_CONTENTS}) + set(JXL_VERSION_PATCH ${CMAKE_MATCH_1}) + set(JPEGXL_VERSION "${JXL_VERSION_MAJOR}.${JXL_VERSION_MINOR}.${JXL_VERSION_PATCH}") + endif() +endif () +endif() diff --git a/cmake/OpenCVFindLibsGrfmt.cmake b/cmake/OpenCVFindLibsGrfmt.cmake index a034a37431..6e53c45ba6 100644 --- a/cmake/OpenCVFindLibsGrfmt.cmake +++ b/cmake/OpenCVFindLibsGrfmt.cmake @@ -226,6 +226,17 @@ if(NOT WEBP_VERSION AND WEBP_INCLUDE_DIR) set(WEBP_VERSION "decoder: ${WEBP_DECODER_ABI_VERSION}, encoder: ${WEBP_ENCODER_ABI_VERSION}, demux: ${WEBP_DEMUX_ABI_VERSION}") endif() +# --- libjxl (optional) --- +if(WITH_JPEGXL) + ocv_clear_vars(HAVE_JPEGXL) + ocv_clear_internal_cache_vars(JPEGXL_INCLUDE_PATHS JPEGXL_LIBRARIES JPEGXL_VERSION) + include("${OpenCV_SOURCE_DIR}/cmake/OpenCVFindJPEGXL.cmake") + if(JPEGXL_FOUND) + set(HAVE_JPEGXL YES) + message(STATUS "Found system JPEG-XL: ver ${JPEGXL_VERSION}") + endif() +endif() + # --- libopenjp2 (optional, check before libjasper) --- if(WITH_OPENJPEG) if(BUILD_OPENJPEG) diff --git a/cmake/templates/cvconfig.h.in b/cmake/templates/cvconfig.h.in index ed53f3bf44..91efb9ab3d 100644 --- a/cmake/templates/cvconfig.h.in +++ b/cmake/templates/cvconfig.h.in @@ -78,6 +78,9 @@ /* IJG JPEG codec */ #cmakedefine HAVE_JPEG +/* JPEG XL codec */ +#cmakedefine HAVE_JPEGXL + /* GDCM DICOM codec */ #cmakedefine HAVE_GDCM diff --git a/doc/tutorials/introduction/config_reference/config_reference.markdown b/doc/tutorials/introduction/config_reference/config_reference.markdown index ab8bdee229..3c3c1d5c99 100644 --- a/doc/tutorials/introduction/config_reference/config_reference.markdown +++ b/doc/tutorials/introduction/config_reference/config_reference.markdown @@ -310,11 +310,12 @@ Following formats can be read by OpenCV without help of any third-party library: | [JPEG2000 with OpenJPEG](https://en.wikipedia.org/wiki/OpenJPEG) | `WITH_OPENJPEG` | _ON_ | `BUILD_OPENJPEG` | | [JPEG2000 with JasPer](https://en.wikipedia.org/wiki/JasPer) | `WITH_JASPER` | _ON_ (see note) | `BUILD_JASPER` | | [EXR](https://en.wikipedia.org/wiki/OpenEXR) | `WITH_OPENEXR` | _ON_ | `BUILD_OPENEXR` | +| [JPEG XL](https://en.wikipedia.org/wiki/JPEG_XL) | `WITH_JPEGXL` | _ON_ | Not supported. (see note) | All libraries required to read images in these formats are included into OpenCV and will be built automatically if not found at the configuration stage. Corresponding `BUILD_*` options will force building and using own libraries, they are enabled by default on some platforms, e.g. Windows. @note OpenJPEG have higher priority than JasPer which is deprecated. In order to use JasPer, OpenJPEG must be disabled. - +@note (JPEG XL) OpenCV doesn't contain libjxl source code, so `BUILD_JPEGXL` is not supported. ### GDAL integration diff --git a/modules/highgui/src/window_w32.cpp b/modules/highgui/src/window_w32.cpp index fa9e35bd89..2543c81c6a 100644 --- a/modules/highgui/src/window_w32.cpp +++ b/modules/highgui/src/window_w32.cpp @@ -2158,6 +2158,9 @@ static void showSaveDialog(CvWindow& window) #ifdef HAVE_JPEG "JPEG files (*.jpeg;*.jpg;*.jpe)\0*.jpeg;*.jpg;*.jpe\0" #endif +#ifdef HAVE_JPEGXL + "JPEG XL files (*.jxl)\0*.jxl\0" +#endif #ifdef HAVE_TIFF "TIFF Files (*.tiff;*.tif)\0*.tiff;*.tif\0" #endif diff --git a/modules/imgcodecs/CMakeLists.txt b/modules/imgcodecs/CMakeLists.txt index 0c52ce7cfb..7f97dde391 100644 --- a/modules/imgcodecs/CMakeLists.txt +++ b/modules/imgcodecs/CMakeLists.txt @@ -23,6 +23,11 @@ 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}) @@ -51,6 +56,12 @@ if(HAVE_TIFF) list(APPEND GRFMT_LIBS ${TIFF_LIBRARIES}) endif() +if(HAVE_JPEGXL) + ocv_include_directories(${JPEGXL_INCLUDE_DIRS}) + message(STATUS "JPEGXL_INCLUDE_DIRS: ${JPEGXL_INCLUDE_DIRS}") + list(APPEND GRFMT_LIBS ${JPEGXL_LIBRARIES}) +endif() + if(HAVE_OPENJPEG) ocv_include_directories(${OPENJPEG_INCLUDE_DIRS}) list(APPEND GRFMT_LIBS ${OPENJPEG_LIBRARIES}) @@ -78,7 +89,7 @@ if(HAVE_OPENEXR) endif() endif() -if(HAVE_PNG OR HAVE_TIFF OR HAVE_OPENEXR OR HAVE_SPNG) +if(HAVE_PNG OR HAVE_TIFF OR HAVE_OPENEXR OR HAVE_SPNG OR HAVE_JPEGXL) ocv_include_directories(${ZLIB_INCLUDE_DIRS}) list(APPEND GRFMT_LIBS ${ZLIB_LIBRARIES}) endif() diff --git a/modules/imgcodecs/include/opencv2/imgcodecs.hpp b/modules/imgcodecs/include/opencv2/imgcodecs.hpp index 25273342d2..cd648c2c6e 100644 --- a/modules/imgcodecs/include/opencv2/imgcodecs.hpp +++ b/modules/imgcodecs/include/opencv2/imgcodecs.hpp @@ -112,13 +112,17 @@ enum ImwriteFlags { IMWRITE_AVIF_QUALITY = 512,//!< For AVIF, it can be a quality between 0 and 100 (the higher the better). Default is 95. IMWRITE_AVIF_DEPTH = 513,//!< For AVIF, it can be 8, 10 or 12. If >8, it is stored/read as CV_32F. Default is 8. IMWRITE_AVIF_SPEED = 514,//!< For AVIF, it is between 0 (slowest) and (fastest). Default is 9. + IMWRITE_JPEGXL_QUALITY = 640,//!< For JPEG XL, it can be a quality from 0 to 100 (the higher is the better). Default value is 95. If set, distance parameter is re-calicurated from quality level automatically. This parameter request libjxl v0.10 or later. + IMWRITE_JPEGXL_EFFORT = 641,//!< For JPEG XL, encoder effort/speed level without affecting decoding speed; it is between 1 (fastest) and 10 (slowest). Default is 7. + IMWRITE_JPEGXL_DISTANCE = 642,//!< For JPEG XL, distance level for lossy compression: target max butteraugli distance, lower = higher quality, 0 = lossless; range: 0 .. 25. Default is 1. + IMWRITE_JPEGXL_DECODING_SPEED = 643,//!< For JPEG XL, decoding speed tier for the provided options; minimum is 0 (slowest to decode, best quality/density), and maximum is 4 (fastest to decode, at the cost of some quality/density). Default is 0. IMWRITE_GIF_LOOP = 1024,//!< For GIF, it can be a loop flag from 0 to 65535. Default is 0 - loop forever. IMWRITE_GIF_SPEED = 1025,//!< For GIF, it is between 1 (slowest) and 100 (fastest). Default is 96. IMWRITE_GIF_QUALITY = 1026, //!< For GIF, it can be a quality from 1 to 8. Default is 2. See cv::ImwriteGifCompressionFlags. IMWRITE_GIF_DITHER = 1027, //!< For GIF, it can be a quality from -1(most dither) to 3(no dither). Default is 0. IMWRITE_GIF_TRANSPARENCY = 1028, //!< For GIF, the alpha channel lower than this will be set to transparent. Default is 1. IMWRITE_GIF_COLORTABLE = 1029 //!< For GIF, 0 means global color table is used, 1 means local color table is used. Default is 0. - }; +}; enum ImwriteJPEGSamplingFactorParams { IMWRITE_JPEG_SAMPLING_FACTOR_411 = 0x411111, //!< 4x1,1x1,1x1 @@ -407,6 +411,10 @@ can be saved using this function, with these exceptions: - 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). - With JPEG 2000 encoder, 8-bit unsigned (CV_8U) and 16-bit unsigned (CV_16U) images can be saved. +- With JPEG XL encoder, 8-bit unsigned (CV_8U), 16-bit unsigned (CV_16U) and 32-bit float(CV_32F) images can be saved. + - JPEG XL images with an alpha channel can be saved using this function. + To do this, create 8-bit (or 16-bit, 32-bit float) 4-channel image BGRA, where the alpha channel goes last. + Fully transparent pixels should have alpha set to 0, fully opaque pixels should have alpha set to 255/65535/1.0. - With PAM encoder, 8-bit unsigned (CV_8U) and 16-bit unsigned (CV_16U) images can be saved. - With PNG encoder, 8-bit unsigned (CV_8U) and 16-bit unsigned (CV_16U) images can be saved. - PNG images with an alpha channel can be saved using this function. To do this, create diff --git a/modules/imgcodecs/src/grfmt_jpegxl.cpp b/modules/imgcodecs/src/grfmt_jpegxl.cpp new file mode 100644 index 0000000000..fc89dd2c6e --- /dev/null +++ b/modules/imgcodecs/src/grfmt_jpegxl.cpp @@ -0,0 +1,420 @@ +// 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 "precomp.hpp" +#include "grfmt_jpegxl.hpp" + +#ifdef HAVE_JPEGXL + +#include +#include +#include + +namespace cv +{ + +/////////////////////// JpegXLDecoder /////////////////// + +JpegXLDecoder::JpegXLDecoder() : m_f(nullptr, &fclose) +{ + m_signature = "\xFF\x0A"; + m_decoder = nullptr; + m_buf_supported = false; + m_type = m_convert = -1; + m_status = JXL_DEC_NEED_MORE_INPUT; +} + +JpegXLDecoder::~JpegXLDecoder() +{ + close(); +} + +void JpegXLDecoder::close() +{ + if (m_decoder) + m_decoder.release(); + if (m_f) + m_f.release(); + m_read_buffer = {}; + m_width = m_height = 0; + m_type = m_convert = -1; + m_status = JXL_DEC_NEED_MORE_INPUT; +} + +// see https://github.com/libjxl/libjxl/blob/v0.10.0/doc/format_overview.md +size_t JpegXLDecoder::signatureLength() const +{ + return 12; // For an ISOBMFF-based container +} + +bool JpegXLDecoder::checkSignature( const String& signature ) const +{ + // A "naked" codestream. + if ( + ( signature.size() >= 2 ) && + ( memcmp( signature.c_str(), "\xFF\x0A", 2 ) == 0 ) + ) + { + return true; + } + + // An ISOBMFF-based container. + // 0x0000_000C_4A58_4C20_0D0A_870A. + if ( + ( signature.size() >= 12 ) && + ( memcmp( signature.c_str(), "\x00\x00\x00\x0C\x4A\x58\x4C\x20\x0D\x0A\x87\x0A", 12 ) == 0 ) + ) + { + return true; + } + + return false; +} + +ImageDecoder JpegXLDecoder::newDecoder() const +{ + return makePtr(); +} + +bool JpegXLDecoder::read(Mat* pimg) +{ + // Open file + if (!m_f) { + m_f.reset(fopen(m_filename.c_str(), "rb")); + if (!m_f) + return false; + } + + // Initialize decoder + if (!m_decoder) { + m_decoder = JxlDecoderMake(nullptr); + if (!m_decoder) + return false; + // Subscribe to the basic info event + JxlDecoderStatus status = JxlDecoderSubscribeEvents(m_decoder.get(), JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE); + if (status != JXL_DEC_SUCCESS) + return false; + } + + // Set up parallel m_parallel_runner + if (!m_parallel_runner) { + m_parallel_runner = JxlThreadParallelRunnerMake(nullptr, cv::getNumThreads()); + if (JXL_DEC_SUCCESS != JxlDecoderSetParallelRunner(m_decoder.get(), + JxlThreadParallelRunner, + m_parallel_runner.get())) { + return false; + } + } + + // Create buffer for reading + const size_t read_buffer_size = 16384; // 16KB chunks + if (m_read_buffer.capacity() < read_buffer_size) + m_read_buffer.resize(read_buffer_size); + + // Create image if needed + if (m_type != -1 && pimg) { + pimg->create(m_height, m_width, m_type); + if (!pimg->isContinuous()) + return false; + if (JXL_DEC_SUCCESS != JxlDecoderSetImageOutBuffer(m_decoder.get(), + &m_format, + pimg->ptr(), + pimg->total() * pimg->elemSize())) { + return false; + } + } + + // Start decoding loop + do { + // Check if we need more input + if (m_status == JXL_DEC_NEED_MORE_INPUT) { + size_t remaining = JxlDecoderReleaseInput(m_decoder.get()); + // Move any remaining bytes to the beginning + if (remaining > 0) + memmove(m_read_buffer.data(), m_read_buffer.data() + m_read_buffer.size() - remaining, remaining); + // Read more data from file + size_t bytes_read = fread(m_read_buffer.data() + remaining, + 1, m_read_buffer.size() - remaining, m_f.get()); + if (bytes_read == 0) { + if (ferror(m_f.get())) { + CV_LOG_WARNING(NULL, "Error reading input file"); + return false; + } + // If we reached EOF but decoder needs more input, file is truncated + if (m_status == JXL_DEC_NEED_MORE_INPUT) { + CV_LOG_WARNING(NULL, "Truncated JXL file"); + return false; + } + } + + // Set input buffer + if (JXL_DEC_SUCCESS != JxlDecoderSetInput(m_decoder.get(), + m_read_buffer.data(), + bytes_read + remaining)) { + return false; + } + } + + // Get the next decoder status + m_status = JxlDecoderProcessInput(m_decoder.get()); + + // Handle different decoder states + switch (m_status) { + case JXL_DEC_BASIC_INFO: { + if (m_type != -1) + return false; + JxlBasicInfo info; + if (JXL_DEC_SUCCESS != JxlDecoderGetBasicInfo(m_decoder.get(), &info)) + return false; + + // total channels (Color + Alpha) + const uint32_t ncn = info.num_color_channels + info.num_extra_channels; + + m_width = info.xsize; + m_height = info.ysize; + m_format = { + ncn, + JXL_TYPE_UINT8, // (temporary) + JXL_LITTLE_ENDIAN, // endianness + 0 // align stride to bytes + }; + if (!m_use_rgb) { + switch (ncn) { + case 3: + m_convert = cv::COLOR_RGB2BGR; + break; + case 4: + m_convert = cv::COLOR_RGBA2BGRA; + break; + default: + m_convert = -1; + } + } + if (info.exponent_bits_per_sample > 0) { + m_format.data_type = JXL_TYPE_FLOAT; + m_type = CV_MAKETYPE( CV_32F, ncn ); + } else { + switch (info.bits_per_sample) { + case 8: + m_format.data_type = JXL_TYPE_UINT8; + m_type = CV_MAKETYPE( CV_8U, ncn ); + break; + case 16: + m_format.data_type = JXL_TYPE_UINT16; + m_type = CV_MAKETYPE( CV_16U, ncn ); + break; + default: + return false; + } + } + if (!pimg) + return true; + break; + } + case JXL_DEC_FULL_IMAGE: { + // Image is ready + if (m_convert != -1) + cv::cvtColor(*pimg, *pimg, m_convert); + break; + } + case JXL_DEC_ERROR: { + close(); + return false; + } + default: + break; + } + } while (m_status != JXL_DEC_SUCCESS); + + return true; +} + +bool JpegXLDecoder::readHeader() +{ + close(); + return read(nullptr); +} + +bool JpegXLDecoder::readData(Mat& img) +{ + if (!m_decoder || m_width == 0 || m_height == 0) + return false; + return read(&img); +} + +/////////////////////// JpegXLEncoder /////////////////// + +JpegXLEncoder::JpegXLEncoder() +{ + m_description = "JPEG XL files (*.jxl)"; + m_buf_supported = true; +} + +JpegXLEncoder::~JpegXLEncoder() +{ +} + +ImageEncoder JpegXLEncoder::newEncoder() const +{ + return makePtr(); +} + +bool JpegXLEncoder::isFormatSupported( int depth ) const +{ + return depth == CV_8U || depth == CV_16U || depth == CV_32F; +} + +bool JpegXLEncoder::write(const Mat& img, const std::vector& params) +{ + m_last_error.clear(); + + JxlEncoderPtr encoder = JxlEncoderMake(nullptr); + if (!encoder) + return false; + + JxlThreadParallelRunnerPtr runner = JxlThreadParallelRunnerMake( + /*memory_manager=*/nullptr, cv::getNumThreads()); + if (JXL_ENC_SUCCESS != JxlEncoderSetParallelRunner(encoder.get(), JxlThreadParallelRunner, runner.get())) + return false; + + CV_CheckDepth(img.depth(), + ( img.depth() == CV_8U || img.depth() == CV_16U || img.depth() == CV_32F ), + "JPEG XL encoder only supports CV_8U, CV_16U, CV_32F"); + CV_CheckChannels(img.channels(), + ( img.channels() == 1 || img.channels() == 3 || img.channels() == 4) , + "JPEG XL encoder only supports 1, 3, 4 channels"); + + WLByteStream strm; + if( m_buf ) { + if( !strm.open( *m_buf ) ) + return false; + } + else if( !strm.open( m_filename )) { + return false; + } + + JxlBasicInfo info; + JxlEncoderInitBasicInfo(&info); + info.xsize = img.cols; + info.ysize = img.rows; + info.uses_original_profile = JXL_FALSE; + + if( img.channels() == 4 ) + { + info.num_color_channels = 3; + info.num_extra_channels = 1; + + info.bits_per_sample = + info.alpha_bits = 8 * static_cast(img.elemSize1()); + + info.exponent_bits_per_sample = + info.alpha_exponent_bits = img.depth() == CV_32F ? 8 : 0; + }else{ + info.num_color_channels = img.channels(); + info.bits_per_sample = 8 * static_cast(img.elemSize1()); + info.exponent_bits_per_sample = img.depth() == CV_32F ? 8 : 0; + } + + if (JxlEncoderSetBasicInfo(encoder.get(), &info) != JXL_ENC_SUCCESS) + return false; + + JxlDataType type = JXL_TYPE_UINT8; + if (img.depth() == CV_32F) + type = JXL_TYPE_FLOAT; + else if (img.depth() == CV_16U) + type = JXL_TYPE_UINT16; + JxlPixelFormat format = {(uint32_t)img.channels(), type, JXL_NATIVE_ENDIAN, 0}; + JxlColorEncoding color_encoding = {}; + JXL_BOOL is_gray(format.num_channels < 3 ? JXL_TRUE : JXL_FALSE); + JxlColorEncodingSetToSRGB(&color_encoding, is_gray); + if (JXL_ENC_SUCCESS != JxlEncoderSetColorEncoding(encoder.get(), &color_encoding)) + return false; + + Mat image; + switch ( img.channels() ) { + case 3: + cv::cvtColor(img, image, cv::COLOR_BGR2RGB); + break; + case 4: + cv::cvtColor(img, image, cv::COLOR_BGRA2RGBA); + break; + case 1: + default: + if(img.isContinuous()) { + image = img; + } else { + image = img.clone(); // reconstruction as continuous image. + } + break; + } + if (!image.isContinuous()) + return false; + + JxlEncoderFrameSettings* frame_settings = JxlEncoderFrameSettingsCreate(encoder.get(), nullptr); + // set frame settings from params if available + for( size_t i = 0; i < params.size(); i += 2 ) + { + if( params[i] == IMWRITE_JPEGXL_QUALITY ) + { +#if JPEGXL_MAJOR_VERSION > 0 || JPEGXL_MINOR_VERSION >= 10 + int quality = params[i+1]; + quality = MIN(MAX(quality, 0), 100); + const float distance = JxlEncoderDistanceFromQuality(static_cast(quality)); + JxlEncoderSetFrameDistance(frame_settings, distance); + if (distance == 0) + JxlEncoderSetFrameLossless(frame_settings, JXL_TRUE); +#else + CV_LOG_ONCE_WARNING(NULL, "Quality parameter is supported with libjxl v0.10.0 or later"); +#endif + } + if( params[i] == IMWRITE_JPEGXL_DISTANCE ) + { + int distance = params[i+1]; + distance = MIN(MAX(distance, 0), 25); + JxlEncoderSetFrameDistance(frame_settings, distance); + if (distance == 0) + JxlEncoderSetFrameLossless(frame_settings, JXL_TRUE); + } + if( params[i] == IMWRITE_JPEGXL_EFFORT ) + { + int effort = params[i+1]; + effort = MIN(MAX(effort, 1), 10); + JxlEncoderFrameSettingsSetOption(frame_settings, JXL_ENC_FRAME_SETTING_EFFORT, effort); + } + if( params[i] == IMWRITE_JPEGXL_DECODING_SPEED ) + { + int speed = params[i+1]; + speed = MIN(MAX(speed, 0), 4); + JxlEncoderFrameSettingsSetOption(frame_settings, JXL_ENC_FRAME_SETTING_DECODING_SPEED, speed); + } + } + if (JXL_ENC_SUCCESS != + JxlEncoderAddImageFrame(frame_settings, &format, + static_cast(image.ptr()), + image.total() * image.elemSize())) { + return false; + } + JxlEncoderCloseInput(encoder.get()); + + const size_t buffer_size = 16384; // 16KB chunks + + std::vector compressed(buffer_size); + JxlEncoderStatus process_result = JXL_ENC_NEED_MORE_OUTPUT; + while (process_result == JXL_ENC_NEED_MORE_OUTPUT) { + uint8_t* next_out = compressed.data(); + size_t avail_out = buffer_size; + process_result = JxlEncoderProcessOutput(encoder.get(), &next_out, &avail_out); + if (JXL_ENC_ERROR == process_result) + return false; + const size_t write_size = buffer_size - avail_out; + if ( strm.putBytes(compressed.data(), write_size) == false ) + return false; + } + return true; +} + +} + +#endif + +/* End of file. */ diff --git a/modules/imgcodecs/src/grfmt_jpegxl.hpp b/modules/imgcodecs/src/grfmt_jpegxl.hpp new file mode 100644 index 0000000000..87e4bfcba5 --- /dev/null +++ b/modules/imgcodecs/src/grfmt_jpegxl.hpp @@ -0,0 +1,68 @@ +// 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 _GRFMT_JPEGXL_H_ +#define _GRFMT_JPEGXL_H_ + +#ifdef HAVE_JPEGXL + +#include "grfmt_base.hpp" +#include +#include +#include +#include + +// Jpeg XL codec + +namespace cv +{ + +/** +* @brief JpegXL codec using libjxl library +*/ + +class JpegXLDecoder CV_FINAL : public BaseImageDecoder +{ +public: + + JpegXLDecoder(); + virtual ~JpegXLDecoder(); + + bool readData( Mat& img ) CV_OVERRIDE; + bool readHeader() CV_OVERRIDE; + void close(); + size_t signatureLength() const CV_OVERRIDE; + bool checkSignature( const String& signature ) const CV_OVERRIDE; + + ImageDecoder newDecoder() const CV_OVERRIDE; + +protected: + std::unique_ptr m_f; + JxlDecoderPtr m_decoder; + JxlThreadParallelRunnerPtr m_parallel_runner; + JxlPixelFormat m_format; + int m_convert; + std::vector m_read_buffer; + JxlDecoderStatus m_status; + +private: + bool read(Mat* pimg); +}; + + +class JpegXLEncoder CV_FINAL : public BaseImageEncoder +{ +public: + JpegXLEncoder(); + virtual ~JpegXLEncoder(); + + bool isFormatSupported( int depth ) const CV_OVERRIDE; + bool write( const Mat& img, const std::vector& params ) CV_OVERRIDE; + ImageEncoder newEncoder() const CV_OVERRIDE; +}; + +} + +#endif + +#endif/*_GRFMT_JPEGXL_H_*/ diff --git a/modules/imgcodecs/src/grfmts.hpp b/modules/imgcodecs/src/grfmts.hpp index 198588630c..9fcc27740c 100644 --- a/modules/imgcodecs/src/grfmts.hpp +++ b/modules/imgcodecs/src/grfmts.hpp @@ -48,6 +48,7 @@ #include "grfmt_gif.hpp" #include "grfmt_sunras.hpp" #include "grfmt_jpeg.hpp" +#include "grfmt_jpegxl.hpp" #include "grfmt_pxm.hpp" #include "grfmt_pfm.hpp" #include "grfmt_tiff.hpp" diff --git a/modules/imgcodecs/src/loadsave.cpp b/modules/imgcodecs/src/loadsave.cpp index e044554350..ec25f8c610 100644 --- a/modules/imgcodecs/src/loadsave.cpp +++ b/modules/imgcodecs/src/loadsave.cpp @@ -210,6 +210,10 @@ struct ImageCodecInitializer decoders.push_back( makePtr() ); encoders.push_back( makePtr() ); #endif + #ifdef HAVE_JPEGXL + decoders.push_back( makePtr() ); + encoders.push_back( makePtr() ); + #endif #ifdef HAVE_OPENJPEG decoders.push_back( makePtr() ); decoders.push_back( makePtr() ); diff --git a/modules/imgcodecs/test/test_jpegxl.cpp b/modules/imgcodecs/test/test_jpegxl.cpp new file mode 100644 index 0000000000..6c5aad181f --- /dev/null +++ b/modules/imgcodecs/test/test_jpegxl.cpp @@ -0,0 +1,186 @@ +// 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" + +namespace opencv_test { namespace { + +#ifdef HAVE_JPEGXL + +typedef tuple MatType_and_Distance; +typedef testing::TestWithParam Imgcodecs_JpegXL_MatType; + +TEST_P(Imgcodecs_JpegXL_MatType, write_read) +{ + const int matType = get<0>(GetParam()); + const int distanceParam = get<1>(GetParam()); + + cv::Scalar col; + // Jpeg XL is lossy compression. + // There may be small differences in decoding results by environments. + double th; + + switch( CV_MAT_DEPTH(matType) ) + { + case CV_16U: + col = cv::Scalar(124 * 256, 76 * 256, 42 * 256, 192 * 256 ); + th = 656; // = 65535 / 100; + break; + case CV_32F: + col = cv::Scalar(0.486, 0.298, 0.165, 0.75); + th = 1.0 / 100.0; + break; + default: + case CV_8U: + col = cv::Scalar(124, 76, 42, 192); + th = 3; // = 255 / 100 (1%); + break; + } + + // If increasing distanceParam, threshold should be increased. + th *= (distanceParam >= 25) ? 5 : ( distanceParam > 2 ) ? 3 : (distanceParam == 2) ? 2: 1; + + bool ret = false; + string tmp_fname = cv::tempfile(".jxl"); + Mat img_org(320, 480, matType, col); + vector param; + param.push_back(IMWRITE_JPEGXL_DISTANCE); + param.push_back(distanceParam); + EXPECT_NO_THROW(ret = imwrite(tmp_fname, img_org, param)); + EXPECT_TRUE(ret); + Mat img_decoded; + EXPECT_NO_THROW(img_decoded = imread(tmp_fname, IMREAD_UNCHANGED)); + EXPECT_FALSE(img_decoded.empty()); + + EXPECT_LE(cvtest::norm(img_org, img_decoded, NORM_INF), th); + + EXPECT_EQ(0, remove(tmp_fname.c_str())); +} + +TEST_P(Imgcodecs_JpegXL_MatType, encode_decode) +{ + const int matType = get<0>(GetParam()); + const int distanceParam = get<1>(GetParam()); + + cv::Scalar col; + // Jpeg XL is lossy compression. + // There may be small differences in decoding results by environments. + double th; + + // If alpha=0, libjxl modify color channels(BGR). So do not set it. + switch( CV_MAT_DEPTH(matType) ) + { + case CV_16U: + col = cv::Scalar(124 * 256, 76 * 256, 42 * 256, 192 * 256 ); + th = 656; // = 65535 / 100; + break; + case CV_32F: + col = cv::Scalar(0.486, 0.298, 0.165, 0.75); + th = 1.0 / 100.0; + break; + default: + case CV_8U: + col = cv::Scalar(124, 76, 42, 192); + th = 3; // = 255 / 100 (1%); + break; + } + + // If increasing distanceParam, threshold should be increased. + th *= (distanceParam >= 25) ? 5 : ( distanceParam > 2 ) ? 3 : (distanceParam == 2) ? 2: 1; + + bool ret = false; + vector buff; + Mat img_org(320, 480, matType, col); + vector param; + param.push_back(IMWRITE_JPEGXL_DISTANCE); + param.push_back(distanceParam); + EXPECT_NO_THROW(ret = imencode(".jxl", img_org, buff, param)); + EXPECT_TRUE(ret); + Mat img_decoded; + EXPECT_NO_THROW(img_decoded = imdecode(buff, IMREAD_UNCHANGED)); + EXPECT_FALSE(img_decoded.empty()); + + EXPECT_LE(cvtest::norm(img_org, img_decoded, NORM_INF), th); +} + +INSTANTIATE_TEST_CASE_P( + /**/, + Imgcodecs_JpegXL_MatType, + testing::Combine( + testing::Values( + CV_8UC1, CV_8UC3, CV_8UC4, + CV_16UC1, CV_16UC3, CV_16UC4, + CV_32FC1, CV_32FC3, CV_32FC4 + ), + testing::Values( // Distance + 0, // Lossless + 1, // Default + 3, // Recomended Lossy Max + 25 // Specification Max + ) +) ); + + +typedef tuple Effort_and_Decoding_speed; +typedef testing::TestWithParam Imgcodecs_JpegXL_Effort_DecodingSpeed; + +TEST_P(Imgcodecs_JpegXL_Effort_DecodingSpeed, encode_decode) +{ + const int effort = get<0>(GetParam()); + const int speed = get<1>(GetParam()); + + cv::Scalar col = cv::Scalar(124,76,42); + // Jpeg XL is lossy compression. + // There may be small differences in decoding results by environments. + double th = 3; // = 255 / 100 (1%); + + bool ret = false; + vector buff; + Mat img_org(320, 480, CV_8UC3, col); + vector param; + param.push_back(IMWRITE_JPEGXL_EFFORT); + param.push_back(effort); + param.push_back(IMWRITE_JPEGXL_DECODING_SPEED); + param.push_back(speed); + EXPECT_NO_THROW(ret = imencode(".jxl", img_org, buff, param)); + EXPECT_TRUE(ret); + Mat img_decoded; + EXPECT_NO_THROW(img_decoded = imdecode(buff, IMREAD_UNCHANGED)); + EXPECT_FALSE(img_decoded.empty()); + + EXPECT_LE(cvtest::norm(img_org, img_decoded, NORM_INF), th); +} + +INSTANTIATE_TEST_CASE_P( + /**/, + Imgcodecs_JpegXL_Effort_DecodingSpeed, + testing::Combine( + testing::Values( // Effort + 1, // fastest + 7, // default + 9 // slowest + ), + testing::Values( // Decoding Speed + 0, // default, slowest, and best quality/density + 2, + 4 // fastest, at the cost of some qulity/density + ) +) ); + +TEST(Imgcodecs_JpegXL, encode_from_uncontinued_image) +{ + cv::Mat src(100, 100, CV_8UC1, Scalar(40,50,10)); + cv::Mat roi = src(cv::Rect(10,20,30,50)); + EXPECT_FALSE(roi.isContinuous()); // uncontinued image + + vector buff; + vector param; + bool ret = false; + EXPECT_NO_THROW(ret = cv::imencode(".jxl", roi, buff, param)); + EXPECT_TRUE(ret); +} + +#endif // HAVE_JPEGXL + +} // namespace +} // namespace opencv_test diff --git a/modules/imgcodecs/test/test_read_write.cpp b/modules/imgcodecs/test/test_read_write.cpp index 7dfd02c67c..1e171d9e8d 100644 --- a/modules/imgcodecs/test/test_read_write.cpp +++ b/modules/imgcodecs/test/test_read_write.cpp @@ -157,6 +157,9 @@ const string exts[] = { #ifdef HAVE_JPEG "jpg", #endif +#ifdef HAVE_JPEGXL + "jxl", +#endif #if (defined(HAVE_JASPER) && defined(OPENCV_IMGCODECS_ENABLE_JASPER_TESTS)) \ || defined(HAVE_OPENJPEG) "jp2", @@ -238,6 +241,8 @@ TEST_P(Imgcodecs_Image, read_write_BGR) double psnrThreshold = 100; if (ext == "jpg") psnrThreshold = 32; + if (ext == "jxl") + psnrThreshold = 30; #if defined(HAVE_JASPER) if (ext == "jp2") psnrThreshold = 95; @@ -268,6 +273,8 @@ TEST_P(Imgcodecs_Image, read_write_GRAYSCALE) double psnrThreshold = 100; if (ext == "jpg") psnrThreshold = 40; + if (ext == "jxl") + psnrThreshold = 40; #if defined(HAVE_JASPER) if (ext == "jp2") psnrThreshold = 70; From f2878eb337b9af6c89544a2dc6cff47277b73844 Mon Sep 17 00:00:00 2001 From: Alberto Zurini Date: Wed, 1 Jan 2025 21:13:40 +0100 Subject: [PATCH 069/102] fix: cast coordinates to int32 for compatibility with line function --- doc/py_tutorials/py_calib3d/py_pose/py_pose.markdown | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/py_tutorials/py_calib3d/py_pose/py_pose.markdown b/doc/py_tutorials/py_calib3d/py_pose/py_pose.markdown index cc06da6902..e4d93d2717 100644 --- a/doc/py_tutorials/py_calib3d/py_pose/py_pose.markdown +++ b/doc/py_tutorials/py_calib3d/py_pose/py_pose.markdown @@ -36,7 +36,8 @@ Now let's create a function, draw which takes the corners in the chessboard (obt **cv.findChessboardCorners()**) and **axis points** to draw a 3D axis. @code{.py} def draw(img, corners, imgpts): - corner = tuple(corners[0].ravel()) + corner = tuple(corners[0].ravel().astype("int32")) + imgpts = imgpts.astype("int32") img = cv.line(img, corner, tuple(imgpts[0].ravel()), (255,0,0), 5) img = cv.line(img, corner, tuple(imgpts[1].ravel()), (0,255,0), 5) img = cv.line(img, corner, tuple(imgpts[2].ravel()), (0,0,255), 5) From 12963ea6995fa7dbf6a113038a6a6fd920b0e588 Mon Sep 17 00:00:00 2001 From: Vincent Rabaud Date: Thu, 2 Jan 2025 14:51:20 +0100 Subject: [PATCH 070/102] Fix heap buffer overflow in cv::PngDecoder::read_from_io Bug: oss-fuzz:386688710 --- modules/imgcodecs/src/grfmt_png.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/imgcodecs/src/grfmt_png.cpp b/modules/imgcodecs/src/grfmt_png.cpp index 32620fdbc0..eba0226d0e 100644 --- a/modules/imgcodecs/src/grfmt_png.cpp +++ b/modules/imgcodecs/src/grfmt_png.cpp @@ -655,7 +655,7 @@ size_t PngDecoder::read_from_io(void* _Buffer, size_t _ElementSize, size_t _Elem if (m_f) return fread(_Buffer, _ElementSize, _ElementCount, m_f); - if (m_buf_pos > m_buf.cols * m_buf.rows * m_buf.elemSize()) + if (m_buf_pos + _ElementSize > m_buf.cols * m_buf.rows * m_buf.elemSize()) CV_Error(Error::StsInternal, "PNG input buffer is incomplete"); memcpy( _Buffer, m_buf.ptr() + m_buf_pos, _ElementSize ); From 2f0035b23ff3789eebbddbfd94dd4deef0cb0bd8 Mon Sep 17 00:00:00 2001 From: Vincent Rabaud Date: Thu, 2 Jan 2025 22:53:06 +0100 Subject: [PATCH 071/102] Fix flaky Imgcodecs_APNG.imwriteanimation_bgcolor --- modules/imgcodecs/src/grfmt_png.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/imgcodecs/src/grfmt_png.cpp b/modules/imgcodecs/src/grfmt_png.cpp index 32620fdbc0..e8b5fcedd4 100644 --- a/modules/imgcodecs/src/grfmt_png.cpp +++ b/modules/imgcodecs/src/grfmt_png.cpp @@ -1444,7 +1444,7 @@ bool PngEncoder::writeanimation(const Animation& animation, const std::vector 0 && !getRect(width, height, frames.back().getPixels(), apngFrame.getPixels(), over1.data(), bpp, rowbytes, 0, 0, 0, 3)) { - frames[i - 1].setDelayNum(frames.back().getDelayNum() + apngFrame.getDelayNum()); + frames.back().setDelayNum(frames.back().getDelayNum() + apngFrame.getDelayNum()); num_frames--; } else From 1281317e1754e5607e5ae89842896758dca5c010 Mon Sep 17 00:00:00 2001 From: Kumataro Date: Fri, 3 Jan 2025 09:53:01 +0900 Subject: [PATCH 072/102] imgcodecs: fix EXR tests --- modules/imgcodecs/src/grfmt_exr.cpp | 24 ++++++++++++++++++++---- modules/imgcodecs/test/test_exr.impl.hpp | 9 +++++++-- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/modules/imgcodecs/src/grfmt_exr.cpp b/modules/imgcodecs/src/grfmt_exr.cpp index e93448e355..0ffd0d2618 100644 --- a/modules/imgcodecs/src/grfmt_exr.cpp +++ b/modules/imgcodecs/src/grfmt_exr.cpp @@ -235,6 +235,17 @@ bool ExrDecoder::readData( Mat& img ) ( (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; + // 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. + bool doReadRGB = m_use_rgb; + bool doPostColorSwap = false; // After decoding, swap BGR to RGB + if(m_use_rgb && (getDefaultAlgorithmHint() == ALGO_HINT_ACCURATE) ) + { + doReadRGB = false; + doPostColorSwap = true; + } + AutoBuffer copy_buffer; if( !justcopy ) @@ -373,7 +384,7 @@ bool ExrDecoder::readData( Mat& img ) if( m_iscolor ) { - if (m_use_rgb) + if (doReadRGB) { if( m_red && (m_red->xSampling != 1 || m_red->ySampling != 1) ) UpSample( data, channelstoread, step / xstep, m_red->xSampling, m_red->ySampling ); @@ -397,7 +408,7 @@ bool ExrDecoder::readData( Mat& img ) if( chromatorgb ) { - if (m_use_rgb) + if (doReadRGB) ChromaToRGB( (float *)data, m_height, channelstoread, step / xstep ); else ChromaToBGR( (float *)data, m_height, channelstoread, step / xstep ); @@ -424,7 +435,7 @@ bool ExrDecoder::readData( Mat& img ) { if( chromatorgb ) { - if (m_use_rgb) + if (doReadRGB) ChromaToRGB( (float *)buffer, 1, defaultchannels, step ); else ChromaToBGR( (float *)buffer, 1, defaultchannels, step ); @@ -452,7 +463,7 @@ bool ExrDecoder::readData( Mat& img ) } if( color ) { - if (m_use_rgb) + if (doReadRGB) { if( m_red && (m_red->xSampling != 1 || m_red->ySampling != 1) ) UpSampleY( data, defaultchannels, step / xstep, m_red->ySampling ); @@ -477,6 +488,11 @@ bool ExrDecoder::readData( Mat& img ) close(); + if(doPostColorSwap) + { + cvtColor( img, img, cv::COLOR_BGR2RGB ); + } + return result; } diff --git a/modules/imgcodecs/test/test_exr.impl.hpp b/modules/imgcodecs/test/test_exr.impl.hpp index 32984ff731..6b4ac0b8d1 100644 --- a/modules/imgcodecs/test/test_exr.impl.hpp +++ b/modules/imgcodecs/test/test_exr.impl.hpp @@ -35,7 +35,8 @@ TEST(Imgcodecs_EXR, readWrite_32FC1) ASSERT_TRUE(cv::imwrite(filenameOutput, img)); // Check generated file size to ensure that it's compressed with proper options - ASSERT_EQ(396u, getFileSize(filenameOutput)); + ASSERT_LE(396u, getFileSize(filenameOutput)); // OpenEXR 2 + ASSERT_LE( getFileSize(filenameOutput), 440u); // OpenEXR 3.2+ const Mat img2 = cv::imread(filenameOutput, IMREAD_UNCHANGED); ASSERT_EQ(img2.type(), img.type()); ASSERT_EQ(img2.size(), img.size()); @@ -199,7 +200,11 @@ TEST(Imgcodecs_EXR, read_YC_changeDepth) cvtColor(img_rgb, img_rgb, COLOR_RGB2BGR); - EXPECT_TRUE(cvtest::norm(img, img_rgb, NORM_INF) == 0); + // See https://github.com/opencv/opencv/issues/26705 + // If ALGO_HINT_ACCURATE is set, norm should be 0. + // If ALGO_HINT_APPROX is set, norm should be 1(or 0). + EXPECT_LE(cvtest::norm(img, img_rgb, NORM_INF), + (cv::getDefaultAlgorithmHint() == ALGO_HINT_ACCURATE)?0:1); // Cannot test writing, EXR encoder doesn't support 8U depth } From 845616d82cae8d8865f1eb4ee032a7b8e0db3f26 Mon Sep 17 00:00:00 2001 From: Vincent Rabaud Date: Thu, 2 Jan 2025 14:39:54 +0100 Subject: [PATCH 073/102] Fix integer overflow in in cv::BmpDecoder::readHeader Bug: oss-fuzz:371546812 --- modules/imgcodecs/src/grfmt_bmp.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/modules/imgcodecs/src/grfmt_bmp.cpp b/modules/imgcodecs/src/grfmt_bmp.cpp index e69a93c78b..a8ca6a046a 100644 --- a/modules/imgcodecs/src/grfmt_bmp.cpp +++ b/modules/imgcodecs/src/grfmt_bmp.cpp @@ -208,7 +208,12 @@ bool BmpDecoder::readHeader() // in 32 bit case alpha channel is used - so require CV_8UC4 type m_type = iscolor ? ((m_bpp == 32 && m_rle_code != BMP_RGB) ? CV_8UC4 : CV_8UC3 ) : CV_8UC1; m_origin = m_height > 0 ? ORIGIN_BL : ORIGIN_TL; - m_height = std::abs(m_height); + if ( m_height == std::numeric_limits::min() ) { + // abs(std::numeric_limits::min()) is undefined behavior. + result = false; + } else { + m_height = std::abs(m_height); + } if( !result ) { From b9505ac861e3b166a515704bfc868a2d505de262 Mon Sep 17 00:00:00 2001 From: Kumataro Date: Fri, 3 Jan 2025 19:22:57 +0900 Subject: [PATCH 074/102] core: validate OPENCV_ALGO_HINT_DEFAULT option --- CMakeLists.txt | 7 +++++++ .../config_reference/config_reference.markdown | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e60406fbe2..74beaee5f1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -767,6 +767,13 @@ elseif(WIN32) endif() endif() +if(DEFINED OPENCV_ALGO_HINT_DEFAULT) + if(NOT OPENCV_ALGO_HINT_DEFAULT STREQUAL "ALGO_HINT_ACCURATE" AND + NOT OPENCV_ALGO_HINT_DEFAULT STREQUAL "ALGO_HINT_APPROX") + message(FATAL_ERROR "OPENCV_ALGO_HINT_DEFAULT should be one of ALGO_HINT_ACCURATE or ALGO_HINT_APPROX.") + endif() +endif() + include(cmake/OpenCVPCHSupport.cmake) include(cmake/OpenCVModule.cmake) diff --git a/doc/tutorials/introduction/config_reference/config_reference.markdown b/doc/tutorials/introduction/config_reference/config_reference.markdown index 3c3c1d5c99..e6ffbb833c 100644 --- a/doc/tutorials/introduction/config_reference/config_reference.markdown +++ b/doc/tutorials/introduction/config_reference/config_reference.markdown @@ -217,7 +217,7 @@ Following options can be used to produce special builds with instrumentation or | `ENABLE_BUILD_HARDENING` | GCC, Clang, MSVC | Enable compiler options which reduce possibility of code exploitation. | | `ENABLE_LTO` | GCC, Clang, MSVC | Enable Link Time Optimization (LTO). | | `ENABLE_THIN_LTO` | Clang | Enable thin LTO which incorporates intermediate bitcode to binaries allowing consumers optimize their applications later. | -| `OPENCV_ALGO_HINT_DEFAULT` | Any | Set default OpenCV implementation hint value: `ALGO_HINT_ACCURATE` or `ALGO_HINT_APROX`. Dangerous! The option changes behaviour globally and may affect accuracy of many algorithms. | +| `OPENCV_ALGO_HINT_DEFAULT` | Any | Set default OpenCV implementation hint value: `ALGO_HINT_ACCURATE` or `ALGO_HINT_APPROX`. Dangerous! The option changes behaviour globally and may affect accuracy of many algorithms. | @see [GCC instrumentation](https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html) @see [Build hardening](https://en.wikipedia.org/wiki/Hardening_(computing)) From 0538e64b13c699387f73a7c4fc2fcecd71832371 Mon Sep 17 00:00:00 2001 From: Vincent Rabaud Date: Fri, 3 Jan 2025 12:13:18 +0100 Subject: [PATCH 075/102] Fix leaks in cv:Merge pull request #26701 from vrabaud:png_leak Fix leaks in cv::PngDecoder #26701 Bug: oss-fuzz:386688709 ### 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 --- modules/imgcodecs/src/grfmt_png.cpp | 75 +++++++++++++---------------- modules/imgcodecs/src/grfmt_png.hpp | 10 ++-- 2 files changed, 39 insertions(+), 46 deletions(-) diff --git a/modules/imgcodecs/src/grfmt_png.cpp b/modules/imgcodecs/src/grfmt_png.cpp index dedc9be515..3236abc637 100644 --- a/modules/imgcodecs/src/grfmt_png.cpp +++ b/modules/imgcodecs/src/grfmt_png.cpp @@ -141,7 +141,6 @@ APNGFrame::APNGFrame() _transparencySize = 0; _delayNum = 1; _delayDen = 1000; - _rows = NULL; } APNGFrame::~APNGFrame() {} @@ -159,7 +158,7 @@ bool APNGFrame::setMat(const cv::Mat& src, unsigned delayNum, unsigned delayDen) _height = src.rows; _colorType = src.channels() == 1 ? PNG_COLOR_TYPE_GRAY : src.channels() == 3 ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA; _pixels = src.data; - _rows = new png_bytep[_height * sizeof(png_bytep)]; + _rows.resize(_height); for (unsigned int i = 0; i < _height; ++i) _rows[i] = _pixels + i * rowbytes; @@ -178,7 +177,6 @@ void APNGFrame::setTransparencySize(int transparencySize) { _transparencySize = void APNGFrame::setDelayNum(unsigned int delayNum) { _delayNum = delayNum; } void APNGFrame::setDelayDen(unsigned int delayDen) { _delayDen = delayDen; } void APNGFrame::setPixels(unsigned char* pixels) { _pixels = pixels; } -void APNGFrame::setRows(unsigned char** rows) { _rows = rows; } PngDecoder::PngDecoder() { @@ -282,7 +280,7 @@ bool PngDecoder::readHeader() id = read_chunk(m_chunkIHDR); } - if (!(id == id_IHDR && m_chunkIHDR.size == 25)) + if (!(id == id_IHDR && m_chunkIHDR.p.size() == 25)) return false; while (true) @@ -302,37 +300,37 @@ bool PngDecoder::readHeader() break; } - if (id == id_acTL && chunk.size == 20) + if (id == id_acTL && chunk.p.size() == 20) { - m_animation.loop_count = png_get_uint_32(chunk.p + 12); + m_animation.loop_count = png_get_uint_32(&chunk.p[12]); if (chunk.p[8] > 0) { chunk.p[8] = 0; chunk.p[9] = 0; - m_frame_count = png_get_uint_32(chunk.p + 8); + m_frame_count = png_get_uint_32(&chunk.p[8]); m_frame_count++; } else - m_frame_count = png_get_uint_32(chunk.p + 8); + m_frame_count = png_get_uint_32(&chunk.p[8]); } if (id == id_fcTL) { m_is_fcTL_loaded = true; - w0 = png_get_uint_32(chunk.p + 12); - h0 = png_get_uint_32(chunk.p + 16); - x0 = png_get_uint_32(chunk.p + 20); - y0 = png_get_uint_32(chunk.p + 24); - delay_num = png_get_uint_16(chunk.p + 28); - delay_den = png_get_uint_16(chunk.p + 30); + w0 = png_get_uint_32(&chunk.p[12]); + h0 = png_get_uint_32(&chunk.p[16]); + x0 = png_get_uint_32(&chunk.p[20]); + y0 = png_get_uint_32(&chunk.p[24]); + delay_num = png_get_uint_16(&chunk.p[28]); + delay_den = png_get_uint_16(&chunk.p[30]); dop = chunk.p[32]; bop = chunk.p[33]; } if (id == id_bKGD) { - int bgcolor = png_get_uint_32(chunk.p + 8); + int bgcolor = png_get_uint_32(&chunk.p[8]); m_animation.bgcolor[3] = (bgcolor >> 24) & 0xFF; m_animation.bgcolor[2] = (bgcolor >> 16) & 0xFF; m_animation.bgcolor[1] = (bgcolor >> 8) & 0xFF; @@ -461,39 +459,37 @@ bool PngDecoder::readData( Mat& img ) } else { - delete[] chunk.p; return false; } - w0 = png_get_uint_32(chunk.p + 12); - h0 = png_get_uint_32(chunk.p + 16); - x0 = png_get_uint_32(chunk.p + 20); - y0 = png_get_uint_32(chunk.p + 24); - delay_num = png_get_uint_16(chunk.p + 28); - delay_den = png_get_uint_16(chunk.p + 30); + w0 = png_get_uint_32(&chunk.p[12]); + h0 = png_get_uint_32(&chunk.p[16]); + x0 = png_get_uint_32(&chunk.p[20]); + y0 = png_get_uint_32(&chunk.p[24]); + delay_num = png_get_uint_16(&chunk.p[28]); + delay_den = png_get_uint_16(&chunk.p[30]); dop = chunk.p[32]; bop = chunk.p[33]; if (int(x0 + w0) > img.cols || int(y0 + h0) > img.rows || dop > 2 || bop > 1) { - delete[] chunk.p; return false; } - memcpy(m_chunkIHDR.p + 8, chunk.p + 12, 8); + memcpy(&m_chunkIHDR.p[8], &chunk.p[12], 8); return true; } else if (id == id_IDAT) { m_is_IDAT_loaded = true; - png_process_data(png_ptr, info_ptr, chunk.p, chunk.size); + png_process_data(png_ptr, info_ptr, chunk.p.data(), chunk.p.size()); } else if (id == id_fdAT && m_is_fcTL_loaded) { m_is_IDAT_loaded = true; - png_save_uint_32(chunk.p + 4, chunk.size - 16); - memcpy(chunk.p + 8, "IDAT", 4); - png_process_data(png_ptr, info_ptr, chunk.p + 4, chunk.size - 4); + png_save_uint_32(&chunk.p[4], static_cast(chunk.p.size() - 16)); + memcpy(&chunk.p[8], "IDAT", 4); + png_process_data(png_ptr, info_ptr, &chunk.p[4], chunk.p.size() - 4); } else if (id == id_IEND) { @@ -514,13 +510,10 @@ bool PngDecoder::readData( Mat& img ) else return false; - delete[] chunk.p; return true; } else - png_process_data(png_ptr, info_ptr, chunk.p, chunk.size); - - delete[] chunk.p; + png_process_data(png_ptr, info_ptr, chunk.p.data(), chunk.p.size()); } return false; } @@ -613,7 +606,7 @@ bool PngDecoder::nextPage() { return ++m_frame_no < (int)m_frame_count; } -void PngDecoder::compose_frame(unsigned char** rows_dst, unsigned char** rows_src, unsigned char _bop, uint32_t x, uint32_t y, uint32_t w, uint32_t h, int channels) +void PngDecoder::compose_frame(std::vector& rows_dst, const std::vector& rows_src, unsigned char _bop, uint32_t x, uint32_t y, uint32_t w, uint32_t h, int channels) { uint32_t i, j; int u, v, al; @@ -668,15 +661,15 @@ uint32_t PngDecoder::read_chunk(Chunk& chunk) unsigned char len[4]; if (read_from_io(&len, 4, 1) == 1) { - chunk.size = png_get_uint_32(len) + 12; - if (chunk.size > PNG_USER_CHUNK_MALLOC_MAX) + const size_t size = png_get_uint_32(len) + 12; + if (size > PNG_USER_CHUNK_MALLOC_MAX) { CV_LOG_WARNING(NULL, "chunk data is too large"); } - chunk.p = new unsigned char[chunk.size]; - memcpy(chunk.p, len, 4); - if (read_from_io(chunk.p + 4, chunk.size - 4, 1) == 1) - return *(uint32_t*)(chunk.p + 4); + chunk.p.resize(size); + memcpy(chunk.p.data(), len, 4); + if (read_from_io(&chunk.p[4], chunk.p.size() - 4, 1) == 1) + return *(uint32_t*)(&chunk.p[4]); } return 0; } @@ -709,7 +702,7 @@ bool PngDecoder::processing_start(void* frame_ptr, const Mat& img) png_set_tRNS_to_alpha(png_ptr); png_process_data(png_ptr, info_ptr, header, 8); - png_process_data(png_ptr, info_ptr, m_chunkIHDR.p, m_chunkIHDR.size); + png_process_data(png_ptr, info_ptr, m_chunkIHDR.p.data(), m_chunkIHDR.p.size()); if ((m_color_type & PNG_COLOR_MASK_COLOR) && img.channels() > 1 && !m_use_rgb) png_set_bgr(png_ptr); // convert RGB to BGR @@ -719,7 +712,7 @@ bool PngDecoder::processing_start(void* frame_ptr, const Mat& img) png_set_rgb_to_gray(png_ptr, 1, 0.299, 0.587); // RGB->Gray for (size_t i = 0; i < m_chunksInfo.size(); i++) - png_process_data(png_ptr, info_ptr, m_chunksInfo[i].p, m_chunksInfo[i].size); + png_process_data(png_ptr, info_ptr, m_chunksInfo[i].p.data(), m_chunksInfo[i].p.size()); return true; } diff --git a/modules/imgcodecs/src/grfmt_png.hpp b/modules/imgcodecs/src/grfmt_png.hpp index c23fb8ab54..4ba22d69d7 100644 --- a/modules/imgcodecs/src/grfmt_png.hpp +++ b/modules/imgcodecs/src/grfmt_png.hpp @@ -49,11 +49,12 @@ #include "bitstrm.hpp" #include #include +#include namespace cv { -struct Chunk { unsigned char* p; uint32_t size; }; +struct Chunk { std::vector p; }; struct OP { unsigned char* p; uint32_t size; int x, y, w, h, valid, filters; }; typedef struct { @@ -101,8 +102,7 @@ public: unsigned int getDelayDen() const { return _delayDen; } void setDelayDen(unsigned int delayDen); - unsigned char** getRows() const { return _rows; } - void setRows(unsigned char** rows); + std::vector& getRows() { return _rows; } private: unsigned char* _pixels; @@ -115,7 +115,7 @@ private: int _transparencySize; unsigned int _delayNum; unsigned int _delayDen; - unsigned char** _rows; + std::vector _rows; }; class PngDecoder CV_FINAL : public BaseImageDecoder @@ -136,7 +136,7 @@ protected: static void row_fn(png_structp png_ptr, png_bytep new_row, png_uint_32 row_num, int pass); bool processing_start(void* frame_ptr, const Mat& img); bool processing_finish(); - void compose_frame(unsigned char** rows_dst, unsigned char** rows_src, unsigned char bop, uint32_t x, uint32_t y, uint32_t w, uint32_t h, int channels); + void compose_frame(std::vector& rows_dst, const std::vector& rows_src, unsigned char bop, uint32_t x, uint32_t y, uint32_t w, uint32_t h, int channels); size_t read_from_io(void* _Buffer, size_t _ElementSize, size_t _ElementCount); uint32_t read_chunk(Chunk& chunk); From b4d03256663b6ceb2994c367be4664f282c9f1ec Mon Sep 17 00:00:00 2001 From: Suleyman TURKMEN Date: Fri, 3 Jan 2025 14:25:31 +0300 Subject: [PATCH 076/102] GIF APNG WEBP AVIF revisions --- modules/imgcodecs/src/grfmt_avif.cpp | 15 --- modules/imgcodecs/src/grfmt_avif.hpp | 4 - modules/imgcodecs/src/grfmt_base.cpp | 15 ++- modules/imgcodecs/src/grfmt_gif.cpp | 32 +++--- modules/imgcodecs/src/grfmt_gif.hpp | 4 +- modules/imgcodecs/src/grfmt_png.cpp | 131 +++++++++++++--------- modules/imgcodecs/src/grfmt_png.hpp | 3 +- modules/imgcodecs/src/grfmt_webp.cpp | 14 --- modules/imgcodecs/src/grfmt_webp.hpp | 1 - modules/imgcodecs/test/test_animation.cpp | 100 +++++++++++++++++ 10 files changed, 210 insertions(+), 109 deletions(-) diff --git a/modules/imgcodecs/src/grfmt_avif.cpp b/modules/imgcodecs/src/grfmt_avif.cpp index 58f6ce91c1..d3fb500604 100644 --- a/modules/imgcodecs/src/grfmt_avif.cpp +++ b/modules/imgcodecs/src/grfmt_avif.cpp @@ -303,21 +303,6 @@ bool AvifEncoder::write(const Mat &img, const std::vector ¶ms) { return writemulti(img_vec, params); } -bool AvifEncoder::writemulti(const std::vector &img_vec, - const std::vector ¶ms) { - - CV_LOG_INFO(NULL, "Multi page image will be written as animation with 1 second frame duration."); - - Animation animation; - animation.frames = img_vec; - - for (size_t i = 0; i < animation.frames.size(); i++) - { - animation.durations.push_back(1000); - } - return writeanimation(animation, params); -} - bool AvifEncoder::writeanimation(const Animation& animation, const std::vector ¶ms) { int bit_depth = 8; diff --git a/modules/imgcodecs/src/grfmt_avif.hpp b/modules/imgcodecs/src/grfmt_avif.hpp index 31f9b00a41..87b765619e 100644 --- a/modules/imgcodecs/src/grfmt_avif.hpp +++ b/modules/imgcodecs/src/grfmt_avif.hpp @@ -41,11 +41,7 @@ class AvifEncoder CV_FINAL : public BaseImageEncoder { ~AvifEncoder() CV_OVERRIDE; bool isFormatSupported(int depth) const CV_OVERRIDE; - bool write(const Mat& img, const std::vector& params) CV_OVERRIDE; - - bool writemulti(const std::vector& img_vec, - const std::vector& params) CV_OVERRIDE; bool writeanimation(const Animation& animation, const std::vector& params) CV_OVERRIDE; ImageEncoder newEncoder() const CV_OVERRIDE; diff --git a/modules/imgcodecs/src/grfmt_base.cpp b/modules/imgcodecs/src/grfmt_base.cpp index d01899fb7e..1e09882780 100644 --- a/modules/imgcodecs/src/grfmt_base.cpp +++ b/modules/imgcodecs/src/grfmt_base.cpp @@ -43,6 +43,7 @@ #include "grfmt_base.hpp" #include "bitstrm.hpp" +#include namespace cv { @@ -139,9 +140,19 @@ bool BaseImageEncoder::setDestination( std::vector& buf ) return true; } -bool BaseImageEncoder::writemulti(const std::vector&, const std::vector& ) +bool BaseImageEncoder::writemulti(const std::vector& img_vec, const std::vector& params) { - return false; + if(img_vec.size() > 1) + CV_LOG_INFO(NULL, "Multi page image will be written as animation with 1 second frame duration."); + + Animation animation; + animation.frames = img_vec; + + for (size_t i = 0; i < animation.frames.size(); i++) + { + animation.durations.push_back(1000); + } + return writeanimation(animation, params); } bool BaseImageEncoder::writeanimation(const Animation&, const std::vector& ) diff --git a/modules/imgcodecs/src/grfmt_gif.cpp b/modules/imgcodecs/src/grfmt_gif.cpp index caf8a8fc4f..5a65ae04b1 100644 --- a/modules/imgcodecs/src/grfmt_gif.cpp +++ b/modules/imgcodecs/src/grfmt_gif.cpp @@ -219,7 +219,7 @@ void GifDecoder::readExtensions() { len = (uchar)m_strm.getByte(); CV_Assert(len == 4); auto flags = (uchar)m_strm.getByte(); - m_strm.getWord(); // delay time, not used + m_animation.durations.push_back(m_strm.getWord() * 10); // delay time opMode = (GifOpMode)((flags & 0x1C) >> 2); hasTransparentColor = flags & 0x01; transparentColor = (uchar)m_strm.getByte(); @@ -407,6 +407,10 @@ bool GifDecoder::getFrameCount_() { while (len) { m_strm.skip(len); len = m_strm.getByte(); + if (len == 3 && m_strm.getByte() == 1) + { + m_animation.loop_count = m_strm.getWord(); + } } } else if (!(type ^ 0x2C)) { // skip image data @@ -490,16 +494,11 @@ bool GifEncoder::isFormatSupported(int depth) const { bool GifEncoder::write(const Mat &img, const std::vector ¶ms) { std::vector img_vec(1, img); - return writeFrames(img_vec, params); + return writemulti(img_vec, params); } -bool GifEncoder::writemulti(const std::vector &img_vec, const std::vector ¶ms) { - return writeFrames(img_vec, params); -} - -bool GifEncoder::writeFrames(const std::vector& img_vec, - const std::vector& params) { - if (img_vec.empty()) { +bool GifEncoder::writeanimation(const Animation& animation, const std::vector& params) { + if (animation.frames.empty()) { return false; } @@ -511,6 +510,8 @@ bool GifEncoder::writeFrames(const std::vector& img_vec, return false; } + loopCount = animation.loop_count; + // confirm the params for (size_t i = 0; i < params.size(); i += 2) { switch (params[i]) { @@ -561,13 +562,13 @@ bool GifEncoder::writeFrames(const std::vector& img_vec, if (fast) { const uchar transparent = 0x92; // 1001_0010: the middle of the color table if (dithering == GRFMT_GIF_None) { - img_vec_ = img_vec; + img_vec_ = animation.frames; transparentColor = transparent; } else { localColorTableSize = 0; int transRGB; const int depth = 3 << 8 | 3 << 4 | 2; // r:g:b = 3:3:2 - for (auto &img: img_vec) { + for (auto &img: animation.frames) { Mat img_(img.size(), img.type()); transRGB = ditheringKernel(img, img_, depth, criticalTransparency); if (transRGB >= 0) { @@ -583,13 +584,13 @@ bool GifEncoder::writeFrames(const std::vector& img_vec, } else if (dithering != GRFMT_GIF_None) { int depth = (int)floor(log2(colorNum) / 3) + dithering; depth = depth << 8 | depth << 4 | depth; - for (auto &img : img_vec) { + for (auto &img : animation.frames) { Mat img_(img.size(), img.type()); ditheringKernel(img, img_, depth, criticalTransparency); img_vec_.push_back(img_); } } else { - img_vec_ = img_vec; + img_vec_ = animation.frames; } bool result = writeHeader(img_vec_); if (!result) { @@ -597,8 +598,9 @@ bool GifEncoder::writeFrames(const std::vector& img_vec, return false; } - for (const auto &img : img_vec_) { - result = writeFrame(img); + for (size_t i = 0; i < img_vec_.size(); i++) { + frameDelay = cvRound(animation.durations[i] / 10); + result = writeFrame(img_vec_[i]); } strm.putByte(0x3B); // trailer diff --git a/modules/imgcodecs/src/grfmt_gif.hpp b/modules/imgcodecs/src/grfmt_gif.hpp index 284a9cf945..8f520745ba 100644 --- a/modules/imgcodecs/src/grfmt_gif.hpp +++ b/modules/imgcodecs/src/grfmt_gif.hpp @@ -86,9 +86,7 @@ public: bool isFormatSupported(int depth) const CV_OVERRIDE; bool write(const Mat& img, const std::vector& params) CV_OVERRIDE; - - bool writemulti(const std::vector& img_vec, - const std::vector& params) CV_OVERRIDE; + bool writeanimation(const Animation& animation, const std::vector& params) CV_OVERRIDE; ImageEncoder newEncoder() const CV_OVERRIDE; diff --git a/modules/imgcodecs/src/grfmt_png.cpp b/modules/imgcodecs/src/grfmt_png.cpp index 3236abc637..5604ddcf28 100644 --- a/modules/imgcodecs/src/grfmt_png.cpp +++ b/modules/imgcodecs/src/grfmt_png.cpp @@ -152,8 +152,7 @@ bool APNGFrame::setMat(const cv::Mat& src, unsigned delayNum, unsigned delayDen) if (!src.empty()) { - png_uint_32 rowbytes = src.cols * src.channels(); - + png_uint_32 rowbytes = src.depth() == CV_16U ? src.cols * src.channels() * 2 : src.cols * src.channels(); _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; @@ -389,7 +388,7 @@ bool PngDecoder::readData( Mat& img ) { if (m_frame_count > 1) { - Mat mat_cur = Mat(img.rows, img.cols, m_type); + Mat mat_cur = Mat::zeros(img.rows, img.cols, m_type); uint32_t id = 0; uint32_t j = 0; uint32_t imagesize = m_width * m_height * mat_cur.channels(); @@ -437,7 +436,7 @@ bool PngDecoder::readData( Mat& img ) if (dop == 2) memcpy(frameNext.getPixels(), frameCur.getPixels(), imagesize); - compose_frame(frameCur.getRows(), frameRaw.getRows(), bop, x0, y0, w0, h0, mat_cur.channels()); + compose_frame(frameCur.getRows(), frameRaw.getRows(), bop, x0, y0, w0, h0, mat_cur); if (delay_den < 1000) delay_num = cvRound(1000.0 / delay_den); m_animation.durations.push_back(delay_num); @@ -495,7 +494,7 @@ bool PngDecoder::readData( Mat& img ) { if (processing_finish()) { - compose_frame(frameCur.getRows(), frameRaw.getRows(), bop, x0, y0, w0, h0, mat_cur.channels()); + compose_frame(frameCur.getRows(), frameRaw.getRows(), bop, x0, y0, w0, h0, mat_cur); if (delay_den < 1000) delay_num = cvRound(1000.0 / delay_den); m_animation.durations.push_back(delay_num); @@ -606,41 +605,81 @@ bool PngDecoder::nextPage() { return ++m_frame_no < (int)m_frame_count; } -void PngDecoder::compose_frame(std::vector& rows_dst, const std::vector& rows_src, unsigned char _bop, uint32_t x, uint32_t y, uint32_t w, uint32_t h, int channels) +void PngDecoder::compose_frame(std::vector& rows_dst, const std::vector& rows_src, unsigned char _bop, uint32_t x, uint32_t y, uint32_t w, uint32_t h, Mat& img) { - uint32_t i, j; - int u, v, al; + int channels = img.channels(); + if (img.depth() == CV_16U) + cv::parallel_for_(cv::Range(0, h), [&](const cv::Range& range) { + for (int j = range.start; j < range.end; j++) { + uint16_t* sp = reinterpret_cast(rows_src[j]); + uint16_t* dp = reinterpret_cast(rows_dst[j + y]) + x * channels; - for (j = 0; j < h; j++) - { - unsigned char* sp = rows_src[j]; - unsigned char* dp = rows_dst[j + y] + x * channels; - - if (_bop == 0) - memcpy(dp, sp, w * channels); - else - for (i = 0; i < w; i++, sp += 4, dp += 4) - { - if (sp[3] == 255) - memcpy(dp, sp, 4); - else - if (sp[3] != 0) - { - if (dp[3] != 0) - { - u = sp[3] * 255; - v = (255 - sp[3]) * dp[3]; - al = u + v; - dp[0] = (sp[0] * u + dp[0] * v) / al; - dp[1] = (sp[1] * u + dp[1] * v) / al; - dp[2] = (sp[2] * u + dp[2] * v) / al; - dp[3] = al / 255; - } - else - memcpy(dp, sp, 4); - } + if (_bop == 0) { + // Overwrite mode: copy source row directly to destination + memcpy(dp, sp, w * channels * sizeof(uint16_t)); } - } + else { + // Blending mode + for (unsigned int i = 0; i < w; i++, sp += channels, dp += channels) { + if (sp[3] == 65535) { // Fully opaque in 16-bit (max value) + memcpy(dp, sp, channels * sizeof(uint16_t)); + } + 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, channels * sizeof(uint16_t)); + } + } + } + } + } + }); + else + cv::parallel_for_(cv::Range(0, h), [&](const cv::Range& range) { + for (int j = range.start; j < range.end; j++) { + unsigned char* sp = rows_src[j]; + unsigned char* dp = rows_dst[j + y] + x * channels; + + if (_bop == 0) { + // Overwrite mode: copy source row directly to destination + memcpy(dp, sp, w * channels); + } + else { + // Blending mode + for (unsigned int i = 0; i < w; i++, sp += channels, dp += channels) { + if (sp[3] == 255) { + // Fully opaque: copy source pixel directly + memcpy(dp, sp, channels); + } + else if (sp[3] != 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, channels); + } + } + } + } + } + }); } size_t PngDecoder::read_from_io(void* _Buffer, size_t _ElementSize, size_t _ElementCount) @@ -742,7 +781,6 @@ bool PngDecoder::processing_finish() void PngDecoder::info_fn(png_structp png_ptr, png_infop info_ptr) { png_set_expand(png_ptr); - png_set_strip_16(png_ptr); (void)png_set_interlace_handling(png_ptr); png_read_update_info(png_ptr, info_ptr); } @@ -1352,21 +1390,6 @@ void PngEncoder::deflateRectFin(unsigned char* zbuf, uint32_t* zsize, int bpp, i deflateEnd(&fin_zstream); } -bool PngEncoder::writemulti(const std::vector& img_vec, const std::vector& params) -{ - CV_Assert(img_vec[0].depth() == CV_8U); - CV_LOG_INFO(NULL, "Multi page image will be written as animation with 1 second frame duration."); - - Animation animation; - animation.frames = img_vec; - - for (size_t i = 0; i < animation.frames.size(); i++) - { - animation.durations.push_back(1000); - } - return writeanimation(animation, params); -} - bool PngEncoder::writeanimation(const Animation& animation, const std::vector& params) { int compression_level = 6; @@ -1433,6 +1456,8 @@ bool PngEncoder::writeanimation(const Animation& animation, const std::vector 0 && !getRect(width, height, frames.back().getPixels(), apngFrame.getPixels(), over1.data(), bpp, rowbytes, 0, 0, 0, 3)) diff --git a/modules/imgcodecs/src/grfmt_png.hpp b/modules/imgcodecs/src/grfmt_png.hpp index 4ba22d69d7..fa2642b8f3 100644 --- a/modules/imgcodecs/src/grfmt_png.hpp +++ b/modules/imgcodecs/src/grfmt_png.hpp @@ -136,7 +136,7 @@ protected: static void row_fn(png_structp png_ptr, png_bytep new_row, png_uint_32 row_num, int pass); bool processing_start(void* frame_ptr, const Mat& img); bool processing_finish(); - void compose_frame(std::vector& rows_dst, const std::vector& rows_src, unsigned char bop, uint32_t x, uint32_t y, uint32_t w, uint32_t h, int channels); + void compose_frame(std::vector& rows_dst, const std::vector& rows_src, unsigned char bop, uint32_t x, uint32_t y, uint32_t w, uint32_t h, Mat& img); size_t read_from_io(void* _Buffer, size_t _ElementSize, size_t _ElementCount); uint32_t read_chunk(Chunk& chunk); @@ -176,7 +176,6 @@ public: bool isFormatSupported( int depth ) const CV_OVERRIDE; bool write( const Mat& img, const std::vector& params ) CV_OVERRIDE; - bool writemulti(const std::vector& img_vec, const std::vector& params) CV_OVERRIDE; bool writeanimation(const Animation& animinfo, const std::vector& params) CV_OVERRIDE; ImageEncoder newEncoder() const CV_OVERRIDE; diff --git a/modules/imgcodecs/src/grfmt_webp.cpp b/modules/imgcodecs/src/grfmt_webp.cpp index a0597068ac..2d55995789 100644 --- a/modules/imgcodecs/src/grfmt_webp.cpp +++ b/modules/imgcodecs/src/grfmt_webp.cpp @@ -392,20 +392,6 @@ bool WebPEncoder::write(const Mat& img, const std::vector& params) return (size > 0) && (bytes_written == size); } -bool WebPEncoder::writemulti(const std::vector& img_vec, const std::vector& params) -{ - CV_LOG_INFO(NULL, "Multi page image will be written as animation with 1 second frame duration."); - - Animation animation; - animation.frames = img_vec; - - for (size_t i = 0; i < animation.frames.size(); i++) - { - animation.durations.push_back(1000); - } - return writeanimation(animation, params); -} - bool WebPEncoder::writeanimation(const Animation& animation, const std::vector& params) { CV_CheckDepthEQ(animation.frames[0].depth(), CV_8U, "WebP codec supports only 8-bit unsigned images"); diff --git a/modules/imgcodecs/src/grfmt_webp.hpp b/modules/imgcodecs/src/grfmt_webp.hpp index 3aba83d4c9..f9ceb5e524 100644 --- a/modules/imgcodecs/src/grfmt_webp.hpp +++ b/modules/imgcodecs/src/grfmt_webp.hpp @@ -90,7 +90,6 @@ public: ~WebPEncoder() CV_OVERRIDE; bool write(const Mat& img, const std::vector& params) CV_OVERRIDE; - bool writemulti(const std::vector& img_vec, const std::vector& params) CV_OVERRIDE; bool writeanimation(const Animation& animation, const std::vector& params) CV_OVERRIDE; ImageEncoder newEncoder() const CV_OVERRIDE; diff --git a/modules/imgcodecs/test/test_animation.cpp b/modules/imgcodecs/test/test_animation.cpp index fcfce4b6ab..e8c42cbcc0 100644 --- a/modules/imgcodecs/test/test_animation.cpp +++ b/modules/imgcodecs/test/test_animation.cpp @@ -99,6 +99,61 @@ static bool fillFrames(Animation& animation, bool hasAlpha, int n = 14) return true; } +#ifdef HAVE_IMGCODEC_GIF + +TEST(Imgcodecs_Gif, imwriteanimation_rgba) +{ + Animation s_animation, l_animation; + EXPECT_TRUE(fillFrames(s_animation, true)); + s_animation.bgcolor = Scalar(0, 0, 0, 0); // TO DO not implemented yet. + + // Create a temporary output filename for saving the animation. + string output = cv::tempfile(".gif"); + + // Write the animation to a .webp file and verify success. + EXPECT_TRUE(imwriteanimation(output, s_animation)); + + // Read the animation back and compare with the original. + EXPECT_TRUE(imreadanimation(output, l_animation)); + + size_t expected_frame_count = s_animation.frames.size(); + + // Verify that the number of frames matches the expected count. + EXPECT_EQ(expected_frame_count, imcount(output)); + EXPECT_EQ(expected_frame_count, l_animation.frames.size()); + + // Check that the background color and loop count match between saved and loaded animations. + EXPECT_EQ(l_animation.bgcolor, s_animation.bgcolor); // written as BGRA order + EXPECT_EQ(l_animation.loop_count, s_animation.loop_count); + + // Verify that the durations of frames match. + for (size_t i = 0; i < l_animation.frames.size() - 1; i++) + EXPECT_EQ(cvRound(s_animation.durations[i] / 10), cvRound(l_animation.durations[i] / 10)); + + EXPECT_TRUE(imreadanimation(output, l_animation, 5, 3)); + EXPECT_EQ(expected_frame_count + 3, l_animation.frames.size()); + EXPECT_EQ(l_animation.frames.size(), l_animation.durations.size()); + EXPECT_EQ(0, cvtest::norm(l_animation.frames[5], l_animation.frames[16], NORM_INF)); + EXPECT_EQ(0, cvtest::norm(l_animation.frames[6], l_animation.frames[17], NORM_INF)); + EXPECT_EQ(0, cvtest::norm(l_animation.frames[7], l_animation.frames[18], NORM_INF)); + + // Verify whether the imread function successfully loads the first frame + Mat frame = imread(output, IMREAD_UNCHANGED); + EXPECT_EQ(0, cvtest::norm(l_animation.frames[0], frame, NORM_INF)); + + std::vector buf; + readFileBytes(output, buf); + vector webp_frames; + + EXPECT_TRUE(imdecodemulti(buf, IMREAD_UNCHANGED, webp_frames)); + EXPECT_EQ(expected_frame_count, webp_frames.size()); + + // Clean up by removing the temporary file. + EXPECT_EQ(0, remove(output.c_str())); +} + +#endif // HAVE_IMGCODEC_GIF + #ifdef HAVE_WEBP TEST(Imgcodecs_WebP, imwriteanimation_rgba) @@ -305,6 +360,51 @@ TEST(Imgcodecs_APNG, imwriteanimation_rgba) EXPECT_EQ(0, remove(output.c_str())); } +TEST(Imgcodecs_APNG, imwriteanimation_rgba16u) +{ + Animation s_animation, l_animation; + EXPECT_TRUE(fillFrames(s_animation, true)); + + for (size_t i = 0; i < s_animation.frames.size(); i++) + { + s_animation.frames[i].convertTo(s_animation.frames[i], CV_16U, 255); + } + // Create a temporary output filename for saving the animation. + string output = cv::tempfile(".png"); + + // Write the animation to a .png file and verify success. + EXPECT_TRUE(imwriteanimation(output, s_animation)); + + // Read the animation back and compare with the original. + EXPECT_TRUE(imreadanimation(output, l_animation)); + + size_t expected_frame_count = s_animation.frames.size() - 2; + + // Verify that the number of frames matches the expected count. + EXPECT_EQ(expected_frame_count, imcount(output)); + EXPECT_EQ(expected_frame_count, l_animation.frames.size()); + + std::vector buf; + readFileBytes(output, buf); + vector apng_frames; + + EXPECT_TRUE(imdecodemulti(buf, IMREAD_UNCHANGED, apng_frames)); + EXPECT_EQ(expected_frame_count, apng_frames.size()); + + apng_frames.clear(); + // Test saving the animation frames as individual still images. + EXPECT_TRUE(imwrite(output, s_animation.frames)); + + // Read back the still images into a vector of Mats. + EXPECT_TRUE(imreadmulti(output, apng_frames)); + + // Expect all frames written as multi-page image + EXPECT_EQ(expected_frame_count, apng_frames.size()); + + // Clean up by removing the temporary file. + EXPECT_EQ(0, remove(output.c_str())); +} + TEST(Imgcodecs_APNG, imwriteanimation_rgb) { Animation s_animation, l_animation; From a6f72f813d307f77e7597447bdba25dcb5b6447d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BCdiger=20Ihle?= Date: Fri, 3 Jan 2025 15:53:00 +0100 Subject: [PATCH 077/102] Merge pull request #26698 from warped-rudi:mediandk2 AndroidMediaNdkVideoWriter pixel format enhancement #26698 * videoio(Android): Add source pixel formats RGBA and GRAY to AndroidMediaNdkVideoWriter Let AndroidMediaNdkVideoWriter::write() deduce source pixel format from matrix type: CV_8UC3 -> BGR (as before) CV_8UC4 -> RGBA (use in conjunction with CvCameraViewFrame) CV_8UC1 -> GRAY * samples/android/video-recorder: Send images to VideoWriter in RGBA 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 - [ ] 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/videoio/src/cap_android_mediandk.cpp | 62 ++++++++++++------- .../samples/recorder/RecorderActivity.java | 7 +-- 2 files changed, 41 insertions(+), 28 deletions(-) diff --git a/modules/videoio/src/cap_android_mediandk.cpp b/modules/videoio/src/cap_android_mediandk.cpp index 5242a17d25..5a434572dd 100644 --- a/modules/videoio/src/cap_android_mediandk.cpp +++ b/modules/videoio/src/cap_android_mediandk.cpp @@ -551,10 +551,12 @@ public: } Mat image = image_.getMat(); - if (CV_8UC3 != image.type() || image.cols > width || image.rows > height) { + if (image.cols > width || image.rows > height || + (CV_8UC1 != image.type() && CV_8UC3 != image.type() && CV_8UC4 != image.type())) { LOGE( - "Expected input to be a mat of maximum %d x %d of type CV_8UC3 (%d), but received %d x %d of type: %d", - width, height, CV_8UC3, + "Expected input to be a mat of maximum %d x %d of type CV_8UC1, CV_8UC3 or CV_8UC4" + " (%d, %d, %d), but received %d x %d of type %d", + width, height, CV_8UC1, CV_8UC3, CV_8UC4, image.cols, image.rows, image.type() ); return; @@ -564,33 +566,45 @@ public: ANativeWindow_Buffer buffer; if (0 != ANativeWindow_lock(surface, &buffer, NULL)) { LOGE("Failed to lock the surface"); - } else { - if (AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM == buffer.format) { - Mat bufferMat(image.rows, image.cols, CV_8UC4, buffer.bits, buffer.stride * 4); - cvtColor(image, bufferMat, CV_BGR2RGBA); - } else { - LOGE("Unknow surface buffer format: %u", buffer.format); - } - - ANativeWindow_unlockAndPost(surface); + return; } - #else - LOGV("[write] image: %d x %d", image.cols, image.rows); - //OpenCV don't support RGB to NV12 so we need to connvert to YV12 and then manually changed it to NV12 + if (AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM == buffer.format) { + Mat bufferMat(image.rows, image.cols, CV_8UC4, buffer.bits, buffer.stride * 4); + switch (image.type()) { + case CV_8UC4: image.copyTo(bufferMat); break; + case CV_8UC3: cvtColor(image, bufferMat, CV_BGR2RGBA); break; + case CV_8UC1: cvtColor(image, bufferMat, CV_GRAY2RGBA); break; + } + } else { + LOGE("Unknown surface buffer format: 0x%x", buffer.format); + } + + ANativeWindow_unlockAndPost(surface); + #else + //OpenCV doesn't support RGB to NV12 so we need to convert to YV12 and then manually changed it to NV12 Mat imageYV12; - cvtColor(image, imageYV12, CV_BGR2YUV_YV12); + switch (image.type()) { + case CV_8UC4: cvtColor(image, imageYV12, CV_RGBA2YUV_YV12); break; + case CV_8UC3: cvtColor(image, imageYV12, CV_BGR2YUV_YV12); break; + case CV_8UC1: imageYV12.create(image.rows + image.rows/2, image.cols, CV_8UC1); + image.copyTo(imageYV12.rowRange(0, image.rows)); + imageYV12.rowRange(image.rows, imageYV12.rows) = 128; + break; + } //convert from YV12 to NV12 - size_t yPlaneSize = width * height; - size_t vPlaneSize = yPlaneSize / 4; + if (image.type() != CV_8UC1) { + size_t yPlaneSize = width * height; + size_t vPlaneSize = yPlaneSize / 4; - Mat channels[2] = { - Mat( vPlaneSize, 1, CV_8UC1, imageYV12.ptr() + yPlaneSize + vPlaneSize ).clone(), - Mat( vPlaneSize, 1, CV_8UC1, imageYV12.ptr() + yPlaneSize ).clone() - }; - Mat vuMat( vPlaneSize, 1, CV_8UC2, imageYV12.ptr() + yPlaneSize ); - merge(channels, 2, vuMat); + Mat channels[2] = { + Mat( vPlaneSize, 1, CV_8UC1, imageYV12.ptr() + yPlaneSize + vPlaneSize ).clone(), + Mat( vPlaneSize, 1, CV_8UC1, imageYV12.ptr() + yPlaneSize ).clone() + }; + Mat vuMat( vPlaneSize, 1, CV_8UC2, imageYV12.ptr() + yPlaneSize ); + merge(channels, 2, vuMat); + } writeBytes( imageYV12.ptr(), imageYV12.rows * imageYV12.cols ); #endif diff --git a/samples/android/video-recorder/src/org/opencv/samples/recorder/RecorderActivity.java b/samples/android/video-recorder/src/org/opencv/samples/recorder/RecorderActivity.java index f7f1af77cf..598d52362b 100644 --- a/samples/android/video-recorder/src/org/opencv/samples/recorder/RecorderActivity.java +++ b/samples/android/video-recorder/src/org/opencv/samples/recorder/RecorderActivity.java @@ -164,16 +164,15 @@ public class RecorderActivity extends CameraActivity implements CvCameraViewList { Log.d(TAG, "Camera frame arrived"); - Mat rgbMat = inputFrame.rgba(); + mVideoFrame = inputFrame.rgba(); - Log.d(TAG, "Size: " + rgbMat.width() + "x" + rgbMat.height()); + Log.d(TAG, "Size: " + mVideoFrame.width() + "x" + mVideoFrame.height()); if (mVideoWriter != null && mVideoWriter.isOpened()) { - Imgproc.cvtColor(rgbMat, mVideoFrame, Imgproc.COLOR_RGBA2BGR); mVideoWriter.write(mVideoFrame); } - return rgbMat; + return mVideoFrame; } @Override From 3e534bb7c819392ce9178fef52f9ded57ebd5586 Mon Sep 17 00:00:00 2001 From: MaximSmolskiy Date: Mon, 6 Jan 2025 01:27:06 +0300 Subject: [PATCH 078/102] Fix tests for ellipse fitting --- modules/imgproc/test/test_fitellipse_ams.cpp | 14 +++++++------- modules/imgproc/test/test_fitellipse_direct.cpp | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/modules/imgproc/test/test_fitellipse_ams.cpp b/modules/imgproc/test/test_fitellipse_ams.cpp index caa5df003d..a4c51fae51 100644 --- a/modules/imgproc/test/test_fitellipse_ams.cpp +++ b/modules/imgproc/test/test_fitellipse_ams.cpp @@ -64,7 +64,7 @@ TEST(Imgproc_FitEllipseAMS_Issue_1, accuracy) { for (size_t i=0; i <=3; i++) { Point2f diff = ellipseAMSTrueVertices[i] - ellipseAMSTestVertices[0]; float d = diff.x * diff.x + diff.y * diff.y; - for (size_t j=1; i <=3; i++) { + for (size_t j=1; j <=3; j++) { diff = ellipseAMSTrueVertices[i] - ellipseAMSTestVertices[j]; float dd = diff.x * diff.x + diff.y * diff.y; if(dd Date: Sun, 5 Jan 2025 14:49:02 +0300 Subject: [PATCH 079/102] Fixed some memory leaks in PNG/APNG implementation. --- modules/imgcodecs/src/grfmt_png.cpp | 49 +++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 13 deletions(-) diff --git a/modules/imgcodecs/src/grfmt_png.cpp b/modules/imgcodecs/src/grfmt_png.cpp index 5604ddcf28..b76a4cfaaf 100644 --- a/modules/imgcodecs/src/grfmt_png.cpp +++ b/modules/imgcodecs/src/grfmt_png.cpp @@ -181,8 +181,8 @@ PngDecoder::PngDecoder() { m_signature = "\x89\x50\x4e\x47\xd\xa\x1a\xa"; m_color_type = 0; - m_png_ptr = 0; - m_info_ptr = m_end_info = 0; + m_png_ptr = nullptr; + m_info_ptr = m_end_info = nullptr; m_f = 0; m_buf_supported = true; m_buf_pos = 0; @@ -203,7 +203,7 @@ PngDecoder::~PngDecoder() if( m_f ) { fclose( m_f ); - m_f = 0; + m_f = nullptr; } if( m_png_ptr ) @@ -212,7 +212,7 @@ PngDecoder::~PngDecoder() png_infop info_ptr = (png_infop)m_info_ptr; png_infop end_info = (png_infop)m_end_info; png_destroy_read_struct( &png_ptr, &info_ptr, &end_info ); - m_png_ptr = m_info_ptr = m_end_info = 0; + m_png_ptr = m_info_ptr = m_end_info = nullptr; } } @@ -241,15 +241,14 @@ bool PngDecoder::readHeader() volatile bool result = false; png_structp png_ptr = png_create_read_struct( PNG_LIBPNG_VER_STRING, 0, 0, 0 ); + png_infop info_ptr = nullptr; + png_infop end_info = nullptr; if( png_ptr ) { - png_infop info_ptr = png_create_info_struct( png_ptr ); - png_infop end_info = png_create_info_struct( png_ptr ); + info_ptr = png_create_info_struct( png_ptr ); + end_info = png_create_info_struct( png_ptr ); - m_png_ptr = png_ptr; - m_info_ptr = info_ptr; - m_end_info = end_info; m_buf_pos = 0; if( info_ptr && end_info ) @@ -266,7 +265,10 @@ bool PngDecoder::readHeader() { m_f = fopen(m_filename.c_str(), "rb"); if (!m_f) + { + png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); return false; + } png_init_io(png_ptr, m_f); if (fread(sig, 1, 8, m_f)) @@ -280,7 +282,10 @@ bool PngDecoder::readHeader() } if (!(id == id_IHDR && m_chunkIHDR.p.size() == 25)) + { + png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); return false; + } while (true) { @@ -288,7 +293,10 @@ bool PngDecoder::readHeader() id = read_chunk(chunk); if ((m_f && feof(m_f)) || (!m_buf.empty() && m_buf_pos > m_buf.total())) + { + png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); return false; + } if (id == id_IDAT) { @@ -381,6 +389,17 @@ bool PngDecoder::readHeader() } } + if(result) + { + m_png_ptr = png_ptr; + m_info_ptr = info_ptr; + m_end_info = end_info; + } + else + { + png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); + } + return result; } @@ -762,19 +781,23 @@ bool PngDecoder::processing_finish() png_structp png_ptr = (png_structp)m_png_ptr; png_infop info_ptr = (png_infop)m_info_ptr; + png_infop end_info = (png_infop)m_end_info; - if (!png_ptr || !info_ptr) + if (!png_ptr) return false; if (setjmp(png_jmpbuf(png_ptr))) { - png_destroy_read_struct(&png_ptr, &info_ptr, 0); + png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); return false; } png_process_data(png_ptr, info_ptr, footer, 12); - png_destroy_read_struct(&png_ptr, &info_ptr, 0); - m_png_ptr = 0; + png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); + m_png_ptr = nullptr; + m_info_ptr = nullptr; + m_end_info = nullptr; + return true; } From 2aee94752a7f8d023507203ec5c6b3a64edf2e7b Mon Sep 17 00:00:00 2001 From: Suleyman TURKMEN Date: Mon, 6 Jan 2025 14:25:08 +0300 Subject: [PATCH 080/102] Merge pull request #26714 from sturkmen72:png Fix for png durations and memory leak #26714 ### 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 --- modules/imgcodecs/src/grfmt_png.cpp | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/modules/imgcodecs/src/grfmt_png.cpp b/modules/imgcodecs/src/grfmt_png.cpp index b76a4cfaaf..c04fd2bf55 100644 --- a/modules/imgcodecs/src/grfmt_png.cpp +++ b/modules/imgcodecs/src/grfmt_png.cpp @@ -423,7 +423,6 @@ bool PngDecoder::readData( Mat& img ) fseek(m_f, -8, SEEK_CUR); else m_buf_pos -= 8; - } else m_mat_next.copyTo(mat_cur); @@ -456,9 +455,9 @@ bool PngDecoder::readData( Mat& img ) memcpy(frameNext.getPixels(), frameCur.getPixels(), imagesize); compose_frame(frameCur.getRows(), frameRaw.getRows(), bop, x0, y0, w0, h0, mat_cur); - if (delay_den < 1000) - delay_num = cvRound(1000.0 / delay_den); - m_animation.durations.push_back(delay_num); + if (!delay_den) + delay_den = 100; + m_animation.durations.push_back(cvRound(1000.*delay_num/delay_den)); if (mat_cur.channels() == img.channels()) mat_cur.copyTo(img); @@ -514,9 +513,9 @@ bool PngDecoder::readData( Mat& img ) if (processing_finish()) { compose_frame(frameCur.getRows(), frameRaw.getRows(), bop, x0, y0, w0, h0, mat_cur); - if (delay_den < 1000) - delay_num = cvRound(1000.0 / delay_den); - m_animation.durations.push_back(delay_num); + if (!delay_den) + delay_den = 100; + m_animation.durations.push_back(cvRound(1000.*delay_num/delay_den)); if (mat_cur.channels() == img.channels()) mat_cur.copyTo(img); @@ -736,6 +735,15 @@ bool PngDecoder::processing_start(void* frame_ptr, const Mat& img) { static uint8_t header[8] = { 137, 80, 78, 71, 13, 10, 26, 10 }; + if (m_png_ptr) + { + png_structp png_ptr = (png_structp)m_png_ptr; + png_infop info_ptr = (png_infop)m_info_ptr; + png_infop end_info = (png_infop)m_end_info; + png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); + m_png_ptr = m_info_ptr = m_end_info = nullptr; + } + png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); png_infop info_ptr = png_create_info_struct(png_ptr); From fc994a6ae867befe08ac037cadbacc9c2f1dac1b Mon Sep 17 00:00:00 2001 From: Masahiro Ogawa Date: Mon, 6 Jan 2025 21:35:35 +0900 Subject: [PATCH 081/102] Merge pull request #21407 from sensyn-robotics:feature/weighted_hough Feature: weighted Hough Transform #21407 ### 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 other license that is incompatible with OpenCV - [x] The PR is proposed to proper branch - [x] There is reference to 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 --- .../imgtrans/hough_lines/hough_lines.markdown | 8 ++- modules/imgproc/include/opencv2/imgproc.hpp | 4 +- modules/imgproc/src/hough.cpp | 42 +++++++---- modules/imgproc/test/test_houghlines.cpp | 47 +++++++++++++ .../ImgTrans/HoughLines_Demo.cpp | 69 ++++++++++++++++--- 5 files changed, 143 insertions(+), 27 deletions(-) diff --git a/doc/tutorials/imgproc/imgtrans/hough_lines/hough_lines.markdown b/doc/tutorials/imgproc/imgtrans/hough_lines/hough_lines.markdown index ca4d9b9a17..12d23db994 100644 --- a/doc/tutorials/imgproc/imgtrans/hough_lines/hough_lines.markdown +++ b/doc/tutorials/imgproc/imgtrans/hough_lines/hough_lines.markdown @@ -83,7 +83,7 @@ Arranging the terms: \f$r = x \cos \theta + y \sin \theta\f$ ### Standard and Probabilistic Hough Line Transform -OpenCV implements two kind of Hough Line Transforms: +OpenCV implements three kind of Hough Line Transforms: a. **The Standard Hough Transform** @@ -97,6 +97,12 @@ b. **The Probabilistic Hough Line Transform** of the detected lines \f$(x_{0}, y_{0}, x_{1}, y_{1})\f$ - In OpenCV it is implemented with the function **HoughLinesP()** +c. **The Weighted Hough Transform** + +- Uses edge intensity instead binary 0 or 1 values in standard Hough transform. +- In OpenCV it is implemented with the function **HoughLines()** with use_edgeval=true. +- See the example in samples/cpp/tutorial_code/ImgTrans/HoughLines_Demo.cpp. + ### What does this program do? - Loads an image - Applies a *Standard Hough Line Transform* and a *Probabilistic Line Transform*. diff --git a/modules/imgproc/include/opencv2/imgproc.hpp b/modules/imgproc/include/opencv2/imgproc.hpp index bb0cab7426..1010d1d256 100644 --- a/modules/imgproc/include/opencv2/imgproc.hpp +++ b/modules/imgproc/include/opencv2/imgproc.hpp @@ -2165,11 +2165,13 @@ Must fall between 0 and max_theta. @param max_theta For standard and multi-scale Hough transform, an upper bound for the angle. Must fall between min_theta and CV_PI. The actual maximum angle in the accumulator may be slightly less than max_theta, depending on the parameters min_theta and theta. +@param use_edgeval True if you want to use weighted Hough transform. */ CV_EXPORTS_W void HoughLines( InputArray image, OutputArray lines, double rho, double theta, int threshold, double srn = 0, double stn = 0, - double min_theta = 0, double max_theta = CV_PI ); + double min_theta = 0, double max_theta = CV_PI, + bool use_edgeval = false ); /** @brief Finds line segments in a binary image using the probabilistic Hough transform. diff --git a/modules/imgproc/src/hough.cpp b/modules/imgproc/src/hough.cpp index 523e4491c5..1c52689da5 100644 --- a/modules/imgproc/src/hough.cpp +++ b/modules/imgproc/src/hough.cpp @@ -120,7 +120,7 @@ static void HoughLinesStandard( InputArray src, OutputArray lines, int type, float rho, float theta, int threshold, int linesMax, - double min_theta, double max_theta ) + double min_theta, double max_theta, bool use_edgeval = false ) { CV_CheckType(type, type == CV_32FC2 || type == CV_32FC3, "Internal error"); @@ -184,17 +184,31 @@ HoughLinesStandard( InputArray src, OutputArray lines, int type, irho, tabSin, tabCos); // stage 1. fill accumulator - for( i = 0; i < height; i++ ) - for( j = 0; j < width; j++ ) - { - if( image[i * step + j] != 0 ) - for(int n = 0; n < numangle; n++ ) - { - int r = cvRound( j * tabCos[n] + i * tabSin[n] ); - r += (numrho - 1) / 2; - accum[(n+1) * (numrho+2) + r+1]++; - } - } + if (use_edgeval) { + for( i = 0; i < height; i++ ) + for( j = 0; j < width; j++ ) + { + if( image[i * step + j] != 0 ) + for(int n = 0; n < numangle; n++ ) + { + int r = cvRound( j * tabCos[n] + i * tabSin[n] ); + r += (numrho - 1) / 2; + accum[(n + 1) * (numrho + 2) + r + 1] += image[i * step + j]; + } + } + } else { + for( i = 0; i < height; i++ ) + for( j = 0; j < width; j++ ) + { + if( image[i * step + j] != 0 ) + for(int n = 0; n < numangle; n++ ) + { + int r = cvRound( j * tabCos[n] + i * tabSin[n] ); + r += (numrho - 1) / 2; + accum[(n + 1) * (numrho + 2) + r + 1]++; + } + } + } // stage 2. find local maximums findLocalMaximums( numrho, numangle, threshold, accum, _sort_buf ); @@ -907,7 +921,7 @@ static bool ocl_HoughLinesP(InputArray _src, OutputArray _lines, double rho, dou void HoughLines( InputArray _image, OutputArray lines, double rho, double theta, int threshold, - double srn, double stn, double min_theta, double max_theta ) + double srn, double stn, double min_theta, double max_theta, bool use_edgeval ) { CV_INSTRUMENT_REGION(); @@ -922,7 +936,7 @@ void HoughLines( InputArray _image, OutputArray lines, ocl_HoughLines(_image, lines, rho, theta, threshold, min_theta, max_theta)); if( srn == 0 && stn == 0 ) - HoughLinesStandard(_image, lines, type, (float)rho, (float)theta, threshold, INT_MAX, min_theta, max_theta ); + HoughLinesStandard(_image, lines, type, (float)rho, (float)theta, threshold, INT_MAX, min_theta, max_theta, use_edgeval ); else HoughLinesSDiv(_image, lines, type, (float)rho, (float)theta, threshold, cvRound(srn), cvRound(stn), INT_MAX, min_theta, max_theta); } diff --git a/modules/imgproc/test/test_houghlines.cpp b/modules/imgproc/test/test_houghlines.cpp index 2d784d7a7a..02eb2d4379 100644 --- a/modules/imgproc/test/test_houghlines.cpp +++ b/modules/imgproc/test/test_houghlines.cpp @@ -340,6 +340,53 @@ TEST(HoughLines, regression_21983) EXPECT_NEAR(lines[0][1], 1.57179642, 1e-4); } +TEST(WeightedHoughLines, horizontal) +{ + Mat img(25, 25, CV_8UC1, Scalar(0)); + // draw lines. from top to bottom, stronger to weaker. + line(img, Point(0, 6), Point(25, 6), Scalar(240)); + line(img, Point(0, 12), Point(25, 12), Scalar(255)); + line(img, Point(0, 18), Point(25, 18), Scalar(220)); + + // detect lines + std::vector lines; + int threshold{220*25-1}; + bool use_edgeval{true}; + HoughLines(img, lines, 1, CV_PI/180, threshold, 0, 0, 0.0, CV_PI, use_edgeval); + + // check results + ASSERT_EQ(3U, lines.size()); + // detected lines is assumed sorted from stronger to weaker. + EXPECT_EQ(12, lines[0][0]); + EXPECT_EQ(6, lines[1][0]); + EXPECT_EQ(18, lines[2][0]); + EXPECT_NEAR(CV_PI/2, lines[0][1], CV_PI/180 + 1e-6); + EXPECT_NEAR(CV_PI/2, lines[1][1], CV_PI/180 + 1e-6); + EXPECT_NEAR(CV_PI/2, lines[2][1], CV_PI/180 + 1e-6); +} + +TEST(WeightedHoughLines, diagonal) +{ + Mat img(25, 25, CV_8UC1, Scalar(0)); + // draw lines. + line(img, Point(0, 0), Point(25, 25), Scalar(128)); + line(img, Point(0, 25), Point(25, 0), Scalar(255)); + + // detect lines + std::vector lines; + int threshold{128*25-1}; + bool use_edgeval{true}; + HoughLines(img, lines, 1, CV_PI/180, threshold, 0, 0, 0.0, CV_PI, use_edgeval); + + // check results + ASSERT_EQ(2U, lines.size()); + // detected lines is assumed sorted from stronger to weaker. + EXPECT_EQ(18, lines[0][0]); // 25*sqrt(2)/2 = 17.67 ~ 18 + EXPECT_EQ(0, lines[1][0]); + EXPECT_NEAR(CV_PI/4, lines[0][1], CV_PI/180 + 1e-6); + EXPECT_NEAR(CV_PI*3/4, lines[1][1], CV_PI/180 + 1e-6); +} + INSTANTIATE_TEST_CASE_P( ImgProc, StandartHoughLinesTest, testing::Combine(testing::Values( "shared/pic5.png", "../stitching/a1.png" ), testing::Values( 1, 10 ), testing::Values( 0.05, 0.1 ), diff --git a/samples/cpp/tutorial_code/ImgTrans/HoughLines_Demo.cpp b/samples/cpp/tutorial_code/ImgTrans/HoughLines_Demo.cpp index 5d1736a77a..91d4266c5a 100644 --- a/samples/cpp/tutorial_code/ImgTrans/HoughLines_Demo.cpp +++ b/samples/cpp/tutorial_code/ImgTrans/HoughLines_Demo.cpp @@ -15,22 +15,27 @@ using namespace std; /// Global variables /** General variables */ -Mat src, edges; +Mat src, canny_edge, sobel_edge; Mat src_gray; -Mat standard_hough, probabilistic_hough; +Mat standard_hough, probabilistic_hough, weighted_hough; int min_threshold = 50; int max_trackbar = 150; +int weightedhough_max_trackbar = 100000; const char* standard_name = "Standard Hough Lines Demo"; const char* probabilistic_name = "Probabilistic Hough Lines Demo"; +const char* weighted_name = "Weighted Hough Lines Demo"; int s_trackbar = max_trackbar; int p_trackbar = max_trackbar; +int e_trackbar = 60; +int w_trackbar = 60000; /// Function Headers void help(); void Standard_Hough( int, void* ); void Probabilistic_Hough( int, void* ); +void Weighted_Hough( int, void* ); /** * @function main @@ -53,22 +58,29 @@ int main( int argc, char** argv ) /// Pass the image to gray cvtColor( src, src_gray, COLOR_RGB2GRAY ); - /// Apply Canny edge detector - Canny( src_gray, edges, 50, 200, 3 ); + /// Apply Canny/Sobel edge detector + Canny( src_gray, canny_edge, 50, 200, 3 ); + Sobel( src_gray, sobel_edge, CV_16S, 1, 0 ); // dx(order of the derivative x)=1,dy=0 /// Create Trackbars for Thresholds char thresh_label[50]; snprintf( thresh_label, sizeof(thresh_label), "Thres: %d + input", min_threshold ); - namedWindow( standard_name, WINDOW_AUTOSIZE ); - createTrackbar( thresh_label, standard_name, &s_trackbar, max_trackbar, Standard_Hough); + createTrackbar( thresh_label, standard_name, &s_trackbar, max_trackbar, Standard_Hough ); namedWindow( probabilistic_name, WINDOW_AUTOSIZE ); - createTrackbar( thresh_label, probabilistic_name, &p_trackbar, max_trackbar, Probabilistic_Hough); + createTrackbar( thresh_label, probabilistic_name, &p_trackbar, max_trackbar, Probabilistic_Hough ); + + char edge_thresh_label[50]; + sprintf( edge_thresh_label, "Edge Thres: input" ); + namedWindow( weighted_name, WINDOW_AUTOSIZE); + createTrackbar( edge_thresh_label, weighted_name, &e_trackbar, max_trackbar, Weighted_Hough); + createTrackbar( thresh_label, weighted_name, &w_trackbar, weightedhough_max_trackbar, Weighted_Hough); /// Initialize Standard_Hough(0, 0); Probabilistic_Hough(0, 0); + Weighted_Hough(0, 0); waitKey(0); return 0; } @@ -90,10 +102,10 @@ void help() void Standard_Hough( int, void* ) { vector s_lines; - cvtColor( edges, standard_hough, COLOR_GRAY2BGR ); + cvtColor( canny_edge, standard_hough, COLOR_GRAY2BGR ); /// 1. Use Standard Hough Transform - HoughLines( edges, s_lines, 1, CV_PI/180, min_threshold + s_trackbar, 0, 0 ); + HoughLines( canny_edge, s_lines, 1, CV_PI/180, min_threshold + s_trackbar, 0, 0 ); /// Show the result for( size_t i = 0; i < s_lines.size(); i++ ) @@ -117,10 +129,10 @@ void Standard_Hough( int, void* ) void Probabilistic_Hough( int, void* ) { vector p_lines; - cvtColor( edges, probabilistic_hough, COLOR_GRAY2BGR ); + cvtColor( canny_edge, probabilistic_hough, COLOR_GRAY2BGR ); /// 2. Use Probabilistic Hough Transform - HoughLinesP( edges, p_lines, 1, CV_PI/180, min_threshold + p_trackbar, 30, 10 ); + HoughLinesP( canny_edge, p_lines, 1, CV_PI/180, min_threshold + p_trackbar, 30, 10 ); /// Show the result for( size_t i = 0; i < p_lines.size(); i++ ) @@ -131,3 +143,38 @@ void Probabilistic_Hough( int, void* ) imshow( probabilistic_name, probabilistic_hough ); } + +/** + * @function Weighted_Hough + * This can detect lines based on the edge intensities. + */ +void Weighted_Hough( int, void* ) +{ + vector s_lines; + + /// prepare + Mat edge_img; + convertScaleAbs(sobel_edge, edge_img ); + // use same threshold for edge with Hough. + threshold( edge_img, edge_img, e_trackbar, 255, cv::THRESH_TOZERO); + cvtColor( edge_img, weighted_hough, COLOR_GRAY2BGR ); + + /// 3. Use Weighted Hough Transform + const bool use_edgeval{true}; + HoughLines( edge_img, s_lines, 1, CV_PI/180, min_threshold + w_trackbar, 0, 0, 0, CV_PI, use_edgeval); + + /// Show the result + for( size_t i = 0; i < s_lines.size(); i++ ) + { + float r = s_lines[i][0], t = s_lines[i][1]; + double cos_t = cos(t), sin_t = sin(t); + double x0 = r*cos_t, y0 = r*sin_t; + double alpha = 1000; + + Point pt1( cvRound(x0 + alpha*(-sin_t)), cvRound(y0 + alpha*cos_t) ); + Point pt2( cvRound(x0 - alpha*(-sin_t)), cvRound(y0 - alpha*cos_t) ); + line( weighted_hough, pt1, pt2, Scalar(255,0,0), 3, LINE_AA ); + } + + imshow( weighted_name, weighted_hough ); +} From 56dd9d51b15bc1b111719de101948ef63cba8c93 Mon Sep 17 00:00:00 2001 From: MaximSmolskiy Date: Mon, 6 Jan 2025 17:13:32 +0300 Subject: [PATCH 082/102] Remove code duplication from tests for ellipse fitting --- modules/imgproc/test/test_fitellipse_ams.cpp | 152 +++--------------- .../imgproc/test/test_fitellipse_direct.cpp | 152 +++--------------- 2 files changed, 52 insertions(+), 252 deletions(-) diff --git a/modules/imgproc/test/test_fitellipse_ams.cpp b/modules/imgproc/test/test_fitellipse_ams.cpp index a4c51fae51..92e7f2e7a2 100644 --- a/modules/imgproc/test/test_fitellipse_ams.cpp +++ b/modules/imgproc/test/test_fitellipse_ams.cpp @@ -8,6 +8,25 @@ namespace opencv_test { namespace { +static bool checkEllipse(const RotatedRect& ellipseAMSTest, const RotatedRect& ellipseAMSTrue, const float tol) { + Point2f ellipseAMSTrueVertices[4]; + Point2f ellipseAMSTestVertices[4]; + ellipseAMSTest.points(ellipseAMSTestVertices); + ellipseAMSTrue.points(ellipseAMSTrueVertices); + float AMSDiff = 0.0f; + for (size_t i=0; i <=3; i++) { + Point2f diff = ellipseAMSTrueVertices[i] - ellipseAMSTestVertices[0]; + float d = diff.x * diff.x + diff.y * diff.y; + for (size_t j=1; j <=3; j++) { + diff = ellipseAMSTrueVertices[i] - ellipseAMSTestVertices[j]; + float dd = diff.x * diff.x + diff.y * diff.y; + if(ddpts; pts.push_back(Point2f(173.41854895999165f, 125.84473135880411f)); @@ -51,29 +70,12 @@ TEST(Imgproc_FitEllipseAMS_Issue_1, accuracy) { pts.push_back(Point2f(6.719616410428614f, 50.15263031354927f)); pts.push_back(Point2f(5.122267598477748f, 46.03603214691343f)); - bool AMSGoodQ; float tol = 0.01f; RotatedRect ellipseAMSTrue = cv::RotatedRect(Point2f(94.4037f, 84.743f), Size2f(190.614f, 153.543f), 19.832f); RotatedRect ellipseAMSTest = fitEllipseAMS(pts); - Point2f ellipseAMSTrueVertices[4]; - Point2f ellipseAMSTestVertices[4]; - ellipseAMSTest.points(ellipseAMSTestVertices); - ellipseAMSTrue.points(ellipseAMSTrueVertices); - float AMSDiff = 0.0f; - for (size_t i=0; i <=3; i++) { - Point2f diff = ellipseAMSTrueVertices[i] - ellipseAMSTestVertices[0]; - float d = diff.x * diff.x + diff.y * diff.y; - for (size_t j=1; j <=3; j++) { - diff = ellipseAMSTrueVertices[i] - ellipseAMSTestVertices[j]; - float dd = diff.x * diff.x + diff.y * diff.y; - if(ddpts; pts.push_back(Point2f(173.41854895999165f, 125.84473135880411f)); @@ -51,29 +70,12 @@ TEST(Imgproc_FitEllipseDirect_Issue_1, accuracy) { pts.push_back(Point2f(6.719616410428614f, 50.15263031354927f)); pts.push_back(Point2f(5.122267598477748f, 46.03603214691343f)); - bool directGoodQ; float tol = 0.01f; RotatedRect ellipseDirectTrue = cv::RotatedRect(Point2f(91.3256f, 90.4668f),Size2f(187.211f, 140.031f), 21.5808f); RotatedRect ellipseDirectTest = fitEllipseDirect(pts); - Point2f ellipseDirectTrueVertices[4]; - Point2f ellipseDirectTestVertices[4]; - ellipseDirectTest.points(ellipseDirectTestVertices); - ellipseDirectTrue.points(ellipseDirectTrueVertices); - float directDiff = 0.0f; - for (size_t i=0; i <=3; i++) { - Point2f diff = ellipseDirectTrueVertices[i] - ellipseDirectTestVertices[0]; - float d = diff.x * diff.x + diff.y * diff.y; - for (size_t j=1; j <=3; j++) { - diff = ellipseDirectTrueVertices[i] - ellipseDirectTestVertices[j]; - float dd = diff.x * diff.x + diff.y * diff.y; - if(dd Date: Mon, 6 Jan 2025 15:19:58 +0100 Subject: [PATCH 083/102] Use RAII to avoid leaks in PNG reader. --- modules/imgcodecs/src/grfmt_png.cpp | 308 ++++++++++++---------------- modules/imgcodecs/src/grfmt_png.hpp | 43 +++- 2 files changed, 175 insertions(+), 176 deletions(-) diff --git a/modules/imgcodecs/src/grfmt_png.cpp b/modules/imgcodecs/src/grfmt_png.cpp index c04fd2bf55..a325b25006 100644 --- a/modules/imgcodecs/src/grfmt_png.cpp +++ b/modules/imgcodecs/src/grfmt_png.cpp @@ -181,8 +181,6 @@ PngDecoder::PngDecoder() { m_signature = "\x89\x50\x4e\x47\xd\xa\x1a\xa"; m_color_type = 0; - m_png_ptr = nullptr; - m_info_ptr = m_end_info = nullptr; m_f = 0; m_buf_supported = true; m_buf_pos = 0; @@ -205,15 +203,6 @@ PngDecoder::~PngDecoder() fclose( m_f ); m_f = nullptr; } - - if( m_png_ptr ) - { - png_structp png_ptr = (png_structp)m_png_ptr; - png_infop info_ptr = (png_infop)m_info_ptr; - png_infop end_info = (png_infop)m_end_info; - png_destroy_read_struct( &png_ptr, &info_ptr, &end_info ); - m_png_ptr = m_info_ptr = m_end_info = nullptr; - } } ImageDecoder PngDecoder::newDecoder() const @@ -240,164 +229,149 @@ bool PngDecoder::readHeader() { volatile bool result = false; - png_structp png_ptr = png_create_read_struct( PNG_LIBPNG_VER_STRING, 0, 0, 0 ); - png_infop info_ptr = nullptr; - png_infop end_info = nullptr; + PngPtrs png_ptrs; + png_structp png_ptr = png_ptrs.getPng(); + png_infop info_ptr = png_ptrs.getInfo(); + png_infop end_info = png_ptrs.getEndInfo(); - if( png_ptr ) + if( png_ptr && info_ptr && end_info ) { - info_ptr = png_create_info_struct( png_ptr ); - end_info = png_create_info_struct( png_ptr ); - m_buf_pos = 0; - - if( info_ptr && end_info ) + if( setjmp( png_jmpbuf( png_ptr ) ) == 0 ) { - if( setjmp( png_jmpbuf( png_ptr ) ) == 0 ) + unsigned char sig[8]; + uint32_t id = 0; + Chunk chunk; + + if( !m_buf.empty() ) + png_set_read_fn(png_ptr, this, (png_rw_ptr)readDataFromBuf ); + else { - unsigned char sig[8]; - uint32_t id = 0; - Chunk chunk; - - if( !m_buf.empty() ) - png_set_read_fn(png_ptr, this, (png_rw_ptr)readDataFromBuf ); - else + m_f = fopen(m_filename.c_str(), "rb"); + if (!m_f) { - m_f = fopen(m_filename.c_str(), "rb"); - if (!m_f) - { - png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); - return false; - } - png_init_io(png_ptr, m_f); - - if (fread(sig, 1, 8, m_f)) - id = read_chunk(m_chunkIHDR); + return false; } + png_init_io(png_ptr, m_f); - if (id != id_IHDR) - { - read_from_io(&sig, 8, 1); + if (fread(sig, 1, 8, m_f)) id = read_chunk(m_chunkIHDR); - } + } - if (!(id == id_IHDR && m_chunkIHDR.p.size() == 25)) + if (id != id_IHDR) + { + read_from_io(&sig, 8, 1); + id = read_chunk(m_chunkIHDR); + } + + if (!(id == id_IHDR && m_chunkIHDR.p.size() == 25)) + { + return false; + } + + while (true) + { + m_is_fcTL_loaded = false; + id = read_chunk(chunk); + + if ((m_f && feof(m_f)) || (!m_buf.empty() && m_buf_pos > m_buf.total())) { - png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); return false; } - while (true) + if (id == id_IDAT) { - m_is_fcTL_loaded = false; - id = read_chunk(chunk); - - if ((m_f && feof(m_f)) || (!m_buf.empty() && m_buf_pos > m_buf.total())) - { - png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); - return false; - } - - if (id == id_IDAT) - { - if (m_f) - fseek(m_f, 0, SEEK_SET); - else - m_buf_pos = 0; - break; - } - - if (id == id_acTL && chunk.p.size() == 20) - { - m_animation.loop_count = png_get_uint_32(&chunk.p[12]); - - if (chunk.p[8] > 0) - { - chunk.p[8] = 0; - chunk.p[9] = 0; - m_frame_count = png_get_uint_32(&chunk.p[8]); - m_frame_count++; - } - else - m_frame_count = png_get_uint_32(&chunk.p[8]); - } - - if (id == id_fcTL) - { - m_is_fcTL_loaded = true; - w0 = png_get_uint_32(&chunk.p[12]); - h0 = png_get_uint_32(&chunk.p[16]); - x0 = png_get_uint_32(&chunk.p[20]); - y0 = png_get_uint_32(&chunk.p[24]); - delay_num = png_get_uint_16(&chunk.p[28]); - delay_den = png_get_uint_16(&chunk.p[30]); - dop = chunk.p[32]; - bop = chunk.p[33]; - } - - if (id == id_bKGD) - { - int bgcolor = png_get_uint_32(&chunk.p[8]); - m_animation.bgcolor[3] = (bgcolor >> 24) & 0xFF; - m_animation.bgcolor[2] = (bgcolor >> 16) & 0xFF; - m_animation.bgcolor[1] = (bgcolor >> 8) & 0xFF; - m_animation.bgcolor[0] = bgcolor & 0xFF; - } - - if (id == id_PLTE || id == id_tRNS) - m_chunksInfo.push_back(chunk); + if (m_f) + fseek(m_f, 0, SEEK_SET); + else + m_buf_pos = 0; + break; } - png_uint_32 wdth, hght; - int bit_depth, color_type, num_trans=0; - png_bytep trans; - png_color_16p trans_values; - - png_read_info( png_ptr, info_ptr ); - png_get_IHDR(png_ptr, info_ptr, &wdth, &hght, - &bit_depth, &color_type, 0, 0, 0); - - m_width = (int)wdth; - m_height = (int)hght; - m_color_type = color_type; - m_bit_depth = bit_depth; - - if (bit_depth <= 8 || bit_depth == 16) + if (id == id_acTL && chunk.p.size() == 20) { - switch (color_type) + m_animation.loop_count = png_get_uint_32(&chunk.p[12]); + + if (chunk.p[8] > 0) { - case PNG_COLOR_TYPE_RGB: - case PNG_COLOR_TYPE_PALETTE: - png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, &trans_values); - if (num_trans > 0) - m_type = CV_8UC4; - else - m_type = CV_8UC3; - break; - case PNG_COLOR_TYPE_GRAY_ALPHA: - case PNG_COLOR_TYPE_RGB_ALPHA: + chunk.p[8] = 0; + chunk.p[9] = 0; + m_frame_count = png_get_uint_32(&chunk.p[8]); + m_frame_count++; + } + else + m_frame_count = png_get_uint_32(&chunk.p[8]); + } + + if (id == id_fcTL) + { + m_is_fcTL_loaded = true; + w0 = png_get_uint_32(&chunk.p[12]); + h0 = png_get_uint_32(&chunk.p[16]); + x0 = png_get_uint_32(&chunk.p[20]); + y0 = png_get_uint_32(&chunk.p[24]); + delay_num = png_get_uint_16(&chunk.p[28]); + delay_den = png_get_uint_16(&chunk.p[30]); + dop = chunk.p[32]; + bop = chunk.p[33]; + } + + if (id == id_bKGD) + { + int bgcolor = png_get_uint_32(&chunk.p[8]); + m_animation.bgcolor[3] = (bgcolor >> 24) & 0xFF; + m_animation.bgcolor[2] = (bgcolor >> 16) & 0xFF; + m_animation.bgcolor[1] = (bgcolor >> 8) & 0xFF; + m_animation.bgcolor[0] = bgcolor & 0xFF; + } + + if (id == id_PLTE || id == id_tRNS) + m_chunksInfo.push_back(chunk); + } + + png_uint_32 wdth, hght; + int bit_depth, color_type, num_trans=0; + png_bytep trans; + png_color_16p trans_values; + + png_read_info( png_ptr, info_ptr ); + png_get_IHDR(png_ptr, info_ptr, &wdth, &hght, + &bit_depth, &color_type, 0, 0, 0); + + m_width = (int)wdth; + m_height = (int)hght; + m_color_type = color_type; + m_bit_depth = bit_depth; + + if (bit_depth <= 8 || bit_depth == 16) + { + switch (color_type) + { + case PNG_COLOR_TYPE_RGB: + case PNG_COLOR_TYPE_PALETTE: + png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, &trans_values); + if (num_trans > 0) m_type = CV_8UC4; - break; - default: - m_type = CV_8UC1; - } - if (bit_depth == 16) - m_type = CV_MAKETYPE(CV_16U, CV_MAT_CN(m_type)); - result = true; + else + m_type = CV_8UC3; + break; + case PNG_COLOR_TYPE_GRAY_ALPHA: + case PNG_COLOR_TYPE_RGB_ALPHA: + m_type = CV_8UC4; + break; + default: + m_type = CV_8UC1; } + if (bit_depth == 16) + m_type = CV_MAKETYPE(CV_16U, CV_MAT_CN(m_type)); + result = true; } } } if(result) { - m_png_ptr = png_ptr; - m_info_ptr = info_ptr; - m_end_info = end_info; - } - else - { - png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); + m_png_ptrs = std::move(png_ptrs); } return result; @@ -430,8 +404,8 @@ bool PngDecoder::readData( Mat& img ) frameCur.setMat(mat_cur); processing_start((void*)&frameRaw, mat_cur); - png_structp png_ptr = (png_structp)m_png_ptr; - png_infop info_ptr = (png_infop)m_info_ptr; + png_structp png_ptr = m_png_ptrs.getPng(); + png_infop info_ptr = m_png_ptrs.getInfo(); while (true) { @@ -540,11 +514,11 @@ bool PngDecoder::readData( Mat& img ) unsigned char** buffer = _buffer.data(); bool color = img.channels() > 1; - png_structp png_ptr = (png_structp)m_png_ptr; - png_infop info_ptr = (png_infop)m_info_ptr; - png_infop end_info = (png_infop)m_end_info; + png_structp png_ptr = m_png_ptrs.getPng(); + png_infop info_ptr = m_png_ptrs.getInfo(); + png_infop end_info = m_png_ptrs.getEndInfo(); - if( m_png_ptr && m_info_ptr && m_end_info && m_width && m_height ) + if( png_ptr && info_ptr && end_info && m_width && m_height ) { if( setjmp( png_jmpbuf ( png_ptr ) ) == 0 ) { @@ -735,30 +709,20 @@ bool PngDecoder::processing_start(void* frame_ptr, const Mat& img) { static uint8_t header[8] = { 137, 80, 78, 71, 13, 10, 26, 10 }; - if (m_png_ptr) - { - png_structp png_ptr = (png_structp)m_png_ptr; - png_infop info_ptr = (png_infop)m_info_ptr; - png_infop end_info = (png_infop)m_end_info; - png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); - m_png_ptr = m_info_ptr = m_end_info = nullptr; - } + PngPtrs png_ptrs; + png_structp png_ptr = png_ptrs.getPng(); + png_infop info_ptr = png_ptrs.getInfo(); - png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); - png_infop info_ptr = png_create_info_struct(png_ptr); - - m_png_ptr = png_ptr; - m_info_ptr = info_ptr; - - if (!png_ptr || !info_ptr) + if (!png_ptr || !info_ptr) { return false; + } if (setjmp(png_jmpbuf(png_ptr))) { - png_destroy_read_struct(&png_ptr, &info_ptr, 0); return false; } + m_png_ptrs = std::move(png_ptrs); png_set_crc_action(png_ptr, PNG_CRC_QUIET_USE, PNG_CRC_QUIET_USE); png_set_progressive_read_fn(png_ptr, frame_ptr, (png_progressive_info_ptr)info_fn, row_fn, NULL); @@ -787,24 +751,22 @@ bool PngDecoder::processing_finish() { static uint8_t footer[12] = { 0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130 }; - png_structp png_ptr = (png_structp)m_png_ptr; - png_infop info_ptr = (png_infop)m_info_ptr; - png_infop end_info = (png_infop)m_end_info; + png_structp png_ptr = m_png_ptrs.getPng(); + png_infop info_ptr = m_png_ptrs.getInfo(); - if (!png_ptr) + if (!png_ptr) { + m_png_ptrs.clear(); return false; + } if (setjmp(png_jmpbuf(png_ptr))) { - png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); + m_png_ptrs.clear(); return false; } png_process_data(png_ptr, info_ptr, footer, 12); - png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); - m_png_ptr = nullptr; - m_info_ptr = nullptr; - m_end_info = nullptr; + m_png_ptrs.clear(); return true; } diff --git a/modules/imgcodecs/src/grfmt_png.hpp b/modules/imgcodecs/src/grfmt_png.hpp index fa2642b8f3..a950b9e941 100644 --- a/modules/imgcodecs/src/grfmt_png.hpp +++ b/modules/imgcodecs/src/grfmt_png.hpp @@ -140,10 +140,47 @@ protected: size_t read_from_io(void* _Buffer, size_t _ElementSize, size_t _ElementCount); uint32_t read_chunk(Chunk& chunk); + struct PngPtrs { + public: + PngPtrs() { + png_ptr = png_create_read_struct( PNG_LIBPNG_VER_STRING, 0, 0, 0 ); + if (png_ptr) { + info_ptr = png_create_info_struct( png_ptr ); + end_info = png_create_info_struct( png_ptr ); + } else { + info_ptr = end_info = nullptr; + } + } + ~PngPtrs() { + clear(); + } + PngPtrs& operator=(PngPtrs&& other) { + clear(); + png_ptr = other.png_ptr; + info_ptr = other.info_ptr; + end_info = other.end_info; + other.png_ptr = nullptr; + other.info_ptr = other.end_info = nullptr; + return *this; + } + void clear() { + if (png_ptr) { + png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); + png_ptr = nullptr; + info_ptr = end_info = nullptr; + } + } + png_structp getPng() const { return png_ptr; } + png_infop getInfo() const { return info_ptr; } + png_infop getEndInfo() const { return end_info; } + private: + png_structp png_ptr; // pointer to decompression structure + png_infop info_ptr; // pointer to image information structure + png_infop end_info; // pointer to one more image information structure + }; + + PngPtrs m_png_ptrs; int m_bit_depth; - void* m_png_ptr; // pointer to decompression structure - void* m_info_ptr; // pointer to image information structure - void* m_end_info; // pointer to one more image information structure FILE* m_f; int m_color_type; Chunk m_chunkIHDR; From 9b85ab0a635cf44e86c75dc867dd253f7e5cf5dd Mon Sep 17 00:00:00 2001 From: MaximSmolskiy Date: Mon, 6 Jan 2025 19:14:57 +0300 Subject: [PATCH 084/102] Fix comment for fitEllipse Java case accurracy test --- modules/imgproc/test/test_fitellipse.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/imgproc/test/test_fitellipse.cpp b/modules/imgproc/test/test_fitellipse.cpp index b564db96da..3e6d0478ca 100644 --- a/modules/imgproc/test/test_fitellipse.cpp +++ b/modules/imgproc/test/test_fitellipse.cpp @@ -94,7 +94,7 @@ TEST(Imgproc_FitEllipse_JavaCase, accuracy) { pts.push_back(Point2f(-1, -1)*scale+shift); pts.push_back(Point2f(1, -1)*scale+shift); - // check that we get almost vertical ellipse centered around (1, 3) + // check that we get almost circle centered around (0, 0) RotatedRect e = fitEllipse(pts); EXPECT_NEAR(e.center.x, 0, 0.01); EXPECT_NEAR(e.center.y, 0, 0.01); From 0e3d71b0e0c3db1d5f1dea0caaae394c88aeb8ab Mon Sep 17 00:00:00 2001 From: Vincent Rabaud Date: Tue, 7 Jan 2025 11:49:30 +0100 Subject: [PATCH 085/102] Remove extra /* in /**/ comment --- modules/imgcodecs/src/grfmt_png.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/imgcodecs/src/grfmt_png.cpp b/modules/imgcodecs/src/grfmt_png.cpp index c04fd2bf55..4ecf32cd4c 100644 --- a/modules/imgcodecs/src/grfmt_png.cpp +++ b/modules/imgcodecs/src/grfmt_png.cpp @@ -67,7 +67,7 @@ * https://github.com/apngasm/apngasm - /* APNG Optimizer 1.4 + * APNG Optimizer 1.4 * * Makes APNG files smaller. * From 0331af01ae020f812e226e510e54f857de78c28f Mon Sep 17 00:00:00 2001 From: MaximSmolskiy Date: Tue, 7 Jan 2025 22:24:48 +0300 Subject: [PATCH 086/102] Change article for fitEllipseDirect function --- doc/opencv.bib | 19 +++++-------------- modules/imgproc/include/opencv2/imgproc.hpp | 2 +- samples/cpp/fitellipse.cpp | 2 +- 3 files changed, 7 insertions(+), 16 deletions(-) diff --git a/doc/opencv.bib b/doc/opencv.bib index e3363a181c..f944747a7d 100644 --- a/doc/opencv.bib +++ b/doc/opencv.bib @@ -424,20 +424,11 @@ publisher = {BMVA Press}, url = {https://www.researchgate.net/profile/Robert_Fisher5/publication/2237785_A_Buyer's_Guide_to_Conic_Fitting/links/0fcfd50f59aeded518000000/A-Buyers-Guide-to-Conic-Fitting.pdf} } -@article{fitzgibbon1999, - abstract = {This work presents a new efficient method for fitting ellipses to scattered data. Previous algorithms either fitted general conics or were computationally expensive. By minimizing the algebraic distance subject to the constraint 4ac-b2=1, the new method incorporates the ellipticity constraint into the normalization factor. The proposed method combines several advantages: It is ellipse-specific, so that even bad data will always return an ellipse. It can be solved naturally by a generalized eigensystem. It is extremely robust, efficient, and easy to implement}, - author = {Fitzgibbon, Andrew and Pilu, Maurizio and Fisher, Robert B.}, - doi = {10.1109/34.765658}, - isbn = {0162-8828}, - issn = {01628828}, - journal = {IEEE Transactions on Pattern Analysis and Machine Intelligence}, - number = {5}, - pages = {476--480}, - pmid = {708}, - title = {Direct least square fitting of ellipses}, - volume = {21}, - year = {1999}, - url = {https://pdfs.semanticscholar.org/090d/25f94cb021bdd3400a2f547f989a6a5e07ec.pdf} +@inproceedings{oy1998NumericallySD, + title = {Numerically Stable Direct Least Squares Fitting of Ellipses}, + author = {Radim Hal oy and Jan Flusser}, + year = {1998}, + url = {https://www.semanticscholar.org/paper/Numerically-Stable-Direct-Least-Squares-Fitting-of-oy-Flusser/9a8607575ba9c6016e9f3db5e52f5ed4d14d5dfd} } @article{Gallego2014ACF, title = {A Compact Formula for the Derivative of a 3-D Rotation in Exponential Coordinates}, diff --git a/modules/imgproc/include/opencv2/imgproc.hpp b/modules/imgproc/include/opencv2/imgproc.hpp index bb0cab7426..90b18eebe0 100644 --- a/modules/imgproc/include/opencv2/imgproc.hpp +++ b/modules/imgproc/include/opencv2/imgproc.hpp @@ -4342,7 +4342,7 @@ CV_EXPORTS_W RotatedRect fitEllipseAMS( InputArray points ); The function calculates the ellipse that fits a set of 2D points. It returns the rotated rectangle in which the ellipse is inscribed. - The Direct least square (Direct) method by @cite Fitzgibbon1999 is used. + The Direct least square (Direct) method by @cite oy1998NumericallySD is used. For an ellipse, this basis set is \f$ \chi= \left(x^2, x y, y^2, x, y, 1\right) \f$, which is a set of six free coefficients \f$ A^T=\left\{A_{\text{xx}},A_{\text{xy}},A_{\text{yy}},A_x,A_y,A_0\right\} \f$. diff --git a/samples/cpp/fitellipse.cpp b/samples/cpp/fitellipse.cpp index 7d217014d5..60f72cd4ef 100644 --- a/samples/cpp/fitellipse.cpp +++ b/samples/cpp/fitellipse.cpp @@ -5,7 +5,7 @@ * contours and approximate it by ellipses using three methods. * 1: OpenCV's original method fitEllipse which implements Fitzgibbon 1995 method. * 2: The Approximate Mean Square (AMS) method fitEllipseAMS proposed by Taubin 1991 - * 3: The Direct least square (Direct) method fitEllipseDirect proposed by Fitzgibbon1999. + * 3: The Direct least square (Direct) method fitEllipseDirect proposed by oy1998NumericallySD. * * Trackbar specify threshold parameter. * From cb959b39158fa44a0d6e03f6883be7f18b1f3a44 Mon Sep 17 00:00:00 2001 From: Vincent Rabaud Date: Tue, 7 Jan 2025 15:03:16 +0100 Subject: [PATCH 087/102] Fix behavior change when PNG buffer is incomplete. --- modules/imgcodecs/src/grfmt_png.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/modules/imgcodecs/src/grfmt_png.cpp b/modules/imgcodecs/src/grfmt_png.cpp index a2ea54b414..509ce13cdf 100644 --- a/modules/imgcodecs/src/grfmt_png.cpp +++ b/modules/imgcodecs/src/grfmt_png.cpp @@ -260,7 +260,8 @@ bool PngDecoder::readHeader() if (id != id_IHDR) { - read_from_io(&sig, 8, 1); + if (read_from_io(&sig, 8, 1) != 1) + return false; id = read_chunk(m_chunkIHDR); } @@ -274,7 +275,7 @@ bool PngDecoder::readHeader() m_is_fcTL_loaded = false; id = read_chunk(chunk); - if ((m_f && feof(m_f)) || (!m_buf.empty() && m_buf_pos > m_buf.total())) + if (!id || (m_f && feof(m_f)) || (!m_buf.empty() && m_buf_pos > m_buf.total())) { return false; } @@ -679,8 +680,10 @@ size_t PngDecoder::read_from_io(void* _Buffer, size_t _ElementSize, size_t _Elem if (m_f) return fread(_Buffer, _ElementSize, _ElementCount, m_f); - if (m_buf_pos + _ElementSize > m_buf.cols * m_buf.rows * m_buf.elemSize()) - CV_Error(Error::StsInternal, "PNG input buffer is incomplete"); + if (m_buf_pos + _ElementSize > m_buf.cols * m_buf.rows * m_buf.elemSize()) { + CV_LOG_WARNING(NULL, "PNG input buffer is incomplete"); + return 0; + } memcpy( _Buffer, m_buf.ptr() + m_buf_pos, _ElementSize ); m_buf_pos += _ElementSize; From 198f23890eefc230419415a139e2c11bed123f8b Mon Sep 17 00:00:00 2001 From: Alexander Smorkalov Date: Wed, 8 Jan 2025 13:28:28 +0300 Subject: [PATCH 088/102] Fixed fread size check for corrupted PNGs. --- modules/imgcodecs/src/grfmt_png.cpp | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/modules/imgcodecs/src/grfmt_png.cpp b/modules/imgcodecs/src/grfmt_png.cpp index 509ce13cdf..6b4cea405c 100644 --- a/modules/imgcodecs/src/grfmt_png.cpp +++ b/modules/imgcodecs/src/grfmt_png.cpp @@ -253,18 +253,12 @@ bool PngDecoder::readHeader() return false; } png_init_io(png_ptr, m_f); - - if (fread(sig, 1, 8, m_f)) - id = read_chunk(m_chunkIHDR); } - if (id != id_IHDR) - { - if (read_from_io(&sig, 8, 1) != 1) - return false; - id = read_chunk(m_chunkIHDR); - } + if (read_from_io(&sig, 8, 1) != 1) + return false; + id = read_chunk(m_chunkIHDR); if (!(id == id_IHDR && m_chunkIHDR.p.size() == 25)) { return false; From 31b0eeea0b44b370fd0712312df4214d4ae1b158 Mon Sep 17 00:00:00 2001 From: Alexander Smorkalov Date: Sat, 4 Jan 2025 18:01:55 +0300 Subject: [PATCH 089/102] Release 4.11.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 c0c7717e1f..7c5eaa1437 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 11 #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 938f89a20e3ab6b6ac37e74cf953526dc7f576b6 Mon Sep 17 00:00:00 2001 From: shyama7004 Date: Wed, 8 Jan 2025 20:17:17 +0530 Subject: [PATCH 090/102] minor change --- modules/objdetect/test/test_qrcode.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/objdetect/test/test_qrcode.cpp b/modules/objdetect/test/test_qrcode.cpp index 9f1ea7a800..3830439765 100644 --- a/modules/objdetect/test/test_qrcode.cpp +++ b/modules/objdetect/test/test_qrcode.cpp @@ -528,7 +528,7 @@ TEST(Objdetect_QRCode_decode, decode_regression_21929) Mat qrImg; QRCodeEncoder::Params params; params.version = 8; // 49x49 - Ptr qrcode_enc = cv::QRCodeEncoder::create(params);; + Ptr qrcode_enc = cv::QRCodeEncoder::create(params); qrcode_enc->encode(expect_msg, qrImg); Mat src; @@ -552,7 +552,7 @@ TEST(Objdetect_QRCode_decode, decode_regression_version_25) Mat qrImg; QRCodeEncoder::Params params; params.version = 25; // 117x117 - Ptr qrcode_enc = cv::QRCodeEncoder::create(params);; + Ptr qrcode_enc = cv::QRCodeEncoder::create(params); qrcode_enc->encode(expect_msg, qrImg); Mat src; From 05bc484eed772af8832dd4a311dd7bf58f9689e8 Mon Sep 17 00:00:00 2001 From: shyama7004 Date: Thu, 9 Jan 2025 20:40:56 +0530 Subject: [PATCH 091/102] addition of POST_BUILD --- modules/python/python_loader.cmake | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/python/python_loader.cmake b/modules/python/python_loader.cmake index 41dce57c18..dc16083c2a 100644 --- a/modules/python/python_loader.cmake +++ b/modules/python/python_loader.cmake @@ -139,6 +139,7 @@ if(${PYTHON}_VERSION_STRING VERSION_GREATER "3.6" AND PYTHON_DEFAULT_VERSION VER # halts on hard error. add_custom_command( TARGET copy_opencv_typing_stubs + POST_BUILD COMMAND ${PYTHON_DEFAULT_EXECUTABLE} ${PYTHON_SOURCE_DIR}/src2/copy_typings_stubs_on_success.py --stubs_dir ${OPENCV_PYTHON_BINDINGS_DIR}/cv2 --output_dir ${__loader_path}/cv2 From d12fa37eed83c755e1cea2b0f54507133375ef65 Mon Sep 17 00:00:00 2001 From: Vincent Rabaud Date: Fri, 10 Jan 2025 09:33:43 +0100 Subject: [PATCH 092/102] Merge pull request #26739 from vrabaud:png_leak Add more boundary checks. #26739 Also fix a bug in read_chunk where we could end up with png_get_uint_32(len) + 12 < 4 ### 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_png.cpp | 54 +++++++++++++++++------------ modules/imgcodecs/src/grfmt_png.hpp | 2 +- 2 files changed, 33 insertions(+), 23 deletions(-) diff --git a/modules/imgcodecs/src/grfmt_png.cpp b/modules/imgcodecs/src/grfmt_png.cpp index 6b4cea405c..744f244a20 100644 --- a/modules/imgcodecs/src/grfmt_png.cpp +++ b/modules/imgcodecs/src/grfmt_png.cpp @@ -255,11 +255,14 @@ bool PngDecoder::readHeader() png_init_io(png_ptr, m_f); } - if (read_from_io(&sig, 8, 1) != 1) + // Read PNG header: 137 80 78 71 13 10 26 10 + if (!read_from_io(&sig, 8)) return false; id = read_chunk(m_chunkIHDR); - if (!(id == id_IHDR && m_chunkIHDR.p.size() == 25)) + // 8=HDR+size, 13=size of IHDR chunk, 4=CRC + // http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html#C.IHDR + if (!(id == id_IHDR && m_chunkIHDR.p.size() == 8 + 13 + 4)) { return false; } @@ -283,23 +286,25 @@ bool PngDecoder::readHeader() break; } - if (id == id_acTL && chunk.p.size() == 20) + if (id == id_acTL) { + // 8=HDR+size, 8=size of acTL chunk, 4=CRC + // https://wiki.mozilla.org/APNG_Specification#%60acTL%60:_The_Animation_Control_Chunk + if (chunk.p.size() != 8 + 8 + 4) + return false; m_animation.loop_count = png_get_uint_32(&chunk.p[12]); - if (chunk.p[8] > 0) - { - chunk.p[8] = 0; - chunk.p[9] = 0; - m_frame_count = png_get_uint_32(&chunk.p[8]); - m_frame_count++; - } - else - m_frame_count = png_get_uint_32(&chunk.p[8]); + m_frame_count = png_get_uint_32(&chunk.p[8]); + if (m_frame_count == 0) + return false; } if (id == id_fcTL) { + // 8=HDR+size, 26=size of fcTL chunk, 4=CRC + // https://wiki.mozilla.org/APNG_Specification#%60fcTL%60:_The_Frame_Control_Chunk + if (chunk.p.size() != 8 + 26 + 4) + return false; m_is_fcTL_loaded = true; w0 = png_get_uint_32(&chunk.p[12]); h0 = png_get_uint_32(&chunk.p[16]); @@ -313,6 +318,11 @@ bool PngDecoder::readHeader() if (id == id_bKGD) { + // 8=HDR+size, ??=size of bKGD chunk, 4=CRC + // The spec is actually more complex: http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html#C.bKGD + // TODO: we only check that 4 bytes can be read from &chunk.p[8]. Fix. + if (chunk.p.size() < 8 + 4) + return false; int bgcolor = png_get_uint_32(&chunk.p[8]); m_animation.bgcolor[3] = (bgcolor >> 24) & 0xFF; m_animation.bgcolor[2] = (bgcolor >> 16) & 0xFF; @@ -669,34 +679,34 @@ void PngDecoder::compose_frame(std::vector& rows_dst, const std::vect }); } -size_t PngDecoder::read_from_io(void* _Buffer, size_t _ElementSize, size_t _ElementCount) +bool PngDecoder::read_from_io(void* buffer, size_t num_bytes) { if (m_f) - return fread(_Buffer, _ElementSize, _ElementCount, m_f); + return fread(buffer, 1, num_bytes, m_f) == num_bytes; - if (m_buf_pos + _ElementSize > m_buf.cols * m_buf.rows * m_buf.elemSize()) { + if (m_buf_pos + num_bytes > m_buf.cols * m_buf.rows * m_buf.elemSize()) { CV_LOG_WARNING(NULL, "PNG input buffer is incomplete"); - return 0; + return false; } - memcpy( _Buffer, m_buf.ptr() + m_buf_pos, _ElementSize ); - m_buf_pos += _ElementSize; - return 1; + memcpy( buffer, m_buf.ptr() + m_buf_pos, num_bytes ); + m_buf_pos += num_bytes; + return true; } uint32_t PngDecoder::read_chunk(Chunk& chunk) { unsigned char len[4]; - if (read_from_io(&len, 4, 1) == 1) + if (read_from_io(&len, 4)) { - const size_t size = png_get_uint_32(len) + 12; + const size_t size = static_cast(png_get_uint_32(len)) + 12; if (size > PNG_USER_CHUNK_MALLOC_MAX) { CV_LOG_WARNING(NULL, "chunk data is too large"); } chunk.p.resize(size); memcpy(chunk.p.data(), len, 4); - if (read_from_io(&chunk.p[4], chunk.p.size() - 4, 1) == 1) + if (read_from_io(&chunk.p[4], chunk.p.size() - 4)) return *(uint32_t*)(&chunk.p[4]); } return 0; diff --git a/modules/imgcodecs/src/grfmt_png.hpp b/modules/imgcodecs/src/grfmt_png.hpp index a950b9e941..dec2cd0b61 100644 --- a/modules/imgcodecs/src/grfmt_png.hpp +++ b/modules/imgcodecs/src/grfmt_png.hpp @@ -137,7 +137,7 @@ protected: bool processing_start(void* frame_ptr, const Mat& img); bool processing_finish(); void compose_frame(std::vector& rows_dst, const std::vector& rows_src, unsigned char bop, uint32_t x, uint32_t y, uint32_t w, uint32_t h, Mat& img); - size_t read_from_io(void* _Buffer, size_t _ElementSize, size_t _ElementCount); + bool read_from_io(void* buffer, size_t num_bytes); uint32_t read_chunk(Chunk& chunk); struct PngPtrs { From 85f9ac4e238d99314c801cc7e975259321f617b2 Mon Sep 17 00:00:00 2001 From: Junrou Nishida Date: Fri, 10 Jan 2025 20:48:56 +0900 Subject: [PATCH 093/102] Merge pull request #26713 from homuler:fix/build-ios-framework Ensure Obj-C header files are generated correctly if under /private/var #26713 Fix #26712 ### 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 --- modules/objc/generator/gen_objc.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/objc/generator/gen_objc.py b/modules/objc/generator/gen_objc.py index 71bdf0fb01..74c370e8bf 100755 --- a/modules/objc/generator/gen_objc.py +++ b/modules/objc/generator/gen_objc.py @@ -1662,7 +1662,9 @@ if __name__ == "__main__": h_files += [os.path.join(root, filename) for filename in fnmatch.filter(filenames, '*.h')] hpp_files += [os.path.join(root, filename) for filename in fnmatch.filter(filenames, '*.hpp')] srcfiles = h_files + hpp_files - srcfiles = [f for f in srcfiles if not re_bad.search(f.replace('\\', '/'))] + # Use relative paths to avoid being affected by the name of the parent directory. + # See https://github.com/opencv/opencv/issues/26712 + srcfiles = [f for f in srcfiles if not re_bad.search(os.path.relpath(f, module_location).replace('\\', '/'))] logging.info("\nFiles (%d):\n%s", len(srcfiles), pformat(srcfiles)) common_headers_fname = os.path.join(misc_location, 'filelist_common') From f00814e38db556ad7a31e6dee203e41eea48e7a6 Mon Sep 17 00:00:00 2001 From: Skreg <85214856+shyama7004@users.noreply.github.com> Date: Fri, 10 Jan 2025 17:32:18 +0530 Subject: [PATCH 094/102] Merge pull request #26602 from shyama7004:minor-fix Improved dumpVector, cv::Rect operator<< and exceptions #26602 - Applied format for vector element formatting to ensure consistent and clear output representation. - Moved `operator<<` to the `cv` namespace to align with OpenCV's coding standards and improve maintainability. - Enhanced error handling by including detailed exception messages using `e.what()` for better debugging. ### 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/core/src/bindings_utils.cpp | 41 ++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/modules/core/src/bindings_utils.cpp b/modules/core/src/bindings_utils.cpp index 78093002f7..6191f26f7c 100644 --- a/modules/core/src/bindings_utils.cpp +++ b/modules/core/src/bindings_utils.cpp @@ -9,7 +9,12 @@ #include #include -namespace cv { namespace utils { +namespace cv { +static inline std::ostream& operator<<(std::ostream& os, const Rect& rect) +{ + return os << "[x=" << rect.x << ", y=" << rect.y << ", w=" << rect.width << ", h=" << rect.height << ']'; +} +namespace utils { String dumpInputArray(InputArray argument) { @@ -51,9 +56,13 @@ String dumpInputArray(InputArray argument) ss << " type(-1)=" << cv::typeToString(argument.type(-1)); } while (0); } + catch (const std::exception& e) + { + ss << " ERROR: exception occurred: " << e.what(); + } catch (...) { - ss << " ERROR: exception occurred, dump is non-complete"; // need to properly support different kinds + ss << " ERROR: unknown exception occurred, dump is non-complete"; } return ss.str(); } @@ -104,9 +113,13 @@ CV_EXPORTS_W String dumpInputArrayOfArrays(InputArrayOfArrays argument) } } while (0); } + catch (const std::exception& e) + { + ss << " ERROR: exception occurred: " << e.what(); + } catch (...) { - ss << " ERROR: exception occurred, dump is non-complete"; // need to properly support different kinds + ss << " ERROR: unknown exception occurred, dump is non-complete"; } return ss.str(); } @@ -151,9 +164,13 @@ CV_EXPORTS_W String dumpInputOutputArray(InputOutputArray argument) ss << " type(-1)=" << cv::typeToString(argument.type(-1)); } while (0); } + catch (const std::exception& e) + { + ss << " ERROR: exception occurred: " << e.what(); + } catch (...) { - ss << " ERROR: exception occurred, dump is non-complete"; // need to properly support different kinds + ss << " ERROR: unknown exception occurred, dump is non-complete"; } return ss.str(); } @@ -204,28 +221,28 @@ CV_EXPORTS_W String dumpInputOutputArrayOfArrays(InputOutputArrayOfArrays argume } } while (0); } + catch (const std::exception& e) + { + ss << " ERROR: exception occurred: " << e.what(); + } catch (...) { - ss << " ERROR: exception occurred, dump is non-complete"; // need to properly support different kinds + ss << " ERROR: unknown exception occurred, dump is non-complete"; } return ss.str(); } -static inline std::ostream& operator<<(std::ostream& os, const cv::Rect& rect) -{ - return os << "[x=" << rect.x << ", y=" << rect.y << ", w=" << rect.width << ", h=" << rect.height << ']'; -} - template static inline String dumpVector(const std::vector& vec, Formatter format) { std::ostringstream oss("[", std::ios::ate); if (!vec.empty()) { - oss << format << vec[0]; + format(oss) << vec[0]; for (std::size_t i = 1; i < vec.size(); ++i) { - oss << ", " << format << vec[i]; + oss << ", "; + format(oss) << vec[i]; } } oss << "]"; From 97f3f390661f2fd1168336820b89eb4383ce8528 Mon Sep 17 00:00:00 2001 From: Maksim Shabunin Date: Fri, 10 Jan 2025 18:34:11 +0300 Subject: [PATCH 095/102] core: fixed VSX intrinsics implementation --- modules/core/include/opencv2/core/hal/intrin_vsx.hpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/core/include/opencv2/core/hal/intrin_vsx.hpp b/modules/core/include/opencv2/core/hal/intrin_vsx.hpp index 2157e1e870..0a0915a22f 100644 --- a/modules/core/include/opencv2/core/hal/intrin_vsx.hpp +++ b/modules/core/include/opencv2/core/hal/intrin_vsx.hpp @@ -262,7 +262,7 @@ OPENCV_HAL_IMPL_VSX_EXTRACT_N(v_float64x2, double) inline _Tpvec v_setzero_##suffix() { return _Tpvec(vec_splats((_Tp)0)); } \ inline _Tpvec v_setall_##suffix(_Tp v) { return _Tpvec(vec_splats((_Tp)v));} \ template <> inline _Tpvec v_setzero_() { return v_setzero_##suffix(); } \ -template <> inline _Tpvec v_setall_(_Tp v) { return v_setall_##suffix(_Tp v); } \ +template <> inline _Tpvec v_setall_(_Tp v) { return v_setall_##suffix(v); } \ template inline _Tpvec v_reinterpret_as_##suffix(const _Tpvec0 &a) \ { return _Tpvec((cast)a.val); } @@ -650,11 +650,11 @@ OPENCV_HAL_IMPL_VSX_SELECT(v_float64x2, vec_bdword2_c) #define OPENCV_HAL_IMPL_VSX_INT_CMP_OP(_Tpvec) \ inline _Tpvec v_eq(const _Tpvec& a, const _Tpvec& b) \ { return _Tpvec(vec_cmpeq(a.val, b.val)); } \ -inline _Tpvec V_ne(const _Tpvec& a, const _Tpvec& b) \ +inline _Tpvec v_ne(const _Tpvec& a, const _Tpvec& b) \ { return _Tpvec(vec_cmpne(a.val, b.val)); } \ inline _Tpvec v_lt(const _Tpvec& a, const _Tpvec& b) \ { return _Tpvec(vec_cmplt(a.val, b.val)); } \ -inline _Tpvec V_gt(const _Tpvec& a, const _Tpvec& b) \ +inline _Tpvec v_gt(const _Tpvec& a, const _Tpvec& b) \ { return _Tpvec(vec_cmpgt(a.val, b.val)); } \ inline _Tpvec v_le(const _Tpvec& a, const _Tpvec& b) \ { return _Tpvec(vec_cmple(a.val, b.val)); } \ @@ -1507,7 +1507,7 @@ inline v_float64x2 v_dotprod_expand(const v_int32x4& a, const v_int32x4& b, cons inline v_int32x4 v_dotprod_fast(const v_int16x8& a, const v_int16x8& b) { return v_dotprod(a, b); } inline v_int32x4 v_dotprod_fast(const v_int16x8& a, const v_int16x8& b, const v_int32x4& c) -{ return v_int32x4(vec_msum(a.val, b.val, vec_int4_z)) + c; } +{ return v_add(v_int32x4(vec_msum(a.val, b.val, vec_int4_z)), c); } // 32 >> 64 inline v_int64x2 v_dotprod_fast(const v_int32x4& a, const v_int32x4& b) { return v_dotprod(a, b); } @@ -1518,7 +1518,7 @@ inline v_int64x2 v_dotprod_fast(const v_int32x4& a, const v_int32x4& b, const v_ inline v_uint32x4 v_dotprod_expand_fast(const v_uint8x16& a, const v_uint8x16& b) { return v_dotprod_expand(a, b); } inline v_uint32x4 v_dotprod_expand_fast(const v_uint8x16& a, const v_uint8x16& b, const v_uint32x4& c) -{ return v_uint32x4(vec_msum(a.val, b.val, vec_uint4_z)) + c; } +{ return v_add(v_uint32x4(vec_msum(a.val, b.val, vec_uint4_z)), c); } inline v_int32x4 v_dotprod_expand_fast(const v_int8x16& a, const v_int8x16& b) { From ee86f1c969627a384a9e20d99a4b5e4d007def05 Mon Sep 17 00:00:00 2001 From: Vincent Rabaud Date: Fri, 10 Jan 2025 14:57:39 +0100 Subject: [PATCH 096/102] Fix remaining bugs in PNG reader - free chunk before a potential longjmp - do not try to allocate when the chunk is > PNG_USER_CHUNK_MALLOC_MAX --- 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 744f244a20..1ecc01f17f 100644 --- a/modules/imgcodecs/src/grfmt_png.cpp +++ b/modules/imgcodecs/src/grfmt_png.cpp @@ -339,6 +339,10 @@ bool PngDecoder::readHeader() png_bytep trans; png_color_16p trans_values; + // Free chunk in case png_read_info uses longjmp. + chunk.p.clear(); + chunk.p.shrink_to_fit(); + png_read_info( png_ptr, info_ptr ); png_get_IHDR(png_ptr, info_ptr, &wdth, &hght, &bit_depth, &color_type, 0, 0, 0); @@ -703,6 +707,7 @@ uint32_t PngDecoder::read_chunk(Chunk& chunk) if (size > PNG_USER_CHUNK_MALLOC_MAX) { CV_LOG_WARNING(NULL, "chunk data is too large"); + return 0; } chunk.p.resize(size); memcpy(chunk.p.data(), len, 4); From 2c2866a7a6a5a6e74972d57a59762336a3d1b126 Mon Sep 17 00:00:00 2001 From: Super <12210305@mail.sustech.edu.cn> Date: Sat, 11 Jan 2025 15:34:49 +0800 Subject: [PATCH 097/102] Merge pull request #26738 from redhecker:fix Fix bugs in GIF decoding #26738 ### Pull Request Readiness Checklist this is related to #25691 i solved two bugs here: 1. the decoding setting: according to [https://www.w3.org/Graphics/GIF/spec-gif89a.txt](https://www.w3.org/Graphics/GIF/spec-gif89a.txt) ``` DEFERRED CLEAR CODE IN LZW COMPRESSION There has been confusion about where clear codes can be found in the data stream. As the specification says, they may appear at anytime. There is not a requirement to send a clear code when the string table is full. It is the encoder's decision as to when the table should be cleared. When the table is full, the encoder can chose to use the table as is, making no changes to it until the encoder chooses to clear it. The encoder during this time sends out codes that are of the maximum Code Size. As we can see from the above, when the decoder's table is full, it must not change the table until a clear code is received. The Code Size is that of the maximum Code Size. Processing other than this is done normally. Because of a large base of decoders that do not handle the decompression in this manner, we ask developers of GIF encoding software to NOT implement this feature until at least January 1991 and later if they see that their particular market is not ready for it. This will give developers of GIF decoding software time to implement this feature and to get it into the hands of their clients before the decoders start "breaking" on the new GIF's. It is not required that encoders change their software to take advantage of the deferred clear code, but it is for decoders. ``` at first i didn't consider this case, thus leads to a bug discussed in #25691. the changes made in function lzwDecode() is aiming at solving this. 2. the fetch method of loopCount: in the codes at https://github.com/opencv/opencv/blob/4.x/modules/imgcodecs/src/grfmt_gif.cpp#L410, if the branch is taken, 3 more bytes will be taken, leading to unpredictable behavior. 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_gif.cpp | 74 +++++++++++++++++++---------- 1 file changed, 50 insertions(+), 24 deletions(-) diff --git a/modules/imgcodecs/src/grfmt_gif.cpp b/modules/imgcodecs/src/grfmt_gif.cpp index 5a65ae04b1..05b27e4ddd 100644 --- a/modules/imgcodecs/src/grfmt_gif.cpp +++ b/modules/imgcodecs/src/grfmt_gif.cpp @@ -4,6 +4,7 @@ #include "precomp.hpp" #include "grfmt_gif.hpp" +#include "opencv2/core/utils/logger.hpp" #ifdef HAVE_IMGCODEC_GIF namespace cv @@ -293,11 +294,12 @@ void GifDecoder::code2pixel(Mat& img, int start, int k){ bool GifDecoder::lzwDecode() { // initialization lzwMinCodeSize = m_strm.getByte(); + const int lzwMaxSize = (1 << 12); // 4096 is the maximum size of the LZW table (12 bits) int lzwCodeSize = lzwMinCodeSize + 1; int clearCode = 1 << lzwMinCodeSize; int exitCode = clearCode + 1; CV_Assert(lzwCodeSize > 2 && lzwCodeSize <= 12); - std::vector lzwExtraTable((1 << 12) + 1); + std::vector lzwExtraTable(lzwMaxSize + 1); int colorTableSize = clearCode; int lzwTableSize = exitCode; @@ -321,6 +323,7 @@ bool GifDecoder::lzwDecode() { // clear code if (!(code ^ clearCode)) { lzwExtraTable.clear(); + lzwExtraTable.resize(lzwMaxSize + 1); // reset the code size, the same as that in the initialization part lzwCodeSize = lzwMinCodeSize + 1; lzwTableSize = exitCode; @@ -341,20 +344,24 @@ bool GifDecoder::lzwDecode() { // output code // 1. renew the lzw extra table - if (code < colorTableSize) { - lzwExtraTable[lzwTableSize].suffix = (uchar)code; - lzwTableSize ++; - lzwExtraTable[lzwTableSize].prefix.clear(); - lzwExtraTable[lzwTableSize].prefix.push_back((uchar)code); - lzwExtraTable[lzwTableSize].length = 2; - } else if (code <= lzwTableSize) { - lzwExtraTable[lzwTableSize].suffix = lzwExtraTable[code].prefix[0]; - lzwTableSize ++; - lzwExtraTable[lzwTableSize].prefix = lzwExtraTable[code].prefix; - lzwExtraTable[lzwTableSize].prefix.push_back(lzwExtraTable[code].suffix); - lzwExtraTable[lzwTableSize].length = lzwExtraTable[code].length + 1; - } else { - return false; + // * notice that if the lzw table size is full, + // * we should use the old table until a clear code is encountered + if (lzwTableSize < lzwMaxSize) { + if (code < colorTableSize) { + lzwExtraTable[lzwTableSize].suffix = (uchar)code; + lzwTableSize ++; + lzwExtraTable[lzwTableSize].prefix.clear(); + lzwExtraTable[lzwTableSize].prefix.push_back((uchar)code); + lzwExtraTable[lzwTableSize].length = 2; + } else if (code <= lzwTableSize) { + lzwExtraTable[lzwTableSize].suffix = lzwExtraTable[code].prefix[0]; + lzwTableSize ++; + lzwExtraTable[lzwTableSize].prefix = lzwExtraTable[code].prefix; + lzwExtraTable[lzwTableSize].prefix.push_back(lzwExtraTable[code].suffix); + lzwExtraTable[lzwTableSize].length = lzwExtraTable[code].length + 1; + } else { + return false; + } } // 2. output to the code stream @@ -368,7 +375,7 @@ bool GifDecoder::lzwDecode() { } // check if the code size is full - if (lzwTableSize > (1 << 12)) { + if (lzwTableSize > lzwMaxSize) { return false; } @@ -402,14 +409,33 @@ bool GifDecoder::getFrameCount_() { while (type != 0x3B) { if (!(type ^ 0x21)) { // skip all kinds of the extensions - m_strm.skip(1); - int len = m_strm.getByte(); - while (len) { - m_strm.skip(len); - len = m_strm.getByte(); - if (len == 3 && m_strm.getByte() == 1) - { - m_animation.loop_count = m_strm.getWord(); + int extension = m_strm.getByte(); + // Application Extension need to be handled for the loop count + if (extension == 0xFF) { + int len = m_strm.getByte(); + while (len) { + if (len == 3) { + if (m_strm.getByte() == 0x01) { + m_animation.loop_count = m_strm.getWord(); + } else { + // this branch should not be reached in normal cases + m_strm.skip(2); + CV_LOG_WARNING(NULL, "found Unknown Application Extension"); + } + } else { + m_strm.skip(len); + } + len = m_strm.getByte(); + } + } else { + // if it does not belong to any of the extension type mentioned in the GIF Specification + if (extension != 0xF9 && extension != 0xFE && extension != 0x01) { + CV_LOG_WARNING(NULL, "found Unknown Extension Type: " + std::to_string(extension)); + } + int len = m_strm.getByte(); + while (len) { + m_strm.skip(len); + len = m_strm.getByte(); } } } else if (!(type ^ 0x2C)) { From e29a70c17fc23c09eb2fc45ac7902ea37ad77802 Mon Sep 17 00:00:00 2001 From: Maksym Ivashechkin Date: Sat, 11 Jan 2025 15:08:58 +0000 Subject: [PATCH 098/102] Merge pull request #26742 from ivashmak:fix_homography_inliers Bug fix for #25546 - Updating inliers for homography estimation #26742 Fixes #25546 ### 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. - [x] The feature is well documented and sample code can be built with the project CMake --- modules/calib3d/src/fundam.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/modules/calib3d/src/fundam.cpp b/modules/calib3d/src/fundam.cpp index 75d3456595..4c8d511a93 100644 --- a/modules/calib3d/src/fundam.cpp +++ b/modules/calib3d/src/fundam.cpp @@ -414,6 +414,11 @@ cv::Mat cv::findHomography( InputArray _points1, InputArray _points2, if( result && npoints > 4 && method != RHO) { + // save the original points before compressing + const int npoints_input = npoints; + const Mat src_input = src.clone(); + const Mat dst_input = dst.clone(); + compressElems( src.ptr(), tempMask.ptr(), 1, npoints ); npoints = compressElems( dst.ptr(), tempMask.ptr(), 1, npoints ); if( npoints > 0 ) @@ -427,6 +432,16 @@ cv::Mat cv::findHomography( InputArray _points1, InputArray _points2, Mat H8(9, 1, CV_64F, H.ptr()); LMSolver::create(makePtr(src, dst), 10)->run(H8); H.convertTo(H, H.type(), scaleFor(H.at(2,2))); + + // find new inliers + const float thr_sqr = static_cast(ransacReprojThreshold * ransacReprojThreshold); + cv::Mat errors; + cb->computeError(src_input, dst_input, H, errors); + uchar* maskptr = tempMask.ptr(); + const float * const errors_ptr = errors.ptr(); + for (int i = 0; i < npoints_input; i++) { + maskptr[i] = static_cast(errors_ptr[i] <= thr_sqr); + } } } From 5b7b887200585547dfe15a527d1f37cce0afc0f6 Mon Sep 17 00:00:00 2001 From: shyama7004 Date: Sun, 12 Jan 2025 19:45:22 +0530 Subject: [PATCH 099/102] Photo_CalibrateDebevec.regression-fix --- modules/photo/test/test_hdr.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/photo/test/test_hdr.cpp b/modules/photo/test/test_hdr.cpp index 10050abbcb..a26e83e49e 100644 --- a/modules/photo/test/test_hdr.cpp +++ b/modules/photo/test/test_hdr.cpp @@ -228,7 +228,7 @@ TEST(Photo_CalibrateDebevec, regression) double max; minMaxLoc(diff, NULL, &max); #if defined(__arm__) || defined(__aarch64__) - ASSERT_LT(max, 0.131); + ASSERT_LT(max, 0.2); #else ASSERT_LT(max, 0.1); #endif From 052b2c43c32651784f60eb736c5abb353af133af Mon Sep 17 00:00:00 2001 From: Diego1V Date: Thu, 9 Jan 2025 19:37:52 +0100 Subject: [PATCH 100/102] Update types inside HoughLinesProbabilistic in order to handle great images. --- modules/imgproc/src/hough.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/modules/imgproc/src/hough.cpp b/modules/imgproc/src/hough.cpp index 1c52689da5..39bdecf2ea 100644 --- a/modules/imgproc/src/hough.cpp +++ b/modules/imgproc/src/hough.cpp @@ -575,7 +575,8 @@ HoughLinesProbabilistic( Mat& image, Point line_end[2]; float a, b; int* adata = accum.ptr(); - int i = point.y, j = point.x, k, x0, y0, dx0, dy0, xflag; + int i = point.y, j = point.x, k, xflag; + int64_t x0, y0, dx0, dy0; int good_line; const int shift = 16; @@ -626,8 +627,8 @@ HoughLinesProbabilistic( Mat& image, for( k = 0; k < 2; k++ ) { - int gap = 0, x = x0, y = y0, dx = dx0, dy = dy0; - + int gap = 0; + int64_t x = x0, y = y0, dx = dx0, dy = dy0 ; if( k > 0 ) dx = -dx, dy = -dy; @@ -636,7 +637,7 @@ HoughLinesProbabilistic( Mat& image, for( ;; x += dx, y += dy ) { uchar* mdata; - int i1, j1; + int64_t i1, j1; if( xflag ) { @@ -661,8 +662,8 @@ HoughLinesProbabilistic( Mat& image, if( *mdata ) { gap = 0; - line_end[k].y = i1; - line_end[k].x = j1; + line_end[k].y = static_cast(i1); + line_end[k].x = static_cast(j1); } else if( ++gap > lineGap ) break; @@ -674,7 +675,7 @@ HoughLinesProbabilistic( Mat& image, for( k = 0; k < 2; k++ ) { - int x = x0, y = y0, dx = dx0, dy = dy0; + int64_t x = x0, y = y0, dx = dx0, dy = dy0; if( k > 0 ) dx = -dx, dy = -dy; @@ -684,7 +685,7 @@ HoughLinesProbabilistic( Mat& image, for( ;; x += dx, y += dy ) { uchar* mdata; - int i1, j1; + int64_t i1, j1; if( xflag ) { From 08a88816ed42a7b0429d7dcc1087746c6135aed6 Mon Sep 17 00:00:00 2001 From: Skreg <85214856+shyama7004@users.noreply.github.com> Date: Mon, 13 Jan 2025 13:21:03 +0530 Subject: [PATCH 101/102] Merge pull request #26753 from shyama7004:RotatedMarkers Fix rotated aruco marker board generation #26753 ### Issue : [25884](https://github.com/opencv/opencv/issues/25884) ### 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/objdetect/src/aruco/aruco_board.cpp | 8 +++- .../objdetect/test/test_boarddetection.cpp | 46 +++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/modules/objdetect/src/aruco/aruco_board.cpp b/modules/objdetect/src/aruco/aruco_board.cpp index f5f274ec0c..b6a6653d40 100644 --- a/modules/objdetect/src/aruco/aruco_board.cpp +++ b/modules/objdetect/src/aruco/aruco_board.cpp @@ -115,7 +115,13 @@ void Board::Impl::generateImage(Size outSize, OutputArray img, int marginSize, i } // get marker - Size dst_sz(outCorners[2] - outCorners[0]); // assuming CCW order + Point2f vecWidth = outCorners[1] - outCorners[0]; + float width = (float)cv::norm(vecWidth); + + Point2f vecHeight = outCorners[2] - outCorners[0]; + float height = (float)cv::norm(vecHeight); + + Size dst_sz(cvRound(width), cvRound(height)); dst_sz.width = dst_sz.height = std::min(dst_sz.width, dst_sz.height); //marker should be square dictionary.generateImageMarker(ids[m], dst_sz.width, marker, borderBits); diff --git a/modules/objdetect/test/test_boarddetection.cpp b/modules/objdetect/test/test_boarddetection.cpp index 0c99e6de61..1fa6e11994 100644 --- a/modules/objdetect/test/test_boarddetection.cpp +++ b/modules/objdetect/test/test_boarddetection.cpp @@ -325,5 +325,51 @@ TEST(CV_ArucoDictionary, extendDictionary) { ASSERT_EQ(custom_dictionary.bytesList.rows, 150); ASSERT_EQ(cv::norm(custom_dictionary.bytesList, base_dictionary.bytesList.rowRange(0, 150)), 0.); } +TEST(CV_ArucoBoardGenerateImage_RotationTest, HandlesRotatedMarkersWithoutBoundingBoxError) +{ + using namespace cv; + using namespace cv::aruco; + Dictionary dict = getPredefinedDictionary(DICT_4X4_50); + DetectorParameters detectorParams; + ArucoDetector detector(dict, detectorParams); + std::vector angles = {0.0f, 45.0f, 90.0f, 135.0f}; + for (auto angle_deg : angles) + { + float angle_rad = angle_deg * static_cast(CV_PI) / 180.0f; + float c = cos(angle_rad); + float s = sin(angle_rad); + std::vector markerCorners(4); + markerCorners[0] = Point3f(0.f, 0.f, 0.f); + markerCorners[1] = Point3f(1.f, 0.f, 0.f); + markerCorners[2] = Point3f(1.f, 1.f, 0.f); + markerCorners[3] = Point3f(0.f, 1.f, 0.f); + for (auto &p : markerCorners) + { + float xNew = p.x * c - p.y * s; + float yNew = p.x * s + p.y * c; + p.x = xNew; + p.y = yNew; + } + std::vector> allObjPoints{markerCorners}; + std::vector ids{0}; + Board board(allObjPoints, dict, ids); + float markerSize = 1.0f; + float rotatedSize = markerSize * std::sqrt(2.0f); + int borderBits = 1; + int marginSize = 20; + int sidePixels = static_cast((rotatedSize + 2.0f * borderBits) * 500) + 2 * marginSize; + Mat outImg; + Size outSize(sidePixels, sidePixels); + ASSERT_NO_THROW(board.generateImage(outSize, outImg, marginSize, borderBits)) + << "board.generateImage() threw an exception at angle " << angle_deg; + std::vector detectedIds; + std::vector> detectedCorners; + detector.detectMarkers(outImg, detectedCorners, detectedIds); + ASSERT_EQ(detectedIds.size(), (size_t)1) + << "Failed to detect single marker at angle: " << angle_deg; + EXPECT_EQ(detectedIds[0], 0) + << "Marker ID mismatch at angle: " << angle_deg; + } +} }} // namespace From bfb54aa69138fe2da67646eede14db6306b10cbf Mon Sep 17 00:00:00 2001 From: Vincent Rabaud Date: Mon, 13 Jan 2025 15:05:55 +0100 Subject: [PATCH 102/102] Remove useless C headers --- apps/traincascade/imagestorage.cpp | 1 - modules/calib3d/src/upnp.h | 1 - modules/calib3d/test/test_cameracalibration.cpp | 15 +++++++-------- .../test/test_cameracalibration_badarg.cpp | 3 --- modules/calib3d/test/test_chesscorners_badarg.cpp | 3 +-- modules/core/perf/perf_reduce.cpp | 1 - modules/core/src/matrix_operations.cpp | 1 - modules/core/src/matrix_sparse.cpp | 1 - modules/core/src/persistence.hpp | 1 - modules/features2d/src/mser.cpp | 1 - modules/features2d/test/test_keypoints.cpp | 1 - modules/imgcodecs/test/test_precomp.hpp | 1 - modules/imgproc/perf/perf_houghcircles.cpp | 1 - modules/videoio/src/cap_android_mediandk.cpp | 8 ++++---- modules/videoio/src/precomp.hpp | 1 - 15 files changed, 12 insertions(+), 28 deletions(-) diff --git a/apps/traincascade/imagestorage.cpp b/apps/traincascade/imagestorage.cpp index a32824c317..29680497b1 100644 --- a/apps/traincascade/imagestorage.cpp +++ b/apps/traincascade/imagestorage.cpp @@ -1,5 +1,4 @@ #include "opencv2/core.hpp" -#include "opencv2/core/core_c.h" #include "opencv2/imgproc.hpp" #include "opencv2/imgcodecs.hpp" diff --git a/modules/calib3d/src/upnp.h b/modules/calib3d/src/upnp.h index 513c6be7a1..c130135e31 100644 --- a/modules/calib3d/src/upnp.h +++ b/modules/calib3d/src/upnp.h @@ -49,7 +49,6 @@ #define OPENCV_CALIB3D_UPNP_H_ #include "precomp.hpp" -#include "opencv2/core/core_c.h" #include #if 0 // fix buffer overflow first (FIXIT mark in .cpp file) diff --git a/modules/calib3d/test/test_cameracalibration.cpp b/modules/calib3d/test/test_cameracalibration.cpp index 7668d74c48..e36ca3e1c4 100644 --- a/modules/calib3d/test/test_cameracalibration.cpp +++ b/modules/calib3d/test/test_cameracalibration.cpp @@ -40,7 +40,6 @@ //M*/ #include "test_precomp.hpp" -#include "opencv2/calib3d/calib3d_c.h" namespace opencv_test { namespace { @@ -754,7 +753,7 @@ void CV_CameraCalibrationTest_CPP::calibrate(Size imageSize, Mat(_objectPoints[i]).convertTo(objectPoints[i], CV_32F); } - size_t nstddev0 = CV_CALIB_NINTRINSIC + imageCount*6, nstddev1 = nstddev0 + _imagePoints[0].size()*3; + size_t nstddev0 = CALIB_NINTRINSIC + imageCount*6, nstddev1 = nstddev0 + _imagePoints[0].size()*3; for( i = nstddev0; i < nstddev1; i++ ) { stdDevs[i] = 0.0; @@ -1433,12 +1432,12 @@ void CV_StereoCalibrationTest::run( int ) double rmsErrorFromStereoCalib = calibrateStereoCamera(objpt, imgpt1, imgpt2, M1, D1, M2, D2, imgsize, R, T, E, F, rotMats1, transVecs1, rmsErrorPerView1, rmsErrorPerView2, TermCriteria(TermCriteria::MAX_ITER+TermCriteria::EPS, 30, 1e-6), - CV_CALIB_SAME_FOCAL_LENGTH - //+ CV_CALIB_FIX_ASPECT_RATIO - + CV_CALIB_FIX_PRINCIPAL_POINT - + CV_CALIB_ZERO_TANGENT_DIST - + CV_CALIB_FIX_K3 - + CV_CALIB_FIX_K4 + CV_CALIB_FIX_K5 //+ CV_CALIB_FIX_K6 + CALIB_SAME_FOCAL_LENGTH + //+ CALIB_FIX_ASPECT_RATIO + + CALIB_FIX_PRINCIPAL_POINT + + CALIB_ZERO_TANGENT_DIST + + CALIB_FIX_K3 + + CALIB_FIX_K4 + CALIB_FIX_K5 //+ CALIB_FIX_K6 ); /* rmsErrorFromStereoCalib /= nframes*npoints; */ if (rmsErrorFromStereoCalib > maxReprojErr) diff --git a/modules/calib3d/test/test_cameracalibration_badarg.cpp b/modules/calib3d/test/test_cameracalibration_badarg.cpp index 046fc1d530..d4d6df1869 100644 --- a/modules/calib3d/test/test_cameracalibration_badarg.cpp +++ b/modules/calib3d/test/test_cameracalibration_badarg.cpp @@ -41,7 +41,6 @@ #include "test_precomp.hpp" #include "test_chessboardgenerator.hpp" -#include "opencv2/core/types_c.h" namespace opencv_test { namespace { @@ -232,8 +231,6 @@ protected: void run(int /* start_from */ ) { - Mat zeros(1, sizeof(CvMat), CV_8U, Scalar(0)); - Mat src_cpp(3, 1, CV_32F); Mat dst_cpp(3, 3, CV_32F); diff --git a/modules/calib3d/test/test_chesscorners_badarg.cpp b/modules/calib3d/test/test_chesscorners_badarg.cpp index 85b2cb54da..f62cfc16c3 100644 --- a/modules/calib3d/test/test_chesscorners_badarg.cpp +++ b/modules/calib3d/test/test_chesscorners_badarg.cpp @@ -41,7 +41,6 @@ #include "test_precomp.hpp" #include "test_chessboardgenerator.hpp" -#include "opencv2/calib3d/calib3d_c.h" namespace opencv_test { namespace { @@ -89,7 +88,7 @@ void CV_ChessboardDetectorBadArgTest::run( int /*start_from */) /* /*//*/ */ int errors = 0; - flags = CV_CALIB_CB_ADAPTIVE_THRESH | CV_CALIB_CB_NORMALIZE_IMAGE; + flags = CALIB_CB_ADAPTIVE_THRESH | CALIB_CB_NORMALIZE_IMAGE; img = cb.clone(); initArgs(); diff --git a/modules/core/perf/perf_reduce.cpp b/modules/core/perf/perf_reduce.cpp index 844303aa7d..bcd10b18c5 100644 --- a/modules/core/perf/perf_reduce.cpp +++ b/modules/core/perf/perf_reduce.cpp @@ -1,5 +1,4 @@ #include "perf_precomp.hpp" -#include "opencv2/core/core_c.h" namespace opencv_test { diff --git a/modules/core/src/matrix_operations.cpp b/modules/core/src/matrix_operations.cpp index 2992f00e73..f1bdd52ae2 100644 --- a/modules/core/src/matrix_operations.cpp +++ b/modules/core/src/matrix_operations.cpp @@ -4,7 +4,6 @@ #include "precomp.hpp" #include "opencv2/core/mat.hpp" -#include "opencv2/core/types_c.h" #include "opencl_kernels_core.hpp" #undef HAVE_IPP diff --git a/modules/core/src/matrix_sparse.cpp b/modules/core/src/matrix_sparse.cpp index 2c9edf8b3a..f9b17d0c69 100644 --- a/modules/core/src/matrix_sparse.cpp +++ b/modules/core/src/matrix_sparse.cpp @@ -4,7 +4,6 @@ #include "precomp.hpp" #include "opencv2/core/mat.hpp" -#include "opencv2/core/types_c.h" namespace cv { diff --git a/modules/core/src/persistence.hpp b/modules/core/src/persistence.hpp index 2a1f73cba8..55ccd51254 100644 --- a/modules/core/src/persistence.hpp +++ b/modules/core/src/persistence.hpp @@ -6,7 +6,6 @@ #ifndef SRC_PERSISTENCE_HPP #define SRC_PERSISTENCE_HPP -#include "opencv2/core/types_c.h" #include #include #include diff --git a/modules/features2d/src/mser.cpp b/modules/features2d/src/mser.cpp index b7e7993449..10913ca1a1 100644 --- a/modules/features2d/src/mser.cpp +++ b/modules/features2d/src/mser.cpp @@ -44,7 +44,6 @@ */ #include "precomp.hpp" -#include "opencv2/imgproc/imgproc_c.h" #include #include "../3rdparty/mscr/chi_table.h" diff --git a/modules/features2d/test/test_keypoints.cpp b/modules/features2d/test/test_keypoints.cpp index c169c97163..696936af8e 100644 --- a/modules/features2d/test/test_keypoints.cpp +++ b/modules/features2d/test/test_keypoints.cpp @@ -40,7 +40,6 @@ //M*/ #include "test_precomp.hpp" -#include "opencv2/core/core_c.h" namespace opencv_test { namespace { diff --git a/modules/imgcodecs/test/test_precomp.hpp b/modules/imgcodecs/test/test_precomp.hpp index fab6606859..f5fb5c82e4 100644 --- a/modules/imgcodecs/test/test_precomp.hpp +++ b/modules/imgcodecs/test/test_precomp.hpp @@ -6,7 +6,6 @@ #include "opencv2/ts.hpp" #include "opencv2/imgcodecs.hpp" -#include "opencv2/imgproc/imgproc_c.h" namespace cv { diff --git a/modules/imgproc/perf/perf_houghcircles.cpp b/modules/imgproc/perf/perf_houghcircles.cpp index a5ca36dbc0..81b577636b 100644 --- a/modules/imgproc/perf/perf_houghcircles.cpp +++ b/modules/imgproc/perf/perf_houghcircles.cpp @@ -2,7 +2,6 @@ // 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 "perf_precomp.hpp" -#include "opencv2/imgproc/types_c.h" namespace opencv_test { diff --git a/modules/videoio/src/cap_android_mediandk.cpp b/modules/videoio/src/cap_android_mediandk.cpp index 5a434572dd..6dfb79843a 100644 --- a/modules/videoio/src/cap_android_mediandk.cpp +++ b/modules/videoio/src/cap_android_mediandk.cpp @@ -573,8 +573,8 @@ public: Mat bufferMat(image.rows, image.cols, CV_8UC4, buffer.bits, buffer.stride * 4); switch (image.type()) { case CV_8UC4: image.copyTo(bufferMat); break; - case CV_8UC3: cvtColor(image, bufferMat, CV_BGR2RGBA); break; - case CV_8UC1: cvtColor(image, bufferMat, CV_GRAY2RGBA); break; + case CV_8UC3: cvtColor(image, bufferMat, COLOR_BGR2RGBA); break; + case CV_8UC1: cvtColor(image, bufferMat, COLOR_GRAY2RGBA); break; } } else { LOGE("Unknown surface buffer format: 0x%x", buffer.format); @@ -585,8 +585,8 @@ public: //OpenCV doesn't support RGB to NV12 so we need to convert to YV12 and then manually changed it to NV12 Mat imageYV12; switch (image.type()) { - case CV_8UC4: cvtColor(image, imageYV12, CV_RGBA2YUV_YV12); break; - case CV_8UC3: cvtColor(image, imageYV12, CV_BGR2YUV_YV12); break; + case CV_8UC4: cvtColor(image, imageYV12, COLOR_RGBA2YUV_YV12); break; + case CV_8UC3: cvtColor(image, imageYV12, COLOR_BGR2YUV_YV12); break; case CV_8UC1: imageYV12.create(image.rows + image.rows/2, image.cols, CV_8UC1); image.copyTo(imageYV12.rowRange(0, image.rows)); imageYV12.rowRange(image.rows, imageYV12.rows) = 128; diff --git a/modules/videoio/src/precomp.hpp b/modules/videoio/src/precomp.hpp index 0ada79e1e2..8ebeec4a9e 100644 --- a/modules/videoio/src/precomp.hpp +++ b/modules/videoio/src/precomp.hpp @@ -73,7 +73,6 @@ #include "opencv2/imgcodecs.hpp" #include "opencv2/imgproc.hpp" -#include "opencv2/imgproc/imgproc_c.h" #include #include