Merge pull request #21942 from pglotov:add-blob-contours

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.
This commit is contained in:
Petr Glotov 2022-10-07 09:07:51 -07:00 committed by GitHub
parent 5cd07006f6
commit a3ebafbdeb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 122 additions and 6 deletions

View File

@ -107,6 +107,10 @@ public:
* Remove keypoints from some image by mask for pixels of this image. * Remove keypoints from some image by mask for pixels of this image.
*/ */
static void runByPixelsMask( std::vector<KeyPoint>& keypoints, const Mat& mask ); 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. * Remove duplicated keypoints.
*/ */
@ -719,6 +723,8 @@ public:
CV_PROP_RW bool filterByConvexity; CV_PROP_RW bool filterByConvexity;
CV_PROP_RW float minConvexity, maxConvexity; CV_PROP_RW float minConvexity, maxConvexity;
CV_PROP_RW bool collectContours;
void read( const FileNode& fn ); void read( const FileNode& fn );
void write( FileStorage& fs ) const; void write( FileStorage& fs ) const;
}; };
@ -726,6 +732,7 @@ public:
CV_WRAP static Ptr<SimpleBlobDetector> CV_WRAP static Ptr<SimpleBlobDetector>
create(const SimpleBlobDetector::Params &parameters = SimpleBlobDetector::Params()); create(const SimpleBlobDetector::Params &parameters = SimpleBlobDetector::Params());
CV_WRAP virtual String getDefaultName() const CV_OVERRIDE; CV_WRAP virtual String getDefaultName() const CV_OVERRIDE;
CV_WRAP virtual const std::vector<std::vector<cv::Point> >& getBlobContours() const;
}; };
//! @} features2d_main //! @} features2d_main

View File

@ -112,7 +112,7 @@ public class SIMPLEBLOBFeatureDetectorTest extends OpenCVTestCase {
detector.write(filename); 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)); assertEquals(truth, readFile(filename));
} }
} }

View File

@ -56,6 +56,12 @@
namespace cv 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 class CV_EXPORTS_W SimpleBlobDetectorImpl : public SimpleBlobDetector
{ {
public: public:
@ -74,9 +80,12 @@ protected:
}; };
virtual void detect( InputArray image, std::vector<KeyPoint>& keypoints, InputArray mask=noArray() ) CV_OVERRIDE; 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; Params params;
std::vector<std::vector<Point> > blobContours;
}; };
/* /*
@ -110,6 +119,8 @@ SimpleBlobDetector::Params::Params()
//minConvexity = 0.8; //minConvexity = 0.8;
minConvexity = 0.95f; minConvexity = 0.95f;
maxConvexity = std::numeric_limits<float>::max(); maxConvexity = std::numeric_limits<float>::max();
collectContours = false;
} }
void SimpleBlobDetector::Params::read(const cv::FileNode& fn ) 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; filterByConvexity = (int)fn["filterByConvexity"] != 0 ? true : false;
minConvexity = fn["minConvexity"]; minConvexity = fn["minConvexity"];
maxConvexity = fn["maxConvexity"]; maxConvexity = fn["maxConvexity"];
collectContours = (int)fn["collectContours"] != 0 ? true : false;
} }
void SimpleBlobDetector::Params::write(cv::FileStorage& fs) const 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 << "filterByConvexity" << (int)filterByConvexity;
fs << "minConvexity" << minConvexity; fs << "minConvexity" << minConvexity;
fs << "maxConvexity" << maxConvexity; fs << "maxConvexity" << maxConvexity;
fs << "collectContours" << (int)collectContours;
} }
SimpleBlobDetectorImpl::SimpleBlobDetectorImpl(const SimpleBlobDetector::Params &parameters) : SimpleBlobDetectorImpl::SimpleBlobDetectorImpl(const SimpleBlobDetector::Params &parameters) :
@ -186,13 +201,16 @@ void SimpleBlobDetectorImpl::write( cv::FileStorage& fs ) const
params.write(fs); 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(); CV_INSTRUMENT_REGION();
Mat image = _image.getMat(), binaryImage = _binaryImage.getMat(); Mat image = _image.getMat(), binaryImage = _binaryImage.getMat();
CV_UNUSED(image); CV_UNUSED(image);
centers.clear(); centers.clear();
contoursOut.clear();
momentss.clear();
std::vector < std::vector<Point> > contours; std::vector < std::vector<Point> > contours;
findContours(binaryImage, contours, RETR_LIST, CHAIN_APPROX_NONE); findContours(binaryImage, contours, RETR_LIST, CHAIN_APPROX_NONE);
@ -291,7 +309,11 @@ void SimpleBlobDetectorImpl::findBlobs(InputArray _image, InputArray _binaryImag
} }
centers.push_back(center); centers.push_back(center);
if (params.collectContours)
{
contoursOut.push_back(contours[contourIdx]);
momentss.push_back(moms);
}
#ifdef DEBUG_BLOB_DETECTOR #ifdef DEBUG_BLOB_DETECTOR
circle( keypointsImage, center.location, 1, Scalar(0,0,255), 1 ); 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(); CV_INSTRUMENT_REGION();
keypoints.clear(); keypoints.clear();
blobContours.clear();
CV_Assert(params.minRepeatability != 0); CV_Assert(params.minRepeatability != 0);
Mat grayscaleImage; Mat grayscaleImage;
if (image.channels() == 3 || image.channels() == 4) 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 < std::vector<Center> > centers;
std::vector<Moments> momentss;
for (double thresh = params.minThreshold; thresh < params.maxThreshold; thresh += params.thresholdStep) for (double thresh = params.minThreshold; thresh < params.maxThreshold; thresh += params.thresholdStep)
{ {
Mat binarizedImage; Mat binarizedImage;
threshold(grayscaleImage, binarizedImage, thresh, 255, THRESH_BINARY); threshold(grayscaleImage, binarizedImage, thresh, 255, THRESH_BINARY);
std::vector < Center > curCenters; 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<Center> > newCenters;
std::vector<std::vector<Point> > newContours;
std::vector<Moments> newMomentss;
for (size_t i = 0; i < curCenters.size(); i++) for (size_t i = 0; i < curCenters.size(); i++)
{ {
bool isNew = true; bool isNew = true;
@ -358,15 +387,37 @@ void SimpleBlobDetectorImpl::detect(InputArray image, std::vector<cv::KeyPoint>&
centers[j][k] = centers[j][k-1]; centers[j][k] = centers[j][k-1];
k--; 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]; centers[j][k] = curCenters[i];
break; break;
} }
} }
if (isNew) if (isNew)
{
newCenters.push_back(std::vector<Center> (1, curCenters[i])); 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)); 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++) 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()) 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) Ptr<SimpleBlobDetector> SimpleBlobDetector::create(const SimpleBlobDetector::Params& params)
{ {
return makePtr<SimpleBlobDetectorImpl>(params); return makePtr<SimpleBlobDetectorImpl>(params);

View 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()); 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 struct KeyPoint_LessThan
{ {

View File

@ -19,4 +19,28 @@ TEST(Features2d_BlobDetector, bug_6667)
detector->detect(image, keypoints); detector->detect(image, keypoints);
ASSERT_NE((int) keypoints.size(), 0); 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 }} // namespace