You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
447 lines
17 KiB
C++
447 lines
17 KiB
C++
/*!
|
|
* \file BlobDetector.h
|
|
* \date 2019/01/31
|
|
*
|
|
* \author Lin, Chi
|
|
* Contact: lin.chi@hzleaper.com
|
|
*
|
|
*
|
|
* \note
|
|
*/
|
|
|
|
#ifndef __BlobDetector_h_
|
|
#define __BlobDetector_h_
|
|
|
|
#include "CVUtils.h"
|
|
#include "StdUtils.h"
|
|
#include "CyclopsEnums.h"
|
|
#include "CyclopsModules.h"
|
|
|
|
#include "DetectRoi.h"
|
|
#include "BlobInstance.h"
|
|
#include "BlobFilter.h"
|
|
|
|
typedef std::pair<Scalar, Scalar> BlobColorRange;
|
|
static inline void operator >> (const FileNode &node, BlobColorRange& cr) {
|
|
node["first"] >> cr.first;
|
|
node["second"] >> cr.second;
|
|
}
|
|
|
|
static inline FileStorage& operator << (FileStorage &fs, BlobColorRange& cr) {
|
|
fs << "{:" << "first" << cr.first << "second" << cr.second << "}";
|
|
return fs;
|
|
}
|
|
|
|
static inline void operator >> (const FileNode &node, vector<BlobColorRange>& crs) {
|
|
for (FileNodeIterator it = node.begin(); it != node.end(); ++it) {
|
|
BlobColorRange cr;
|
|
*it >> cr;
|
|
crs.push_back(cr);
|
|
}
|
|
}
|
|
|
|
static inline FileStorage& operator << (FileStorage &fs, vector<BlobColorRange>& crs) {
|
|
fs << "[";
|
|
for (auto it = crs.begin(); it != crs.end(); ++it) {
|
|
fs << *it;
|
|
}
|
|
fs << "]";
|
|
return fs;
|
|
}
|
|
|
|
|
|
/*! \brief Locate single or multiple blobs in the given image and ROI.
|
|
*
|
|
* Note: Both greyscale and color source is supported.
|
|
* You may want to specify the blob color range use addBlobColor(),
|
|
* or let the algorithm to pick color range automatically using selectBlobColor().
|
|
* You can also exclude some background pixels from blob extraction by specifying the background color range.
|
|
*
|
|
* 1) If you are using Cyclops as static library,
|
|
* 1.1) and you want global factory to manage the detector for you, initialize the detector
|
|
* via BlobDetector::getInstance().
|
|
* Example:
|
|
* \code{.cpp}
|
|
* BlobDetector::Ptr bdPtr = BlobDetector::getInstance("detect sth");
|
|
* bdPtr->reset(3); // on a color image
|
|
* bdPtr->addBlobColor(Scalar(40, 200, 210), Scalar(50, 255, 255)); // define blob color range
|
|
* // define two filters
|
|
* BlobFilter& bf = bdPtr->addFilter();
|
|
* bf.setPerimeter(20, 20000);
|
|
* bf.setMode(BlobFilterMode::Perimeter);
|
|
* bf = bdPtr->addFilter();
|
|
* bf.setWidth(30, 600);
|
|
* bf.setMode(BlobFilterMode::Width);
|
|
* BlobInstance bestBlob; // result blob
|
|
* bool ret = bdPtr->detectBest(gIBImg, bestBlob);
|
|
*
|
|
* // delete it later
|
|
* BlobDetector::deleteInstance("detect sth");
|
|
* \endcode
|
|
*
|
|
* 1.2) or, if you wish to manage the detector yourself:
|
|
* \code{.cpp}
|
|
* BlobDetector::Ptr bdPtr = std::make_shared<BlobDetector>(); // remember to hold the smart pointer
|
|
* // balabala, same as above
|
|
* // ...
|
|
* \endcode
|
|
*
|
|
* 2) If you are using Cyclops as dynamic library,
|
|
* initialize and manipulate the detector via CyclopsModules APIs.
|
|
* Example:
|
|
* \code{.cpp}
|
|
* // get a long-term detector, and give it an unique name
|
|
* BlobDetector::Ptr bdPtr = GetModuleInstance<BlobDetector>("detect sth");
|
|
* // get for temporary usage
|
|
* BlobDetector::Ptr localBdPtr = GetModuleInstance<BlobDetector>();
|
|
*
|
|
* // delete it later
|
|
* DeleteModuleInstance<BlobDetector>("detect sth");
|
|
* \endcode
|
|
*
|
|
* see BlobTest for unit test
|
|
*/
|
|
class BlobDetector : public ICyclopsModuleInstance
|
|
{
|
|
public:
|
|
/*! \fn getChannel
|
|
* Get number of channel, see also setChannel()
|
|
*/
|
|
DECLARE_PARAMETER_GET(int, Channel)
|
|
/*! \fn setExcludeBoundary
|
|
* Define whether to exclude blobs that intersect the ROI boundary, default to false, see also getExcludeBoundary()
|
|
* \fn getExcludeBoundary
|
|
* Get value of whether to exclude blobs that intersect the ROI boundary, see also setExcludeBoundary()
|
|
*/
|
|
DECLARE_PARAMETER(bool, ExcludeBoundary)
|
|
/*! \fn setFillHole
|
|
* Define to enable the contribution of holesin the overall blob statistics, default to false, see also getFillHole()
|
|
* For example, filling holes inside a blob would increase its area.
|
|
* \fn getFillHole
|
|
* Get value of to enable the contribution of holesin the overall blob statistics, see also setFillHole()
|
|
*/
|
|
DECLARE_PARAMETER(bool, FillHole)
|
|
/*! \fn setThresholdType
|
|
* Define threshold method used to separate blob and background, default to ThresholdType::Custom, see also getThresholdType()
|
|
* \fn getThresholdType
|
|
* Get value of long_name, see also setThresholdType()
|
|
*/
|
|
DECLARE_PARAMETER2(ThresholdType, ThresholdType, ThresholdType::Custom, ThresholdType::LocalAdaptive)
|
|
/*! \fn setPolarity
|
|
* Define polarity of blob when global/local auto-thresholding is used,
|
|
* either black blob on white background(Polarity::BlackOnWhite, default),
|
|
* or white blob on black background(Polarity::WhiteOnBlack), see also getPolarity()
|
|
* \fn getPolarity
|
|
* Get value of polarity of circle edge, see also setPolarity()
|
|
*/
|
|
DECLARE_PARAMETER2(Polarity, Polarity, Polarity::BlackOnWhite, Polarity::WhiteOnBlack)
|
|
/*! \fn setSortBy
|
|
* Define which property used for sorting the blobs, default to SortBy::Area, see also getSortBy()
|
|
* \fn getSortBy
|
|
* Get value of which property used for sorting the blobs, see also setSortBy()
|
|
*/
|
|
DECLARE_PARAMETER(SortBy, SortBy)
|
|
/*! \fn setLocalThresholdBias
|
|
* Define a constant value added to local theshold, default to 20, see also getLocalThresholdBias()
|
|
* \fn getLocalThresholdBias
|
|
* Get value of a constant value added to local theshold, see also setLocalThresholdBias()
|
|
*/
|
|
DECLARE_PARAMETER(int, LocalThresholdBias)
|
|
/*! \fn setSoftThreshold
|
|
* Define whether to use soft thresholding, default to false, see also getSoftThreshold()
|
|
* \fn getSoftThreshold
|
|
* Get value of whether to use soft thresholding, see also setSoftThreshold()
|
|
*/
|
|
DECLARE_PARAMETER_GET(bool, UseSoftThreshold)
|
|
/*! \fn setSoftThresholdRange
|
|
* Define the weighted range of soft thresholding. For example, if we define blob color range to be [100, 200],
|
|
* and soft thresholding range to be 5, then, [0, 95] and [205, 255] are background, [105, 195] are foreground,
|
|
* 96 ~ 104 are weighted as 0.1 ~ 0.9, 196 ~ 204 are weighted as 0.1 ~ 0.9.
|
|
* Default to 5, see also getSoftThresholdRange()
|
|
* \fn getSoftThresholdRange
|
|
* Get value of the weighted range of soft thresholding, see also setSoftThresholdRange()
|
|
*/
|
|
DECLARE_PARAMETER_GET(int, SoftThresholdRange)
|
|
/*! \fn setBlobBounderSize
|
|
* Define image bounder size of each blob instance, default to 1, see also getBlobBounderSize()
|
|
* \fn getBlobBounderSize
|
|
* Get image bounder size of each blob instance, see also setBlobBounderSize()
|
|
*/
|
|
DECLARE_PARAMETER2(int, BlobBounderSize, 1, 10)
|
|
/*! \fn setContourType
|
|
* Define which type of contour we are going to find, either external or hole contour, default to ContourType::External,
|
|
* see also getContourType()
|
|
* \fn getContourType
|
|
* Get value of which type of contour we are going to find, see also setContourType()
|
|
*/
|
|
DECLARE_PARAMETER(ContourType, ContourType)
|
|
/*! \fn setHierarchy
|
|
* Define which hierachy we are going to find(0-based), default to -1 means all, see also getHierarchy()
|
|
* \fn getHierarchy
|
|
* Get value of which hierachy we are going to find, see also setHierarchy()
|
|
*/
|
|
DECLARE_PARAMETER(int, Hierarchy)
|
|
|
|
public:
|
|
BlobDetector() :
|
|
mExcludeBoundary(false), mFillHole(false), mSortBy(SortBy::Area),
|
|
mThresholdType(ThresholdType::Custom), mPolarity(Polarity::BlackOnWhite), mLocalThresholdBias(20),
|
|
mChannel(1),
|
|
mUseSoftThreshold(false), mSoftThresholdRange(5), mBlobBounderSize(1),
|
|
mContourType(ContourType::External), mHierarchy(-1)
|
|
{}
|
|
virtual ~BlobDetector () {}
|
|
|
|
/*! \fn serializeToMemory
|
|
* Serialize the blob detector into a in-memory string, see also deserializeFromMemory()
|
|
* @param str used to take the output serialization result
|
|
* @return true for succeed, false for fail
|
|
* \fn serializeToFile
|
|
* Serialize the blob detector into a text file, see also deserializeFromFile()
|
|
* @param filename file name (full path) where we will write the data
|
|
* @return true for succeed, false for fail
|
|
* \fn deserializeFromMemory
|
|
* Deserialize the blob detector from in-memory string, see also serializeToMemory()
|
|
* @param str in-memory string
|
|
* @return true for succeed, false for fail
|
|
* \fn deserializeFromFile
|
|
* Deserialize the blob detector from a text file, see also serializeToFile()
|
|
* @param filename file name (full path) where we will read the data
|
|
* @return true for succeed, false for fail
|
|
*/
|
|
DECL_SERIALIZE_FUNCS
|
|
|
|
//! Smart pointer to hold an instance of BlobDetector
|
|
typedef std::shared_ptr<BlobDetector> Ptr;
|
|
DECL_GET_INSTANCE(BlobDetector::Ptr)
|
|
|
|
/*! return whether this is a well initialized */
|
|
virtual bool hasInit() const;
|
|
|
|
/*! Reset detector:
|
|
* 1. Define number of channel, only 1(gray) or 3(color) channel is acceptable
|
|
* 2. Clear blob and background color setting, and blob filters
|
|
*/
|
|
virtual void reset(int cn);
|
|
|
|
/*! Add new color range [c1, c2] for blob. Pixels in this range will be treated as blob, return the index */
|
|
virtual int addBlobColor(const Scalar& c1, const Scalar& c2);
|
|
|
|
/*! Add new color range [c1, c2] for background. Pixels in this range will be treated as background, return the index */
|
|
virtual int addBackgroundColor(const Scalar& c1, const Scalar& c2);
|
|
|
|
/*! Add a new blob filter */
|
|
virtual BlobFilter& addFilter();
|
|
|
|
/*! Select a color range for blob or background on a image. You should then explicitly add the color range as blob or background color
|
|
* @param img input image for color selection
|
|
* @param seedPoint the initial point for flood fill
|
|
* @param tol maximum relative difference of neighbor pixels that build the selection
|
|
* @param c1 lower bound of the new added color range
|
|
* @param c2 upper bound of the new added color range
|
|
* @param rngMask pass out the selected pixels as a binary mask, null if you don't want it
|
|
*/
|
|
virtual void selectColor(const Mat& img, const Point& seedPoint, Scalar& c1, Scalar& c2, Mat* rngMask = nullptr, int tol = 10);
|
|
|
|
vector<BlobColorRange>& getBlobColors() { return mBlobColors; }
|
|
vector<BlobColorRange>& getBackgroundColors() { return mBackgroundColors; }
|
|
vector<BlobFilter>& getFilters() { return mFilters; }
|
|
|
|
BlobColorRange& getBlobColor(int idx) {
|
|
_ASSERTE(mBlobColors.size() > idx);
|
|
return mBlobColors[idx];
|
|
}
|
|
BlobColorRange& getBackgroundColor(int idx) {
|
|
_ASSERTE(mBackgroundColors.size() > idx);
|
|
return mBackgroundColors[idx];
|
|
}
|
|
BlobFilter& getFilter(int idx) {
|
|
_ASSERTE(mFilters.size() > idx);
|
|
return mFilters[idx];
|
|
}
|
|
|
|
void deleteBlobColor(int idx) {
|
|
if (mBlobColors.size() > idx && idx >= 0)
|
|
mBlobColors.erase(mBlobColors.begin() + idx);
|
|
}
|
|
void deleteBackgroundColor(int idx) {
|
|
if (mBackgroundColors.size() > idx && idx >= 0)
|
|
mBackgroundColors.erase(mBackgroundColors.begin() + idx);
|
|
}
|
|
void deleteFilter(int idx) {
|
|
if (mFilters.size() > idx && idx >= 0)
|
|
mFilters.erase(mFilters.begin() + idx);
|
|
}
|
|
|
|
void clearBlobColors() { mBlobColors.clear(); }
|
|
void clearBackgroundColors() { mBackgroundColors.clear(); }
|
|
void clearFilters() { mFilters.clear(); }
|
|
|
|
virtual Mat genColorMask(const Mat& img, const BlobColorRange& cr);
|
|
|
|
virtual void refreshBlobColor();
|
|
virtual void refreshBackgroundColor();
|
|
|
|
virtual void setUseSoftThreshold(bool val);
|
|
virtual void setSoftThresholdRange(int val);
|
|
|
|
/*! \deprecated Define whether to use auto-thresholding for binarize image:
|
|
* 0 for not use, -1 for use below auto-value as blob, 1 for above, see also getUseAutoThreshold()
|
|
* If auto-thresholding is enabled, the customized blob and background colors will be ignored.
|
|
*/
|
|
DEPRECATED void setUseAutoThreshold(int val) {
|
|
if (val == 0) { mThresholdType = ThresholdType::Custom; }
|
|
else if (val < 0) { mThresholdType = ThresholdType::GlobalAdaptive; mPolarity = Polarity::BlackOnWhite; }
|
|
else if (val > 0) { mThresholdType = ThresholdType::GlobalAdaptive; mPolarity = Polarity::WhiteOnBlack; }
|
|
}
|
|
|
|
/*! \deprecated Get value of whether to user auto-thresholding for binarize image, see also setUseAutoThreshold() */
|
|
DEPRECATED int getUseAutoThreshold() const {
|
|
if (mThresholdType == ThresholdType::Custom) return 0;
|
|
else if (mThresholdType == ThresholdType::GlobalAdaptive && mPolarity == Polarity::BlackOnWhite) return -1;
|
|
else return 1;
|
|
}
|
|
|
|
/*! Detect the best blob using the detector. By best, we mean the first ranking blob after applying sort strategy
|
|
* @param img input image for detection
|
|
* @param bestBlob output the best blob, return at most 100 results
|
|
* @param allScores output all blobs on the image as a score map, no matter if it's filtered. Pass null if you don't want it
|
|
* @param mask input mask for exclude some pixel from detection
|
|
* @return true for found, false otherwise
|
|
*/
|
|
virtual bool detectBest(const Mat& img,
|
|
BlobInstance& bestBlob,
|
|
Mat* allScores = nullptr,
|
|
Mat* mask = nullptr);
|
|
|
|
/** @overload */
|
|
virtual bool detectBest(const Mat& img, const vector<Point2f>& roi,
|
|
BlobInstance& bestBlob,
|
|
Mat* allScores = nullptr);
|
|
|
|
/** @overload */
|
|
virtual bool detectBest(const Mat& img, DetectRoi& droi,
|
|
BlobInstance& bestBlob,
|
|
Mat* allScores = nullptr);
|
|
|
|
/*! Detect multiple best blobs using the detector. By best, we mean the front ranking blobs after applying sort strategy
|
|
* @param img input image for detection
|
|
* @param bestBlobs output the best blobs, return at most 100 or 2 * maxCount results
|
|
* @param allScores output all blobs on the image as a score map, no matter if it's filtered. Pass null if you don't want it
|
|
* @param minCount expected minimum count of the found instances
|
|
* @param maxCount expected maximum count of the found instances
|
|
* @param mask input mask for exclude some pixel from detection
|
|
* @return count of the found instances, it may below minCount but wouldn't exceed maxCount. return 0 if we found nothing good.
|
|
*/
|
|
virtual int detectMulti(const Mat& img,
|
|
vector<BlobInstance>* bestBlobs,
|
|
Mat* allScores = nullptr,
|
|
int minCount = 0,
|
|
int maxCount = 10,
|
|
Mat* mask = nullptr);
|
|
|
|
/** @overload */
|
|
virtual int detectMulti(const Mat& img, const vector<Point2f>& roi,
|
|
vector<BlobInstance>* bestBlobs,
|
|
Mat* allScores = nullptr,
|
|
int minCount = 0,
|
|
int maxCount = 10);
|
|
|
|
/** @overload */
|
|
virtual int detectMulti(const Mat& img, DetectRoi& droi,
|
|
vector<BlobInstance>* bestBlobs,
|
|
Mat* allScores = nullptr,
|
|
int minCount = 0,
|
|
int maxCount = 10);
|
|
|
|
|
|
/*! Draw blob on the image
|
|
* @param img on what we should draw
|
|
* @param blob the detected blob get from detectBest
|
|
* @param drawCountour whether we should draw the contour
|
|
* @param countourColor the color of contour
|
|
* @param drawCenter whether we should draw the center, as a small rect
|
|
* @param centerColor the color of center
|
|
* @return canvas
|
|
*/
|
|
virtual Mat drawBlob(const Mat& img, BlobInstance& blob,
|
|
bool drawContour, Scalar contourColor,
|
|
bool drawCenter, Scalar centerColor);
|
|
|
|
/** @overload */
|
|
virtual Mat drawBlobs(const Mat& img, vector<BlobInstance>& blobs,
|
|
bool drawContour, Scalar contourColor,
|
|
bool drawCenter, Scalar centerColor);
|
|
|
|
static inline float getPrimePropFromSortBy(BlobInstance& bestBlob, SortBy sortBy)
|
|
{
|
|
switch (sortBy) {
|
|
case SortBy::Area: return bestBlob.getArea();
|
|
case SortBy::Length: return bestBlob.getPerimeter();
|
|
case SortBy::Width: return bestBlob.getWidth();
|
|
case SortBy::Height: return bestBlob.getWidth();
|
|
case SortBy::LeftToRight:
|
|
case SortBy::RightToLeft:
|
|
return bestBlob.getCenter().x;
|
|
case SortBy::TopDown:
|
|
case SortBy::DownTop:
|
|
return bestBlob.getCenter().y;
|
|
case SortBy::Score: return bestBlob.getConfidence();
|
|
case SortBy::Circularity: return bestBlob.getCircularity();
|
|
case SortBy::Convexity: return bestBlob.getConvexity();
|
|
case SortBy::Inertia: return bestBlob.getInertia();
|
|
default:
|
|
_ASSERTE(false && "we should never get here");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
typedef std::function<bool(BlobInstance& b1, BlobInstance& b2)> SortByFunc;
|
|
static inline SortByFunc getSortByFunc(SortBy sortBy)
|
|
{
|
|
#define GenSortByFunc(SortByEnum, BlobProp, CompareOP)\
|
|
else if (sortBy == SortBy::SortByEnum) {\
|
|
return [](BlobInstance& b1, BlobInstance& b2) {\
|
|
if (b1.BlobProp == b2.BlobProp) return b1.getArea() > b2.getArea();\
|
|
else return b1.BlobProp CompareOP b2.BlobProp;\
|
|
};\
|
|
}
|
|
if (sortBy == SortBy::None) {
|
|
return nullptr;
|
|
}
|
|
GenSortByFunc(LeftToRight, getCenter().x, < )
|
|
GenSortByFunc(RightToLeft, getCenter().x, > )
|
|
GenSortByFunc(TopDown, getCenter().y, < )
|
|
GenSortByFunc(DownTop, getCenter().y, > )
|
|
GenSortByFunc(Score, getConfidence(), > )
|
|
GenSortByFunc(Area, getArea(), > )
|
|
GenSortByFunc(Length, getPerimeter(), > )
|
|
GenSortByFunc(Width, getWidth(), > )
|
|
GenSortByFunc(Height, getHeight(), > )
|
|
GenSortByFunc(Circularity, getCircularity(), > )
|
|
GenSortByFunc(Convexity, getConvexity(), > )
|
|
GenSortByFunc(Inertia, getInertia(), > )
|
|
else {
|
|
_ASSERTE(false && "we should never get here");
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
private:
|
|
virtual bool serialize(FileStorage& fs);
|
|
virtual bool deserialize(const FileNode& fs);
|
|
Mat binarizeAutoGlobal(const Mat& img);
|
|
Mat binarizeAutoLocal(const Mat& img);
|
|
Mat binarizeCustomThres(const Mat& img);
|
|
|
|
private:
|
|
vector<BlobColorRange> mBlobColors;
|
|
vector<BlobColorRange> mBackgroundColors;
|
|
Mat mBlobLUT;
|
|
Mat mBackgroundLUT;
|
|
vector<BlobFilter> mFilters;
|
|
};
|
|
|
|
#endif // BlobDetector_h_
|
|
|