1045 lines
33 KiB
C++
1045 lines
33 KiB
C++
/************************************************************************
|
|
Blob.cpp
|
|
|
|
FUNCTIONALITY: Implementation of the Blob class and some helper classes to perform
|
|
some calculations on it
|
|
AUTHOR: Inspecta S.L.
|
|
MODIFICATIONS (Modification, Author, Date):
|
|
|
|
**************************************************************************/
|
|
|
|
|
|
#include "blob.h"
|
|
#include <list>
|
|
#include "opencv2/core/types_c.h"
|
|
#include "opencv2/imgproc/imgproc_c.h"
|
|
|
|
Blob::Blob()
|
|
{
|
|
m_area = m_perimeter = -1;
|
|
m_externalPerimeter = m_meanGray = m_stdDevGray = -1;
|
|
m_boundingBox.width = -1;
|
|
m_ellipse.size.width = -1;
|
|
m_id = -1;
|
|
m_toBeDeleted = 0;
|
|
m_deleteRequestOwnerBlob = NULL;
|
|
m_isJoined = false;
|
|
m_startPassed = false;
|
|
}
|
|
|
|
Blob::Blob(LabelID id, const cv::Point& startPoint, const cv::Size& originalImageSize) : m_externalContour(startPoint, originalImageSize)
|
|
{
|
|
m_externalContour.m_parent = this;
|
|
m_id = id;
|
|
m_area = m_perimeter = -1;
|
|
m_externalPerimeter = m_meanGray = m_stdDevGray = -1;
|
|
m_boundingBox.width = -1;
|
|
m_ellipse.size.width = -1;
|
|
m_originalImageSize = originalImageSize;
|
|
m_toBeDeleted = 0;
|
|
m_deleteRequestOwnerBlob = NULL;
|
|
m_isJoined = false;
|
|
m_startPassed = false;
|
|
}
|
|
|
|
//! Copy constructor
|
|
Blob::Blob(const Blob& src)
|
|
{
|
|
*this = src;
|
|
}
|
|
|
|
Blob::Blob(const Blob *src)
|
|
{
|
|
if (src != NULL) {
|
|
*this = *src;
|
|
}
|
|
}
|
|
|
|
Blob& Blob::operator=(const Blob& src)
|
|
{
|
|
if (this != &src) {
|
|
m_id = src.m_id;
|
|
m_area = src.m_area;
|
|
m_perimeter = src.m_perimeter;
|
|
m_externalPerimeter = src.m_externalPerimeter;
|
|
m_meanGray = src.m_meanGray;
|
|
m_stdDevGray = src.m_stdDevGray;
|
|
m_boundingBox = src.m_boundingBox;
|
|
m_ellipse = src.m_ellipse;
|
|
m_originalImageSize = src.m_originalImageSize;
|
|
m_toBeDeleted = src.m_toBeDeleted;
|
|
m_deleteRequestOwnerBlob = src.m_deleteRequestOwnerBlob;
|
|
m_startPassed = false;
|
|
//! clear all current blob contours
|
|
clearContours();
|
|
m_externalContour = src.m_externalContour;
|
|
|
|
//! copy all internal contours
|
|
if (src.m_internalContours.size() != 0) {
|
|
//m_internalContours = t_contourList(src.m_internalContours.size());
|
|
BlobContourList::const_iterator itSrc,enSrc;
|
|
BlobContourList::iterator it;
|
|
|
|
itSrc = src.m_internalContours.begin();
|
|
enSrc = src.m_internalContours.end();
|
|
|
|
while (itSrc != enSrc) {
|
|
m_internalContours.push_back(new BlobContour(*itSrc));
|
|
itSrc++;
|
|
}
|
|
}
|
|
}
|
|
|
|
m_isJoined = src.m_isJoined;
|
|
m_joinedBlobs.clear();
|
|
if (m_isJoined) {
|
|
std::list<Blob*>::const_iterator it,en = src.m_joinedBlobs.end();
|
|
for (it = src.m_joinedBlobs.begin(); it != en; ++it) {
|
|
m_joinedBlobs.push_back(new Blob(*it));
|
|
}
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
Blob::~Blob()
|
|
{
|
|
if (m_isJoined) {
|
|
std::list<Blob*>::iterator it,en = m_joinedBlobs.end();
|
|
for (it = m_joinedBlobs.begin(); it != en; ++it) {
|
|
delete (*it);
|
|
}
|
|
}
|
|
|
|
clearContours();
|
|
}
|
|
|
|
void Blob::clearContours()
|
|
{
|
|
BlobContourList::iterator it = m_internalContours.begin(), en = m_internalContours.end();
|
|
for (; it != en; ++it) {
|
|
delete (*it);
|
|
}
|
|
m_internalContours.clear();
|
|
m_externalContour.reset();
|
|
}
|
|
|
|
void Blob::addInternalContour(const BlobContour &newContour)
|
|
{
|
|
m_internalContours.push_back(new BlobContour(newContour));
|
|
}
|
|
|
|
//! Shows if the blob has associated information
|
|
bool Blob::isEmpty()
|
|
{
|
|
return getExternalContour()->m_contour.size() == 0;
|
|
}
|
|
|
|
/**
|
|
- FUNCTIONS: Area
|
|
- FUNCTIONALITY: Get blob area, ie. external contour area minus internal contours area
|
|
- PARAMETERS: -
|
|
- RESULT: -
|
|
- RESTRICTIONS: -
|
|
- AUTHOR: trees
|
|
- DATE OF CREATION: 30/04/2008
|
|
- MODIFICATION: Date. Author. Description.
|
|
*/
|
|
double Blob::area(AreaMode areaCompMode)
|
|
{
|
|
double area=0;
|
|
switch (areaCompMode)
|
|
{
|
|
case GREEN:
|
|
{
|
|
BlobContourList::iterator itContour;
|
|
if (m_isJoined) {
|
|
std::list<Blob *>::iterator it, en = m_joinedBlobs.end();
|
|
for (it = m_joinedBlobs.begin(); it != en; ++it) {
|
|
area += (*it)->area();
|
|
}
|
|
}
|
|
else {
|
|
area = m_externalContour.getArea();
|
|
itContour = m_internalContours.begin();
|
|
while (itContour != m_internalContours.end()) {
|
|
if (*itContour) {
|
|
area -= (*itContour)->getArea();
|
|
}
|
|
itContour++;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case PIXELWISE:
|
|
{
|
|
cv::Rect bbox = getBoundingBox();
|
|
cv::Mat image = cv::Mat::zeros(bbox.height, bbox.width, CV_8UC1);
|
|
fillBlob(image, cv::Scalar(255), -bbox.x, -bbox.y, true);
|
|
area = countNonZero(image);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return area;
|
|
}
|
|
|
|
/**
|
|
- FUNCTIONS: Perimeter
|
|
- FUNCTIONALITY: Get perimeter blob, ie. sum of the length of all the contours
|
|
- PARAMETERS: -
|
|
- RESULT: -
|
|
- RESTRICTIONS: -
|
|
- AUTHOR: trees
|
|
- DATE OF CREATION: 30/04/2008
|
|
- MODIFICATION: Date. Author. Description.
|
|
*/
|
|
double Blob::perimeter()
|
|
{
|
|
double perimeter = 0;
|
|
BlobContourList::iterator itContour;
|
|
|
|
if (m_isJoined) {
|
|
std::list<Blob *>::iterator it, en = m_joinedBlobs.end();
|
|
for (it = m_joinedBlobs.begin(); it != en; ++it) {
|
|
perimeter += (*it)->perimeter();
|
|
}
|
|
}
|
|
else {
|
|
perimeter = m_externalContour.getPerimeter();
|
|
itContour = m_internalContours.begin();
|
|
while (itContour != m_internalContours.end()) {
|
|
if (*itContour) {
|
|
perimeter += (*itContour)->getPerimeter();
|
|
}
|
|
itContour++;
|
|
}
|
|
}
|
|
|
|
return perimeter;
|
|
}
|
|
|
|
/**
|
|
- FUNCTIONS: exterior
|
|
- FUNCIONALITAT: Return true for external blobs
|
|
- PARAMETERS:
|
|
- xBorder: true to consider blobs touching horizontal borders as external
|
|
- yBorder: true to consider blobs touching vertical borders as external
|
|
- RESULTAT:
|
|
-
|
|
- RESTRICCIONS:
|
|
-
|
|
- AUTOR: rborras
|
|
- DATE OF CREATION: 2008/05/06
|
|
- MODIFICATION: Data. Autor. Description.
|
|
*/
|
|
int Blob::exterior(IplImage *mask, bool xBorder /* = true */, bool yBorder /* = true */)
|
|
{
|
|
int result = 0;
|
|
if (externalPerimeter(mask, xBorder, yBorder) > 0)
|
|
{
|
|
result = 1;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
int Blob::exterior(const cv::Mat& mask, bool xBorder /* = true */, bool yBorder /* = true */)
|
|
{
|
|
IplImage temp = cvIplImage(mask);
|
|
return exterior(&temp, xBorder, yBorder);
|
|
}
|
|
|
|
/**
|
|
- FUNCTIONS: externalPerimeter
|
|
- FUNCIONALITAT: Get external perimeter (perimeter touching image borders)
|
|
- PARAMETERS:
|
|
- maskImage: if != NULL, counts maskImage black pixels as external pixels and contour points touching them are counted as external contour points.
|
|
- xBorder: true to consider blobs touching horizontal borders as external
|
|
- yBorder: true to consider blobs touching vertical borders as external
|
|
- RESULTAT:
|
|
-
|
|
- RESTRICCIONS:
|
|
-
|
|
- AUTOR: rborras
|
|
- DATE OF CREATION: 2008/05/05
|
|
- MODIFICATION: Data. Autor. Description.
|
|
- NOTA: If BlobContour::getContourPoints aproximates contours with a method different that NONE, this function will not give correct results
|
|
*/
|
|
double Blob::externalPerimeter(IplImage *maskImage, bool xBorder /* = true */, bool yBorder /* = true */)
|
|
{
|
|
PointList externalContour, externalPoints;
|
|
cv::Point actualPoint, previousPoint;
|
|
bool find = false;
|
|
//int i,j;
|
|
int delta = 0;
|
|
|
|
//! it is calculated?
|
|
/*if (m_externalPerimeter != -1)
|
|
{
|
|
return m_externalPerimeter;
|
|
}*/
|
|
|
|
|
|
if (m_isJoined) {
|
|
//! it an en are always different at first assignment (if m_isJoined is true I have at least one joined blob).
|
|
std::list<Blob *>::iterator it = m_joinedBlobs.begin(),en = m_joinedBlobs.end();
|
|
m_externalPerimeter = (*it)->externalPerimeter(maskImage, xBorder, yBorder);
|
|
it++;
|
|
for (; it != en; ++it) {
|
|
m_externalPerimeter += (*it)->externalPerimeter(maskImage, xBorder, yBorder);
|
|
}
|
|
return m_externalPerimeter;
|
|
}
|
|
|
|
|
|
//! get contour pixels
|
|
externalContour = m_externalContour.getContourPoints();
|
|
m_externalPerimeter = 0;
|
|
|
|
//! there are contour pixels?
|
|
if (externalContour.size() == 0) {
|
|
return m_externalPerimeter;
|
|
}
|
|
|
|
PointList::iterator it = externalContour.begin(), en = externalContour.end();
|
|
|
|
previousPoint.x = -1;
|
|
|
|
//! which contour pixels touch border?
|
|
for (; it != en; ++it) {
|
|
actualPoint = *(it);
|
|
find = false;
|
|
|
|
//! pixel is touching border?
|
|
if (xBorder & ((actualPoint.x == 0) || (actualPoint.x == m_originalImageSize.width - 1)) ||
|
|
yBorder & ((actualPoint.y == 0) || (actualPoint.y == m_originalImageSize.height - 1))) {
|
|
find = true;
|
|
}
|
|
else {
|
|
if (maskImage != NULL) {
|
|
//! verify if some of 8-connected neighbours is black in mask
|
|
char *pMask;
|
|
|
|
pMask = (maskImage->imageData + actualPoint.x - 1 + (actualPoint.y - 1) * maskImage->widthStep);
|
|
|
|
for (int i = 0; i < 3; i++, pMask++) {
|
|
if (*pMask == 0 && !find) {
|
|
find = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!find) {
|
|
pMask = (maskImage->imageData + actualPoint.x - 1 + (actualPoint.y) * maskImage->widthStep);
|
|
|
|
for (int i = 0; i < 3; i++, pMask++) {
|
|
if (*pMask == 0 && !find) {
|
|
find = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!find) {
|
|
pMask = (maskImage->imageData + actualPoint.x - 1 + (actualPoint.y + 1) * maskImage->widthStep);
|
|
|
|
for (int i = 0; i < 3; i++, pMask++) {
|
|
if (*pMask == 0 && !find) {
|
|
find = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (find) {
|
|
if (previousPoint.x > 0) {
|
|
delta = abs(previousPoint.x - actualPoint.x) + abs(previousPoint.y - actualPoint.y);
|
|
}
|
|
|
|
//! calculate separately each external contour segment
|
|
if (delta > 2) {
|
|
m_externalPerimeter += arcLength(externalPoints, false);
|
|
|
|
externalPoints.clear();
|
|
delta = 0;
|
|
previousPoint.x = -1;
|
|
}
|
|
externalPoints.push_back(actualPoint);
|
|
previousPoint = actualPoint;
|
|
}
|
|
|
|
}
|
|
|
|
if (externalPoints.size() != 0) {
|
|
m_externalPerimeter += arcLength(externalPoints, false);
|
|
}
|
|
//! divide by two because external points have one side inside the blob and the other outside
|
|
//! Perimeter of external points counts both sides, so it must be divided
|
|
m_externalPerimeter /= 2.0;
|
|
return m_externalPerimeter;
|
|
}
|
|
|
|
double Blob::externalPerimeter(const cv::Mat& maskImage, bool xBorder /* = true */, bool yBorder /* = true */) {
|
|
if (!maskImage.data) {
|
|
return externalPerimeter(NULL, xBorder /* = true */, yBorder /* = true */);
|
|
}
|
|
else {
|
|
IplImage temp = cvIplImage(maskImage);
|
|
return externalPerimeter(&temp, xBorder /* = true */, yBorder /* = true */);
|
|
}
|
|
}
|
|
|
|
//! Compute blob's moment (p,q up to MAX_CALCULATED_MOMENTS)
|
|
double Blob::moment(int p, int q, bool intContours /*= true*/)
|
|
{
|
|
double moment=0;
|
|
BlobContourList::iterator itContour;
|
|
|
|
if (m_isJoined) {
|
|
std::list<Blob *>::iterator it,en = m_joinedBlobs.end();
|
|
for (it = m_joinedBlobs.begin(); it != en; ++it) {
|
|
moment += (*it)->moment(p, q);
|
|
}
|
|
}
|
|
else {
|
|
moment = m_externalContour.getMoment(p, q);
|
|
if (intContours) {
|
|
itContour = m_internalContours.begin();
|
|
while (itContour != m_internalContours.end()) {
|
|
moment -= (*itContour)->getMoment(p, q);
|
|
itContour++;
|
|
}
|
|
}
|
|
}
|
|
return moment;
|
|
}
|
|
|
|
/**
|
|
- FUNCTIONS: mean
|
|
- FUNCIONALITAT: Get blob mean color in input image
|
|
- PARAMETERS:
|
|
- image: image from gray color are extracted
|
|
- RESULTAT:
|
|
-
|
|
- RESTRICCIONS:
|
|
-
|
|
- AUTOR: rborras
|
|
- DATE OF CREATION: 2008/05/06
|
|
- MODIFICATION: Data. Autor. Description.
|
|
*/
|
|
double Blob::mean(IplImage *image)
|
|
{
|
|
//! Create a mask with same size as blob bounding box
|
|
IplImage *mask;
|
|
CvScalar mean, std;
|
|
cv::Point offset;
|
|
|
|
getBoundingBox();
|
|
|
|
if (m_boundingBox.height == 0 ||m_boundingBox.width == 0 || !CV_IS_IMAGE(image)) {
|
|
m_meanGray = 0;
|
|
return m_meanGray;
|
|
}
|
|
|
|
//! apply ROI and mask to input image to compute mean gray and standard deviation
|
|
mask = cvCreateImage(cvSize(m_boundingBox.width, m_boundingBox.height), IPL_DEPTH_8U, 1);
|
|
cvSetZero(mask);
|
|
|
|
offset.x = -m_boundingBox.x;
|
|
offset.y = -m_boundingBox.y;
|
|
|
|
cv::Mat mask_mat = cv::cvarrToMat(mask);
|
|
|
|
//! If joined
|
|
if (m_isJoined) {
|
|
std::list<Blob *>::iterator it,en = m_joinedBlobs.end();
|
|
for (it = m_joinedBlobs.begin(); it != en; ++it) {
|
|
//cvDrawContours(mask, (*it)->m_externalContour.getContourPoints(), CV_RGB(255, 255, 255), CV_RGB(255, 255, 255),0, CV_FILLED, 8, offset);
|
|
//vector<PointList> conts;
|
|
//conts.push_back((*it)->m_externalContour.getContourPoints());
|
|
drawContours(mask_mat,(*it)->m_externalContour.getContours(), -1, CV_RGB(255, 255, 255), CV_FILLED, 8, cv::noArray(), 2147483647, offset);
|
|
BlobContourList::iterator itint = (*it)->m_internalContours.begin();
|
|
while(itint != (*it)->m_internalContours.end()) {
|
|
//cvDrawContours(mask, (*itint).getContourPoints(), CV_RGB(0, 0, 0), CV_RGB(0, 0, 0),0, CV_FILLED, 8, offset);
|
|
drawContours(mask_mat, (*itint)->getContours(), -1, CV_RGB(0, 0, 0), CV_FILLED, 8, cv::noArray(), 2147483647, offset);
|
|
itint++;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
//! draw contours on mask
|
|
//cvDrawContours(mask, m_externalContour.getContourPoints(), CV_RGB(255, 255, 255), CV_RGB(255, 255, 255), 0, CV_FILLED, 8, offset);
|
|
//vector<PointList> conts;
|
|
//conts.push_back(m_externalContour.getContourPoints());
|
|
drawContours(mask_mat, m_externalContour.getContours(), -1, CV_RGB(255, 255, 255), CV_FILLED, 8, cv::noArray(), 2147483647, offset);
|
|
//! draw internal contours
|
|
BlobContourList::iterator it = m_internalContours.begin();
|
|
while(it != m_internalContours.end())
|
|
{
|
|
//cvDrawContours(mask, (*it).getContourPoints(), CV_RGB(0, 0, 0), CV_RGB(0, 0, 0),0, CV_FILLED, 8, offset);
|
|
drawContours(mask_mat, (*it)->getContours(), -1, CV_RGB(0, 0, 0), CV_FILLED, 8, cv::noArray(), 2147483647, offset);
|
|
it++;
|
|
}
|
|
}
|
|
|
|
cvSetImageROI(image, cvRect(m_boundingBox));
|
|
cvAvgSdv(image, &mean, &std, mask);
|
|
|
|
m_meanGray = mean.val[0];
|
|
m_stdDevGray = std.val[0];
|
|
|
|
cvReleaseImage(&mask);
|
|
cvResetImageROI(image);
|
|
|
|
return m_meanGray;
|
|
}
|
|
|
|
double Blob::mean(const cv::Mat& image) {
|
|
IplImage temp = cvIplImage(image);
|
|
return mean(&temp);
|
|
}
|
|
|
|
double Blob::stdDev(IplImage *image) {
|
|
//! call mean calculation (where also standard deviation is calculated)
|
|
mean(image);
|
|
|
|
return m_stdDevGray;
|
|
}
|
|
|
|
double Blob::stdDev(const cv::Mat& image) {
|
|
IplImage temp = cvIplImage(image);
|
|
return stdDev(&temp);
|
|
}
|
|
|
|
//void Blob::meanStdDev(Mat image, double *mean, double *stddev)
|
|
//{
|
|
// IplImage temp = cvIplImage(image);
|
|
// mean(&temp);
|
|
// *mean = m_meanGray;
|
|
// *stddev = m_stdDevGray;
|
|
// return;
|
|
//}
|
|
|
|
void Blob::meanStdDev(const cv::Mat& image, cv::Scalar& mean, cv::Scalar& stddev)
|
|
{
|
|
getBoundingBox();
|
|
//! Create a mask with same size as blob bounding box and set it to 0
|
|
cv::Mat mask_mat = cv::Mat(m_boundingBox.height, m_boundingBox.width, CV_8UC1, cv::Scalar(0));
|
|
cv::Point offset(-m_boundingBox.x, -m_boundingBox.y);
|
|
|
|
//! If joined
|
|
if (m_isJoined) {
|
|
std::list<Blob *>::iterator it,en = m_joinedBlobs.end();
|
|
for (it = m_joinedBlobs.begin();it!=en;it++) {
|
|
drawContours(mask_mat, (*it)->m_externalContour.getContours(), -1, CV_RGB(255, 255, 255), CV_FILLED, 8, cv::noArray(), 2147483647, offset);
|
|
BlobContourList::iterator itint = (*it)->m_internalContours.begin();
|
|
while (itint != (*it)->m_internalContours.end()) {
|
|
drawContours(mask_mat, (*itint)->getContours(), -1, CV_RGB(0, 0, 0), CV_FILLED, 8, cv::noArray(), 2147483647, offset);
|
|
itint++;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
//! draw contours on mask
|
|
drawContours(mask_mat, m_externalContour.getContours(), -1, CV_RGB(255, 255, 255), CV_FILLED, 8, cv::noArray(), 2147483647, offset);
|
|
//! draw internal contours
|
|
BlobContourList::iterator it = m_internalContours.begin();
|
|
while (it != m_internalContours.end()) {
|
|
drawContours(mask_mat, (*it)->getContours(), -1, CV_RGB(0, 0, 0), CV_FILLED, 8, cv::noArray(), 2147483647, offset);
|
|
it++;
|
|
}
|
|
}
|
|
|
|
cv::meanStdDev(image(m_boundingBox), mean, stddev, mask_mat);
|
|
}
|
|
|
|
/**
|
|
- FUNCTIONS: getBoundingBox
|
|
- FUNCIONALITAT: Get bounding box (without rotation) of a blob
|
|
- PARAMETERS:
|
|
-
|
|
- RESULTAT:
|
|
-
|
|
- RESTRICCIONS:
|
|
-
|
|
- AUTOR: rborras
|
|
- DATE OF CREATION: 2008/05/06
|
|
- MODIFICATION: Data. Autor. Description.
|
|
*/
|
|
cv::Rect Blob::getBoundingBox()
|
|
{
|
|
//! it is calculated?
|
|
if (m_boundingBox.width != -1) {
|
|
return m_boundingBox;
|
|
}
|
|
|
|
if (m_isJoined) {
|
|
cv::Rect bigRect;
|
|
bigRect.x = 1000000;
|
|
bigRect.y = 1000000;
|
|
bigRect.height = 0;
|
|
bigRect.width = 0;
|
|
std::list<Blob *>::iterator it, en = m_joinedBlobs.end();
|
|
int maxX = 0, maxY = 0;
|
|
for (it = m_joinedBlobs.begin(); it != en; ++it) {
|
|
cv::Rect temp = (*it)->getBoundingBox();
|
|
if (bigRect.x > temp.x) {
|
|
bigRect.x = temp.x;
|
|
}
|
|
if (bigRect.y > temp.y) {
|
|
bigRect.y = temp.y;
|
|
}
|
|
//! -1 in order to obtain the correct measure (I'm looking for the points)
|
|
if (maxX < temp.x + temp.width) {
|
|
maxX = temp.x + temp.width - 1;
|
|
}
|
|
if (maxY < temp.y + temp.height) {
|
|
maxY = temp.y + temp.height - 1;
|
|
}
|
|
}
|
|
//! +1 in order to obtain the correct measure
|
|
bigRect.width = maxX - bigRect.x + 1;
|
|
bigRect.height = maxY - bigRect.y + 1;
|
|
m_boundingBox = bigRect;
|
|
return m_boundingBox;
|
|
}
|
|
|
|
PointList externalContour;
|
|
|
|
//! get contour pixels
|
|
externalContour = m_externalContour.getContourPoints();
|
|
|
|
//! it is an empty blob?
|
|
m_boundingBox.x = 1000000;
|
|
m_boundingBox.y = 1000000;
|
|
m_boundingBox.width = 0;
|
|
m_boundingBox.height = 0;
|
|
|
|
|
|
PointList::iterator it = externalContour.begin(), en = externalContour.end();
|
|
for (; it != en; ++it) {
|
|
cv::Point& actualPoint = *it;
|
|
|
|
m_boundingBox.x = MIN(actualPoint.x, m_boundingBox.x);
|
|
m_boundingBox.y = MIN(actualPoint.y, m_boundingBox.y);
|
|
|
|
m_boundingBox.width = MAX(actualPoint.x, m_boundingBox.width);
|
|
m_boundingBox.height = MAX(actualPoint.y, m_boundingBox.height);
|
|
}
|
|
|
|
//! +1 in order to take into account single pixel blobs.
|
|
//! In this case, a single pixel has a bounding box like Rect(x,y,1,1), which is ok with opencv functions
|
|
m_boundingBox.width = m_boundingBox.width - m_boundingBox.x + 1;
|
|
m_boundingBox.height = m_boundingBox.height - m_boundingBox.y + 1;
|
|
|
|
return m_boundingBox;
|
|
}
|
|
|
|
/**
|
|
- FUNCTIONS: getEllipse
|
|
- FUNCIONALITAT: Calculates bounding ellipse of external contour points
|
|
- PARAMETERS:
|
|
-
|
|
- RESULTAT:
|
|
-
|
|
- RESTRICCIONS:
|
|
-
|
|
- AUTOR: rborras
|
|
- DATE OF CREATION: 2008/05/06
|
|
- MODIFICATION: Data. Autor. Description.
|
|
- NOTA: Calculation is made using second order moment aproximation
|
|
*/
|
|
cv::RotatedRect Blob::getEllipse()
|
|
{
|
|
//! it is calculated?
|
|
if (m_ellipse.size.width != -1) {
|
|
return m_ellipse;
|
|
}
|
|
|
|
double u00, u11, u01, u10, u20, u02, delta, num, den, temp;
|
|
|
|
//! central moments calculation
|
|
u00 = moment(0,0);
|
|
|
|
//! empty blob?
|
|
if (u00 <= 0) {
|
|
m_ellipse.size.width = 0;
|
|
m_ellipse.size.height = 0;
|
|
m_ellipse.center.x = 0;
|
|
m_ellipse.center.y = 0;
|
|
m_ellipse.angle = 0;
|
|
return m_ellipse;
|
|
}
|
|
u10 = moment(1, 0) / u00;
|
|
u01 = moment(0, 1) / u00;
|
|
|
|
u11 = -(moment(1, 1) - moment(1, 0) * moment(0, 1) / u00) / u00;
|
|
u20 = (moment(2, 0) - moment(1, 0) * moment(1, 0) / u00) / u00;
|
|
u02 = (moment(0, 2) - moment(0, 1) * moment(0, 1) / u00) / u00;
|
|
|
|
|
|
//! elipse calculation
|
|
delta = sqrt(4*u11*u11 + (u20-u02)*(u20-u02));
|
|
m_ellipse.center.x = (float)u10;
|
|
m_ellipse.center.y = (float)u01;
|
|
|
|
temp = u20 + u02 + delta;
|
|
if (temp > 0) {
|
|
m_ellipse.size.width = (float)sqrt(2 * (u20 + u02 + delta));
|
|
}
|
|
else {
|
|
m_ellipse.size.width = 0;
|
|
return m_ellipse;
|
|
}
|
|
|
|
temp = u20 + u02 - delta;
|
|
if (temp > 0) {
|
|
m_ellipse.size.height = (float)sqrt(2 * (u20 + u02 - delta));
|
|
}
|
|
else {
|
|
m_ellipse.size.height = 0;
|
|
return m_ellipse;
|
|
}
|
|
|
|
//! elipse orientation
|
|
if (u20 > u02) {
|
|
num = u02 - u20 + sqrt((u02 - u20) * (u02 - u20) + 4 * u11 * u11);
|
|
den = 2*u11;
|
|
}
|
|
else {
|
|
num = 2*u11;
|
|
den = u20 - u02 + sqrt((u20 - u02) * (u20 - u02) + 4 * u11 * u11);
|
|
}
|
|
|
|
if (num != 0 && den != 00) {
|
|
m_ellipse.angle = (float)(180.0 + (180.0 / CV_PI) * atan(num / den));
|
|
}
|
|
else {
|
|
m_ellipse.angle = 0;
|
|
}
|
|
|
|
return m_ellipse;
|
|
}
|
|
|
|
/**
|
|
- FUNCTION: fillBlob
|
|
- FUNCTIONALITY:
|
|
- Fills the blob with a specified colour
|
|
- PARAMETERS:
|
|
- image: where to paint
|
|
- color: colour to paint the blob
|
|
- offset: point offset for drawing
|
|
- intContours: do not paint the internal holes (leave them transm_parent)
|
|
- srcImage: image from where to copy the internal holes contents
|
|
- RESULT: - modified input image
|
|
- RESTRICTIONS:
|
|
- AUTHOR: Ricard Borràs
|
|
- CREATION DATE: 25-05-2005.
|
|
- MODIFICATION: - sep/2013. Luca Nardelli. Added functionality to consider internal contours when filling the blob.
|
|
*/
|
|
void Blob::fillBlob(IplImage* image, cv::Scalar color, int offsetX , int offsetY, bool intContours, const IplImage* srcImage)
|
|
{
|
|
if (srcImage == NULL) {
|
|
fillBlob(cv::cvarrToMat(image), color, offsetX, offsetY, intContours, cv::Mat());
|
|
}
|
|
else {
|
|
fillBlob(cv::cvarrToMat(image), color, offsetX, offsetY, intContours, cv::cvarrToMat(srcImage));
|
|
}
|
|
}
|
|
|
|
void Blob::fillBlob(const cv::Mat& image, cv::Scalar color, int offsetX, int offsetY, bool intContours, const cv::Mat& srcImage) {
|
|
CV_FUNCNAME("Blob::fillBlob");
|
|
__CV_BEGIN__;
|
|
if (srcImage.data && intContours) CV_ASSERT(image.size() == srcImage.size() && image.type() == srcImage.type());
|
|
{
|
|
cv::Rect bbox = getBoundingBox();
|
|
cv::Point drawOffset(offsetX, offsetY);
|
|
cv::Size imSz = image.size();
|
|
if (bbox.x + offsetX + bbox.width >= imSz.width) {
|
|
bbox.width = imSz.width - bbox.x - offsetX;
|
|
}
|
|
else if (bbox.x + offsetX < 0) {
|
|
bbox.x = -offsetX;
|
|
bbox.width = bbox.width + offsetX;
|
|
}
|
|
if (bbox.y+offsetY+bbox.height >= imSz.height) {
|
|
bbox.height = imSz.height - bbox.y - offsetY;
|
|
}
|
|
else if (bbox.y + offsetY < 0) {
|
|
bbox.y = -offsetY;
|
|
bbox.height = bbox.height + offsetY;
|
|
}
|
|
if (bbox.width <0 || bbox.height < 0) {
|
|
return;
|
|
}
|
|
if (bbox.width == 0) {
|
|
bbox.width++;
|
|
}
|
|
if (bbox.height == 0) {
|
|
bbox.height++;
|
|
}
|
|
if (m_isJoined) {
|
|
std::list<Blob *>::iterator itBlob = m_joinedBlobs.begin(), enBlob = m_joinedBlobs.end();
|
|
for (itBlob = m_joinedBlobs.begin(); itBlob != enBlob; ++itBlob) {
|
|
(*itBlob)->fillBlob(image, color, offsetX, offsetY, intContours, srcImage);
|
|
}
|
|
//if (intContours) {
|
|
// Point offset(-bbox.x, -bbox.y);
|
|
// Size sz(bbox.width, bbox.height);
|
|
// Mat temp(sz,image.type());
|
|
// Mat mask(sz, CV_8UC1);
|
|
// mask.setTo(0);
|
|
// for (itBlob = m_joinedBlobs.begin(); itBlob != enBlob; ++itBlob) {
|
|
// Blob *curBlob = *itBlob;
|
|
// BlobContourList::iterator it = curBlob->m_internalContours.begin(),en = curBlob->m_internalContours.end();
|
|
// for (it; it != en; ++it) {
|
|
// drawContours(mask,(*it)->getContours(),-1,255,CV_FILLED,8,noArray(),2147483647,offset);
|
|
// }
|
|
// }
|
|
// srcImage(bbox).copyTo(temp,mask);
|
|
// for (itBlob = m_joinedBlobs.begin(); itBlob != enBlob; ++itBlob) {
|
|
// drawContours(image,(*itBlob)->m_externalContour.getContours(),-1,color,CV_FILLED,8,noArray(),2147483647,drawOffset);
|
|
// }
|
|
// temp.copyTo(image(bbox+drawOffset),mask);
|
|
// for (itBlob = m_joinedBlobs.begin(); itBlob != enBlob; ++itBlob) {
|
|
// Blob *curBlob = *itBlob;
|
|
// BlobContourList::const_iterator it = curBlob->m_internalContours.begin(),en = curBlob->m_internalContours.end();
|
|
// for (it;it!=en;it++) {
|
|
// drawContours(image,(*it)->getContours(),-1,color,1,8,noArray(),2147483647,drawOffset);
|
|
// }
|
|
// }
|
|
//}
|
|
//else {
|
|
// for (itBlob = m_joinedBlobs.begin();itBlob!=enBlob;itBlob++) {
|
|
// drawContours(image,(*itBlob)->m_externalContour.getContours(),-1,color,CV_FILLED,8,noArray(),2147483647,drawOffset);
|
|
// }
|
|
//}
|
|
}
|
|
else {
|
|
if (intContours) {
|
|
cv::Point offset(-bbox.x, -bbox.y);
|
|
BlobContourList::iterator it = m_internalContours.begin(), en = m_internalContours.end();
|
|
cv::Mat temp(bbox.height, bbox.width, image.type());
|
|
drawContours(image, m_externalContour.getContours(), -1, color, CV_FILLED, 8, cv::noArray(), 2147483647, drawOffset);
|
|
if (srcImage.data) {
|
|
cv::Mat mask(bbox.height, bbox.width, CV_8UC1);
|
|
mask.setTo(0);
|
|
for (; it != en; ++it) {
|
|
drawContours(mask, (*it)->getContours(), -1, 255, CV_FILLED, 8, cv::noArray(), 2147483647, offset);
|
|
}
|
|
srcImage(bbox).copyTo(temp,mask);
|
|
cv::Mat image_roi = image(bbox + drawOffset);
|
|
temp.copyTo(image_roi, mask);
|
|
}
|
|
else {
|
|
for (; it != en; ++it) {
|
|
drawContours(image, (*it)->getContours(), -1,CV_RGB(0, 0, 0), CV_FILLED, 8, cv::noArray(), 2147483647, drawOffset);
|
|
}
|
|
}
|
|
|
|
for (it = m_internalContours.begin(); it != en; ++it) {
|
|
drawContours(image, (*it)->getContours(), -1, color, 1, 8, cv::noArray(), 2147483647, drawOffset);
|
|
}
|
|
}
|
|
else {
|
|
drawContours(image,m_externalContour.getContours(), -1, color, CV_FILLED, 8, cv::noArray(), 2147483647, drawOffset);
|
|
}
|
|
}
|
|
}
|
|
__CV_END__;
|
|
}
|
|
|
|
/**
|
|
- FUNCTION: getConvexHull
|
|
- FUNCTIONALITY: Calculates the convex hull polygon of the blob
|
|
- PARAMETERS:
|
|
- dst: where to store the result
|
|
- RESULT:
|
|
- true if no error ocurred
|
|
- RESTRICTIONS:
|
|
- AUTHOR: Ricard Borràs
|
|
- CREATION DATE: 25-05-2005.
|
|
- MODIFICATION: Date. Author. Description.
|
|
*/
|
|
void Blob::getConvexHull(Contours& hull)
|
|
{
|
|
hull.clear();
|
|
PointList extCont;
|
|
hull.push_back(PointList());
|
|
if (m_isJoined) {
|
|
int numPts = 0;
|
|
BlobList::iterator it, en = m_joinedBlobs.end();
|
|
for (it = m_joinedBlobs.begin(); it != en; ++it) {
|
|
numPts += (*it)->getExternalContour()->getContourPoints().size();
|
|
}
|
|
hull[0].reserve(numPts);
|
|
extCont.reserve(numPts);
|
|
for (it = m_joinedBlobs.begin(); it != en; ++it) {
|
|
const PointList& pts = (*it)->getExternalContour()->getContourPoints();
|
|
extCont.insert(extCont.end(), pts.begin(), pts.end());
|
|
}
|
|
}
|
|
else {
|
|
extCont = m_externalContour.getContourPoints();
|
|
}
|
|
cv::convexHull(extCont, hull[0], true, true);
|
|
}
|
|
|
|
/**
|
|
- FUNCTION: joinBlob
|
|
- FUNCTIONALITY: Joins the 2 blobs, creating another blob which contains the 2 joined ones
|
|
- PARAMETERS:
|
|
- blob: blob to join with the calling one
|
|
- RESULT:
|
|
- Joined blob
|
|
- RESTRICTIONS: Only external contours are added
|
|
- AUTHOR: Ricard Borràs
|
|
- CREATION DATE: 25-05-2005.
|
|
- MODIFICATION: 08-2013, Luca Nardelli & Saverio Murgia, Created a working version of the join blob function
|
|
*/
|
|
void Blob::joinBlob(Blob* blob)
|
|
{
|
|
//! Check on m_storage in order to not add empty blobs.
|
|
if (!m_isJoined && !isEmpty()) {
|
|
this->m_joinedBlobs.push_back(new Blob(this));
|
|
}
|
|
if (blob->m_isJoined) {
|
|
std::list<Blob *>::iterator it, en = blob->m_joinedBlobs.end();
|
|
for (it = blob->m_joinedBlobs.begin(); it != en; ++it) {
|
|
this->m_joinedBlobs.push_back(new Blob(*it));
|
|
}
|
|
}
|
|
else {
|
|
this->m_joinedBlobs.push_back(new Blob(blob));
|
|
}
|
|
|
|
this->m_isJoined = true;
|
|
this->m_boundingBox.width = -1;
|
|
this->m_externalPerimeter = -1;
|
|
this->m_meanGray = -1;
|
|
}
|
|
|
|
void Blob::requestDeletion(Blob* blob)
|
|
{
|
|
//! If the blob has already been reported for deletion, then I also report the blobs that have requested it to be deleted
|
|
while (blob->m_deleteRequestOwnerBlob != NULL) {
|
|
Blob *temp = blob;
|
|
blob->m_deleteRequestOwnerBlob->m_toBeDeleted = 1;
|
|
blob = blob->m_deleteRequestOwnerBlob;
|
|
temp->m_deleteRequestOwnerBlob = this;
|
|
}
|
|
blob->m_toBeDeleted = 1;
|
|
blob->m_deleteRequestOwnerBlob = this;
|
|
}
|
|
|
|
int Blob::getNumJoinedBlobs()
|
|
{
|
|
return m_joinedBlobs.size();
|
|
}
|
|
|
|
void Blob::shiftBlob(int x, int y)
|
|
{
|
|
m_externalContour.shiftBlobContour(x,y);
|
|
BlobContourList::iterator it, en = m_internalContours.end();
|
|
for (it = m_internalContours.begin(); it != en; ++it) {
|
|
(*it)->shiftBlobContour(x,y);
|
|
}
|
|
m_boundingBox.x += x;
|
|
m_boundingBox.y += y;
|
|
}
|
|
|
|
cv::Point Blob::getCenter()
|
|
{
|
|
return cv::Point((int)(getBoundingBox().x + getBoundingBox().width * 0.5), (int)(getBoundingBox().y + getBoundingBox().height * 0.5));
|
|
}
|
|
|
|
int Blob::overlappingPixels(Blob* blob)
|
|
{
|
|
cv::Rect r1 = getBoundingBox(),r2 = blob->getBoundingBox();
|
|
cv::Rect interRect = r1 & r2;
|
|
if (interRect.width == 0 || interRect.height == 0) {
|
|
return 0;
|
|
}
|
|
//! Minimum containing rectangle
|
|
cv::Rect minContainingRect = r1 | r2;
|
|
cv::Mat m1 = cv::Mat::zeros(minContainingRect.height, minContainingRect.width, CV_8UC1);
|
|
cv::Mat m2 = cv::Mat::zeros(minContainingRect.height, minContainingRect.width, CV_8UC1);
|
|
fillBlob(m1, cv::Scalar(255), -minContainingRect.x, -minContainingRect.y, true);
|
|
blob->fillBlob(m2, cv::Scalar(255), -minContainingRect.x, -minContainingRect.y, true);
|
|
return countNonZero(m1 & m2);
|
|
}
|
|
|
|
double Blob::density(AreaMode areaCalculationMode)
|
|
{
|
|
double density = 0;
|
|
BlobContourList::iterator itContour;
|
|
switch (areaCalculationMode) {
|
|
case GREEN:
|
|
{
|
|
double blobArea = area();
|
|
Contours cHull, cHullPts;
|
|
getConvexHull(cHull);
|
|
//approxPolyDP(cHull, cHullPts, 0.001, true);
|
|
double cHullArea = fabs(contourArea(cHull[0], false));
|
|
density = blobArea/cHullArea;
|
|
break;
|
|
}
|
|
case PIXELWISE:
|
|
{
|
|
Contours cHull;
|
|
getConvexHull(cHull);
|
|
cv::Rect bbox = getBoundingBox();
|
|
cv::Mat blMat = cv::Mat::zeros(bbox.height, bbox.width, CV_8UC1);
|
|
cv::Mat cHullMat = cv::Mat::zeros(bbox.height, bbox.width, CV_8UC1);
|
|
fillBlob(blMat, cv::Scalar(255), -bbox.x, -bbox.y, true);
|
|
drawContours(cHullMat, cHull, -1, cv::Scalar(255), -1, 8, cv::noArray(), 2147483647, cv::Point(-bbox.x, -bbox.y));
|
|
int totArea = countNonZero(cHullMat);
|
|
int actArea = countNonZero(blMat);
|
|
density = (double)actArea/totArea;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return density;
|
|
}
|
|
|
|
void Blob::getExtremes(cv::Point& xmax,cv::Point& xmin, cv::Point& ymax, cv::Point& ymin)
|
|
{
|
|
const PointList& pts = m_externalContour.getContourPoints();
|
|
/*Contours hull;
|
|
getConvexHull(hull);
|
|
const PointList &pts = hull[0];*/
|
|
int nPts = pts.size();
|
|
xmax = pts[0];
|
|
xmin = pts[0];
|
|
ymax = pts[0];
|
|
ymin = pts[0];
|
|
for (int i = 1; i < nPts; ++i) {
|
|
if (xmax.x < pts[i].x) {
|
|
xmax = pts[i];
|
|
}
|
|
else if (xmin.x > pts[i].x) {
|
|
xmin = pts[i];
|
|
}
|
|
if (ymax.y < pts[i].y) {
|
|
ymax = pts[i];
|
|
}
|
|
else if (ymin.y > pts[i].y) {
|
|
ymin = pts[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
cv::Point2f Blob::getCentroid(bool useInternalContours)
|
|
{
|
|
float m00 = moment(0, 0, useInternalContours), m10 = moment(1, 0, useInternalContours);
|
|
float m01 = moment(0, 1, useInternalContours);
|
|
float x = m10 / m00, y = m01 / m00;
|
|
return cv::Point2f(x, y);
|
|
}
|
|
|
|
|