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.

439 lines
11 KiB
C++

// Copyright (C) 2007 by Cristóbal Carnero Liñán
// grendel.ccl@gmail.com
//
// This file is part of cvBlob.
//
// cvBlob is free software: you can redistribute it and/or modify
// it under the terms of the Lesser GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// cvBlob is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// Lesser GNU General Public License for more details.
//
// You should have received a copy of the Lesser GNU General Public License
// along with cvBlob. If not, see <http://www.gnu.org/licenses/>.
//
#include <climits>
#define _USE_MATH_DEFINES
#include <cmath>
#include <deque>
#include <iostream>
#include <fstream>
#include <sstream>
using namespace std;
#if (defined(_WIN32) || defined(__WIN32__) || defined(__TOS_WIN__) || defined(__WINDOWS__) || (defined(__APPLE__) & defined(__MACH__)))
#include <cv.h>
#elif (CV_MAJOR_VERSION == 2) && (CV_MINOR_VERSION < 2)
#include <opencv/cv.h>
#else
#include <opencv2/opencv.hpp>
#endif
#include "cvblob.h"
#ifdef M_PI
const double pi = M_PI;
#else
const double pi = std::atan(1.)*4.;
#endif // M_PI
namespace cvb
{
void cvRenderContourChainCode(CvContourChainCode const *contour, IplImage const *img, CvScalar const &color)
{
CV_FUNCNAME("cvRenderContourChainCode");
__CV_BEGIN__;
{
CV_ASSERT(img&&(img->depth==IPL_DEPTH_8U)&&(img->nChannels==3));
int stepDst = img->widthStep/(img->depth/8);
int img_width = img->width;
int img_height = img->height;
int img_offset = 0;
if(img->roi)
{
img_width = img->roi->width;
img_height = img->roi->height;
img_offset = (img->nChannels * img->roi->xOffset) + (img->roi->yOffset * stepDst);
}
unsigned char *imgData = (unsigned char *)img->imageData + img_offset;
unsigned int x = contour->startingPoint.x;
unsigned int y = contour->startingPoint.y;
for (CvChainCodes::const_iterator it=contour->chainCode.begin(); it!=contour->chainCode.end(); ++it)
{
imgData[img->nChannels*x+img->widthStep*y+0] = (unsigned char)(color.val[0]); // Blue
imgData[img->nChannels*x+img->widthStep*y+1] = (unsigned char)(color.val[1]); // Green
imgData[img->nChannels*x+img->widthStep*y+2] = (unsigned char)(color.val[2]); // Red
x += cvChainCodeMoves[*it][0];
y += cvChainCodeMoves[*it][1];
}
}
__CV_END__;
}
CvContourPolygon *cvConvertChainCodesToPolygon(CvContourChainCode const *cc)
{
CV_FUNCNAME("cvConvertChainCodesToPolygon");
__CV_BEGIN__;
{
CV_ASSERT(cc!=NULL);
CvContourPolygon *contour = new CvContourPolygon;
unsigned int x = cc->startingPoint.x;
unsigned int y = cc->startingPoint.y;
contour->push_back(cvPoint(x, y));
if (cc->chainCode.size())
{
CvChainCodes::const_iterator it=cc->chainCode.begin();
CvChainCode lastCode = *it;
x += cvChainCodeMoves[*it][0];
y += cvChainCodeMoves[*it][1];
++it;
for (; it!=cc->chainCode.end(); ++it)
{
if (lastCode!=*it)
{
contour->push_back(cvPoint(x, y));
lastCode=*it;
}
x += cvChainCodeMoves[*it][0];
y += cvChainCodeMoves[*it][1];
}
}
return contour;
}
__CV_END__;
}
void cvRenderContourPolygon(CvContourPolygon const *contour, IplImage *img, CvScalar const &color)
{
CV_FUNCNAME("cvRenderContourPolygon");
__CV_BEGIN__;
{
CV_ASSERT(img&&(img->depth==IPL_DEPTH_8U)&&(img->nChannels==3));
CvContourPolygon::const_iterator it=contour->begin();
if (it!=contour->end())
{
unsigned int fx, x, fy, y;
fx = x = it->x;
fy = y = it->y;
for (; it!=contour->end(); ++it)
{
cvLine(img, cvPoint(x, y), cvPoint(it->x, it->y), color, 1);
x = it->x;
y = it->y;
}
cvLine(img, cvPoint(x, y), cvPoint(fx, fy), color, 1);
}
}
__CV_END__;
}
double cvContourPolygonArea(CvContourPolygon const *p)
{
CV_FUNCNAME("cvContourPolygonArea");
__CV_BEGIN__;
{
CV_ASSERT(p!=NULL);
if (p->size()<=2)
return 1.;
CvContourPolygon::const_iterator it=p->begin();
CvPoint lastPoint = p->back();
double a = 0.;
for (; it!=p->end(); ++it)
{
a += lastPoint.x*it->y - lastPoint.y*it->x;
lastPoint = *it;
}
return a*0.5;
}
__CV_END__;
}
double cvContourChainCodePerimeter(CvContourChainCode const *c)
{
CV_FUNCNAME("cvContourChainCodePerimeter");
__CV_BEGIN__;
{
CV_ASSERT(c!=NULL);
double perimeter = 0.;
for(CvChainCodes::const_iterator it=c->chainCode.begin(); it!=c->chainCode.end(); ++it)
{
if ((*it)%2)
perimeter+=sqrt(1.+1.);
else
perimeter+=1.;
}
return perimeter;
}
__CV_END__;
}
double cvContourPolygonPerimeter(CvContourPolygon const *p)
{
CV_FUNCNAME("cvContourPolygonPerimeter");
__CV_BEGIN__;
{
CV_ASSERT(p!=NULL);
double perimeter = cvDistancePointPoint((*p)[p->size()-1], (*p)[0]);
for (unsigned int i=0; i<p->size()-1; i++)
perimeter+=cvDistancePointPoint((*p)[i], (*p)[i+1]);
return perimeter;
}
__CV_END__;
}
double cvContourPolygonCircularity(const CvContourPolygon *p)
{
CV_FUNCNAME("cvContourPolygonCircularity");
__CV_BEGIN__;
{
CV_ASSERT(p!=NULL);
double l = cvContourPolygonPerimeter(p);
double c = (l*l/cvContourPolygonArea(p)) - 4.*pi;
if (c>=0.)
return c;
else // This could happen if the blob it's only a pixel: the perimeter will be 0. Another solution would be to force "cvContourPolygonPerimeter" to be 1 or greater.
return 0.;
}
__CV_END__;
}
void simplifyPolygonRecursive(CvContourPolygon const *p, int const i1, int const i2, bool *pnUseFlag, double const delta)
{
CV_FUNCNAME("cvSimplifyPolygonRecursive");
__CV_BEGIN__;
{
int endIndex = (i2<0)?p->size():i2;
if (abs(i1-endIndex)<=1)
return;
CvPoint firstPoint = (*p)[i1];
CvPoint lastPoint = (i2<0)?p->front():(*p)[i2];
double furtherDistance=0.;
int furtherIndex=0;
for (int i=i1+1; i<endIndex; i++)
{
double d = cvDistanceLinePoint(firstPoint, lastPoint, (*p)[i]);
if ((d>=delta)&&(d>furtherDistance))
{
furtherDistance=d;
furtherIndex=i;
}
}
if (furtherIndex)
{
pnUseFlag[furtherIndex]=true;
simplifyPolygonRecursive(p, i1, furtherIndex, pnUseFlag, delta);
simplifyPolygonRecursive(p, furtherIndex, i2, pnUseFlag, delta);
}
}
__CV_END__;
}
CvContourPolygon *cvSimplifyPolygon(CvContourPolygon const *p, double const delta)
{
CV_FUNCNAME("cvSimplifyPolygon");
__CV_BEGIN__;
{
CV_ASSERT(p!=NULL);
double furtherDistance=0.;
unsigned int furtherIndex=0;
CvContourPolygon::const_iterator it=p->begin();
++it;
for (unsigned int i=1; it!=p->end(); ++it, i++)
{
double d = cvDistancePointPoint(*it, p->front());
if (d>furtherDistance)
{
furtherDistance = d;
furtherIndex = i;
}
}
if (furtherDistance<delta)
{
CvContourPolygon *result = new CvContourPolygon;
result->push_back(p->front());
return result;
}
bool *pnUseFlag = new bool[p->size()];
for (unsigned int i=1; i<p->size(); i++) pnUseFlag[i] = false;
pnUseFlag[0] = pnUseFlag[furtherIndex] = true;
simplifyPolygonRecursive(p, 0, furtherIndex, pnUseFlag, delta);
simplifyPolygonRecursive(p, furtherIndex, -1, pnUseFlag, delta);
CvContourPolygon *result = new CvContourPolygon;
for (unsigned int i=0; i<p->size(); i++)
if (pnUseFlag[i])
result->push_back((*p)[i]);
delete[] pnUseFlag;
return result;
}
__CV_END__;
}
CvContourPolygon *cvPolygonContourConvexHull(CvContourPolygon const *p)
{
CV_FUNCNAME("cvPolygonContourConvexHull");
__CV_BEGIN__;
{
CV_ASSERT(p!=NULL);
if (p->size()<=3)
{
return new CvContourPolygon(p->begin(), p->end());
}
deque<CvPoint> dq;
if (cvCrossProductPoints((*p)[0], (*p)[1], (*p)[2])>0)
{
dq.push_back((*p)[0]);
dq.push_back((*p)[1]);
}
else
{
dq.push_back((*p)[1]);
dq.push_back((*p)[0]);
}
dq.push_back((*p)[2]);
dq.push_front((*p)[2]);
for (unsigned int i=3; i<p->size(); i++)
{
int s = dq.size();
if ((cvCrossProductPoints((*p)[i], dq.at(0), dq.at(1))>=0) && (cvCrossProductPoints(dq.at(s-2), dq.at(s-1), (*p)[i])>=0))
continue; // TODO Optimize.
while (cvCrossProductPoints(dq.at(s-2), dq.at(s-1), (*p)[i])<0)
{
dq.pop_back();
s = dq.size();
}
dq.push_back((*p)[i]);
while (cvCrossProductPoints((*p)[i], dq.at(0), dq.at(1))<0)
dq.pop_front();
dq.push_front((*p)[i]);
}
return new CvContourPolygon(dq.begin(), dq.end());
}
__CV_END__;
}
void cvWriteContourPolygonCSV(const CvContourPolygon& p, const string& filename)
{
ofstream f;
f.open(filename.c_str());
f << p << endl;
f.close();
}
void cvWriteContourPolygonSVG(const CvContourPolygon& p, const string& filename, const CvScalar& stroke, const CvScalar& fill)
{
int minx=INT_MAX;
int miny=INT_MAX;
int maxx=INT_MIN;
int maxy=INT_MIN;
stringstream buffer("");
for (CvContourPolygon::const_iterator it=p.begin(); it!=p.end(); ++it)
{
if (it->x>maxx)
maxx = it->x;
if (it->x<minx)
minx = it->x;
if (it->y>maxy)
maxy = it->y;
if (it->y<miny)
miny = it->y;
buffer << it->x << "," << it->y << " ";
}
ofstream f;
f.open(filename.c_str());
f << "<?xml version=\"1.0\" encoding=\"ISO-8859-1\" standalone=\"no\"?>" << endl;
f << "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 20010904//EN\" \"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">" << endl;
f << "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xml:space=\"preserve\" width=\"" << maxx-minx << "px\" height=\"" << maxy-miny << "px\" viewBox=\"" << minx << " " << miny << " " << maxx << " " << maxy << "\" zoomAndPan=\"disable\" >" << endl;
f << "<polygon fill=\"rgb(" << fill.val[0] << "," << fill.val[1] << "," << fill.val[2] << ")\" stroke=\"rgb(" << stroke.val[0] << "," << stroke.val[1] << "," << stroke.val[2] << ")\" stroke-width=\"1\" points=\"" << buffer.str() << "\"/>" << endl;
f << "</svg>" << endl;
f.close();
}
}
ostream& operator<< (ostream& output, const cvb::CvContourPolygon& p)
{
for (cvb::CvContourPolygon::const_iterator it=p.begin(); it!=p.end(); ++it)
output << it->x << ", " << it->y << endl;
return output;
}