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

5 years ago
/*!
* \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_