Merge pull request #21942 from pglotov:add-blob-contours
authorPetr Glotov <1294313+pglotov@users.noreply.github.com>
Fri, 7 Oct 2022 16:07:51 +0000 (09:07 -0700)
committerGitHub <noreply@github.com>
Fri, 7 Oct 2022 16:07:51 +0000 (19:07 +0300)
added blob contours to blob detector

* added blob contours

* Fixed Java regression test after new parameter addition to SimpleBlobDetector.

* Added stub implementation of SimpleBlobDetector::getBlobContours to presume source API compatibility.

modules/features2d/include/opencv2/features2d.hpp
modules/features2d/misc/java/test/SIMPLEBLOBFeatureDetectorTest.java
modules/features2d/src/blobdetector.cpp
modules/features2d/src/keypoint.cpp
modules/features2d/test/test_blobdetector.cpp

index 952d24c..03f776d 100644 (file)
@@ -108,6 +108,10 @@ public:
      */
     static void runByPixelsMask( std::vector<KeyPoint>& 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<KeyPoint> &keypoints, std::vector<std::vector<Point> > &removeFrom, const Mat &mask);
+    /*
      * Remove duplicated keypoints.
      */
     static void removeDuplicated( std::vector<KeyPoint>& 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<SimpleBlobDetector>
     create(const SimpleBlobDetector::Params &parameters = SimpleBlobDetector::Params());
   CV_WRAP virtual String getDefaultName() const CV_OVERRIDE;
+  CV_WRAP virtual const std::vector<std::vector<cv::Point> >& getBlobContours() const;
 };
 
 //! @} features2d_main
index 1d8517b..34c0d94 100644 (file)
@@ -112,7 +112,7 @@ public class SIMPLEBLOBFeatureDetectorTest extends OpenCVTestCase {
 
         detector.write(filename);
 
-        String truth = "<?xml version=\"1.0\"?>\n<opencv_storage>\n<format>3</format>\n<thresholdStep>10.</thresholdStep>\n<minThreshold>50.</minThreshold>\n<maxThreshold>220.</maxThreshold>\n<minRepeatability>2</minRepeatability>\n<minDistBetweenBlobs>10.</minDistBetweenBlobs>\n<filterByColor>1</filterByColor>\n<blobColor>0</blobColor>\n<filterByArea>1</filterByArea>\n<minArea>25.</minArea>\n<maxArea>5000.</maxArea>\n<filterByCircularity>0</filterByCircularity>\n<minCircularity>8.0000001192092896e-01</minCircularity>\n<maxCircularity>3.4028234663852886e+38</maxCircularity>\n<filterByInertia>1</filterByInertia>\n<minInertiaRatio>1.0000000149011612e-01</minInertiaRatio>\n<maxInertiaRatio>3.4028234663852886e+38</maxInertiaRatio>\n<filterByConvexity>1</filterByConvexity>\n<minConvexity>9.4999998807907104e-01</minConvexity>\n<maxConvexity>3.4028234663852886e+38</maxConvexity>\n</opencv_storage>\n";
+        String truth = "<?xml version=\"1.0\"?>\n<opencv_storage>\n<format>3</format>\n<thresholdStep>10.</thresholdStep>\n<minThreshold>50.</minThreshold>\n<maxThreshold>220.</maxThreshold>\n<minRepeatability>2</minRepeatability>\n<minDistBetweenBlobs>10.</minDistBetweenBlobs>\n<filterByColor>1</filterByColor>\n<blobColor>0</blobColor>\n<filterByArea>1</filterByArea>\n<minArea>25.</minArea>\n<maxArea>5000.</maxArea>\n<filterByCircularity>0</filterByCircularity>\n<minCircularity>8.0000001192092896e-01</minCircularity>\n<maxCircularity>3.4028234663852886e+38</maxCircularity>\n<filterByInertia>1</filterByInertia>\n<minInertiaRatio>1.0000000149011612e-01</minInertiaRatio>\n<maxInertiaRatio>3.4028234663852886e+38</maxInertiaRatio>\n<filterByConvexity>1</filterByConvexity>\n<minConvexity>9.4999998807907104e-01</minConvexity>\n<maxConvexity>3.4028234663852886e+38</maxConvexity>\n<collectContours>0</collectContours>\n</opencv_storage>\n";
         assertEquals(truth, readFile(filename));
     }
 }
