一、预备知识:
Deluanary三角细分及voronoi图概念。
Delaunay三角剖分几种生成方法。
二、Opencv中delaunary三角细分和voronoi图的实现方法
opencv使用四方边缘结构来存储deluanary三角细分和voronoi图的边缘信息,数据类型为CvQuadEdge2D,而存储CvQuadEdge2D的数据结构为CvSubdiv2D。CvSubdiv2D包含delaunary三角细分和voronoi剖分的所有边的信息。
2.1、四方边缘结构(QE)
每个QE单元存储四条有向边,其中两条为主边(Primal Edge):e0和e2,描述当前的二维平面剖分S;另外两条为对偶边(Dual Edge):e1和e3,描述S的对偶图。
每条Edge定义有三个基本行为:
Org:返回当前有向边的起点。
Rot:返回当前QE单元中逆时针旋转90度后的有向边,也即当前Edge的对偶边。
Onext:返回S中以当前有向边Org为基点逆时针旋转到的下一条Edge。
可以选取QE单元的任意一条Edge作为起始边来表示QE中的四条Edge。设起始边为e0。,则任意的有向边可以表示为(e, r),有如下关系:
基于基本行为还可以派生出更多的行为:
边e的起点和终点是给定点集中的两个点,边e是Deluanary三角形某个三角形的边,eRot是边e对应Voronoi图中的一条边。一个四方边缘结构包含点集中某两点的Delauanry三角形边信息和Voronoi图边信息,eLnext、eDnext,eOnext、eRnext是属于其他边缘四方结构的Delauanry边和Voronoi边,不同边缘四方结构通过eLnext、eDnext,eOnext、eRnext产生空间联系。所有的边缘四方结构存储在一个CvSubdiv2D数据结构中。
CvSubdiv2D数据结构有点复杂,也许需要有点相关专业基础才能理解,至少本人是没有理解透,虽然不能理解,但只要知道怎么用就 行了。下面例子详细说明了如何操作该数据结构。
三、实现代码
新建一个工程,添加一下两个文件:
1. delaunayfunc.h
2. delaunayfunc.cpp
delaunayfunc.h 代码
/* delaunayfunc.h */
#include <opencv2/legacy/legacy.hpp>
#include<opencv2\opencv.hpp>
#include <opencv2/features2d/features2d.hpp>
#include<opencv2/nonfree/nonfree.hpp>
#include<iostream>
using namespace std;
using namespace cv;
CvSubdiv2D* init_delaunay( CvMemStorage* storage,CvRect rect ) ;
inline void draw_edge( IplImage* img, CvSubdiv2DEdge edge, CvScalar color );
void draw_delaunay( IplImage* img, CvSubdiv2D* subdiv,CvScalar delaunay_color);
void draw_voronoi( CvSubdiv2D* subdiv, IplImage* img );
void draw_facet( IplImage* img, CvSubdiv2DEdge edge );
inline void draw_point( IplImage* img, CvPoint2D32f fp, CvScalar color );
void locate_pointInDelaunay( CvSubdiv2D* subdiv, CvPoint2D32f fp, IplImage* img, CvScalar active_color );
void locate_pointInVoronoi(CvSubdiv2D* subdiv,CvPoint2D32f fp,IplImage* img,CvScalar color);
delaunayfunc.cpp 代码
/* delaunayfunc.cpp */
#include "delaunayfunc.h"
//初始化初始三角形
CvSubdiv2D* init_delaunay( CvMemStorage* storage,CvRect rect )
{
CvSubdiv2D* subdiv;//三角剖分的数据单元
subdiv = cvCreateSubdiv2D( CV_SEQ_KIND_SUBDIV2D, sizeof(*subdiv),
sizeof(CvSubdiv2DPoint),
sizeof(CvQuadEdge2D),
storage );
cvInitSubdivDelaunay2D( subdiv, rect );
return subdiv;
}
//画出三角剖分的某一条边
inline void draw_edge( IplImage* img, CvSubdiv2DEdge edge, CvScalar color )
{
CvSubdiv2DPoint* org_pt;//源顶点
CvSubdiv2DPoint* dst_pt;//目地顶点
CvPoint2D32f org;
CvPoint2D32f dst;
CvPoint iorg, idst;
org_pt = cvSubdiv2DEdgeOrg(edge);//通过边获取顶点
dst_pt = cvSubdiv2DEdgeDst(edge);
if( org_pt && dst_pt )//如果两个端点不为空
{
org = org_pt->pt;
dst = dst_pt->pt;
iorg = cvPoint( cvRound( org.x ), cvRound( org.y ));
idst = cvPoint( cvRound( dst.x ), cvRound( dst.y ));
cvLine( img, iorg, idst, color, 1, CV_AA, 0 );
}
}
//画出Deluanary细分
void draw_delaunay( IplImage* img, CvSubdiv2D* subdiv, CvScalar delaunay_color )
{
CvSeqReader reader;
int i, total = subdiv->edges->total;//边的数量
int elem_size = subdiv->edges->elem_size;//边的大小
cvStartReadSeq( (CvSeq*)(subdiv->edges), &reader, 0 );//使用CvSeqReader遍历Delaunay或者Voronoi边
for( i = 0; i < total; i++ )
{
CvQuadEdge2D* edge = (CvQuadEdge2D*)(reader.ptr);
//CvSubdiv2DEdge edge1 = (CvSubdiv2DEdge)(reader.ptr); //这样赋值也可以,其实edge1和edge指向的是同一片内存,因为edge的最低2位是00,可以使用下面的方式验证
//size_t t = (size_t)edge;size_t t1 = (size_t)edge1;if (t == t1) printf("edge1 == edge\n");
if( CV_IS_SET_ELEM( edge ))
{
draw_edge( img, (CvSubdiv2DEdge)edge, delaunay_color );
}
CV_NEXT_SEQ_ELEM( elem_size, reader );
}
}
//画出voronoi面
void draw_voronoi( CvSubdiv2D* subdiv, IplImage* img )
{
CvSeqReader reader;
int i, total = subdiv->edges->total;//边缘总数
int elem_size = subdiv->edges->elem_size;//边缘的大小
cvStartReadSeq( (CvSeq*)(subdiv->edges), &reader, 0 );
for( i = 0; i < total; i++ )
{
CvQuadEdge2D* edge = (CvQuadEdge2D*)(reader.ptr);//获取四方边缘
if( CV_IS_SET_ELEM( edge ))//判断边缘是否在边缘集中
{
CvSubdiv2DEdge e = (CvSubdiv2DEdge)edge;//edge是四方边缘的指针,而CvSubdiv2DEdge高位表示四方边缘的指针。
//cout<<(e&3)<<endl;//通过测试e低2位即索引值应该设置为0了,即输入边缘
// left 逆时针遍历小区域
draw_facet( img, cvSubdiv2DRotateEdge( e, 1 ));//e为Delaunay边,获得Delaunay边对应的voronoi边,即e的旋转边缘
// right 顺时针遍历小区域
//draw_facet( img, cvSubdiv2DRotateEdge( e, 3 ));//反向的旋转边缘
}
CV_NEXT_SEQ_ELEM( elem_size, reader );//移动到下一个位置
}
}
//画出voronoi中某点占有的小区域
void draw_facet( IplImage* img, CvSubdiv2DEdge edge )
{
//cout<<edge<<endl;//edge低两位表示表示索引,高位表示四方边缘指针。
//cout<<(edge&3)<<endl;
CvSubdiv2DEdge t = edge;//当我们按上面的调用形式时,edge为eRot。
int i, count = 0;
CvPoint* buf = 0;
Point2d *buf1=0;
// count number of edges in facet //面内边的计数
do
{
count++;
t = cvSubdiv2DGetEdge( t, CV_NEXT_AROUND_LEFT );
} while (t != edge );//我们绕着一个voronoi单元一周,遍历该vornonoi边缘所拥有的边缘数。
buf = (CvPoint*)malloc( count * sizeof(buf[0]));
buf1=(Point2d*)malloc(count*sizeof(buf1[0]));
// gather points
t = edge;
for( i = 0; i < count; i++ )
{
CvSubdiv2DPoint* pt = cvSubdiv2DEdgeOrg( t );//第一次获取eRot边缘的起始点
if( !pt ) break;//如果得不到该源点,则退出循环
buf[i] = cvPoint( cvRound(pt->pt.x), cvRound(pt->pt.y));//将该点转换为cvPoint类型点,存储在buf中
t = cvSubdiv2DGetEdge( t, CV_NEXT_AROUND_LEFT );//然后绕着vornonoi单元,左旋转。
}
if( i == count )//如果所有的点都存储起来了。
{
CvSubdiv2DPoint* pt = cvSubdiv2DEdgeDst( cvSubdiv2DRotateEdge( edge, 1 ));//这里eRot的旋转边缘应该是reversed e,那么目的点,就是e的源点。
cvFillConvexPoly( img, buf, count, CV_RGB(rand()&255,rand()&255,rand()&255), CV_AA, 0 );//颜色随机填充凸多边形
for(i=0;i<count;i++)
{
buf1[i].x=buf[i].x;
buf1[i].y=buf[i].y;
}
Mat mat_img(img);
cvPolyLine( img, &buf, &count, 1, 1, CV_RGB(0,200,0), 1, CV_AA, 0);//画出线。
draw_point( img, pt->pt, CV_RGB(255,0,0));//用红色画出画出剖分顶点。
}
free( buf );
}
//定位给定点所在Delaunary三角型的三边,关键在于确定给定点与定位边的位置关系
void locate_pointInDelaunay( CvSubdiv2D* subdiv, CvPoint2D32f fp, IplImage* img,
CvScalar active_color )
{
CvSubdiv2DEdge e;
CvSubdiv2DEdge e0 = 0;
CvSubdiv2DPoint* p = NULL;
CvSubdiv2DPoint* org_pt;//源顶点
CvSubdiv2DPoint* dst_pt;//目地顶点
CvPoint2D32f org;
CvPoint2D32f dst;
//CvPoint iorg, idst;
cvSubdiv2DLocate( subdiv, fp, &e0, &p ); //定位点的位置
org_pt = cvSubdiv2DEdgeOrg(e0);//通过边获取顶点
dst_pt = cvSubdiv2DEdgeDst(e0);
if( org_pt && dst_pt )//如果两个端点不为空
{
org = org_pt->pt;
dst = dst_pt->pt;
// iorg = cvPoint( cvRound( org.x ), cvRound( org.y ));
// idst = cvPoint( cvRound( dst.x ), cvRound( dst.y ));
}
if( e0 )
{
e = e0;
do
{
draw_edge( img, e, active_color );
e = cvSubdiv2DGetEdge(e,CV_NEXT_AROUND_RIGHT);
cvSaveImage("i.jpg",img);
}
while( e != e0 );
}
cvSaveImage("i.jpg",img);
//draw_point( img, fp, active_color );
}
void locate_pointInVoronoi(CvSubdiv2D* subdiv,CvPoint2D32f fp,IplImage* img,CvScalar color)
{
CvSubdiv2DPoint* pt = cvFindNearestPoint2D(subdiv,fp);
CvSubdiv2DEdge e = 0;
CvSubdiv2DEdge e0 = 0;
CvSubdiv2DPoint* p = NULL;
CvSubdiv2DPoint* org_pt,*dst_pt;
CvPoint2D32f org;
CvPoint iorg;
cvSubdiv2DLocate( subdiv, fp, &e0, &p ); //定位点的位置
draw_facet(img,cvSubdiv2DRotateEdge(e0,1));
//*
org_pt = cvSubdiv2DEdgeOrg(e0);
org = org_pt->pt;
iorg = cvPoint(cvRound(org.x),cvRound(org.y));
if( e0 )
{
e = e0;
do
{
// draw_edge( img, e, active_color );
e = cvSubdiv2DGetEdge(e,CV_NEXT_AROUND_LEFT);
}
while( e != e0 );
}
/**/
}
//画出三角剖分的顶点
inline void draw_point( IplImage* img, CvPoint2D32f fp, CvScalar color )
{
cvCircle( img, cvPoint(cvRound(fp.x), cvRound(fp.y)),3, color, CV_FILLED, 8, 0 );
}
在主工程文件中添加代码
/* 主工程文件中添加的代码 */
#include <opencv2/legacy/legacy.hpp>
#include<opencv2\opencv.hpp>
#include <opencv2/features2d/features2d.hpp>
#include<opencv2/nonfree/nonfree.hpp>
#include<iostream>
#include "delaunayfunc.h"
using namespace std;
using namespace cv;
void main()
{
// Delaunay 三角形初始化工作
CvScalar active_facet_color, delaunay_color, voronoi_color, bkgnd_color;
active_facet_color = CV_RGB( 255, 0, 0 );
delaunay_color = CV_RGB( 255,0,0);
voronoi_color = CV_RGB(0, 180, 0);
bkgnd_color = CV_RGB(255,255,255);
char delaunay[] = "delaunay";
char Voronoi[] = "Voronoi";
cvNamedWindow( delaunay, 1 );
cvNamedWindow(Voronoi,1);
CvRect rect = { 0, 0, 600, 600};
CvMemStorage* storage = cvCreateMemStorage(0); ;
CvSubdiv2D* subdiv= init_delaunay( storage, rect );
IplImage* img_delaunay = cvCreateImage( cvSize(rect.width,rect.height), 8, 3 );
IplImage* img_Voronoi = cvCreateImage( cvSize(rect.width,rect.height), 8, 3 );
cvSet( img_delaunay, bkgnd_color, 0 );
cvSet( img_Voronoi, bkgnd_color, 0 );
for(int i = 0;i<20;i++)
{
CvPoint2D32f fp = cvPoint2D32f( (float)(rand()%(rect.width-10)+5),
(float)(rand()%(rect.height-10)+5));
cvSubdivDelaunay2DInsert( subdiv, fp );//插入新点,重新计算新生成的Delaunary三角形
draw_point(img_delaunay,fp,cvScalarAll(0)); //高亮插入点
}
cvCalcSubdivVoronoi2D( subdiv );//由Delaunary三角形计算Voronoi图
draw_delaunay( img_delaunay, subdiv, delaunay_color);
cvShowImage( delaunay, img_delaunay );
cvSet( img_Voronoi, bkgnd_color, 0 );//重新刷新画布,即设置背景颜色为白色
draw_voronoi( subdiv, img_Voronoi );//画出voronoi划分
cvShowImage(Voronoi,img_Voronoi);
cvWaitKey(0);
cvReleaseMemStorage( &storage );
cvReleaseImage(&img_delaunay);
cvReleaseImage(&img_Voronoi);
cvDestroyAllWindows();
}
结果如下图:
四、引用:
http://www.cnblogs.com/soroman/archive/2007/05/17/750430.html
转载请注明作者和出处:http://blog.csdn.net/holamirai,未经允许请勿用于商业用途