方法类似于网格思路,但是使用三角形和四边形等易于计算的基本基本图元可以达到更快速目的。大致思路就是将连通域轮廓分解为更多基本土元,然后计算出这些基本土元的重心和面积之后再将他们呢一一合并。
为了偷懒我直接载入了下面这张黑白图:
图-1
首先需要得到轮廓的原始数据,使用Opencv函数 cvFindContours(...) 来提取,得到轮廓如下:
图-2
虽然这个轮廓很完美,但是不能直接用于多边形细分,过多的棱边需要太多计算量。函数 cvApproxPoly(..) 可以对轮廓进行“简化”,得到一个更加稀疏新轮廓。cvApproxPoly(..) 倒数第二个参数2.45版本里叫做eps决定了函数的逼近精度,我使用5得到了下面这个逼近后的新轮廓:
图-3
这张图标出了新轮廓的点和起点:
图-4
简单的轮廓意味着更少的计算量和凸包缺陷,但是精度也更差。说道这里不得不提一下凸包缺陷(说白了就是苹果被啃了一口),一个没有凸缺陷的轮廓的重心和面积计算是相当简便的(轮廓内随便找个点就可以将N个元素的图轮廓细分为N个三角形,最后一一合并即可,参考这里),例如下边这个凸轮廓(红标)就是图-4去掉凸缺陷后得到新轮廓:
图-5
既然已经知道了结果那么过程也就不难猜了,再计算出那些凸缺陷处的轮廓并将所有轮廓再一一合并就可以了。
顺便值得一提的是图-5里正好有一处特殊情况,红标1和2之间实际上有数个凸缺陷,但是了计算简单在逼近轮廓生成凸轮廓的过程中我将它们看做一个凸缺陷,随后对这个缺陷进行细分的时候见那些位于图轮廓内部的基本土元的面积设为正,外部为负。
当 使用精度5计算逼近轮廓时的计算结果:
C++项目,Opencv使用了2.45版本,代码写的比较糟糕,又长又烂,看完它肯定心灵和体力上的双重煎熬。
如果你使用了预编译头文件请将宏定义放到头文件中。
//
// 例子测试:使用三角形,矩形细分连通域轮廓多边形计算连通域重心 //
// !!图像以及轮廓点坐标以左上角为原点 //
// 高国文@2014-2-11 //
// //
//
#define _CRT_SECURE_NO_WARNINGS
#define SAME_TRUE_DIFF_FALSE(p,q) (((p) == (q))? (true):(false)) // 同真异假
C++头文件/
#include <iostream> //
#include <vector> //
#include <math.h> //
#include <algorithm> //
using namespace std; //
OpenCV 2.45///
#include <opencv\cv.h> //
#include <opencv\cxcore.h> //
#include <opencv\highgui.h> //
#pragma comment(lib, "opencv_calib3d245.lib") //
#pragma comment(lib, "opencv_core245.lib") //
#pragma comment(lib, "opencv_highgui245.lib") //
#pragma comment(lib, "opencv_imgproc245.lib") //
using namespace cv; //
//
// 轮廓点
typedef struct _contourPoint{
bool isHeadBoundary; // 是否是凸包缺陷头边界
bool isFootBoundary; // 是否是凸包缺陷尾边界
int nextBoundStride; // 下一个边界的跨距
CvPoint coord; // 坐标数据
}contourPoint;
inline contourPoint createContourPoint(const int &x, const int &y) { return{ false, false, -1, cvPoint(x, y) }; }
// 轮廓
typedef struct _contour{
bool isCCW; // 轮廓上的点是否为逆时针排列
CvPoint cog; // 轮廓重心
float area; // 轮廓面积
vector<contourPoint> seq; // 轮廓数据
}contour;
// 毛边
typedef struct _roughStuff{
bool isCCW; // 轮廓上的点是否为逆时针排列
CvPoint cog; // 三角形或四边形的重心
float area; // 轮廓面积
vector<CvPoint> seq; // 轮廓数据
}roughStuff;
void zeroStuff(roughStuff &stuff) { stuff.isCCW = false; stuff.cog = { 0, 0 }; stuff.area = 0; stuff.seq.clear(); }
// 线段
typedef struct _lineSegment{
CvPoint a, b;
}lineSegment, lineINT;
inline lineSegment createLine(const CvPoint &p1, const CvPoint &p2) { return{ p1, p2 }; }
inline lineSegment createLine(const contourPoint &p1, const contourPoint &p2) { return{ cvPoint(p1.coord.x, p1.coord.y), cvPoint(p2.coord.x, p2.coord.y) }; }
// 打印版本号
void dumpVersion();
// 打印轮廓每条边是否和轮廓相切(符合相切的条件,与轮廓有且只有两个焦点)
void dumpTangentialInfo(const vector<contourPoint> &seq);
// 计算重心
CvPoint calCOG(contour &c);
// 检查轮廓是否为逆时针
bool checkContourCCW(const vector<contourPoint> &seq);
bool checkContourCCW(const vector<CvPoint> &seq);
// 检查三角形轮廓是否为逆时针
bool checkTrianCCW(const vector<contourPoint> &trian);
bool checkTrianCCW(const vector<CvPoint> &trian);
// 计算轮廓的某条边所在直线是否与轮廓相切
bool checkTangential(const int &index, const vector<contourPoint> &seq);
bool checkTangential(const int &index, const vector