index fb79831..9fb4ac4 100644 (file)
 namespace cv
 {
 
+// TODO: To be removed in 5.x branch
+const std::vector<std::vector<cv::Point> >& 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<KeyPoint>& keypoints, InputArray mask=noArray() ) CV_OVERRIDE;
-  virtual void findBlobs(InputArray image, InputArray binaryImage, std::vector<Center> &centers) const;
+  virtual void findBlobs(InputArray image, InputArray binaryImage, std::vector<Center> &centers,
+                         std::vector<std::vector<Point> > &contours, std::vector<Moments> &moments) const;
+  virtual const std::vector<std::vector<Point> >& getBlobContours() const CV_OVERRIDE;
 
   Params params;
+  std::vector<std::vector<Point> > blobContours;
 };
 
 /*
@@ -110,6 +119,8 @@ SimpleBlobDetector::Params::Params()
     //minConvexity = 0.8;
     minConvexity = 0.95f;
     maxConvexity = std::numeric_limits<float>::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 &parameters) :
@@ -186,13 +201,16 @@ void SimpleBlobDetectorImpl::write( cv::FileStorage& fs ) const
     params.write(fs);
 }
 
-void SimpleBlobDetectorImpl::findBlobs(InputArray _image, InputArray _binaryImage, std::vector<Center> &centers) const
+void SimpleBlobDetectorImpl::findBlobs(InputArray _image, InputArray _binaryImage, std::vector<Center> &centers,
+                                       std::vector<std::vector<Point> > &contoursOut, std::vector<Moments> &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<Point> > 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::KeyPoint>&
     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<cv::KeyPoint>&
     }
 
     std::vector < std::vector<Center> > centers;
+    std::vector<Moments> 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<std::vector<Point> > curContours;
+        std::vector<Moments> curMomentss;
+        findBlobs(grayscaleImage, binarizedImage, curCenters, curContours, curMomentss);
         std::vector < std::vector<Center> > newCenters;
+        std::vector<std::vector<Point> > newContours;
+        std::vector<Moments> newMomentss;
         for (size_t i = 0; i < curCenters.size(); i++)
         {
             bool isNew = true;
@@ -358,15 +387,37 @@ void SimpleBlobDetectorImpl::detect(InputArray image, std::vector<cv::KeyPoint>&
                         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<Center> (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<cv::KeyPoint>&
 
     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<std::vector<Point> >& SimpleBlobDetectorImpl::getBlobContours() const {
+    return blobContours;
+}
+
 Ptr<SimpleBlobDetector> SimpleBlobDetector::create(const SimpleBlobDetector::Params& params)
 {
     return makePtr<SimpleBlobDetectorImpl>(params);
index 21d9eb3..4d2007f 100644 (file)
@@ -165,6 +165,29 @@ void KeyPointsFilter::runByPixelsMask( std::vector<KeyPoint>& 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 <typename T>
+void runByPixelsMask2(std::vector<KeyPoint> &keypoints, std::vector<T> &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<KeyPoint> &keypoints, std::vector<std::vector<Point> > &removeFrom, const Mat &mask)
+{
+    runByPixelsMask2(keypoints, removeFrom, mask);
+}
 
 struct KeyPoint_LessThan
 {
index c6ed09e..2aa99b6 100644 (file)
@@ -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<KeyPoint> keypoints;
+
+    Ptr<SimpleBlobDetector> detector = SimpleBlobDetector::create(params);
+    detector->detect(image, keypoints);
+    ASSERT_NE((int)keypoints.size(), 0);
+
+    ASSERT_GT((int)detector->getBlobContours().size(), 0);
+    std::vector<Point> 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