30局部与分割-三角剖分delaunay和voronoi划分
简介
Delaunay三角剖分是1934年发明的将空间点连接为三角形,使得所有三角形中最小角最大的一个技术。
如果你熟悉计算机图形学,你便会知道Delaunay三角剖分是变现三维形状的基础。如果我们在三维空间渲染一个,我们可以通过这个物体的投影来建立二维视觉图,并用二维Delaunay三角剖分来分析识别该物体,或者将它与实物相比较。Delaunay剖分是连接计算机视觉与计算机图形学的桥梁。然而使用OpenCV实现三角剖分的不足之处就是OpenCV只实现了二维的Delaunay剖分。如果我们能够对三维点进行三角剖分,也就是说构成立体视觉,那么我们可以在三维的计算机图形和计算机视觉进行无缝的转换。然而二维三角剖分通常用于计算机视觉中标记空间目标的特征或运动场景跟踪,目标识别,或两个不同的摄像机的场景匹配(如图从立体图像中获得深度信息)。
重要提示
1、程序的关键是通过下面**号强调的利用CvSeqReader遍历所有的Delaunay或者Voronoi.
2、Delaunay或者Voronoi之间的桥梁就是通过函数,cvSubdiv2DRotateEdge()实现边的旋转(切换)。
3、这个Delaunay是个有向图,还是双向的。具体怎么实现的,没有较深的数据结构为基础是很难理解的,至少现在我还不是很懂具体的实现,关于它的使用还是摸索阶段。OpenCV水很深,如果这东西只是为我们用,或者了解,而不是研究,我们没有必要纠结的太深。
三角剖分delaunay和voronoi划分基本原理
Bowyer-Watson算法
目前采用逐点插入方式生成的Delaunay三角网的算法主要基于Bowyer-Watson算法,Bowyer-Watson算法的主要步骤如下:
1)建立初始三角网格:针对给定的点集V,找到一个包含该点集的矩形R,我们称R为辅助窗口。连接R的任意一条对角线,形成两个三角形,作为初始Delaunay三角网格。
2)逐点插入:假设目前已经有一个Delaunay三角网格T,现在在它里面再插入一个点P,需要找到该点P所在的三角形。从P所在的三角形开始,搜索该三角形的邻近三角形,并进行空外接圆检测。找到外接圆包含点P的所有的三角形并删除这些三角形,形成一个包含P的多边形空腔,我们称之为Delaunay空腔。然后连接P与Delaunay腔的每一个顶点,形成新的Delaunay三角网格。
3)删除辅助窗口R:重复步骤2),当点集V中所有点都已经插入到三角形网格中后,将顶点包含辅助窗口R的三角形全部删除。
在这些步骤中,快速定位点所在的三角形、确定点的影响并构建Delaunay腔的过程是每插入一个点都会进行的。随着点数的增加,三角形数目增加很快,因此缩短这两个过程的计算时间,是提高算法效率的关键。
算法执行图示如下:
编程中用到的相关结构图
1:通过 cvSubdiv2DRotateEdge函数切换delaunay和voronoi边:
2:通过cvSubdiv2DGetEdge函数获取下一条delaunay边(或者voronoi边):
相关代码
#include <opencv2/imgproc/imgproc_c.h>
#include <opencv2/legacy/legacy.hpp>
#include "opencv2/highgui/highgui.hpp"
#include <stdio.h>
static void help(void)//提示函数
{
printf("\nThis program demostrates iterative construction of\n"
"delaunay triangulation and voronoi tesselation.\n"
"It draws a random set of points in an image and then delaunay triangulates them.\n"
"Usage: \n"
"./delaunay \n"
"\nThis program builds the traingulation interactively, you may stop this process by\n"
"hitting any key.\n");
}
//delaunay初始化函数,申请内存
static 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;
}
//绘制点,圆形3:半径
static void draw_subdiv_point(IplImage* img, CvPoint2D32f fp, CvScalar color)
{
cvCircle(img, cvPoint(cvRound(fp.x), cvRound(fp.y)), 3, color, CV_FILLED, 8, 0);
}
//绘制边
static void draw_subdiv_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);
}
}
/*********************************************************
//绘制delaunay和voronoi边
delaunay:黑色
voronoi:绿色
*********************************************************/
static void draw_subdiv(IplImage* img, CvSubdiv2D* subdiv,
CvScalar delaunay_color, CvScalar voronoi_color)
{
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))//4条边其中2条是反边;0:delaunay边,1:voronoi边
{
draw_subdiv_edge(img, (CvSubdiv2DEdge)edge + 1, voronoi_color);
draw_subdiv_edge(img, (CvSubdiv2DEdge)edge, delaunay_color);
}
CV_NEXT_SEQ_ELEM(elem_size, reader);
}
}
/******************************************************
locate_point:参数含义
subdiv:细分结构;
fp:随机生成的点,距离正方形的边沿为5cm
img:画板600x600
active_color:颜色值,蓝色
作用:
1:绘制新添加的点
2:重新绘制新增点旁边的Delaunay边为红色
******************************************************/
static void locate_point(CvSubdiv2D* subdiv, CvPoint2D32f fp, IplImage* img,
CvScalar active_color)
{
CvSubdiv2DEdge e;
CvSubdiv2DEdge e0 = 0;
CvSubdiv2DPoint* p = 0;
cvSubdiv2DLocate(subdiv, fp, &e0, &p);//根据新添加的点,找到最近的Delaunay边
if (e0)
{
e = e0;
do
{
draw_subdiv_edge(img, e, active_color);//绘制Delaunay三角剖分的边为红色
e = cvSubdiv2DGetEdge(e, CV_NEXT_AROUND_LEFT);//遍历Delaunay三角剖分的边
} while (e != e0);//逆时针遍历新添加点所在区域的Delaunay
}
draw_subdiv_point(img, fp, active_color);//在画板上绘制点
}
//填充Voronoi四边形,并绘制黑色轮廓
static void draw_subdiv_facet(IplImage* img, CvSubdiv2DEdge edge)
{
CvSubdiv2DEdge t = edge;
int i, count = 0;
CvPoint* buf = 0;
// count number of edges in facet
do
{
count++;
t = cvSubdiv2DGetEdge(t, CV_NEXT_AROUND_LEFT);
} while (t != edge);//逆时针遍历一圈Voronoi边
buf = (CvPoint*)malloc(count * sizeof(buf[0]));
// gather points
t = edge;
for (i = 0; i < count; i++)//获取Voronoi四边形的四个点
{
CvSubdiv2DPoint* pt = cvSubdiv2DEdgeOrg(t);
if (!pt) break;
buf[i] = cvPoint(cvRound(pt->pt.x), cvRound(pt->pt.y));//将4个点放入buf中
t = cvSubdiv2DGetEdge(t, CV_NEXT_AROUND_LEFT);//指向下一条边
}
if (i == count)//所有的边都收集完了之后
{
CvSubdiv2DPoint* pt = cvSubdiv2DEdgeDst(cvSubdiv2DRotateEdge(edge, 1));//从Voronoi转到Delaunay获取到之前添加的点
cvFillConvexPoly(img, buf, count, CV_RGB(rand() & 255, rand() & 255, rand() & 255), CV_AA, 0);//将Voronoi四边形填充为随机的颜色
cvPolyLine(img, &buf, &count, 1, 1, CV_RGB(0, 0, 0), 1, CV_AA, 0);//绘制Voronoi四边形轮廓为黑色
draw_subdiv_point(img, pt->pt, CV_RGB(0, 0, 0));//将点绘制为白色
}
free(buf);//释放内存
}
static void paint_voronoi(CvSubdiv2D* subdiv, IplImage* img)
{
CvSeqReader reader;
int i, total = subdiv->edges->total;
int elem_size = subdiv->edges->elem_size;
cvCalcSubdivVoronoi2D(subdiv);//重新计算Voronoi
cvStartReadSeq((CvSeq*)(subdiv->edges), &reader, 0);
for (i = 0; i < total; i++)
{
CvQuadEdge2D* edge = (CvQuadEdge2D*)(reader.ptr);//遍历delaunay
if (CV_IS_SET_ELEM(edge))//填充每一块Voronoi四边形
{
CvSubdiv2DEdge e = (CvSubdiv2DEdge)edge;
// left
draw_subdiv_facet(img, cvSubdiv2DRotateEdge(e, 1));//eRot边
// right
draw_subdiv_facet(img, cvSubdiv2DRotateEdge(e, 3));//eRot reverse边
}
CV_NEXT_SEQ_ELEM(elem_size, reader);//获取下一个delaunay边
}
}
static void run(void)
{
char win[] = "source";
int i;
CvRect rect = { 0, 0, 600, 600 };//创建一个正方形
CvMemStorage* storage;
CvSubdiv2D* subdiv;//声明细分结果
IplImage* img;
CvScalar active_facet_color, delaunay_color, voronoi_color, bkgnd_color;//声明各种颜色
active_facet_color = CV_RGB(255, 0, 0);//红色
delaunay_color = CV_RGB(0, 0, 0);//黑色delaunay边
voronoi_color = CV_RGB(0, 180, 0);//绿色voronoi边
bkgnd_color = CV_RGB(255, 255, 255);//白色
img = cvCreateImage(cvSize(rect.width, rect.height), 8, 3);//创建一幅图像
cvSet(img, bkgnd_color, 0);//设置图像背景为白色
cvNamedWindow(win, 1);//创建一个窗口
storage = cvCreateMemStorage(0);//声明一个存储器
subdiv = init_delaunay(storage, rect);//初始化delaunay,主要申请一些内存
printf("Delaunay triangulation will be build now interactively.\n"
"To stop the process, press any key\n\n");
for (i = 0; i < 200; i++)//200个随机点
{
CvPoint2D32f fp = cvPoint2D32f((float)(rand() % (rect.width - 10) + 5),
(float)(rand() % (rect.height - 10) + 5));//生成随机点,距离正方形的边沿为5cm
locate_point(subdiv, fp, img, active_facet_color);//重新绘制新增点旁边的Delaunay边为红色
cvShowImage(win, img);//刷新画板
if (cvWaitKey(100) >= 0)
break;
//while (1) {//用于调试
// if (cvWaitKey(100) == 'n') {
// break;
// }
//}
cvSubdivDelaunay2DInsert(subdiv, fp);//插入新增加的点
cvCalcSubdivVoronoi2D(subdiv);//计算Voronoi细分
cvSet(img, bkgnd_color, 0);//将画板清零
draw_subdiv(img, subdiv, delaunay_color, voronoi_color);//重新绘制Delaunay和Voronoi边
cvShowImage(win, img);//显示画板
if (cvWaitKey(100) >= 0)
break;
}
cvSet(img, bkgnd_color, 0);//将画板清零
paint_voronoi(subdiv, img);//绘制voronoi
cvShowImage(win, img);
cvWaitKey(0);
cvReleaseMemStorage(&storage);
cvReleaseImage(&img);
cvDestroyWindow(win);
}
int main(int argc, char** argv)
{
(void)argc; (void)argv;
help();
run();
return 0;
}
#ifdef _EiC
main(1, "delaunay.c");
#endif