diff --git a/modules/features2d/include/opencv2/features2d.hpp b/modules/features2d/include/opencv2/features2d.hpp index 952d24ca0c..03f776d578 100644 --- a/modules/features2d/include/opencv2/features2d.hpp +++ b/modules/features2d/include/opencv2/features2d.hpp @@ -107,6 +107,10 @@ public: * Remove keypoints from some image by mask for pixels of this image. */ static void runByPixelsMask( std::vector& keypoints, const Mat& mask ); + /* + * Remove objects from some image and a vector of points by mask for pixels of this image + */ + static void runByPixelsMask2VectorPoint(std::vector &keypoints, std::vector > &removeFrom, const Mat &mask); /* * Remove duplicated keypoints. */ @@ -719,6 +723,8 @@ public: CV_PROP_RW bool filterByConvexity; CV_PROP_RW float minConvexity, maxConvexity; + CV_PROP_RW bool collectContours; + void read( const FileNode& fn ); void write( FileStorage& fs ) const; }; @@ -726,6 +732,7 @@ public: CV_WRAP static Ptr create(const SimpleBlobDetector::Params ¶meters = SimpleBlobDetector::Params()); CV_WRAP virtual String getDefaultName() const CV_OVERRIDE; + CV_WRAP virtual const std::vector >& getBlobContours() const; }; //! @} features2d_main diff --git a/modules/features2d/misc/java/test/SIMPLEBLOBFeatureDetectorTest.java b/modules/features2d/misc/java/test/SIMPLEBLOBFeatureDetectorTest.java index 1d8517bbd4..34c0d94a76 100644 --- a/modules/features2d/misc/java/test/SIMPLEBLOBFeatureDetectorTest.java +++ b/modules/features2d/misc/java/test/SIMPLEBLOBFeatureDetectorTest.java @@ -112,7 +112,7 @@ public class SIMPLEBLOBFeatureDetectorTest extends OpenCVTestCase { detector.write(filename); - String truth = "\n\n3\n10.\n50.\n220.\n2\n10.\n1\n0\n1\n25.\n5000.\n0\n8.0000001192092896e-01\n3.4028234663852886e+38\n1\n1.0000000149011612e-01\n3.4028234663852886e+38\n1\n9.4999998807907104e-01\n3.4028234663852886e+38\n\n"; + String truth = "\n\n3\n10.\n50.\n220.\n2\n10.\n1\n0\n1\n25.\n5000.\n0\n8.0000001192092896e-01\n3.4028234663852886e+38\n1\n1.0000000149011612e-01\n3.4028234663852886e+38\n1\n9.4999998807907104e-01\n3.4028234663852886e+38\n0\n\n"; assertEquals(truth, readFile(filename)); } } diff --git a/modules/features2d/src/blobdetector.cpp b/modules/features2d/src/blobdetector.cpp index fb79831807..9fb4ac4974 100644 --- a/modules/features2d/src/blobdetector.cpp +++ b/modules/features2d/src/blobdetector.cpp @@ -56,6 +56,12 @@ namespace cv { +// TODO: To be removed in 5.x branch +const std::vector >& SimpleBlobDetector::getBlobContours() const +{ + CV_Error(Error::StsNotImplemented, "Method SimpleBlobDetector::getBlobContours() is not implemented"); +} + class CV_EXPORTS_W SimpleBlobDetectorImpl : public SimpleBlobDetector { public: @@ -74,9 +80,12 @@ protected: }; virtual void detect( InputArray image, std::vector& keypoints, InputArray mask=noArray() ) CV_OVERRIDE; - virtual void findBlobs(InputArray image, InputArray binaryImage, std::vector
¢ers) const; + virtual void findBlobs(InputArray image, InputArray binaryImage, std::vector
¢ers, + std::vector > &contours, std::vector &moments) const; + virtual const std::vector >& getBlobContours() const CV_OVERRIDE; Params params; + std::vector > blobContours; }; /* @@ -110,6 +119,8 @@ SimpleBlobDetector::Params::Params() //minConvexity = 0.8; minConvexity = 0.95f; maxConvexity = std::numeric_limits::max(); + + collectContours = false; } void SimpleBlobDetector::Params::read(const cv::FileNode& fn ) @@ -139,6 +150,8 @@ void SimpleBlobDetector::Params::read(const cv::FileNode& fn ) filterByConvexity = (int)fn["filterByConvexity"] != 0 ? true : false; minConvexity = fn["minConvexity"]; maxConvexity = fn["maxConvexity"]; + + collectContours = (int)fn["collectContours"] != 0 ? true : false; } void SimpleBlobDetector::Params::write(cv::FileStorage& fs) const @@ -168,6 +181,8 @@ void SimpleBlobDetector::Params::write(cv::FileStorage& fs) const fs << "filterByConvexity" << (int)filterByConvexity; fs << "minConvexity" << minConvexity; fs << "maxConvexity" << maxConvexity; + + fs << "collectContours" << (int)collectContours; } SimpleBlobDetectorImpl::SimpleBlobDetectorImpl(const SimpleBlobDetector::Params ¶meters) : @@ -186,13 +201,16 @@ void SimpleBlobDetectorImpl::write( cv::FileStorage& fs ) const params.write(fs); } -void SimpleBlobDetectorImpl::findBlobs(InputArray _image, InputArray _binaryImage, std::vector
¢ers) const +void SimpleBlobDetectorImpl::findBlobs(InputArray _image, InputArray _binaryImage, std::vector
¢ers, + std::vector > &contoursOut, std::vector &momentss) const { CV_INSTRUMENT_REGION(); Mat image = _image.getMat(), binaryImage = _binaryImage.getMat(); CV_UNUSED(image); centers.clear(); + contoursOut.clear(); + momentss.clear(); std::vector < std::vector > contours; findContours(binaryImage, contours, RETR_LIST, CHAIN_APPROX_NONE); @@ -291,7 +309,11 @@ void SimpleBlobDetectorImpl::findBlobs(InputArray _image, InputArray _binaryImag } centers.push_back(center); - + if (params.collectContours) + { + contoursOut.push_back(contours[contourIdx]); + momentss.push_back(moms); + } #ifdef DEBUG_BLOB_DETECTOR circle( keypointsImage, center.location, 1, Scalar(0,0,255), 1 ); @@ -308,6 +330,8 @@ void SimpleBlobDetectorImpl::detect(InputArray image, std::vector& CV_INSTRUMENT_REGION(); keypoints.clear(); + blobContours.clear(); + CV_Assert(params.minRepeatability != 0); Mat grayscaleImage; if (image.channels() == 3 || image.channels() == 4) @@ -333,14 +357,19 @@ void SimpleBlobDetectorImpl::detect(InputArray image, std::vector& } std::vector < std::vector
> centers; + std::vector momentss; for (double thresh = params.minThreshold; thresh < params.maxThreshold; thresh += params.thresholdStep) { Mat binarizedImage; threshold(grayscaleImage, binarizedImage, thresh, 255, THRESH_BINARY); std::vector < Center > curCenters; - findBlobs(grayscaleImage, binarizedImage, curCenters); + std::vector > curContours; + std::vector curMomentss; + findBlobs(grayscaleImage, binarizedImage, curCenters, curContours, curMomentss); std::vector < std::vector
> newCenters; + std::vector > newContours; + std::vector newMomentss; for (size_t i = 0; i < curCenters.size(); i++) { bool isNew = true; @@ -358,15 +387,37 @@ void SimpleBlobDetectorImpl::detect(InputArray image, std::vector& centers[j][k] = centers[j][k-1]; k--; } + + if (params.collectContours) + { + if (curCenters[i].confidence > centers[j][k].confidence + || (curCenters[i].confidence == centers[j][k].confidence && curMomentss[i].m00 > momentss[j].m00)) + { + blobContours[j] = curContours[i]; + momentss[j] = curMomentss[i]; + } + } centers[j][k] = curCenters[i]; break; } } if (isNew) + { newCenters.push_back(std::vector
(1, curCenters[i])); + if (params.collectContours) + { + newContours.push_back(curContours[i]); + newMomentss.push_back(curMomentss[i]); + } + } } std::copy(newCenters.begin(), newCenters.end(), std::back_inserter(centers)); + if (params.collectContours) + { + std::copy(newContours.begin(), newContours.end(), std::back_inserter(blobContours)); + std::copy(newMomentss.begin(), newMomentss.end(), std::back_inserter(momentss)); + } } for (size_t i = 0; i < centers.size(); i++) @@ -387,10 +438,21 @@ void SimpleBlobDetectorImpl::detect(InputArray image, std::vector& if (!mask.empty()) { - KeyPointsFilter::runByPixelsMask(keypoints, mask.getMat()); + if (params.collectContours) + { + KeyPointsFilter::runByPixelsMask2VectorPoint(keypoints, blobContours, mask.getMat()); + } + else + { + KeyPointsFilter::runByPixelsMask(keypoints, mask.getMat()); + } } } +const std::vector >& SimpleBlobDetectorImpl::getBlobContours() const { + return blobContours; +} + Ptr SimpleBlobDetector::create(const SimpleBlobDetector::Params& params) { return makePtr(params); diff --git a/modules/features2d/src/keypoint.cpp b/modules/features2d/src/keypoint.cpp index 21d9eb30f7..4d2007f6d7 100644 --- a/modules/features2d/src/keypoint.cpp +++ b/modules/features2d/src/keypoint.cpp @@ -165,6 +165,29 @@ void KeyPointsFilter::runByPixelsMask( std::vector& keypoints, const M keypoints.erase(std::remove_if(keypoints.begin(), keypoints.end(), MaskPredicate(mask)), keypoints.end()); } +/* + * Remove objects from some image and a vector by mask for pixels of this image + */ +template +void runByPixelsMask2(std::vector &keypoints, std::vector &removeFrom, const Mat &mask) +{ + if (mask.empty()) + return; + + MaskPredicate maskPredicate(mask); + removeFrom.erase(std::remove_if(removeFrom.begin(), removeFrom.end(), + [&](const T &x) + { + auto index = &x - &removeFrom.front(); + return maskPredicate(keypoints[index]); + }), + removeFrom.end()); + keypoints.erase(std::remove_if(keypoints.begin(), keypoints.end(), maskPredicate), keypoints.end()); +} +void KeyPointsFilter::runByPixelsMask2VectorPoint(std::vector &keypoints, std::vector > &removeFrom, const Mat &mask) +{ + runByPixelsMask2(keypoints, removeFrom, mask); +} struct KeyPoint_LessThan { diff --git a/modules/features2d/test/test_blobdetector.cpp b/modules/features2d/test/test_blobdetector.cpp index c6ed09e6a2..2aa99b60f7 100644 --- a/modules/features2d/test/test_blobdetector.cpp +++ b/modules/features2d/test/test_blobdetector.cpp @@ -19,4 +19,28 @@ TEST(Features2d_BlobDetector, bug_6667) detector->detect(image, keypoints); ASSERT_NE((int) keypoints.size(), 0); } + +TEST(Features2d_BlobDetector, withContours) +{ + cv::Mat image = cv::Mat(cv::Size(100, 100), CV_8UC1, cv::Scalar(255, 255, 255)); + cv::circle(image, Point(50, 50), 20, cv::Scalar(0), -1); + SimpleBlobDetector::Params params; + params.minThreshold = 250; + params.maxThreshold = 260; + params.minRepeatability = 1; // https://github.com/opencv/opencv/issues/6667 + params.collectContours = true; + std::vector keypoints; + + Ptr detector = SimpleBlobDetector::create(params); + detector->detect(image, keypoints); + ASSERT_NE((int)keypoints.size(), 0); + + ASSERT_GT((int)detector->getBlobContours().size(), 0); + std::vector contour = detector->getBlobContours()[0]; + ASSERT_TRUE(std::any_of(contour.begin(), contour.end(), + [](Point p) + { + return abs(p.x - 30) < 2 && abs(p.y - 50) < 2; + })); +} }} // namespace