From 7e14b4bba1766641975c88ca8a0bbf51511461f4 Mon Sep 17 00:00:00 2001 From: leatherwang Date: Tue, 4 Jan 2022 11:36:48 +0800 Subject: [PATCH 01/16] Fix array out of range when too many line segments extracted --- modules/line_descriptor/src/binary_descriptor.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/line_descriptor/src/binary_descriptor.cpp b/modules/line_descriptor/src/binary_descriptor.cpp index c9620c38fb..6e5b1577d1 100644 --- a/modules/line_descriptor/src/binary_descriptor.cpp +++ b/modules/line_descriptor/src/binary_descriptor.cpp @@ -2467,6 +2467,11 @@ int BinaryDescriptor::EDLineDetector::EDline( cv::Mat &image, LineChains &lines offsetInLineArray = pLineSID[numOfLines]; // line was not accepted, the offset is set back } } + // Avoid array out of range + if(numOfLines >= lines.sId.size()) { + lines.sId.push_back(offsetInLineArray); + pLineSID = &lines.sId.front(); + } //Extract line segments from the remaining pixel; Current chain has been shortened already. } } //end for(unsigned int edgeID=0; edgeID Date: Tue, 11 Jan 2022 18:11:17 +0100 Subject: [PATCH 02/16] ovis: update documentation notably, make param order match the argment order --- modules/ovis/include/opencv2/ovis.hpp | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/modules/ovis/include/opencv2/ovis.hpp b/modules/ovis/include/opencv2/ovis.hpp index 699e56e368..0c60599963 100644 --- a/modules/ovis/include/opencv2/ovis.hpp +++ b/modules/ovis/include/opencv2/ovis.hpp @@ -88,7 +88,7 @@ class CV_EXPORTS_W WindowScene { virtual ~WindowScene(); /** - * set window background to custom image + * set window background to custom image/ color * @param image */ CV_WRAP virtual void setBackground(InputArray image) = 0; @@ -101,8 +101,8 @@ class CV_EXPORTS_W WindowScene { * * this way you can add distortion or SSAO effects. * The effects themselves must be defined inside Ogre .compositor scripts. - * @see addResourceLocation * @param names compositor names that will be applied in order of appearance + * @see addResourceLocation */ CV_WRAP virtual void setCompositors(const std::vector& names) = 0; @@ -110,12 +110,12 @@ class CV_EXPORTS_W WindowScene { * place an entity of a mesh in the scene * * the mesh needs to be created beforehand. Either programmatically - * by e.g. @ref createPointCloudMesh or by placing an Ogre .mesh file in a resource location. - * @see addResourceLocation + * by e.g. @ref createPointCloudMesh or by placing the respective file in a resource location. * @param name entity name * @param meshname mesh name - * @param rot @ref Rodrigues vector or 3x3 rotation matrix * @param tvec translation + * @param rot @ref Rodrigues vector or 3x3 rotation matrix + * @see addResourceLocation */ CV_WRAP virtual void createEntity(const String& name, const String& meshname, InputArray tvec = noArray(), InputArray rot = noArray()) = 0; @@ -131,13 +131,14 @@ class CV_EXPORTS_W WindowScene { * @param name entity name * @param prop @ref EntityProperty * @param value the value + * @param subEntityIdx index of the sub-entity (default: all) */ - CV_WRAP virtual void setEntityProperty(const String& name, int prop, const Scalar& value) = 0; - - /// @overload CV_WRAP virtual void setEntityProperty(const String& name, int prop, const String& value, int subEntityIdx = -1) = 0; + /// @overload + CV_WRAP virtual void setEntityProperty(const String& name, int prop, const Scalar& value) = 0; + /** * get the property of an entity * @param name entity name @@ -153,8 +154,8 @@ class CV_EXPORTS_W WindowScene { * @param K intrinsic matrix * @param imsize image size * @param zFar far plane in camera coordinates - * @param rot @ref Rodrigues vector or 3x3 rotation matrix * @param tvec translation + * @param rot @ref Rodrigues vector or 3x3 rotation matrix * @param color line color * @return the extents of the Frustum at far plane, where the top left corner denotes the principal * point offset @@ -167,8 +168,8 @@ class CV_EXPORTS_W WindowScene { /** * creates a point light in the scene * @param name entity name - * @param rot @ref Rodrigues vector or 3x3 rotation matrix * @param tvec translation + * @param rot @ref Rodrigues vector or 3x3 rotation matrix * @param diffuseColor * @param specularColor */ @@ -180,8 +181,8 @@ class CV_EXPORTS_W WindowScene { /** * update entity pose by transformation in the parent coordinate space. (pre-rotation) * @param name entity name - * @param rot @ref Rodrigues vector or 3x3 rotation matrix * @param tvec translation + * @param rot @ref Rodrigues vector or 3x3 rotation matrix */ CV_WRAP virtual void updateEntityPose(const String& name, InputArray tvec = noArray(), InputArray rot = noArray()) = 0; @@ -189,8 +190,8 @@ class CV_EXPORTS_W WindowScene { /** * set entity pose in the world coordinate space. * @param name enitity name - * @param rot @ref Rodrigues vector or 3x3 rotation matrix * @param tvec translation + * @param rot @ref Rodrigues vector or 3x3 rotation matrix * @param invert use the inverse of the given pose */ CV_WRAP virtual void setEntityPose(const String& name, InputArray tvec = noArray(), @@ -263,8 +264,8 @@ class CV_EXPORTS_W WindowScene { /** * Sets the current camera pose - * @param rot @ref Rodrigues vector or 3x3 rotation matrix * @param tvec translation + * @param rot @ref Rodrigues vector or 3x3 rotation matrix * @param invert use the inverse of the given pose */ CV_WRAP virtual void setCameraPose(InputArray tvec = noArray(), InputArray rot = noArray(), From fdb53d633dfe7c924d38f820bc45ee31e3ba12c6 Mon Sep 17 00:00:00 2001 From: cudawarped Date: Tue, 11 Jan 2022 20:58:30 +0000 Subject: [PATCH 03/16] Merge pull request #3144 from cudawarped:test_cuda_event Test cv::cuda::event --- modules/cudaarithm/test/test_event.cpp | 104 +++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 modules/cudaarithm/test/test_event.cpp diff --git a/modules/cudaarithm/test/test_event.cpp b/modules/cudaarithm/test/test_event.cpp new file mode 100644 index 0000000000..375c51d0d3 --- /dev/null +++ b/modules/cudaarithm/test/test_event.cpp @@ -0,0 +1,104 @@ +// 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_CUDA + +#include + +#include "opencv2/core/cuda.hpp" +#include "opencv2/core/cuda_stream_accessor.hpp" +#include "opencv2/ts/cuda_test.hpp" + +namespace opencv_test { namespace { + +struct AsyncEvent : testing::TestWithParam +{ + cv::cuda::HostMem src; + cv::cuda::GpuMat d_src; + + cv::cuda::HostMem dst; + cv::cuda::GpuMat d_dst; + + cv::cuda::Stream stream; + + virtual void SetUp() + { + cv::cuda::DeviceInfo devInfo = GetParam(); + cv::cuda::setDevice(devInfo.deviceID()); + + src = cv::cuda::HostMem(cv::cuda::HostMem::PAGE_LOCKED); + + cv::Mat m = randomMat(cv::Size(128, 128), CV_8UC1); + m.copyTo(src); + } +}; + +void deviceWork(void* userData) +{ + AsyncEvent* test = reinterpret_cast(userData); + test->d_src.upload(test->src, test->stream); + test->d_src.convertTo(test->d_dst, CV_32S, test->stream); + test->d_dst.download(test->dst, test->stream); +} + +CUDA_TEST_P(AsyncEvent, WrapEvent) +{ + cudaEvent_t cuda_event = NULL; + ASSERT_EQ(cudaSuccess, cudaEventCreate(&cuda_event)); + { + cv::cuda::Event cudaEvent = cv::cuda::EventAccessor::wrapEvent(cuda_event); + deviceWork(this); + cudaEvent.record(stream); + cudaEvent.waitForCompletion(); + cv::Mat dst_gold; + src.createMatHeader().convertTo(dst_gold, CV_32S); + ASSERT_MAT_NEAR(dst_gold, dst, 0); + } + ASSERT_EQ(cudaSuccess, cudaEventDestroy(cuda_event)); +} + +CUDA_TEST_P(AsyncEvent, WithFlags) +{ + cv::cuda::Event cudaEvent = cv::cuda::Event(cv::cuda::Event::CreateFlags::BLOCKING_SYNC); + deviceWork(this); + cudaEvent.record(stream); + cudaEvent.waitForCompletion(); + cv::Mat dst_gold; + src.createMatHeader().convertTo(dst_gold, CV_32S); + ASSERT_MAT_NEAR(dst_gold, dst, 0); +} + +CUDA_TEST_P(AsyncEvent, Timing) +{ + const std::vector eventFlags = { cv::cuda::Event::CreateFlags::BLOCKING_SYNC , cv::cuda::Event::CreateFlags::BLOCKING_SYNC | Event::CreateFlags::DISABLE_TIMING }; + const std::vector shouldFail = { false, true }; + for (size_t i = 0; i < eventFlags.size(); i++) { + const auto& flags = eventFlags.at(i); + cv::cuda::Event startEvent = cv::cuda::Event(flags); + cv::cuda::Event stopEvent = cv::cuda::Event(flags); + startEvent.record(stream); + deviceWork(this); + stopEvent.record(stream); + stopEvent.waitForCompletion(); + cv::Mat dst_gold; + src.createMatHeader().convertTo(dst_gold, CV_32S); + ASSERT_MAT_NEAR(dst_gold, dst, 0); + bool failed = false; + try { + const double elTimeMs = Event::elapsedTime(startEvent, stopEvent); + ASSERT_GT(elTimeMs, 0); + } + catch (cv::Exception ex) { + failed = true; + } + ASSERT_EQ(failed, shouldFail.at(i)); + } +} + +INSTANTIATE_TEST_CASE_P(CUDA_Event, AsyncEvent, ALL_DEVICES); + +}} // namespace +#endif // HAVE_CUDA From 5c5dedcfc16d7ff0179a7367a024aa7a147e8e29 Mon Sep 17 00:00:00 2001 From: Dan Oprea Date: Thu, 20 Jan 2022 15:06:13 +0100 Subject: [PATCH 04/16] https://github.com/opencv/opencv_contrib/issues/3152 --- modules/xphoto/src/inpainting_fsr.impl.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/xphoto/src/inpainting_fsr.impl.hpp b/modules/xphoto/src/inpainting_fsr.impl.hpp index 8e9e0eb716..f47e2c79e1 100644 --- a/modules/xphoto/src/inpainting_fsr.impl.hpp +++ b/modules/xphoto/src/inpainting_fsr.impl.hpp @@ -318,7 +318,7 @@ icvGetTodoBlocks(Mat& sampled_img, Mat& sampling_mask, std::vector< std::tuple< Mat error_mask_2d = sampling_mask(Range(yblock_counter*block_size - top_border, std::min(img_height, (yblock_counter*block_size + block_size + bottom_border))), Range(xblock_counter*block_size - left_border, std::min(img_width, (xblock_counter*block_size + block_size + right_border)))); // determine normalized and weighted standard deviation - if (block_size > block_size_min) + if (block_size > block_size_min && xblock_counter < sigma_n_array.cols && yblock_counter < sigma_n_array.rows) { double sigma_n = icvStandardDeviation(distorted_block_2d, error_mask_2d); sigma_n_array.at( yblock_counter, xblock_counter) = sigma_n; From d9e700891ab8e2f5fc5d62b0a096d0beaec05ad0 Mon Sep 17 00:00:00 2001 From: Dan Date: Tue, 25 Jan 2022 16:38:38 +0100 Subject: [PATCH 05/16] Merge pull request #3157 from danopdev:4.x https://github.com/opencv/opencv_contrib/issues/1578 --- modules/xphoto/src/inpainting.cpp | 3 +++ modules/xphoto/src/photomontage.hpp | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/modules/xphoto/src/inpainting.cpp b/modules/xphoto/src/inpainting.cpp index abbce4c4e2..d6a406ca62 100644 --- a/modules/xphoto/src/inpainting.cpp +++ b/modules/xphoto/src/inpainting.cpp @@ -281,6 +281,9 @@ namespace xphoto /** Writing result **/ for (size_t i = 0; i < labelSeq.size(); ++i) { + if (pPath[i].x >= img.cols || pPath[i].y >= img.rows) + continue; + cv::Vec val = pointSeq[i][labelSeq[i]]; img.template at >(pPath[i]) = val; } diff --git a/modules/xphoto/src/photomontage.hpp b/modules/xphoto/src/photomontage.hpp index 2f790b4887..bdafb360dc 100644 --- a/modules/xphoto/src/photomontage.hpp +++ b/modules/xphoto/src/photomontage.hpp @@ -132,6 +132,12 @@ template void Photomontage :: setWeights(GCGraph &graph, const int idx1, const int idx2, const int l1, const int l2, const int lx) { + if ((size_t)idx1 >= pointSeq.size() || (size_t)idx2 >= pointSeq.size() + || (size_t)l1 >= pointSeq[idx1].size() || (size_t)l1 >= pointSeq[idx2].size() + || (size_t)l2 >= pointSeq[idx1].size() || (size_t)l2 >= pointSeq[idx2].size() + || (size_t)lx >= pointSeq[idx1].size() || (size_t)lx >= pointSeq[idx2].size()) + return; + if (l1 == l2) { /** Link from A to B **/ From 678d0fc5be01c476449e771ae72d15a29ba34448 Mon Sep 17 00:00:00 2001 From: Pavel Rojtberg Date: Tue, 25 Jan 2022 18:27:56 +0100 Subject: [PATCH 06/16] ovis: handle bg material change after shader genration --- modules/ovis/src/ovis.cpp | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/modules/ovis/src/ovis.cpp b/modules/ovis/src/ovis.cpp index c685fb0425..1fa6db9295 100644 --- a/modules/ovis/src/ovis.cpp +++ b/modules/ovis/src/ovis.cpp @@ -443,14 +443,21 @@ class WindowSceneImpl : public WindowScene _createTexture(name, image.getMat()); + // ensure bgplane is visible + bgplane->setVisible(true); bgplane->setDefaultUVs(); - Pass* rpass = bgplane->getMaterial()->getBestTechnique()->getPasses()[0]; - rpass->getTextureUnitStates()[0]->setTextureName(name); - rpass->getTextureUnitStates()[0]->setTextureAddressingMode(TAM_CLAMP); + Pass* rpass = bgplane->getMaterial()->getTechnique(0)->getPasses()[0]; + auto tus = rpass->getTextureUnitStates()[0]; - // ensure bgplane is visible - bgplane->setVisible(true); + if(tus->getTextureName() != name) + { + RTShader::ShaderGenerator::getSingleton().invalidateMaterial( + RTShader::ShaderGenerator::DEFAULT_SCHEME_NAME, *bgplane->getMaterial()); + + tus->setTextureName(name); + tus->setTextureAddressingMode(TAM_CLAMP); + } } void setCompositors(const std::vector& names) CV_OVERRIDE From 64a639e1830eaa36c4f0ab855b2b822f25493909 Mon Sep 17 00:00:00 2001 From: Suleyman TURKMEN Date: Wed, 26 Jan 2022 12:01:28 +0300 Subject: [PATCH 07/16] Update ISSUE_TEMPLATE.md and PULL_REQUEST_TEMPLATE.md --- .github/ISSUE_TEMPLATE.md | 26 ++++++++++++++++---------- .github/PULL_REQUEST_TEMPLATE.md | 6 +++--- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 595ce5c29f..309524886e 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,3 +1,10 @@ + + ##### System information (version) - [ ] I checked the problem with documentation, FAQ, open issues, - answers.opencv.org, Stack Overflow, etc and have not found solution + forum.opencv.org, Stack Overflow, etc and have not found any solution - - [ ] I updated to latest OpenCV version and the issue is still there + - [ ] I updated to the latest OpenCV version and the issue is still there - [ ] There is reproducer code and related data files: videos, images, onnx, etc diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 0454d1aed8..5e2e911cc8 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -3,9 +3,9 @@ See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [ ] I agree to contribute to the project under Apache 2 License. -- [ ] 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 -- [ ] The PR is proposed to proper branch -- [ ] There is reference to original bug report and related work +- [ ] 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 From 081bebdc4cd099aec942a802a3f72a754e2bd7da Mon Sep 17 00:00:00 2001 From: Suleyman TURKMEN Date: Wed, 26 Jan 2022 11:04:36 +0300 Subject: [PATCH 08/16] improvements on EdgeDrawing --- .../include/opencv2/ximgproc/edge_drawing.hpp | 58 +++++++++++++------ modules/ximgproc/src/edge_drawing.cpp | 7 +++ 2 files changed, 48 insertions(+), 17 deletions(-) diff --git a/modules/ximgproc/include/opencv2/ximgproc/edge_drawing.hpp b/modules/ximgproc/include/opencv2/ximgproc/edge_drawing.hpp index e33b23b089..c1a3658cd6 100644 --- a/modules/ximgproc/include/opencv2/ximgproc/edge_drawing.hpp +++ b/modules/ximgproc/include/opencv2/ximgproc/edge_drawing.hpp @@ -25,61 +25,84 @@ class CV_EXPORTS_W EdgeDrawing : public Algorithm enum GradientOperator { PREWITT = 0, - SOBEL = 1, - SCHARR = 2, - LSD = 3 + SOBEL = 1, + SCHARR = 2, + LSD = 3 }; struct CV_EXPORTS_W_SIMPLE Params { CV_WRAP Params(); - //! Parameter Free mode will be activated when this value is true. + //! Parameter Free mode will be activated when this value is set as true. Default value is false. CV_PROP_RW bool PFmode; - //! indicates the operator used for gradient calculation.The following operation flags are available(cv::ximgproc::EdgeDrawing::GradientOperator) + /** @brief indicates the operator used for gradient calculation. + + one of the flags cv::ximgproc::EdgeDrawing::GradientOperator. Default value is PREWITT + */ CV_PROP_RW int EdgeDetectionOperator; - //! threshold value used to create gradient image. + //! threshold value of gradiential difference between pixels. Used to create gradient image. Default value is 20 CV_PROP_RW int GradientThresholdValue; - //! threshold value used to create gradient image. + //! threshold value used to select anchor points. Default value is 0 CV_PROP_RW int AnchorThresholdValue; + //! Default value is 1 CV_PROP_RW int ScanInterval; - //! minimun connected pixels length processed to create an edge segment. + /** @brief minimun connected pixels length processed to create an edge segment. + + in gradient image, minimum connected pixels length processed to create an edge segment. pixels having upper value than GradientThresholdValue + will be processed. Default value is 10 + */ CV_PROP_RW int MinPathLength; - //! sigma value for internal GaussianBlur() function. + //! sigma value for internal GaussianBlur() function. Default value is 1.0 CV_PROP_RW float Sigma; CV_PROP_RW bool SumFlag; - //! when this value is true NFA (Number of False Alarms) algorithm will be used for line and ellipse validation. + //! Default value is true. indicates if NFA (Number of False Alarms) algorithm will be used for line and ellipse validation. CV_PROP_RW bool NFAValidation; //! minimun line length to detect. CV_PROP_RW int MinLineLength; + //! Default value is 6.0 CV_PROP_RW double MaxDistanceBetweenTwoLines; + //! Default value is 1.0 CV_PROP_RW double LineFitErrorThreshold; + //! Default value is 1.3 CV_PROP_RW double MaxErrorThreshold; void read(const FileNode& fn); void write(FileStorage& fs) const; }; - /** @brief Detects edges and prepares them to detect lines and ellipses. + /** @brief Detects edges in a grayscale image and prepares them to detect lines and ellipses. - @param src input image + @param src 8-bit, single-channel, grayscale input image. */ CV_WRAP virtual void detectEdges(InputArray src) = 0; + + /** @brief returns Edge Image prepared by detectEdges() function. + + @param dst returns 8-bit, single-channel output image. + */ CV_WRAP virtual void getEdgeImage(OutputArray dst) = 0; + + /** @brief returns Gradient Image prepared by detectEdges() function. + + @param dst returns 16-bit, single-channel output image. + */ CV_WRAP virtual void getGradientImage(OutputArray dst) = 0; + /** @brief returns Edge Segments prepared by detectEdges() function. + */ CV_WRAP virtual std::vector > getSegments() = 0; /** @brief Detects lines. - @param lines output Vec<4f> contains start point and end point of detected lines. - @note you should call detectEdges() method before call this. + @param lines output Vec<4f> contains the start point and the end point of detected lines. + @note you should call detectEdges() before calling this function. */ CV_WRAP virtual void detectLines(OutputArray lines) = 0; /** @brief Detects circles and ellipses. - @param ellipses output Vec<6d> contains center point and perimeter for circles. - @note you should call detectEdges() method before call this. + @param ellipses output Vec<6d> contains center point and perimeter for circles, center point, axes and angle for ellipses. + @note you should call detectEdges() before calling this function. */ CV_WRAP virtual void detectEllipses(OutputArray ellipses) = 0; @@ -87,7 +110,8 @@ class CV_EXPORTS_W EdgeDrawing : public Algorithm /** @brief sets parameters. - this function is meant to be used for parameter setting in other languages than c++. + this function is meant to be used for parameter setting in other languages than c++ like python. + @param parameters */ CV_WRAP void setParams(const EdgeDrawing::Params& parameters); virtual ~EdgeDrawing() { } diff --git a/modules/ximgproc/src/edge_drawing.cpp b/modules/ximgproc/src/edge_drawing.cpp index 62166f98c8..25051b6365 100644 --- a/modules/ximgproc/src/edge_drawing.cpp +++ b/modules/ximgproc/src/edge_drawing.cpp @@ -112,6 +112,7 @@ class EdgeDrawingImpl : public EdgeDrawing void detectLines(OutputArray lines) CV_OVERRIDE; void detectEllipses(OutputArray ellipses) CV_OVERRIDE; + virtual String getDefaultName() const CV_OVERRIDE; virtual void read(const FileNode& fn) CV_OVERRIDE; virtual void write(FileStorage& fs) const CV_OVERRIDE; @@ -315,6 +316,11 @@ void EdgeDrawing::Params::write(cv::FileStorage& fs) const fs << "MaxErrorThreshold" << MaxErrorThreshold; } +String EdgeDrawingImpl::getDefaultName() const +{ + return String("EdgeDrawing"); +} + void EdgeDrawingImpl::read(const cv::FileNode& fn) { params.read(fn); @@ -343,6 +349,7 @@ EdgeDrawingImpl::~EdgeDrawingImpl() void EdgeDrawingImpl::detectEdges(InputArray src) { + CV_Assert(!src.empty() && src.type() == CV_8UC1); gradThresh = params.GradientThresholdValue; anchorThresh = params.AnchorThresholdValue; op = params.EdgeDetectionOperator; From 00b33cbd8fd50c91e76fc8ec7a3b9342497dd923 Mon Sep 17 00:00:00 2001 From: Suleyman TURKMEN Date: Sat, 29 Jan 2022 00:32:28 +0300 Subject: [PATCH 09/16] Update edge_drawing.hpp --- .../include/opencv2/ximgproc/edge_drawing.hpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/modules/ximgproc/include/opencv2/ximgproc/edge_drawing.hpp b/modules/ximgproc/include/opencv2/ximgproc/edge_drawing.hpp index c1a3658cd6..6842b35ca4 100644 --- a/modules/ximgproc/include/opencv2/ximgproc/edge_drawing.hpp +++ b/modules/ximgproc/include/opencv2/ximgproc/edge_drawing.hpp @@ -37,7 +37,7 @@ class CV_EXPORTS_W EdgeDrawing : public Algorithm CV_PROP_RW bool PFmode; /** @brief indicates the operator used for gradient calculation. - one of the flags cv::ximgproc::EdgeDrawing::GradientOperator. Default value is PREWITT + one of the flags cv::ximgproc::EdgeDrawing::GradientOperator. Default value is PREWITT */ CV_PROP_RW int EdgeDetectionOperator; //! threshold value of gradiential difference between pixels. Used to create gradient image. Default value is 20 @@ -48,8 +48,8 @@ class CV_EXPORTS_W EdgeDrawing : public Algorithm CV_PROP_RW int ScanInterval; /** @brief minimun connected pixels length processed to create an edge segment. - in gradient image, minimum connected pixels length processed to create an edge segment. pixels having upper value than GradientThresholdValue - will be processed. Default value is 10 + in gradient image, minimum connected pixels length processed to create an edge segment. pixels having upper value than GradientThresholdValue + will be processed. Default value is 10 */ CV_PROP_RW int MinPathLength; //! sigma value for internal GaussianBlur() function. Default value is 1.0 @@ -59,11 +59,11 @@ class CV_EXPORTS_W EdgeDrawing : public Algorithm CV_PROP_RW bool NFAValidation; //! minimun line length to detect. CV_PROP_RW int MinLineLength; - //! Default value is 6.0 + //! Default value is 6.0 CV_PROP_RW double MaxDistanceBetweenTwoLines; - //! Default value is 1.0 + //! Default value is 1.0 CV_PROP_RW double LineFitErrorThreshold; - //! Default value is 1.3 + //! Default value is 1.3 CV_PROP_RW double MaxErrorThreshold; void read(const FileNode& fn); @@ -88,7 +88,7 @@ class CV_EXPORTS_W EdgeDrawing : public Algorithm */ CV_WRAP virtual void getGradientImage(OutputArray dst) = 0; - /** @brief returns Edge Segments prepared by detectEdges() function. + /** @brief Returns std::vector> of detected edge segments, see detectEdges() */ CV_WRAP virtual std::vector > getSegments() = 0; @@ -111,7 +111,7 @@ class CV_EXPORTS_W EdgeDrawing : public Algorithm /** @brief sets parameters. this function is meant to be used for parameter setting in other languages than c++ like python. - @param parameters + @param parameters */ CV_WRAP void setParams(const EdgeDrawing::Params& parameters); virtual ~EdgeDrawing() { } From fa50e84c4c6638b64e67e889547f3f0db932365e Mon Sep 17 00:00:00 2001 From: Aditya Mehrotra Date: Sat, 29 Jan 2022 11:14:36 -0500 Subject: [PATCH 10/16] Grammatically updated the readme --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index f2c0d0f023..8d3ecda041 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ This repository is intended for the development of so-called "extra" modules, contributed functionality. New modules quite often do not have stable API, -and they are not well-tested. Thus, they shouldn't be released as a part of +and they are not well-tested. Thus, they shouldn't be released as a part of the official OpenCV distribution, since the library maintains binary compatibility, and tries to provide decent performance and stability. @@ -33,7 +33,7 @@ $ cmake -DOPENCV_EXTRA_MODULES_PATH=/modules -DBUILD_opencv_lega If you also want to build the samples from the "samples" folder of each module, also include the "-DBUILD_EXAMPLES=ON" option. -If you prefer using the gui version of cmake (cmake-gui), then, you can add `opencv_contrib` modules within `opencv` core by doing the following: +If you prefer using the GUI version of CMake (cmake-gui), then, you can add `opencv_contrib` modules within `opencv` core by doing the following: 1. Start cmake-gui. @@ -55,6 +55,6 @@ If you prefer using the gui version of cmake (cmake-gui), then, you can add `ope In order to keep a clean overview containing all contributed modules, the following files need to be created/adapted: -1. Update the README.md file under the modules folder. Here, you add your model with a single line description. +1. Update the README.md file under the modules folder. Here, you add your model with a single-line description. -2. Add a README.md inside your own module folder. This README explains which functionality (separate functions) is available, links to the corresponding samples and explains in somewhat more detail what the module is expected to do. If any extra requirements are needed to build the module without problems, add them here also. +2. Add a README.md inside your own module folder. This README explains which functionality (separate functions) is available, links to the corresponding samples, and explains in somewhat more detail what the module is expected to do. If any extra requirements are needed to build the module without problems, add them here also. From d72a91f006172101cfc1d10b405ad99f6876d69b Mon Sep 17 00:00:00 2001 From: AleksandrPanov Date: Tue, 1 Feb 2022 19:15:58 +0300 Subject: [PATCH 11/16] fixed bad shape of markers (1x4) in several cases and added tests --- modules/aruco/src/aruco.cpp | 28 +++--------------- modules/aruco/test/test_charucodetection.cpp | 31 ++++++++++++++++++++ 2 files changed, 35 insertions(+), 24 deletions(-) diff --git a/modules/aruco/src/aruco.cpp b/modules/aruco/src/aruco.cpp index 0de4b73f8e..e365f00564 100644 --- a/modules/aruco/src/aruco.cpp +++ b/modules/aruco/src/aruco.cpp @@ -1486,7 +1486,7 @@ void refineDetectedMarkers(InputArray _image, const Ptr &_board, _convertToGrey(_image, grey); // vector of final detected marker corners and ids - vector< Mat > finalAcceptedCorners; + vector > finalAcceptedCorners; vector< int > finalAcceptedIds; // fill with the current markers finalAcceptedCorners.resize(_detectedCorners.total()); @@ -1597,38 +1597,18 @@ void refineDetectedMarkers(InputArray _image, const Ptr &_board, // parse output if(finalAcceptedIds.size() != _detectedIds.total()) { - _detectedCorners.clear(); - _detectedIds.clear(); - // parse output Mat(finalAcceptedIds).copyTo(_detectedIds); - - _detectedCorners.create((int)finalAcceptedCorners.size(), 1, CV_32FC2); - for(unsigned int i = 0; i < finalAcceptedCorners.size(); i++) { - _detectedCorners.create(4, 1, CV_32FC2, i, true); - for(int j = 0; j < 4; j++) { - _detectedCorners.getMat(i).ptr< Point2f >()[j] = - finalAcceptedCorners[i].ptr< Point2f >()[j]; - } - } + _copyVector2Output(finalAcceptedCorners, _detectedCorners); // recalculate _rejectedCorners based on alreadyIdentified - vector< Mat > finalRejected; + vector > finalRejected; for(unsigned int i = 0; i < alreadyIdentified.size(); i++) { if(!alreadyIdentified[i]) { finalRejected.push_back(_rejectedCorners.getMat(i).clone()); } } - - _rejectedCorners.clear(); - _rejectedCorners.create((int)finalRejected.size(), 1, CV_32FC2); - for(unsigned int i = 0; i < finalRejected.size(); i++) { - _rejectedCorners.create(4, 1, CV_32FC2, i, true); - for(int j = 0; j < 4; j++) { - _rejectedCorners.getMat(i).ptr< Point2f >()[j] = - finalRejected[i].ptr< Point2f >()[j]; - } - } + _copyVector2Output(finalRejected, _rejectedCorners); if(_recoveredIdxs.needed()) { Mat(recoveredIdxs).copyTo(_recoveredIdxs); diff --git a/modules/aruco/test/test_charucodetection.cpp b/modules/aruco/test/test_charucodetection.cpp index e9cc4a3234..a36623c94c 100644 --- a/modules/aruco/test/test_charucodetection.cpp +++ b/modules/aruco/test/test_charucodetection.cpp @@ -793,4 +793,35 @@ TEST(CV_ArucoTutorial, can_find_diamondmarkers) EXPECT_EQ(counterGoldCornersIds, counterRes); // check the number of ArUco markers } +TEST(Charuco, issue_14014) +{ + string imgPath = cvtest::findDataFile("aruco/recover.png"); + Mat img = imread(imgPath); + + Ptr dict = aruco::getPredefinedDictionary(aruco::PREDEFINED_DICTIONARY_NAME(cv::aruco::DICT_7X7_250)); + Ptr board = aruco::CharucoBoard::create(8, 5, 0.03455f, 0.02164f, dict); + Ptr detectorParams = aruco::DetectorParameters::create(); + detectorParams->cornerRefinementMethod = aruco::CORNER_REFINE_SUBPIX; + detectorParams->cornerRefinementMinAccuracy = 0.01; + + vector corners, rejectedPoints; + vector ids; + + aruco::detectMarkers(img, dict, corners, ids, detectorParams, rejectedPoints); + + ASSERT_EQ(corners.size(), 19ull); + EXPECT_EQ(Size(4, 1), corners[0].size()); // check dimension of detected corners + + ASSERT_EQ(rejectedPoints.size(), 21ull); + EXPECT_EQ(Size(4, 1), rejectedPoints[0].size()); // check dimension of detected corners + + aruco::refineDetectedMarkers(img, board, corners, ids, rejectedPoints); + + ASSERT_EQ(corners.size(), 20ull); + EXPECT_EQ(Size(4, 1), corners[0].size()); // check dimension of rejected corners after successfully refine + + ASSERT_EQ(rejectedPoints.size(), 20ull); + EXPECT_EQ(Size(4, 1), rejectedPoints[0].size()); // check dimension of rejected corners after successfully refine +} + }} // namespace From 54a7c7c6279756f56baa9697386dd1ae45cf16d2 Mon Sep 17 00:00:00 2001 From: cudawarped <12133430+cudawarped@users.noreply.github.com> Date: Wed, 2 Feb 2022 09:11:55 +0000 Subject: [PATCH 12/16] Fix bug due to missing step. (cherry picked from commit 1d207e772285dd2d99eed886c70cb342f0dba8e4) --- modules/cudafilters/src/filtering.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/cudafilters/src/filtering.cpp b/modules/cudafilters/src/filtering.cpp index 764e6f6309..daab3acde1 100644 --- a/modules/cudafilters/src/filtering.cpp +++ b/modules/cudafilters/src/filtering.cpp @@ -281,7 +281,7 @@ namespace Size wholeSize; src.locateROI(wholeSize, ofs); - GpuMat srcWhole(wholeSize, src.type(), src.datastart); + GpuMat srcWhole(wholeSize, src.type(), src.datastart, src.step); func_(srcWhole, ofs.x, ofs.y, dst, kernel_.ptr(), kernel_.cols, kernel_.rows, anchor_.x, anchor_.y, From 55cbe767acaf8dcc4ec078255d6c02f2182657be Mon Sep 17 00:00:00 2001 From: cudawarped <12133430+cudawarped@users.noreply.github.com> Date: Wed, 2 Feb 2022 14:26:56 +0000 Subject: [PATCH 13/16] Add locateROI test case to check GpuMat::dataend calculation. --- modules/cudaarithm/test/test_gpumat.cpp | 43 +++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/modules/cudaarithm/test/test_gpumat.cpp b/modules/cudaarithm/test/test_gpumat.cpp index b4d59b1644..b9dba371dc 100644 --- a/modules/cudaarithm/test/test_gpumat.cpp +++ b/modules/cudaarithm/test/test_gpumat.cpp @@ -386,6 +386,49 @@ INSTANTIATE_TEST_CASE_P(CUDA, GpuMat_ConvertTo, testing::Combine( ALL_DEPTH, WHOLE_SUBMAT)); +//////////////////////////////////////////////////////////////////////////////// +// locateROI + +PARAM_TEST_CASE(GpuMat_LocateROI, cv::cuda::DeviceInfo, cv::Size, MatDepth, UseRoi) +{ + cv::cuda::DeviceInfo devInfo; + cv::Size size; + int depth; + bool useRoi; + + virtual void SetUp() + { + devInfo = GET_PARAM(0); + size = GET_PARAM(1); + depth = GET_PARAM(2); + useRoi = GET_PARAM(3); + + cv::cuda::setDevice(devInfo.deviceID()); + } +}; + +CUDA_TEST_P(GpuMat_LocateROI, locateROI) +{ + Point ofsGold; + Size wholeSizeGold; + GpuMat src = createMat(size, depth, wholeSizeGold, ofsGold, useRoi); + + Point ofs; + Size wholeSize; + src.locateROI(wholeSize, ofs); + ASSERT_TRUE(ofs == ofsGold && wholeSize == wholeSizeGold); + + GpuMat srcPtr(src.size(), src.type(), src.data, src.step); + src.locateROI(wholeSize, ofs); + ASSERT_TRUE(ofs == ofsGold && wholeSize == wholeSizeGold); +} + +INSTANTIATE_TEST_CASE_P(CUDA, GpuMat_LocateROI, testing::Combine( + ALL_DEVICES, + DIFFERENT_SIZES, + ALL_DEPTH, + WHOLE_SUBMAT)); + //////////////////////////////////////////////////////////////////////////////// // ensureSizeIsEnough From f1200c5fbf2ec7a195c2877f58a5cafb845f7ae9 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Thu, 3 Feb 2022 11:36:06 +0000 Subject: [PATCH 14/16] cmake(sfm): fix sfm static target dependencies --- modules/sfm/src/libmv_light/libmv/correspondence/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/sfm/src/libmv_light/libmv/correspondence/CMakeLists.txt b/modules/sfm/src/libmv_light/libmv/correspondence/CMakeLists.txt index 95dbebe447..b950665325 100644 --- a/modules/sfm/src/libmv_light/libmv/correspondence/CMakeLists.txt +++ b/modules/sfm/src/libmv_light/libmv/correspondence/CMakeLists.txt @@ -8,7 +8,7 @@ FILE(GLOB CORRESPONDENCE_HDRS *.h) ADD_LIBRARY(correspondence STATIC ${CORRESPONDENCE_SRC} ${CORRESPONDENCE_HDRS}) -TARGET_LINK_LIBRARIES(correspondence LINK_PRIVATE ${GLOG_LIBRARIES} multiview) +ocv_target_link_libraries(correspondence LINK_PRIVATE ${GLOG_LIBRARIES} multiview opencv_imgcodecs) IF(TARGET Eigen3::Eigen) TARGET_LINK_LIBRARIES(correspondence LINK_PUBLIC Eigen3::Eigen) ENDIF() From 80eb045bf6276e249797856d73d61f9548a497d7 Mon Sep 17 00:00:00 2001 From: Stefano Allegretti Date: Thu, 3 Feb 2022 17:49:41 +0100 Subject: [PATCH 15/16] Add Connected Components Labeling in CUDA --- modules/cudaimgproc/doc/cudaimgproc.bib | 10 + .../include/opencv2/cudaimgproc.hpp | 44 ++ .../samples/connected_components.cpp | 58 +++ .../cudaimgproc/src/connectedcomponents.cpp | 49 ++ .../src/cuda/connectedcomponents.cu | 345 +++++++++++++ .../test/test_connectedcomponents.cpp | 479 ++++++++++++++++++ 6 files changed, 985 insertions(+) create mode 100644 modules/cudaimgproc/doc/cudaimgproc.bib create mode 100644 modules/cudaimgproc/samples/connected_components.cpp create mode 100644 modules/cudaimgproc/src/connectedcomponents.cpp create mode 100644 modules/cudaimgproc/src/cuda/connectedcomponents.cu create mode 100644 modules/cudaimgproc/test/test_connectedcomponents.cpp diff --git a/modules/cudaimgproc/doc/cudaimgproc.bib b/modules/cudaimgproc/doc/cudaimgproc.bib new file mode 100644 index 0000000000..cb36ade7f5 --- /dev/null +++ b/modules/cudaimgproc/doc/cudaimgproc.bib @@ -0,0 +1,10 @@ +@article{Allegretti2019, + title={Optimized block-based algorithms to label connected components on GPUs}, + author={Allegretti, Stefano and Bolelli, Federico and Grana, Costantino}, + journal={IEEE Transactions on Parallel and Distributed Systems}, + volume={31}, + number={2}, + pages={423--438}, + year={2019}, + publisher={IEEE} +} diff --git a/modules/cudaimgproc/include/opencv2/cudaimgproc.hpp b/modules/cudaimgproc/include/opencv2/cudaimgproc.hpp index 038dc9d053..e9ad379b95 100644 --- a/modules/cudaimgproc/include/opencv2/cudaimgproc.hpp +++ b/modules/cudaimgproc/include/opencv2/cudaimgproc.hpp @@ -731,6 +731,50 @@ type. CV_EXPORTS_W void blendLinear(InputArray img1, InputArray img2, InputArray weights1, InputArray weights2, OutputArray result, Stream& stream = Stream::Null()); +/////////////////// Connected Components Labeling ///////////////////// + +//! Connected Components Algorithm +enum ConnectedComponentsAlgorithmsTypes { + CCL_DEFAULT = -1, //!< BKE @cite Allegretti2019 algorithm for 8-way connectivity. + CCL_BKE = 0, //!< BKE @cite Allegretti2019 algorithm for 8-way connectivity. +}; + + +/** @brief Computes the Connected Components Labeled image of a binary image. + +The function takes as input a binary image and performs Connected Components Labeling. The output +is an image where each Connected Component is assigned a unique label (integer value). +ltype specifies the output label image type, an important consideration based on the total +number of labels or alternatively the total number of pixels in the source image. +ccltype specifies the connected components labeling algorithm to use, currently +BKE @cite Allegretti2019 is supported, see the #ConnectedComponentsAlgorithmsTypes +for details. Note that labels in the output are not required to be sequential. + +@param image The 8-bit single-channel image to be labeled. +@param labels Destination labeled image. +@param connectivity Connectivity to use for the labeling procedure. 8 for 8-way connectivity is supported. +@param ltype Output image label type. Currently CV_32S is supported. +@param ccltype Connected components algorithm type (see the #ConnectedComponentsAlgorithmsTypes). + +@note A sample program demonstrating Connected Components Labeling in CUDA can be found at\n +opencv_contrib_source_code/modules/cudaimgproc/samples/connected_components.cpp + +*/ +CV_EXPORTS_AS(connectedComponentsWithAlgorithm) void connectedComponents(InputArray image, OutputArray labels, + int connectivity, int ltype, cv::cuda::ConnectedComponentsAlgorithmsTypes ccltype); + + +/** @overload + +@param image The 8-bit single-channel image to be labeled. +@param labels Destination labeled image. +@param connectivity Connectivity to use for the labeling procedure. 8 for 8-way connectivity is supported. +@param ltype Output image label type. Currently CV_32S is supported. +*/ +CV_EXPORTS_W void connectedComponents(InputArray image, OutputArray labels, + int connectivity = 8, int ltype = CV_32S); + + //! @} }} // namespace cv { namespace cuda { diff --git a/modules/cudaimgproc/samples/connected_components.cpp b/modules/cudaimgproc/samples/connected_components.cpp new file mode 100644 index 0000000000..0998a6a1c6 --- /dev/null +++ b/modules/cudaimgproc/samples/connected_components.cpp @@ -0,0 +1,58 @@ +#include + +#include +#include "opencv2/imgproc.hpp" +#include "opencv2/imgcodecs.hpp" +#include "opencv2/highgui.hpp" +#include "opencv2/cudaimgproc.hpp" + +using namespace cv; +using namespace std; +using namespace cv::cuda; + + +void colorLabels(const Mat1i& labels, Mat3b& colors) { + colors.create(labels.size()); + for (int r = 0; r < labels.rows; ++r) { + int const* labels_row = labels.ptr(r); + Vec3b* colors_row = colors.ptr(r); + for (int c = 0; c < labels.cols; ++c) { + colors_row[c] = Vec3b(labels_row[c] * 131 % 255, labels_row[c] * 241 % 255, labels_row[c] * 251 % 255); + } + } +} + + +int main(int argc, const char** argv) +{ + CommandLineParser parser(argc, argv, "{@image|stuff.jpg|image for converting to a grayscale}"); + parser.about("This program finds connected components in a binary image and assign each of them a different color.\n" + "The connected components labeling is performed in GPU.\n"); + parser.printMessage(); + + String inputImage = parser.get(0); + Mat1b img = imread(samples::findFile(inputImage), IMREAD_GRAYSCALE); + Mat1i labels; + + if (img.empty()) + { + cout << "Could not read input image file: " << inputImage << endl; + return EXIT_FAILURE; + } + + + GpuMat d_img, d_labels; + d_img.upload(img); + + cuda::connectedComponents(d_img, d_labels, 8, CV_32S); + + d_labels.download(labels); + + Mat3b colors; + colorLabels(labels, colors); + + imshow("Labels", colors); + waitKey(0); + + return EXIT_SUCCESS; +} diff --git a/modules/cudaimgproc/src/connectedcomponents.cpp b/modules/cudaimgproc/src/connectedcomponents.cpp new file mode 100644 index 0000000000..e2e2bde057 --- /dev/null +++ b/modules/cudaimgproc/src/connectedcomponents.cpp @@ -0,0 +1,49 @@ +// 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" + +using namespace cv; +using namespace cv::cuda; + +#if !defined (HAVE_CUDA) || defined (CUDA_DISABLER) + +void cv::cuda::connectedComponents(InputArray img_, OutputArray labels_, int connectivity, + int ltype, ConnectedComponentsAlgorithmsTypes ccltype) { throw_no_cuda(); } + +#else /* !defined (HAVE_CUDA) */ + +namespace cv { namespace cuda { namespace device { namespace imgproc { + void BlockBasedKomuraEquivalence(const cv::cuda::GpuMat& img, cv::cuda::GpuMat& labels); +}}}} + + +void cv::cuda::connectedComponents(InputArray img_, OutputArray labels_, int connectivity, + int ltype, ConnectedComponentsAlgorithmsTypes ccltype) { + const cv::cuda::GpuMat img = img_.getGpuMat(); + cv::cuda::GpuMat& labels = labels_.getGpuMatRef(); + + CV_Assert(img.channels() == 1); + CV_Assert(connectivity == 8); + CV_Assert(ltype == CV_32S); + CV_Assert(ccltype == CCL_BKE || ccltype == CCL_DEFAULT); + + int iDepth = img_.depth(); + CV_Assert(iDepth == CV_8U || iDepth == CV_8S); + + labels.create(img.size(), CV_MAT_DEPTH(ltype)); + + if ((ccltype == CCL_BKE || ccltype == CCL_DEFAULT) && connectivity == 8 && ltype == CV_32S) { + using cv::cuda::device::imgproc::BlockBasedKomuraEquivalence; + BlockBasedKomuraEquivalence(img, labels); + } + +} + +void cv::cuda::connectedComponents(InputArray img_, OutputArray labels_, int connectivity, int ltype) { + cv::cuda::connectedComponents(img_, labels_, connectivity, ltype, CCL_DEFAULT); +} + + +#endif /* !defined (HAVE_CUDA) */ diff --git a/modules/cudaimgproc/src/cuda/connectedcomponents.cu b/modules/cudaimgproc/src/cuda/connectedcomponents.cu new file mode 100644 index 0000000000..4946fa2627 --- /dev/null +++ b/modules/cudaimgproc/src/cuda/connectedcomponents.cu @@ -0,0 +1,345 @@ +// 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. + +#if !defined CUDA_DISABLER + +#include "opencv2/core/cuda/common.hpp" +#include "opencv2/core/cuda/emulation.hpp" +#include "opencv2/core/cuda/transform.hpp" +#include "opencv2/core/cuda/functional.hpp" +#include "opencv2/core/cuda/utility.hpp" +#include "opencv2/core/cuda.hpp" + +using namespace cv::cuda; +using namespace cv::cuda::device; + + +namespace cv { namespace cuda { namespace device { namespace imgproc { + +constexpr int kblock_rows = 16; +constexpr int kblock_cols = 16; + +namespace { +enum class Info : unsigned char { a = 0, b = 1, c = 2, d = 3, P = 4, Q = 5, R = 6, S = 7 }; + +// Only use it with unsigned numeric types +template +__device__ __forceinline__ unsigned char HasBit(T bitmap, Info pos) { + return (bitmap >> static_cast(pos)) & 1; +} + +template +__device__ __forceinline__ unsigned char HasBit(T bitmap, unsigned char pos) { + return (bitmap >> pos) & 1; +} + +// Only use it with unsigned numeric types +__device__ __forceinline__ void SetBit(unsigned char& bitmap, Info pos) { + bitmap |= (1 << static_cast(pos)); +} + +// Returns the root index of the UFTree +__device__ unsigned Find(const int* s_buf, unsigned n) { + while (s_buf[n] != n) { + n = s_buf[n]; + } + return n; +} + +__device__ unsigned FindAndCompress(int* s_buf, unsigned n) { + unsigned id = n; + while (s_buf[n] != n) { + n = s_buf[n]; + s_buf[id] = n; + } + return n; +} + +// Merges the UFTrees of a and b, linking one root to the other +__device__ void Union(int* s_buf, unsigned a, unsigned b) { + + bool done; + + do { + + a = Find(s_buf, a); + b = Find(s_buf, b); + + if (a < b) { + int old = atomicMin(s_buf + b, a); + done = (old == b); + b = old; + } + else if (b < a) { + int old = atomicMin(s_buf + a, b); + done = (old == a); + a = old; + } + else { + done = true; + } + + } while (!done); + +} + + +__global__ void InitLabeling(const cuda::PtrStepSzb img, cuda::PtrStepSzi labels, unsigned char* last_pixel) { + unsigned row = (blockIdx.y * kblock_rows + threadIdx.y) * 2; + unsigned col = (blockIdx.x * kblock_cols + threadIdx.x) * 2; + unsigned img_index = row * img.step + col; + unsigned labels_index = row * (labels.step / labels.elem_size) + col; + + if (row < labels.rows && col < labels.cols) { + + unsigned P = 0; + + // Bitmask representing two kinds of information + // Bits 0, 1, 2, 3 are set if pixel a, b, c, d are foreground, respectively + // Bits 4, 5, 6, 7 are set if block P, Q, R, S need to be merged to X in Merge phase + unsigned char info = 0; + + char buffer alignas(int)[4]; + *(reinterpret_cast(buffer)) = 0; + + // Read pairs of consecutive values in memory at once + if (col + 1 < img.cols) { + // This does not depend on endianness + *(reinterpret_cast(buffer)) = *(reinterpret_cast(img.data + img_index)); + + if (row + 1 < img.rows) { + *(reinterpret_cast(buffer + 2)) = *(reinterpret_cast(img.data + img_index + img.step)); + } + } + else { + buffer[0] = img.data[img_index]; + + if (row + 1 < img.rows) { + buffer[2] = img.data[img_index + img.step]; + } + } + + if (buffer[0]) { + P |= 0x777; + SetBit(info, Info::a); + } + if (buffer[1]) { + P |= (0x777 << 1); + SetBit(info, Info::b); + } + if (buffer[2]) { + P |= (0x777 << 4); + SetBit(info, Info::c); + } + if (buffer[3]) { + SetBit(info, Info::d); + } + + if (col == 0) { + P &= 0xEEEE; + } + if (col + 1 >= img.cols) { + P &= 0x3333; + } + else if (col + 2 >= img.cols) { + P &= 0x7777; + } + + if (row == 0) { + P &= 0xFFF0; + } + if (row + 1 >= img.rows) { + P &= 0x00FF; + } + else if (row + 2 >= img.rows) { + P &= 0x0FFF; + } + + // P is now ready to be used to find neighbor blocks + // P value avoids range errors + + int father_offset = 0; + + // P square + if (HasBit(P, 0) && img.data[img_index - img.step - 1]) { + father_offset = -(2 * (labels.step / labels.elem_size) + 2); + } + + // Q square + if ((HasBit(P, 1) && img.data[img_index - img.step]) || (HasBit(P, 2) && img.data[img_index + 1 - img.step])) { + if (!father_offset) { + father_offset = -(2 * (labels.step / labels.elem_size)); + } + else { + SetBit(info, Info::Q); + } + } + + // R square + if (HasBit(P, 3) && img.data[img_index + 2 - img.step]) { + if (!father_offset) { + father_offset = -(2 * (labels.step / labels.elem_size) - 2); + } + else { + SetBit(info, Info::R); + } + } + + // S square + if ((HasBit(P, 4) && img.data[img_index - 1]) || (HasBit(P, 8) && img.data[img_index + img.step - 1])) { + if (!father_offset) { + father_offset = -2; + } + else { + SetBit(info, Info::S); + } + } + + labels.data[labels_index] = labels_index + father_offset; + if (col + 1 < labels.cols) { + last_pixel = reinterpret_cast(labels.data + labels_index + 1); + } + else if (row + 1 < labels.rows) { + last_pixel = reinterpret_cast(labels.data + labels_index + labels.step / labels.elem_size); + } + *last_pixel = info; + } +} + +__global__ void Merge(cuda::PtrStepSzi labels, unsigned char* last_pixel) { + + unsigned row = (blockIdx.y * kblock_rows + threadIdx.y) * 2; + unsigned col = (blockIdx.x * kblock_cols + threadIdx.x) * 2; + unsigned labels_index = row * (labels.step / labels.elem_size) + col; + + if (row < labels.rows && col < labels.cols) { + + if (col + 1 < labels.cols) { + last_pixel = reinterpret_cast(labels.data + labels_index + 1); + } + else if (row + 1 < labels.rows) { + last_pixel = reinterpret_cast(labels.data + labels_index + labels.step / labels.elem_size); + } + unsigned char info = *last_pixel; + + if (HasBit(info, Info::Q)) { + Union(labels.data, labels_index, labels_index - 2 * (labels.step / labels.elem_size)); + } + if (HasBit(info, Info::R)) { + Union(labels.data, labels_index, labels_index - 2 * (labels.step / labels.elem_size) + 2); + } + if (HasBit(info, Info::S)) { + Union(labels.data, labels_index, labels_index - 2); + } + } +} + +__global__ void Compression(cuda::PtrStepSzi labels) { + unsigned row = (blockIdx.y * kblock_rows + threadIdx.y) * 2; + unsigned col = (blockIdx.x * kblock_cols + threadIdx.x) * 2; + unsigned labels_index = row * (labels.step / labels.elem_size) + col; + + if (row < labels.rows && col < labels.cols) { + FindAndCompress(labels.data, labels_index); + } +} + +__global__ void FinalLabeling(const cuda::PtrStepSzb img, cuda::PtrStepSzi labels) { + + unsigned row = (blockIdx.y * kblock_rows + threadIdx.y) * 2; + unsigned col = (blockIdx.x * kblock_cols + threadIdx.x) * 2; + unsigned labels_index = row * (labels.step / labels.elem_size) + col; + + if (row < labels.rows && col < labels.cols) { + + int label; + unsigned char info; + unsigned long long buffer; + + if (col + 1 < labels.cols) { + buffer = *reinterpret_cast(labels.data + labels_index); + label = (buffer & (0xFFFFFFFF)) + 1; + info = (buffer >> 32) & 0xFFFFFFFF; + } + else { + label = labels[labels_index] + 1; + if (row + 1 < labels.rows) { + info = labels[labels_index + labels.step / labels.elem_size]; + } + else { + // Read from the input image + // "a" is already in position 0 + info = img[row * img.step + col]; + } + } + + if (col + 1 < labels.cols) { + *reinterpret_cast(labels.data + labels_index) = + (static_cast(HasBit(info, Info::b) * label) << 32) | (HasBit(info, Info::a) * label); + + if (row + 1 < labels.rows) { + *reinterpret_cast(labels.data + labels_index + labels.step / labels.elem_size) = + (static_cast(HasBit(info, Info::d) * label) << 32) | (HasBit(info, Info::c) * label); + } + } + else { + labels[labels_index] = HasBit(info, Info::a) * label; + + if (row + 1 < labels.rows) { + labels[labels_index + (labels.step / labels.elem_size)] = HasBit(info, Info::c) * label; + } + } + + } + +} + +} + + +void BlockBasedKomuraEquivalence(const cv::cuda::GpuMat& img, cv::cuda::GpuMat& labels) { + + dim3 grid_size; + dim3 block_size; + unsigned char* last_pixel; + bool last_pixel_allocated; + + last_pixel_allocated = false; + if ((img.rows == 1 || img.cols == 1) && !((img.rows + img.cols) % 2)) { + cudaSafeCall(cudaMalloc(&last_pixel, sizeof(unsigned char))); + last_pixel_allocated = true; + } + else { + last_pixel = labels.data + ((labels.rows - 2) * labels.step) + (labels.cols - 2) * labels.elemSize(); + } + + grid_size = dim3((((img.cols + 1) / 2) - 1) / kblock_cols + 1, (((img.rows + 1) / 2) - 1) / kblock_rows + 1, 1); + block_size = dim3(kblock_cols, kblock_rows, 1); + + InitLabeling << > > (img, labels, last_pixel); + cudaSafeCall(cudaGetLastError()); + + Compression << > > (labels); + cudaSafeCall(cudaGetLastError()); + + Merge << > > (labels, last_pixel); + cudaSafeCall(cudaGetLastError()); + + Compression << > > (labels); + cudaSafeCall(cudaGetLastError()); + + FinalLabeling << > > (img, labels); + cudaSafeCall(cudaGetLastError()); + + if (last_pixel_allocated) { + cudaSafeCall(cudaFree(last_pixel)); + } + cudaSafeCall(cudaDeviceSynchronize()); + +} + +}}}} + + +#endif /* CUDA_DISABLER */ diff --git a/modules/cudaimgproc/test/test_connectedcomponents.cpp b/modules/cudaimgproc/test/test_connectedcomponents.cpp new file mode 100644 index 0000000000..8461f3d178 --- /dev/null +++ b/modules/cudaimgproc/test/test_connectedcomponents.cpp @@ -0,0 +1,479 @@ +// 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_CUDA + +namespace opencv_test { +namespace { + +// This function force a row major order for the labels +template +void normalize_labels_impl(Mat& labels) { + std::map map_new_labels; + LabelT i_max_new_label = 0; + + for (int r = 0; r < labels.rows; ++r) { + LabelT* const mat_row = labels.ptr(r); + for (int c = 0; c < labels.cols; ++c) { + LabelT iCurLabel = mat_row[c]; + if (iCurLabel > 0) { + if (map_new_labels.find(iCurLabel) == map_new_labels.end()) { + map_new_labels[iCurLabel] = ++i_max_new_label; + } + mat_row[c] = map_new_labels.at(iCurLabel); + } + } + } +} + +void normalize_labels(Mat& labels) { + + int type = labels.type(); + int depth = type & CV_MAT_DEPTH_MASK; + int chans = 1 + (type >> CV_CN_SHIFT); + + CV_Assert(chans == 1); + CV_Assert(depth == CV_16U || depth == CV_16S || depth == CV_32S); + + switch (depth) { + case CV_16U: normalize_labels_impl(labels); break; + case CV_16S: normalize_labels_impl(labels); break; + case CV_32S: normalize_labels_impl(labels); break; + default: CV_Assert(0); + } +} + + +//////////////////////////////////////////////////////// +// ConnectedComponents + +PARAM_TEST_CASE(ConnectedComponents, cv::cuda::DeviceInfo, int, int, cv::cuda::ConnectedComponentsAlgorithmsTypes) +{ + cv::cuda::DeviceInfo devInfo; + int connectivity; + int ltype; + cv::cuda::ConnectedComponentsAlgorithmsTypes algo; + + virtual void SetUp() + { + devInfo = GET_PARAM(0); + connectivity = GET_PARAM(1); + ltype = GET_PARAM(2); + algo = GET_PARAM(3); + + cv::cuda::setDevice(devInfo.deviceID()); + } +}; + +CUDA_TEST_P(ConnectedComponents, Chessboard_Even) +{ + std::initializer_list sizes{ 16, 16 }; + cv::Mat1b input; + cv::Mat1i correct_output_int; + cv::Mat correct_output; + + // Chessboard image with even number of rows and cols + // Note that this is the maximum number of labels for 4-way connectivity + { + input = cv::Mat1b(sizes, { + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1 + }); + + if (connectivity == 8) { + correct_output_int = cv::Mat1i(sizes, { + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1 + }); + } + else { + correct_output_int = cv::Mat1i(sizes, { + 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0, + 0, 9, 0, 10, 0, 11, 0, 12, 0, 13, 0, 14, 0, 15, 0, 16, + 17, 0, 18, 0, 19, 0, 20, 0, 21, 0, 22, 0, 23, 0, 24, 0, + 0, 25, 0, 26, 0, 27, 0, 28, 0, 29, 0, 30, 0, 31, 0, 32, + 33, 0, 34, 0, 35, 0, 36, 0, 37, 0, 38, 0, 39, 0, 40, 0, + 0, 41, 0, 42, 0, 43, 0, 44, 0, 45, 0, 46, 0, 47, 0, 48, + 49, 0, 50, 0, 51, 0, 52, 0, 53, 0, 54, 0, 55, 0, 56, 0, + 0, 57, 0, 58, 0, 59, 0, 60, 0, 61, 0, 62, 0, 63, 0, 64, + 65, 0, 66, 0, 67, 0, 68, 0, 69, 0, 70, 0, 71, 0, 72, 0, + 0, 73, 0, 74, 0, 75, 0, 76, 0, 77, 0, 78, 0, 79, 0, 80, + 81, 0, 82, 0, 83, 0, 84, 0, 85, 0, 86, 0, 87, 0, 88, 0, + 0, 89, 0, 90, 0, 91, 0, 92, 0, 93, 0, 94, 0, 95, 0, 96, + 97, 0, 98, 0, 99, 0, 100, 0, 101, 0, 102, 0, 103, 0, 104, 0, + 0, 105, 0, 106, 0, 107, 0, 108, 0, 109, 0, 110, 0, 111, 0, 112, + 113, 0, 114, 0, 115, 0, 116, 0, 117, 0, 118, 0, 119, 0, 120, 0, + 0, 121, 0, 122, 0, 123, 0, 124, 0, 125, 0, 126, 0, 127, 0, 128 + }); + } + } + + correct_output_int.convertTo(correct_output, CV_MAT_DEPTH(ltype)); + + cv::Mat labels; + cv::Mat diff; + + cv::cuda::GpuMat d_input; + cv::cuda::GpuMat d_labels; + d_input.upload(input); + EXPECT_NO_THROW(cv::cuda::connectedComponents(d_input, d_labels, connectivity, ltype, algo)); + d_labels.download(labels); + normalize_labels(labels); + + diff = labels != correct_output; + EXPECT_EQ(cv::countNonZero(diff), 0); + +} + +CUDA_TEST_P(ConnectedComponents, Chessboard_Odd) +{ + std::initializer_list sizes{ 15, 15 }; + cv::Mat1b input; + cv::Mat1i correct_output_int; + cv::Mat correct_output; + + // Chessboard image with even number of rows and cols + // Note that this is the maximum number of labels for 4-way connectivity + { + input = Mat1b(sizes, { + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1 + }); + + if (connectivity == 8) { + correct_output_int = Mat1i(sizes, { + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1 + }); + } + else { + correct_output_int = Mat1i(sizes, { + 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, + 0, 9, 0, 10, 0, 11, 0, 12, 0, 13, 0, 14, 0, 15, 0, + 16, 0, 17, 0, 18, 0, 19, 0, 20, 0, 21, 0, 22, 0, 23, + 0, 24, 0, 25, 0, 26, 0, 27, 0, 28, 0, 29, 0, 30, 0, + 31, 0, 32, 0, 33, 0, 34, 0, 35, 0, 36, 0, 37, 0, 38, + 0, 39, 0, 40, 0, 41, 0, 42, 0, 43, 0, 44, 0, 45, 0, + 46, 0, 47, 0, 48, 0, 49, 0, 50, 0, 51, 0, 52, 0, 53, + 0, 54, 0, 55, 0, 56, 0, 57, 0, 58, 0, 59, 0, 60, 0, + 61, 0, 62, 0, 63, 0, 64, 0, 65, 0, 66, 0, 67, 0, 68, + 0, 69, 0, 70, 0, 71, 0, 72, 0, 73, 0, 74, 0, 75, 0, + 76, 0, 77, 0, 78, 0, 79, 0, 80, 0, 81, 0, 82, 0, 83, + 0, 84, 0, 85, 0, 86, 0, 87, 0, 88, 0, 89, 0, 90, 0, + 91, 0, 92, 0, 93, 0, 94, 0, 95, 0, 96, 0, 97, 0, 98, + 0, 99, 0, 100, 0, 101, 0, 102, 0, 103, 0, 104, 0, 105, 0, + 106, 0, 107, 0, 108, 0, 109, 0, 110, 0, 111, 0, 112, 0, 113 + }); + } + } + + correct_output_int.convertTo(correct_output, CV_MAT_DEPTH(ltype)); + + cv::Mat labels; + cv::Mat diff; + + cv::cuda::GpuMat d_input; + cv::cuda::GpuMat d_labels; + d_input.upload(input); + EXPECT_NO_THROW(cv::cuda::connectedComponents(d_input, d_labels, connectivity, ltype, algo)); + d_labels.download(labels); + normalize_labels(labels); + + diff = labels != correct_output; + EXPECT_EQ(cv::countNonZero(diff), 0); + +} + +CUDA_TEST_P(ConnectedComponents, Maxlabels_8conn_Even) +{ + std::initializer_list sizes{ 16, 16 }; + cv::Mat1b input; + cv::Mat1i correct_output_int; + cv::Mat correct_output; + + // Chessboard image with even number of rows and cols + // Note that this is the maximum number of labels for 4-way connectivity + { + input = Mat1b(sizes, { + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }); + + correct_output_int = Mat1i(sizes, { + 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 9, 0, 10, 0, 11, 0, 12, 0, 13, 0, 14, 0, 15, 0, 16, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 17, 0, 18, 0, 19, 0, 20, 0, 21, 0, 22, 0, 23, 0, 24, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 25, 0, 26, 0, 27, 0, 28, 0, 29, 0, 30, 0, 31, 0, 32, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 33, 0, 34, 0, 35, 0, 36, 0, 37, 0, 38, 0, 39, 0, 40, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 41, 0, 42, 0, 43, 0, 44, 0, 45, 0, 46, 0, 47, 0, 48, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 49, 0, 50, 0, 51, 0, 52, 0, 53, 0, 54, 0, 55, 0, 56, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 57, 0, 58, 0, 59, 0, 60, 0, 61, 0, 62, 0, 63, 0, 64, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }); + + } + + correct_output_int.convertTo(correct_output, CV_MAT_DEPTH(ltype)); + + cv::Mat labels; + cv::Mat diff; + + cv::cuda::GpuMat d_input; + cv::cuda::GpuMat d_labels; + d_input.upload(input); + EXPECT_NO_THROW(cv::cuda::connectedComponents(d_input, d_labels, connectivity, ltype, algo)); + d_labels.download(labels); + normalize_labels(labels); + + diff = labels != correct_output; + EXPECT_EQ(cv::countNonZero(diff), 0); + +} + +CUDA_TEST_P(ConnectedComponents, Maxlabels_8conn_Odd) +{ + std::initializer_list sizes{ 15, 15 }; + cv::Mat1b input; + cv::Mat1i correct_output_int; + cv::Mat correct_output; + + // Chessboard image with even number of rows and cols + // Note that this is the maximum number of labels for 4-way connectivity + { + input = Mat1b(sizes, { + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1 + }); + + correct_output_int = Mat1i(sizes, { + 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 9, 0, 10, 0, 11, 0, 12, 0, 13, 0, 14, 0, 15, 0, 16, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 17, 0, 18, 0, 19, 0, 20, 0, 21, 0, 22, 0, 23, 0, 24, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 25, 0, 26, 0, 27, 0, 28, 0, 29, 0, 30, 0, 31, 0, 32, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 33, 0, 34, 0, 35, 0, 36, 0, 37, 0, 38, 0, 39, 0, 40, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 41, 0, 42, 0, 43, 0, 44, 0, 45, 0, 46, 0, 47, 0, 48, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 49, 0, 50, 0, 51, 0, 52, 0, 53, 0, 54, 0, 55, 0, 56, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 57, 0, 58, 0, 59, 0, 60, 0, 61, 0, 62, 0, 63, 0, 64 + }); + + } + + correct_output_int.convertTo(correct_output, CV_MAT_DEPTH(ltype)); + + cv::Mat labels; + cv::Mat diff; + + cv::cuda::GpuMat d_input; + cv::cuda::GpuMat d_labels; + d_input.upload(input); + EXPECT_NO_THROW(cv::cuda::connectedComponents(d_input, d_labels, connectivity, ltype, algo)); + d_labels.download(labels); + normalize_labels(labels); + + diff = labels != correct_output; + EXPECT_EQ(cv::countNonZero(diff), 0); + +} + +CUDA_TEST_P(ConnectedComponents, Single_Row) +{ + std::initializer_list sizes{ 1, 15 }; + cv::Mat1b input; + cv::Mat1i correct_output_int; + cv::Mat correct_output; + + // Chessboard image with even number of rows and cols + // Note that this is the maximum number of labels for 4-way connectivity + { + input = Mat1b(sizes, { 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1 }); + correct_output_int = Mat1i(sizes, { 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8 }); + } + + correct_output_int.convertTo(correct_output, CV_MAT_DEPTH(ltype)); + + cv::Mat labels; + cv::Mat diff; + + cv::cuda::GpuMat d_input; + cv::cuda::GpuMat d_labels; + d_input.upload(input); + EXPECT_NO_THROW(cv::cuda::connectedComponents(d_input, d_labels, connectivity, ltype, algo)); + d_labels.download(labels); + normalize_labels(labels); + + diff = labels != correct_output; + EXPECT_EQ(cv::countNonZero(diff), 0); + +} + +CUDA_TEST_P(ConnectedComponents, Single_Column) +{ + std::initializer_list sizes{ 15, 1 }; + cv::Mat1b input; + cv::Mat1i correct_output_int; + cv::Mat correct_output; + + // Chessboard image with even number of rows and cols + // Note that this is the maximum number of labels for 4-way connectivity + { + input = Mat1b(sizes, { 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1 }); + correct_output_int = Mat1i(sizes, { 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8 }); + } + + correct_output_int.convertTo(correct_output, CV_MAT_DEPTH(ltype)); + + cv::Mat labels; + cv::Mat diff; + + cv::cuda::GpuMat d_input; + cv::cuda::GpuMat d_labels; + d_input.upload(input); + EXPECT_NO_THROW(cv::cuda::connectedComponents(d_input, d_labels, connectivity, ltype, algo)); + d_labels.download(labels); + normalize_labels(labels); + + diff = labels != correct_output; + EXPECT_EQ(cv::countNonZero(diff), 0); + +} + + +CUDA_TEST_P(ConnectedComponents, Concentric_Circles) +{ + string img_path = cvtest::TS::ptr()->get_data_path() + "connectedcomponents/concentric_circles.png"; + string exp_path = cvtest::TS::ptr()->get_data_path() + "connectedcomponents/ccomp_exp.png"; + + Mat img = imread(img_path, 0); + EXPECT_FALSE(img.empty()); + + Mat exp = imread(exp_path, 0); + EXPECT_FALSE(exp.empty()); + + Mat labels; + exp.convertTo(exp, ltype); + + GpuMat d_img; + GpuMat d_labels; + d_img.upload(img); + + EXPECT_NO_THROW(cv::cuda::connectedComponents(d_img, d_labels, connectivity, ltype, algo)); + + d_labels.download(labels); + + normalize_labels(labels); + + Mat diff = labels != exp; + EXPECT_EQ(cv::countNonZero(diff), 0); + +} + + +INSTANTIATE_TEST_CASE_P(CUDA_ImgProc, ConnectedComponents, testing::Combine( + ALL_DEVICES, + testing::Values(8), + testing::Values(CV_32S), + testing::Values(cv::cuda::CCL_DEFAULT, cv::cuda::CCL_BKE) +)); + + +} +} // namespace +#endif // HAVE_CUDA From 51078a539b8f8ae978620628ba1cd594933ba3f2 Mon Sep 17 00:00:00 2001 From: Alexander Panov Date: Thu, 3 Feb 2022 21:28:29 +0300 Subject: [PATCH 16/16] Merge pull request #3151 from AleksandrPanov:rebase_aruco_speedup Feature/aruco speedup (rebased with 4.x) * initial commit of new aruco functionality * add performance FHD tile tests and fix pyramid bugs * remove global ArUco3 params (threshold) * add ArUco params to test classes Co-authored-by: Steffen Urban --- modules/aruco/include/opencv2/aruco.hpp | 24 ++ modules/aruco/perf/perf_aruco.cpp | 297 +++++++++++++++++++++ modules/aruco/perf/perf_main.cpp | 3 + modules/aruco/perf/perf_precomp.hpp | 11 + modules/aruco/samples/detector_params.yml | 7 + modules/aruco/src/aruco.cpp | 211 +++++++++++---- modules/aruco/test/test_arucodetection.cpp | 47 ++-- modules/aruco/test/test_boarddetection.cpp | 56 ++-- 8 files changed, 577 insertions(+), 79 deletions(-) create mode 100644 modules/aruco/perf/perf_aruco.cpp create mode 100644 modules/aruco/perf/perf_main.cpp create mode 100644 modules/aruco/perf/perf_precomp.hpp diff --git a/modules/aruco/include/opencv2/aruco.hpp b/modules/aruco/include/opencv2/aruco.hpp index ee21369939..98a411c570 100644 --- a/modules/aruco/include/opencv2/aruco.hpp +++ b/modules/aruco/include/opencv2/aruco.hpp @@ -146,6 +146,24 @@ enum CornerRefineMethod{ * Parameter is the standard deviation in pixels. Very noisy images benefit from non-zero values (e.g. 0.8). (default 0.0) * - detectInvertedMarker: to check if there is a white marker. In order to generate a "white" marker just * invert a normal marker by using a tilde, ~markerImage. (default false) + * - useAruco3Detection: to enable the new and faster Aruco detection strategy. The most important observation from the authors of + * Romero-Ramirez et al: Speeded up detection of squared fiducial markers (2018) is, that the binary + * code of a marker can be reliably detected if the canonical image (that is used to extract the binary code) + * has a size of minSideLengthCanonicalImg (in practice tau_c=16-32 pixels). + * Link to article: https://www.researchgate.net/publication/325787310_Speeded_Up_Detection_of_Squared_Fiducial_Markers + * In addition, very small markers are barely useful for pose estimation and thus a we can define a minimum marker size that we + * still want to be able to detect (e.g. 50x50 pixel). + * To decouple this from the initial image size they propose to resize the input image + * to (I_w_r, I_h_r) = (tau_c / tau_dot_i) * (I_w, I_h), with tau_dot_i = tau_c + max(I_w,I_h) * tau_i. + * Here tau_i (parameter: minMarkerLengthRatioOriginalImg) is a ratio in the range [0,1]. + * If we set this to 0, the smallest marker we can detect + * has a side length of tau_c. If we set it to 1 the marker would fill the entire image. + * For a FullHD video a good value to start with is 0.1. + * - minSideLengthCanonicalImg: minimum side length of a marker in the canonical image. + * Latter is the binarized image in which contours are searched. + * So all contours with a size smaller than minSideLengthCanonicalImg*minSideLengthCanonicalImg will omitted from the search. + * - minMarkerLengthRatioOriginalImg: range [0,1], eq (2) from paper + * The parameter tau_i has a direct influence on the processing speed. */ struct CV_EXPORTS_W DetectorParameters { @@ -188,6 +206,12 @@ struct CV_EXPORTS_W DetectorParameters { // to detect white (inverted) markers CV_PROP_RW bool detectInvertedMarker; + + // New Aruco functionality proposed in the paper: + // Romero-Ramirez et al: Speeded up detection of squared fiducial markers (2018) + CV_PROP_RW bool useAruco3Detection; + CV_PROP_RW int minSideLengthCanonicalImg; + CV_PROP_RW float minMarkerLengthRatioOriginalImg; }; diff --git a/modules/aruco/perf/perf_aruco.cpp b/modules/aruco/perf/perf_aruco.cpp new file mode 100644 index 0000000000..6ab830fa28 --- /dev/null +++ b/modules/aruco/perf/perf_aruco.cpp @@ -0,0 +1,297 @@ +// 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 "perf_precomp.hpp" + +namespace opencv_test { +using namespace perf; + +typedef tuple UseArucoParams; +typedef TestBaseWithParam EstimateAruco; +#define ESTIMATE_PARAMS Combine(Values(false, true), Values(-1)) + +static double deg2rad(double deg) { return deg * CV_PI / 180.; } + +class MarkerPainter +{ +private: + int imgMarkerSize = 0; + Mat cameraMatrix; +public: + MarkerPainter(const int size) + { + setImgMarkerSize(size); + } + + void setImgMarkerSize(const int size) + { + imgMarkerSize = size; + cameraMatrix = Mat::eye(3, 3, CV_64FC1); + cameraMatrix.at(0, 0) = cameraMatrix.at(1, 1) = imgMarkerSize; + cameraMatrix.at(0, 2) = imgMarkerSize / 2.0; + cameraMatrix.at(1, 2) = imgMarkerSize / 2.0; + } + + static std::pair getSyntheticRT(double yaw, double pitch, double distance) + { + auto rvec_tvec = std::make_pair(Mat(3, 1, CV_64FC1), Mat(3, 1, CV_64FC1)); + Mat& rvec = rvec_tvec.first; + Mat& tvec = rvec_tvec.second; + + // Rvec + // first put the Z axis aiming to -X (like the camera axis system) + Mat rotZ(3, 1, CV_64FC1); + rotZ.ptr(0)[0] = 0; + rotZ.ptr(0)[1] = 0; + rotZ.ptr(0)[2] = -0.5 * CV_PI; + + Mat rotX(3, 1, CV_64FC1); + rotX.ptr(0)[0] = 0.5 * CV_PI; + rotX.ptr(0)[1] = 0; + rotX.ptr(0)[2] = 0; + + Mat camRvec, camTvec; + composeRT(rotZ, Mat(3, 1, CV_64FC1, Scalar::all(0)), rotX, Mat(3, 1, CV_64FC1, Scalar::all(0)), + camRvec, camTvec); + + // now pitch and yaw angles + Mat rotPitch(3, 1, CV_64FC1); + rotPitch.ptr(0)[0] = 0; + rotPitch.ptr(0)[1] = pitch; + rotPitch.ptr(0)[2] = 0; + + Mat rotYaw(3, 1, CV_64FC1); + rotYaw.ptr(0)[0] = yaw; + rotYaw.ptr(0)[1] = 0; + rotYaw.ptr(0)[2] = 0; + + composeRT(rotPitch, Mat(3, 1, CV_64FC1, Scalar::all(0)), rotYaw, + Mat(3, 1, CV_64FC1, Scalar::all(0)), rvec, tvec); + + // compose both rotations + composeRT(camRvec, Mat(3, 1, CV_64FC1, Scalar::all(0)), rvec, + Mat(3, 1, CV_64FC1, Scalar::all(0)), rvec, tvec); + + // Tvec, just move in z (camera) direction the specific distance + tvec.ptr(0)[0] = 0.; + tvec.ptr(0)[1] = 0.; + tvec.ptr(0)[2] = distance; + return rvec_tvec; +} + + std::pair > getProjectMarker(int id, double yaw, double pitch, + const Ptr& parameters, + const Ptr& dictionary) + { + auto marker_corners = std::make_pair(Mat(imgMarkerSize, imgMarkerSize, CV_8UC1, Scalar::all(255)), vector()); + Mat& img = marker_corners.first; + vector& corners = marker_corners.second; + + // canonical image + const int markerSizePixels = static_cast(imgMarkerSize/sqrt(2.f)); + aruco::drawMarker(dictionary, id, markerSizePixels, img, parameters->markerBorderBits); + + // get rvec and tvec for the perspective + const double distance = 0.1; + auto rvec_tvec = MarkerPainter::getSyntheticRT(yaw, pitch, distance); + Mat& rvec = rvec_tvec.first; + Mat& tvec = rvec_tvec.second; + + const float markerLength = 0.05f; + vector markerObjPoints; + markerObjPoints.emplace_back(Point3f(-markerLength / 2.f, +markerLength / 2.f, 0)); + markerObjPoints.emplace_back(markerObjPoints[0] + Point3f(markerLength, 0, 0)); + markerObjPoints.emplace_back(markerObjPoints[0] + Point3f(markerLength, -markerLength, 0)); + markerObjPoints.emplace_back(markerObjPoints[0] + Point3f(0, -markerLength, 0)); + + // project markers and draw them + Mat distCoeffs(5, 1, CV_64FC1, Scalar::all(0)); + projectPoints(markerObjPoints, rvec, tvec, cameraMatrix, distCoeffs, corners); + + vector originalCorners; + originalCorners.emplace_back(Point2f(0.f, 0.f)); + originalCorners.emplace_back(originalCorners[0]+Point2f((float)markerSizePixels, 0)); + originalCorners.emplace_back(originalCorners[0]+Point2f((float)markerSizePixels, (float)markerSizePixels)); + originalCorners.emplace_back(originalCorners[0]+Point2f(0, (float)markerSizePixels)); + + Mat transformation = getPerspectiveTransform(originalCorners, corners); + + warpPerspective(img, img, transformation, Size(imgMarkerSize, imgMarkerSize), INTER_NEAREST, BORDER_CONSTANT, + Scalar::all(255)); + return marker_corners; + } + + std::pair > > getProjectMarkersTile(const int numMarkers, + const Ptr& params, + const Ptr& dictionary) + { + Mat tileImage(imgMarkerSize*numMarkers, imgMarkerSize*numMarkers, CV_8UC1, Scalar::all(255)); + map > idCorners; + + int iter = 0, pitch = 0, yaw = 0; + for (int i = 0; i < numMarkers; i++) + { + for (int j = 0; j < numMarkers; j++) + { + int currentId = iter; + auto marker_corners = getProjectMarker(currentId, deg2rad(70+yaw), deg2rad(pitch), params, dictionary); + Point2i startPoint(j*imgMarkerSize, i*imgMarkerSize); + Mat tmp_roi = tileImage(Rect(startPoint.x, startPoint.y, imgMarkerSize, imgMarkerSize)); + marker_corners.first.copyTo(tmp_roi); + + for (Point2f& point: marker_corners.second) + point += static_cast(startPoint); + idCorners[currentId] = marker_corners.second; + auto test = idCorners[currentId]; + yaw = (yaw + 10) % 51; // 70+yaw >= 70 && 70+yaw <= 120 + iter++; + } + pitch = (pitch + 60) % 360; + } + return std::make_pair(tileImage, idCorners); + } +}; + +static inline double getMaxDistance(map > &golds, const vector& ids, + const vector >& corners) +{ + std::map mapDist; + for (const auto& el : golds) + mapDist[el.first] = std::numeric_limits::max(); + for (size_t i = 0; i < ids.size(); i++) + { + int id = ids[i]; + const auto gold_corners = golds.find(id); + if (gold_corners != golds.end()) { + double distance = 0.; + for (int c = 0; c < 4; c++) + distance = std::max(distance, cv::norm(gold_corners->second[c] - corners[i][c])); + mapDist[id] = distance; + } + } + return std::max_element(std::begin(mapDist), std::end(mapDist), + [](const pair& p1, const pair& p2){return p1.second < p2.second;})->second; +} + +PERF_TEST_P(EstimateAruco, ArucoFirst, ESTIMATE_PARAMS) +{ + UseArucoParams testParams = GetParam(); + Ptr dictionary = aruco::getPredefinedDictionary(aruco::DICT_6X6_250); + Ptr detectorParams = aruco::DetectorParameters::create(); + detectorParams->minDistanceToBorder = 1; + detectorParams->markerBorderBits = 1; + detectorParams->cornerRefinementMethod = cv::aruco::CORNER_REFINE_SUBPIX; + + const int markerSize = 100; + const int numMarkersInRow = 9; + //USE_ARUCO3 + detectorParams->useAruco3Detection = get<0>(testParams); + if (detectorParams->useAruco3Detection) { + detectorParams->minSideLengthCanonicalImg = 32; + detectorParams->minMarkerLengthRatioOriginalImg = 0.04f / numMarkersInRow; + } + MarkerPainter painter(markerSize); + auto image_map = painter.getProjectMarkersTile(numMarkersInRow, detectorParams, dictionary); + + // detect markers + vector > corners; + vector ids; + TEST_CYCLE() + { + aruco::detectMarkers(image_map.first, dictionary, corners, ids, detectorParams); + } + ASSERT_EQ(numMarkersInRow*numMarkersInRow, static_cast(ids.size())); + double maxDistance = getMaxDistance(image_map.second, ids, corners); + ASSERT_LT(maxDistance, 3.); + SANITY_CHECK_NOTHING(); +} + +PERF_TEST_P(EstimateAruco, ArucoSecond, ESTIMATE_PARAMS) +{ + UseArucoParams testParams = GetParam(); + Ptr dictionary = aruco::getPredefinedDictionary(aruco::DICT_6X6_250); + Ptr detectorParams = aruco::DetectorParameters::create(); + detectorParams->minDistanceToBorder = 1; + detectorParams->markerBorderBits = 1; + detectorParams->cornerRefinementMethod = cv::aruco::CORNER_REFINE_SUBPIX; + + //USE_ARUCO3 + detectorParams->useAruco3Detection = get<0>(testParams); + if (detectorParams->useAruco3Detection) { + detectorParams->minSideLengthCanonicalImg = 64; + detectorParams->minMarkerLengthRatioOriginalImg = 0.f; + } + const int markerSize = 200; + const int numMarkersInRow = 11; + MarkerPainter painter(markerSize); + auto image_map = painter.getProjectMarkersTile(numMarkersInRow, detectorParams, dictionary); + + // detect markers + vector > corners; + vector ids; + TEST_CYCLE() + { + aruco::detectMarkers(image_map.first, dictionary, corners, ids, detectorParams); + } + ASSERT_EQ(numMarkersInRow*numMarkersInRow, static_cast(ids.size())); + double maxDistance = getMaxDistance(image_map.second, ids, corners); + ASSERT_LT(maxDistance, 3.); + SANITY_CHECK_NOTHING(); +} + +struct Aruco3Params +{ + bool useAruco3Detection = false; + float minMarkerLengthRatioOriginalImg = 0.f; + int minSideLengthCanonicalImg = 0; + + Aruco3Params(bool useAruco3, float minMarkerLen, int minSideLen): useAruco3Detection(useAruco3), + minMarkerLengthRatioOriginalImg(minMarkerLen), + minSideLengthCanonicalImg(minSideLen) {} + friend std::ostream& operator<<(std::ostream& os, const Aruco3Params& d) + { + os << d.useAruco3Detection << " " << d.minMarkerLengthRatioOriginalImg << " " << d.minSideLengthCanonicalImg; + return os; + } +}; +typedef tuple> ArucoTestParams; + +typedef TestBaseWithParam EstimateLargeAruco; +#define ESTIMATE_FHD_PARAMS Combine(Values(Aruco3Params(false, 0.f, 0), Aruco3Params(true, 0.f, 32), \ +Aruco3Params(true, 0.015f, 32), Aruco3Params(true, 0.f, 16), Aruco3Params(true, 0.0069f, 16)), \ +Values(std::make_pair(1440, 1), std::make_pair(480, 3), std::make_pair(144, 10))) + +PERF_TEST_P(EstimateLargeAruco, ArucoFHD, ESTIMATE_FHD_PARAMS) +{ + ArucoTestParams testParams = GetParam(); + Ptr dictionary = aruco::getPredefinedDictionary(aruco::DICT_6X6_250); + Ptr detectorParams = aruco::DetectorParameters::create(); + detectorParams->minDistanceToBorder = 1; + detectorParams->markerBorderBits = 1; + detectorParams->cornerRefinementMethod = cv::aruco::CORNER_REFINE_SUBPIX; + + //USE_ARUCO3 + detectorParams->useAruco3Detection = get<0>(testParams).useAruco3Detection; + if (detectorParams->useAruco3Detection) { + detectorParams->minSideLengthCanonicalImg = get<0>(testParams).minSideLengthCanonicalImg; + detectorParams->minMarkerLengthRatioOriginalImg = get<0>(testParams).minMarkerLengthRatioOriginalImg; + } + const int markerSize = get<1>(testParams).first; // 1440 or 480 or 144 + const int numMarkersInRow = get<1>(testParams).second; // 1 or 3 or 144 + MarkerPainter painter(markerSize); // num pixels is 1440x1440 as in FHD 1920x1080 + auto image_map = painter.getProjectMarkersTile(numMarkersInRow, detectorParams, dictionary); + + // detect markers + vector > corners; + vector ids; + TEST_CYCLE() + { + aruco::detectMarkers(image_map.first, dictionary, corners, ids, detectorParams); + } + ASSERT_EQ(numMarkersInRow*numMarkersInRow, static_cast(ids.size())); + double maxDistance = getMaxDistance(image_map.second, ids, corners); + ASSERT_LT(maxDistance, 3.); + SANITY_CHECK_NOTHING(); +} + +} diff --git a/modules/aruco/perf/perf_main.cpp b/modules/aruco/perf/perf_main.cpp new file mode 100644 index 0000000000..7145ca13aa --- /dev/null +++ b/modules/aruco/perf/perf_main.cpp @@ -0,0 +1,3 @@ +#include "perf_precomp.hpp" + +CV_PERF_TEST_MAIN(aruco) diff --git a/modules/aruco/perf/perf_precomp.hpp b/modules/aruco/perf/perf_precomp.hpp new file mode 100644 index 0000000000..e4df3216e0 --- /dev/null +++ b/modules/aruco/perf/perf_precomp.hpp @@ -0,0 +1,11 @@ +// 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_PERF_PRECOMP_HPP__ +#define __OPENCV_PERF_PRECOMP_HPP__ + +#include "opencv2/ts.hpp" +#include "opencv2/aruco.hpp" +#include "opencv2/calib3d.hpp" + +#endif diff --git a/modules/aruco/samples/detector_params.yml b/modules/aruco/samples/detector_params.yml index 065f854c3c..1155c25126 100644 --- a/modules/aruco/samples/detector_params.yml +++ b/modules/aruco/samples/detector_params.yml @@ -21,3 +21,10 @@ perspectiveRemoveIgnoredMarginPerCell: 0.13 maxErroneousBitsInBorderRate: 0.04 minOtsuStdDev: 5.0 errorCorrectionRate: 0.6 + +# new aruco 3 functionality +useAruco3Detection: 0 +minSideLengthCanonicalImg: 32 # 16, 32, 64 --> tau_c from the paper +minMarkerLengthRatioOriginalImg: 0.02 # range [0,0.2] --> tau_i from the paper +cameraMotionSpeed: 0.1 # range [0,1) --> tau_s from the paper +useGlobalThreshold: 0 diff --git a/modules/aruco/src/aruco.cpp b/modules/aruco/src/aruco.cpp index 8c0f472801..8f0ec63a94 100644 --- a/modules/aruco/src/aruco.cpp +++ b/modules/aruco/src/aruco.cpp @@ -44,6 +44,8 @@ the use of this software, even if advised of the possibility of such damage. #include "apriltag_quad_thresh.hpp" #include "zarray.hpp" +#include + //#define APRIL_DEBUG #ifdef APRIL_DEBUG #include "opencv2/imgcodecs.hpp" @@ -87,7 +89,11 @@ DetectorParameters::DetectorParameters() aprilTagMaxLineFitMse(10.0), aprilTagMinWhiteBlackDiff(5), aprilTagDeglitch(0), - detectInvertedMarker(false){} + detectInvertedMarker(false), + useAruco3Detection(false), + minSideLengthCanonicalImg(32), + minMarkerLengthRatioOriginalImg(0.0) +{} /** @@ -137,6 +143,10 @@ bool DetectorParameters::readDetectorParameters(const FileNode& fn, PtrmaxErroneousBitsInBorderRate); checkRead |= readParameter(fn["minOtsuStdDev"], params->minOtsuStdDev); checkRead |= readParameter(fn["errorCorrectionRate"], params->errorCorrectionRate); + // new aruco 3 functionality + checkRead |= readParameter(fn["useAruco3Detection"], params->useAruco3Detection); + checkRead |= readParameter(fn["minSideLengthCanonicalImg"], params->minSideLengthCanonicalImg); + checkRead |= readParameter(fn["minMarkerLengthRatioOriginalImg"], params->minMarkerLengthRatioOriginalImg); return checkRead; } @@ -173,7 +183,7 @@ static void _threshold(InputArray _in, OutputArray _out, int winSize, double con static void _findMarkerContours(InputArray _in, vector< vector< Point2f > > &candidates, vector< vector< Point > > &contoursOut, double minPerimeterRate, double maxPerimeterRate, double accuracyRate, - double minCornerDistanceRate, int minDistanceToBorder) { + double minCornerDistanceRate, int minDistanceToBorder, int minSize) { CV_Assert(minPerimeterRate > 0 && maxPerimeterRate > 0 && accuracyRate > 0 && minCornerDistanceRate >= 0 && minDistanceToBorder >= 0); @@ -184,6 +194,11 @@ static void _findMarkerContours(InputArray _in, vector< vector< Point2f > > &can unsigned int maxPerimeterPixels = (unsigned int)(maxPerimeterRate * max(_in.getMat().cols, _in.getMat().rows)); + // for aruco3 functionality + if (minSize != 0) { + minPerimeterPixels = 4*minSize; + } + Mat contoursImg; _in.getMat().copyTo(contoursImg); vector< vector< Point > > contours; @@ -422,10 +437,9 @@ static void _detectInitialCandidates(const Mat &grey, vector< vector< Point2f > _findMarkerContours(thresh, candidatesArrays[i], contoursArrays[i], params->minMarkerPerimeterRate, params->maxMarkerPerimeterRate, params->polygonalApproxAccuracyRate, params->minCornerDistanceRate, - params->minDistanceToBorder); + params->minDistanceToBorder, params->minSideLengthCanonicalImg); } }); - // join candidates for(int i = 0; i < nScales; i++) { for(unsigned int j = 0; j < candidatesArrays[i].size(); j++) { @@ -439,25 +453,20 @@ static void _detectInitialCandidates(const Mat &grey, vector< vector< Point2f > /** * @brief Detect square candidates in the input image */ -static void _detectCandidates(InputArray _image, vector< vector< vector< Point2f > > >& candidatesSetOut, +static void _detectCandidates(InputArray _grayImage, vector< vector< vector< Point2f > > >& candidatesSetOut, vector< vector< vector< Point > > >& contoursSetOut, const Ptr &_params) { + Mat grey = _grayImage.getMat(); + CV_DbgAssert(grey.total() != 0); + CV_DbgAssert(grey.type() == CV_8UC1); - Mat image = _image.getMat(); - CV_Assert(image.total() != 0); - - /// 1. CONVERT TO GRAY - Mat grey; - _convertToGrey(image, grey); - + /// 1. DETECT FIRST SET OF CANDIDATES vector< vector< Point2f > > candidates; vector< vector< Point > > contours; - /// 2. DETECT FIRST SET OF CANDIDATES _detectInitialCandidates(grey, candidates, contours, _params); - - /// 3. SORT CORNERS + /// 2. SORT CORNERS _reorderCandidatesCorners(candidates); - /// 4. FILTER OUT NEAR CANDIDATE PAIRS + /// 3. FILTER OUT NEAR CANDIDATE PAIRS // save the outter/inner border (i.e. potential candidates) _filterTooCloseCandidates(candidates, candidatesSetOut, contours, contoursSetOut, _params->minMarkerDistanceRate, _params->detectInvertedMarker); @@ -568,17 +577,24 @@ static int _getBorderErrors(const Mat &bits, int markerSize, int borderSize) { * 2 if the candidate is a white candidate */ static uint8_t _identifyOneCandidate(const Ptr& dictionary, InputArray _image, - vector& _corners, int& idx, - const Ptr& params, int& rotation) + const vector& _corners, int& idx, + const Ptr& params, int& rotation, + const float scale = 1.f) { - CV_Assert(_corners.size() == 4); - CV_Assert(_image.getMat().total() != 0); - CV_Assert(params->markerBorderBits > 0); - + CV_DbgAssert(_corners.size() == 4); + CV_DbgAssert(_image.getMat().total() != 0); + CV_DbgAssert(params->markerBorderBits > 0); uint8_t typ=1; // get bits + // scale corners to the correct size to search on the corresponding image pyramid + vector scaled_corners(4); + for (int i = 0; i < 4; ++i) { + scaled_corners[i].x = _corners[i].x * scale; + scaled_corners[i].y = _corners[i].y * scale; + } + Mat candidateBits = - _extractBits(_image, _corners, dictionary->markerSize, params->markerBorderBits, + _extractBits(_image, scaled_corners, dictionary->markerSize, params->markerBorderBits, params->perspectiveRemovePixelPerCell, params->perspectiveRemoveIgnoredMarginPerCell, params->minOtsuStdDev); @@ -618,28 +634,28 @@ static uint8_t _identifyOneCandidate(const Ptr& dictionary, InputArr /** * @brief Copy the contents of a corners vector to an OutputArray, settings its size. */ -static void _copyVector2Output(vector< vector< Point2f > > &vec, OutputArrayOfArrays out) { +static void _copyVector2Output(vector< vector< Point2f > > &vec, OutputArrayOfArrays out, const float scale = 1.f) { out.create((int)vec.size(), 1, CV_32FC2); if(out.isMatVector()) { for (unsigned int i = 0; i < vec.size(); i++) { out.create(4, 1, CV_32FC2, i); Mat &m = out.getMatRef(i); - Mat(Mat(vec[i]).t()).copyTo(m); + Mat(Mat(vec[i]).t()*scale).copyTo(m); } } else if(out.isUMatVector()) { for (unsigned int i = 0; i < vec.size(); i++) { out.create(4, 1, CV_32FC2, i); UMat &m = out.getUMatRef(i); - Mat(Mat(vec[i]).t()).copyTo(m); + Mat(Mat(vec[i]).t()*scale).copyTo(m); } } else if(out.kind() == _OutputArray::STD_VECTOR_VECTOR){ for (unsigned int i = 0; i < vec.size(); i++) { out.create(4, 1, CV_32FC2, i); Mat m = out.getMat(i); - Mat(Mat(vec[i]).t()).copyTo(m); + Mat(Mat(vec[i]).t()*scale).copyTo(m); } } else { @@ -655,26 +671,46 @@ static void correctCornerPosition( vector< Point2f >& _candidate, int rotate){ std::rotate(_candidate.begin(), _candidate.begin() + 4 - rotate, _candidate.end()); } +static size_t _findOptPyrImageForCanonicalImg( + const std::vector& img_pyr, + const int scaled_width, + const int cur_perimeter, + const int min_perimeter) { + CV_Assert(scaled_width > 0); + size_t optLevel = 0; + float dist = std::numeric_limits::max(); + for (size_t i = 0; i < img_pyr.size(); ++i) { + const float scale = img_pyr[i].cols / static_cast(scaled_width); + const float perimeter_scaled = cur_perimeter * scale; + // instead of std::abs() favor the larger pyramid level by checking if the distance is postive + // will slow down the algorithm but find more corners in the end + const float new_dist = perimeter_scaled - min_perimeter; + if (new_dist < dist && new_dist > 0.f) { + dist = new_dist; + optLevel = i; + } + } + return optLevel; +} + /** * @brief Identify square candidates according to a marker dictionary */ -static void _identifyCandidates(InputArray _image, vector< vector< vector< Point2f > > >& _candidatesSet, + +static void _identifyCandidates(InputArray grey, + const std::vector& image_pyr, + vector< vector< vector< Point2f > > >& _candidatesSet, vector< vector< vector > >& _contoursSet, const Ptr &_dictionary, vector< vector< Point2f > >& _accepted, vector< vector >& _contours, vector< int >& ids, const Ptr ¶ms, OutputArrayOfArrays _rejected = noArray()) { - + CV_DbgAssert(grey.getMat().total() != 0); + CV_DbgAssert(grey.getMat().type() == CV_8UC1); int ncandidates = (int)_candidatesSet[0].size(); vector< vector< Point2f > > accepted; vector< vector< Point2f > > rejected; - vector< vector< Point > > contours; - CV_Assert(_image.getMat().total() != 0); - - Mat grey; - _convertToGrey(_image.getMat(), grey); - vector< int > idsTmp(ncandidates, -1); vector< int > rotated(ncandidates, 0); vector< uint8_t > validCandidates(ncandidates, 0); @@ -685,10 +721,22 @@ static void _identifyCandidates(InputArray _image, vector< vector< vector< Point const int end = range.end; vector< vector< Point2f > >& candidates = params->detectInvertedMarker ? _candidatesSet[1] : _candidatesSet[0]; + vector< vector< Point > >& contourS = params->detectInvertedMarker ? _contoursSet[1] : _contoursSet[0]; for(int i = begin; i < end; i++) { - int currId; - validCandidates[i] = _identifyOneCandidate(_dictionary, grey, candidates[i], currId, params, rotated[i]); + int currId = -1; + // implements equation (4) + if (params->useAruco3Detection) { + const int perimeterOfContour = static_cast(contourS[i].size()); + const int min_perimeter = params->minSideLengthCanonicalImg * 4; + const size_t nearestImgId = _findOptPyrImageForCanonicalImg(image_pyr, grey.cols(), perimeterOfContour, min_perimeter); + const float scale = image_pyr[nearestImgId].cols / static_cast(grey.cols()); + + validCandidates[i] = _identifyOneCandidate(_dictionary, image_pyr[nearestImgId], candidates[i], currId, params, rotated[i], scale); + } + else { + validCandidates[i] = _identifyOneCandidate(_dictionary, grey, candidates[i], currId, params, rotated[i]); + } if(validCandidates[i] > 0) idsTmp[i] = currId; @@ -1021,6 +1069,25 @@ static void _apriltag(Mat im_orig, const Ptr & _params, std: _zarray_destroy(quads); } +static inline void findCornerInPyrImage(const float scale_init, const int closest_pyr_image_idx, + const std::vector& grey_pyramid, Mat corners, + const Ptr& params) { + // scale them to the closest pyramid level + if (scale_init != 1.f) + corners *= scale_init; // scale_init * scale_pyr + for (int idx = closest_pyr_image_idx - 1; idx >= 0; --idx) { + // scale them to new pyramid level + corners *= 2.f; // *= scale_pyr; + // use larger win size for larger images + const int subpix_win_size = std::max(grey_pyramid[idx].cols, grey_pyramid[idx].rows) > 1080 ? 5 : 3; + cornerSubPix(grey_pyramid[idx], corners, + Size(subpix_win_size, subpix_win_size), + Size(-1, -1), + TermCriteria(TermCriteria::MAX_ITER | TermCriteria::EPS, + params->cornerRefinementMaxIterations, + params->cornerRefinementMinAccuracy)); + } +} /** */ @@ -1029,32 +1096,77 @@ void detectMarkers(InputArray _image, const Ptr &_dictionary, Output OutputArrayOfArrays _rejectedImgPoints, InputArrayOfArrays camMatrix, InputArrayOfArrays distCoeff) { CV_Assert(!_image.empty()); + CV_Assert(_params->markerBorderBits > 0); + // check that the parameters are set correctly if Aruco3 is used + CV_Assert(!(_params->useAruco3Detection == true && + _params->minSideLengthCanonicalImg == 0 && + _params->minMarkerLengthRatioOriginalImg == 0.0)); Mat grey; _convertToGrey(_image.getMat(), grey); - /// STEP 1: Detect marker candidates + // Aruco3 functionality is the extension of Aruco. + // The description can be found in: + // [1] Speeded up detection of squared fiducial markers, 2018, FJ Romera-Ramirez et al. + // if Aruco3 functionality if not wanted + // change some parameters to be sure to turn it off + if (!_params->useAruco3Detection) { + _params->minMarkerLengthRatioOriginalImg = 0.0; + _params->minSideLengthCanonicalImg = 0; + } + else { + // always turn on corner refinement in case of Aruco3, due to upsampling + _params->cornerRefinementMethod = CORNER_REFINE_SUBPIX; + } + + /// Step 0: equation (2) from paper [1] + const float fxfy = (!_params->useAruco3Detection ? 1.f : _params->minSideLengthCanonicalImg / + (_params->minSideLengthCanonicalImg + std::max(grey.cols, grey.rows)*_params->minMarkerLengthRatioOriginalImg)); + + /// Step 1: create image pyramid. Section 3.4. in [1] + std::vector grey_pyramid; + int closest_pyr_image_idx = 0, num_levels = 0; + //// Step 1.1: resize image with equation (1) from paper [1] + if (_params->useAruco3Detection) { + const float scale_pyr = 2.f; + const float img_area = static_cast(grey.rows*grey.cols); + const float min_area_marker = static_cast(_params->minSideLengthCanonicalImg*_params->minSideLengthCanonicalImg); + // find max level + num_levels = static_cast(log2(img_area / min_area_marker)/scale_pyr); + // the closest pyramid image to the downsampled segmentation image + // will later be used as start index for corner upsampling + const float scale_img_area = img_area * fxfy * fxfy; + closest_pyr_image_idx = cvRound(log2(img_area / scale_img_area)/scale_pyr); + } + cv::buildPyramid(grey, grey_pyramid, num_levels); + + // resize to segmentation image + // in this reduces size the contours will be detected + if (fxfy != 1.f) + cv::resize(grey, grey, cv::Size(cvRound(fxfy * grey.cols), cvRound(fxfy * grey.rows))); + + /// STEP 2: Detect marker candidates vector< vector< Point2f > > candidates; vector< vector< Point > > contours; vector< int > ids; vector< vector< vector< Point2f > > > candidatesSet; vector< vector< vector< Point > > > contoursSet; - /// STEP 1.a Detect marker candidates :: using AprilTag + + /// STEP 2.a Detect marker candidates :: using AprilTag if(_params->cornerRefinementMethod == CORNER_REFINE_APRILTAG){ _apriltag(grey, _params, candidates, contours); candidatesSet.push_back(candidates); contoursSet.push_back(contours); } - - /// STEP 1.b Detect marker candidates :: traditional way + /// STEP 2.b Detect marker candidates :: traditional way else _detectCandidates(grey, candidatesSet, contoursSet, _params); /// STEP 2: Check candidate codification (identify markers) - _identifyCandidates(grey, candidatesSet, contoursSet, _dictionary, candidates, contours, ids, _params, - _rejectedImgPoints); + _identifyCandidates(grey, grey_pyramid, candidatesSet, contoursSet, _dictionary, + candidates, contours, ids, _params, _rejectedImgPoints); // copy to output arrays _copyVector2Output(candidates, _corners); @@ -1064,13 +1176,17 @@ void detectMarkers(InputArray _image, const Ptr &_dictionary, Output if( _params->cornerRefinementMethod == CORNER_REFINE_SUBPIX ) { CV_Assert(_params->cornerRefinementWinSize > 0 && _params->cornerRefinementMaxIterations > 0 && _params->cornerRefinementMinAccuracy > 0); - - //// do corner refinement for each of the detected markers + // Do subpixel estimation. In Aruco3 start on the lowest pyramid level and upscale the corners parallel_for_(Range(0, _corners.cols()), [&](const Range& range) { const int begin = range.start; const int end = range.end; for (int i = begin; i < end; i++) { + if (_params->useAruco3Detection) { + const float scale_init = (float) grey_pyramid[closest_pyr_image_idx].cols / grey.cols; + findCornerInPyrImage(scale_init, closest_pyr_image_idx, grey_pyramid, _corners.getMat(i), _params); + } + else cornerSubPix(grey, _corners.getMat(i), Size(_params->cornerRefinementWinSize, _params->cornerRefinementWinSize), Size(-1, -1), @@ -1098,6 +1214,11 @@ void detectMarkers(InputArray _image, const Ptr &_dictionary, Output _copyVector2Output(candidates, _corners); } } + if (_params->cornerRefinementMethod != CORNER_REFINE_APRILTAG && + _params->cornerRefinementMethod != CORNER_REFINE_SUBPIX) { + // scale to orignal size, this however will lead to inaccurate detections! + _copyVector2Output(candidates, _corners, 1.f/fxfy); + } } /** diff --git a/modules/aruco/test/test_arucodetection.cpp b/modules/aruco/test/test_arucodetection.cpp index 7219a542bf..9cc08bd011 100644 --- a/modules/aruco/test/test_arucodetection.cpp +++ b/modules/aruco/test/test_arucodetection.cpp @@ -246,6 +246,13 @@ static Mat projectMarker(Ptr &dictionary, int id, Mat cameraM return img; } +enum class ArucoAlgParams +{ + USE_DEFAULT = 0, + USE_APRILTAG=1, /// Detect marker candidates :: using AprilTag + DETECT_INVERTED_MARKER, /// Check if there is a white marker + USE_ARUCO3 /// Check if aruco3 should be used +}; /** @@ -253,22 +260,15 @@ static Mat projectMarker(Ptr &dictionary, int id, Mat cameraM */ class CV_ArucoDetectionPerspective : public cvtest::BaseTest { public: - CV_ArucoDetectionPerspective(); - - enum checkWithParameter{ - USE_APRILTAG=1, /// Detect marker candidates :: using AprilTag - DETECT_INVERTED_MARKER, /// Check if there is a white marker - }; + CV_ArucoDetectionPerspective(ArucoAlgParams arucoAlgParam) : arucoAlgParams(arucoAlgParam) {} protected: void run(int); + ArucoAlgParams arucoAlgParams; }; -CV_ArucoDetectionPerspective::CV_ArucoDetectionPerspective() {} - - -void CV_ArucoDetectionPerspective::run(int tryWith) { +void CV_ArucoDetectionPerspective::run(int) { int iter = 0; int szEnclosed = 0; @@ -297,15 +297,20 @@ void CV_ArucoDetectionPerspective::run(int tryWith) { projectMarker(dictionary, currentId, cameraMatrix, deg2rad(yaw), deg2rad(pitch), distance, imgSize, markerBorder, groundTruthCorners, szEnclosed); // marker :: Inverted - if(CV_ArucoDetectionPerspective::DETECT_INVERTED_MARKER == tryWith){ + if(ArucoAlgParams::DETECT_INVERTED_MARKER == arucoAlgParams){ img = ~img; params->detectInvertedMarker = true; } - if(CV_ArucoDetectionPerspective::USE_APRILTAG == tryWith){ + if(ArucoAlgParams::USE_APRILTAG == arucoAlgParams){ params->cornerRefinementMethod = cv::aruco::CORNER_REFINE_APRILTAG; } + if (ArucoAlgParams::USE_ARUCO3 == arucoAlgParams) { + params->useAruco3Detection = true; + params->cornerRefinementMethod = cv::aruco::CORNER_REFINE_SUBPIX; + } + // detect markers vector< vector< Point2f > > corners; vector< int > ids; @@ -331,7 +336,7 @@ void CV_ArucoDetectionPerspective::run(int tryWith) { } } // change the state :: to detect an enclosed inverted marker - if( CV_ArucoDetectionPerspective::DETECT_INVERTED_MARKER == tryWith && distance == 0.1 ){ + if(ArucoAlgParams::DETECT_INVERTED_MARKER == arucoAlgParams && distance == 0.1){ distance -= 0.1; szEnclosed++; } @@ -523,15 +528,21 @@ void CV_ArucoBitCorrection::run(int) { typedef CV_ArucoDetectionPerspective CV_AprilTagDetectionPerspective; typedef CV_ArucoDetectionPerspective CV_InvertedArucoDetectionPerspective; +typedef CV_ArucoDetectionPerspective CV_Aruco3DetectionPerspective; TEST(CV_InvertedArucoDetectionPerspective, algorithmic) { - CV_InvertedArucoDetectionPerspective test; - test.safe_run(CV_ArucoDetectionPerspective::DETECT_INVERTED_MARKER); + CV_InvertedArucoDetectionPerspective test(ArucoAlgParams::DETECT_INVERTED_MARKER); + test.safe_run(); } TEST(CV_AprilTagDetectionPerspective, algorithmic) { - CV_AprilTagDetectionPerspective test; - test.safe_run(CV_ArucoDetectionPerspective::USE_APRILTAG); + CV_AprilTagDetectionPerspective test(ArucoAlgParams::USE_APRILTAG); + test.safe_run(); +} + +TEST(CV_Aruco3DetectionPerspective, algorithmic) { + CV_Aruco3DetectionPerspective test(ArucoAlgParams::USE_ARUCO3); + test.safe_run(); } TEST(CV_ArucoDetectionSimple, algorithmic) { @@ -540,7 +551,7 @@ TEST(CV_ArucoDetectionSimple, algorithmic) { } TEST(CV_ArucoDetectionPerspective, algorithmic) { - CV_ArucoDetectionPerspective test; + CV_ArucoDetectionPerspective test(ArucoAlgParams::USE_DEFAULT); test.safe_run(); } diff --git a/modules/aruco/test/test_boarddetection.cpp b/modules/aruco/test/test_boarddetection.cpp index 474bc372f1..dc3a92ec4e 100644 --- a/modules/aruco/test/test_boarddetection.cpp +++ b/modules/aruco/test/test_boarddetection.cpp @@ -151,23 +151,34 @@ static Mat projectBoard(Ptr &board, Mat cameraMatrix, double y return img; } - +enum class ArucoAlgParams +{ + USE_DEFAULT = 0, + USE_ARUCO3 = 1 +}; /** * @brief Check pose estimation of aruco board */ class CV_ArucoBoardPose : public cvtest::BaseTest { public: - CV_ArucoBoardPose(); + CV_ArucoBoardPose(ArucoAlgParams arucoAlgParams) + { + params = aruco::DetectorParameters::create(); + params->minDistanceToBorder = 3; + if (arucoAlgParams == ArucoAlgParams::USE_ARUCO3) { + params->useAruco3Detection = true; + params->cornerRefinementMethod = aruco::CORNER_REFINE_SUBPIX; + params->minSideLengthCanonicalImg = 16; + } + } protected: + Ptr params; void run(int); }; -CV_ArucoBoardPose::CV_ArucoBoardPose() {} - - void CV_ArucoBoardPose::run(int) { int iter = 0; @@ -197,8 +208,6 @@ void CV_ArucoBoardPose::run(int) { vector< vector< Point2f > > corners; vector< int > ids; - Ptr params = aruco::DetectorParameters::create(); - params->minDistanceToBorder = 3; params->markerBorderBits = markerBorder; aruco::detectMarkers(img, dictionary, corners, ids, params); @@ -253,16 +262,21 @@ void CV_ArucoBoardPose::run(int) { */ class CV_ArucoRefine : public cvtest::BaseTest { public: - CV_ArucoRefine(); + CV_ArucoRefine(ArucoAlgParams arucoAlgParams) + { + params = aruco::DetectorParameters::create(); + params->minDistanceToBorder = 3; + params->cornerRefinementMethod = aruco::CORNER_REFINE_SUBPIX; + if (arucoAlgParams == ArucoAlgParams::USE_ARUCO3) + params->useAruco3Detection = true; + } protected: + Ptr params; void run(int); }; -CV_ArucoRefine::CV_ArucoRefine() {} - - void CV_ArucoRefine::run(int) { int iter = 0; @@ -293,9 +307,6 @@ void CV_ArucoRefine::run(int) { // detect markers vector< vector< Point2f > > corners, rejected; vector< int > ids; - Ptr params = aruco::DetectorParameters::create(); - params->minDistanceToBorder = 3; - params->cornerRefinementMethod = aruco::CORNER_REFINE_SUBPIX; params->markerBorderBits = markerBorder; aruco::detectMarkers(img, dictionary, corners, ids, params, rejected); @@ -326,12 +337,25 @@ void CV_ArucoRefine::run(int) { TEST(CV_ArucoBoardPose, accuracy) { - CV_ArucoBoardPose test; + CV_ArucoBoardPose test(ArucoAlgParams::USE_DEFAULT); + test.safe_run(); +} + +typedef CV_ArucoBoardPose CV_Aruco3BoardPose; +TEST(CV_Aruco3BoardPose, accuracy) { + CV_Aruco3BoardPose test(ArucoAlgParams::USE_ARUCO3); test.safe_run(); } +typedef CV_ArucoRefine CV_Aruco3Refine; + TEST(CV_ArucoRefine, accuracy) { - CV_ArucoRefine test; + CV_ArucoRefine test(ArucoAlgParams::USE_DEFAULT); + test.safe_run(); +} + +TEST(CV_Aruco3Refine, accuracy) { + CV_Aruco3Refine test(ArucoAlgParams::USE_ARUCO3); test.safe_run(); }