百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

如何使用OpenCV进行Delaunay三角剖分和Voronoi图

bigegpt 2024-08-28 12:07 1 浏览




图1.左:使用dlib检测到具有标志性建筑的奥巴马总统图像。中心:地标的Delaunay三角剖分。右:对应的Voronoi图。

俄国数学家鲍里斯·尼古拉耶维奇·德劳内(Boris Nikolaevich Delaunay)用两种不同的方式拼写了他的姓氏-法劳德出版社的德劳内和其他地方的德隆。很少有人拥有以他们命名的算法或概念。但是Delaunay有一个数学概念是以他姓氏的每次拼写来命名的!— Delaunay三角剖分和Delone集。似乎还不够,到1913年,他成为俄罗斯三大登山家之一!

Delaunay博士 顾问是乔治·沃罗诺伊(Georgy Voronoy),后被命名为沃罗诺伊图。还有另一个有趣的琐事-Voronoy的博士学位。顾问是安德烈·马可夫(Andrey Markov)(是的,是马可夫链的马可夫,马可夫成名)。

什么是Delaunay三角剖分?



图2:Delaunay三角剖分偏爱小角度

给定平面中的一组点,三角剖分指的是将平面细分为三角形,这些点为顶点。在图1中,我们在左侧图像上看到了一组地标,在中间图像上看到了三角剖分。一组点可以有许多可能的三角剖分,但是Delaunay三角剖分之所以突出是因为它具有一些不错的特性。在Delaunay三角剖分中,选择三角形时应确保没有点位于任何三角形的外接圆之内。图2.显示了四个点A,B,C和D的Delaunay三角剖分。在顶部图像中,为了使该三角剖分成为有效的Delaunay三角剖分,C点应该在三角形ABD的外接圆之外,而A点应该在三角形的外侧。三角形BCD的外接圆。

Delaunay三角剖分的一个有趣特性是它不支持“瘦”三角形(即,一个大角度的三角形)。

图2显示了在移动点时三角剖分如何变化以拾取“胖”三角形。在顶部图像中,点B和D在x = 1.5处具有x坐标,在底部图像中,它们向右移至x = 1.75。在顶部图像角度中,ABC和ABD的角度较大,而Delaunay三角剖分在B和D之间创建了一条边缘,将两个大角度分为较小的角度ABD,ADB,CDB和CBD。另一方面,在底部图像中,角度BCD太大,并且Delaunay三角剖分创建了边缘AC以划分大角度。

有很多算法可以找到一组点的Delaunay三角剖分。最明显(但不是最有效)的方法是从任何三角剖分开始,然后检查任何三角形的外接圆是否包含另一个点。如果是这样,则翻转边缘(如图2所示)并继续直到没有外接圆包含点的三角形。

关于Delaunay三角剖分的任何讨论都必须包括Voronoi图,因为一组点的Voronoi图是其Delaunay三角剖分的数学对偶。

什么是Voronoi图?



图3. Voronoi图

给定平面中的一组点,Voronoi图会划分空间,以使边界线与相邻点等距。图3.显示了根据显示为黑点的点计算的Voronoi图的示例。您会注意到,每条边界线都经过两个点的中心。如果将相邻的Voronoi地区中的点连接起来,则会得到Delaunay三角剖分!

Delaunay三角剖分和Voronoi图之间的联系不止一种。乔治·沃罗诺伊(Georgy Voronoy)是鲍里斯·德劳内(Boris Delaunay)的博士学位。顾问。

给定一组点,您可以使用Subdiv2D类计算Delaunay三角剖分或Voronoi图。步骤如下。下一部分显示了一个完整的工作示例。

1.收集向量中的所有点。

C++


vector<Point2f> points;
// This is how you can add one point. 
points.push_back(Point2f(x,y));

Python


points = []
# This is how you can add one point. 
points.append((x, y))

2.使用矩形(rect)定义要分区的空间。如果您在上一步中定义的点是在图像上定义的,则此矩形可以是(0,0,width,height)。否则,您可以选择一个包围所有点的矩形。

C++


Mat img = imread("image.jpg");
Size size = img.size();
Rect rect(0, 0, size.width, size.height);

Python


img = cv2.imread("image.jpg");
size = img.shape
rect = (0, 0, size[1], size[0])

3.使用在上一步中获得的矩形创建Subdiv2D实例。

C++


Subdiv2D subdiv(rect);

Python


subdiv = cv2.Subdiv2D(rect);

4.使用subdiv.insert(point)将点插入subdiv中。上面的视频显示了在细分中添加点时的三角剖分动画。

5.使用subdiv.getTriangleList获取Delaunay三角形的列表。

6.使用subdiv.getVoronoiFacetList获取Voronoi构面的列表。

Delaunay三角剖分和Voronoi图的OpenCV示例

这是一个完整的工作示例。我已经从OpenCV附带的示例中复制了一些代码,并对其进行了简化和修改,以适应我们的目的。OpenCV附带的python示例使用旧的(丑陋的)接口,因此我从头开始编写了它。此代码假定图像存储在image.jpg中,点存储在points.txt中。points.txt的每一行都包含一个点的x和y坐标,这些点之间用空格隔开。例如

207 242

210 269

214 297

220 322

229 349

C ++示例


#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>
#include <fstream>
 
using namespace cv;
using namespace std;
 
// Draw a single point
static void draw_point( Mat& img, Point2f fp, Scalar color )
{
 circle( img, fp, 2, color, CV_FILLED, CV_AA, 0 );
}
 
// Draw delaunay triangles
static void draw_delaunay( Mat& img, Subdiv2D& subdiv, Scalar delaunay_color )
{
 
 vector<Vec6f> triangleList;
 subdiv.getTriangleList(triangleList);
 vector<Point> pt(3);
 Size size = img.size();
 Rect rect(0,0, size.width, size.height);
 
 for( size_t i = 0; i < triangleList.size(); i++ )
 {
 Vec6f t = triangleList[i];
 pt[0] = Point(cvRound(t[0]), cvRound(t[1]));
 pt[1] = Point(cvRound(t[2]), cvRound(t[3]));
 pt[2] = Point(cvRound(t[4]), cvRound(t[5]));
 
 // Draw rectangles completely inside the image.
 if ( rect.contains(pt[0]) && rect.contains(pt[1]) && rect.contains(pt[2]))
 {
 line(img, pt[0], pt[1], delaunay_color, 1, CV_AA, 0);
 line(img, pt[1], pt[2], delaunay_color, 1, CV_AA, 0);
 line(img, pt[2], pt[0], delaunay_color, 1, CV_AA, 0);
 }
 }
}
 
//Draw voronoi diagram
static void draw_voronoi( Mat& img, Subdiv2D& subdiv )
{
 vector<vector<Point2f> > facets;
 vector<Point2f> centers;
 subdiv.getVoronoiFacetList(vector<int>(), facets, centers);
 
 vector<Point> ifacet;
 vector<vector<Point> > ifacets(1);
 
 for( size_t i = 0; i < facets.size(); i++ )
 {
 ifacet.resize(facets[i].size());
 for( size_t j = 0; j < facets[i].size(); j++ )
 ifacet[j] = facets[i][j];
 
 Scalar color;
 color[0] = rand() & 255;
 color[1] = rand() & 255;
 color[2] = rand() & 255;
 fillConvexPoly(img, ifacet, color, 8, 0);
 
 ifacets[0] = ifacet;
 polylines(img, ifacets, true, Scalar(), 1, CV_AA, 0);
 circle(img, centers[i], 3, Scalar(), CV_FILLED, CV_AA, 0);
 }
}
 
 
int main( int argc, char** argv)
{
 
 // Define window names
 string win_delaunay = "Delaunay Triangulation";
 string win_voronoi = "Voronoi Diagram";
 
 // Turn on animation while drawing triangles
 bool animate = true;
 
 // Define colors for drawing.
 Scalar delaunay_color(255,255,255), points_color(0, 0, 255);
 
 // Read in the image.
 Mat img = imread("image.jpg");
 
 // Keep a copy around
 Mat img_orig = img.clone();
 
 // Rectangle to be used with Subdiv2D
 Size size = img.size();
 Rect rect(0, 0, size.width, size.height);
 
 // Create an instance of Subdiv2D
 Subdiv2D subdiv(rect);
 
 // Create a vector of points.
 vector<Point2f> points;
 
 // Read in the points from a text file
 ifstream ifs("points.txt");
 int x, y;
 while(ifs >> x >> y)
 {
 points.push_back(Point2f(x,y));
 }
 
 // Insert points into subdiv
 for( vector<Point2f>::iterator it = points.begin(); it != points.end(); it++)
 {
 subdiv.insert(*it);
 // Show animation
 if (animate)
 {
 Mat img_copy = img_orig.clone();
 // Draw delaunay triangles
 draw_delaunay( img_copy, subdiv, delaunay_color );
 imshow(win_delaunay, img_copy);
 waitKey(100);
 }
 
 }
 
 // Draw delaunay triangles
 draw_delaunay( img, subdiv, delaunay_color );
 
 // Draw points
 for( vector<Point2f>::iterator it = points.begin(); it != points.end(); it++)
 {
 draw_point(img, *it, points_color);
 }
 
 // Allocate space for Voronoi Diagram
 Mat img_voronoi = Mat::zeros(img.rows, img.cols, CV_8UC3);
 
 // Draw Voronoi diagram
 draw_voronoi( img_voronoi, subdiv );
 
 // Show results.
 imshow( win_delaunay, img);
 imshow( win_voronoi, img_voronoi);
 waitKey(0);
 
 return 0;
}

Python


#!/usr/bin/python
 
import cv2
import numpy as np
import random
 
# Check if a point is inside a rectangle
def rect_contains(rect, point) :
 if point[0] < rect[0] :
 return False
 elif point[1] < rect[1] :
 return False
 elif point[0] > rect[2] :
 return False
 elif point[1] > rect[3] :
 return False
 return True
 
# Draw a point
def draw_point(img, p, color ) :
 cv2.circle( img, p, 2, color, cv2.cv.CV_FILLED, cv2.CV_AA, 0 )
 
 
# Draw delaunay triangles
def draw_delaunay(img, subdiv, delaunay_color ) :
 
 triangleList = subdiv.getTriangleList();
 size = img.shape
 r = (0, 0, size[1], size[0])
 
 for t in triangleList :
 
 pt1 = (t[0], t[1])
 pt2 = (t[2], t[3])
 pt3 = (t[4], t[5])
 
 if rect_contains(r, pt1) and rect_contains(r, pt2) and rect_contains(r, pt3) :
 
 cv2.line(img, pt1, pt2, delaunay_color, 1, cv2.CV_AA, 0)
 cv2.line(img, pt2, pt3, delaunay_color, 1, cv2.CV_AA, 0)
 cv2.line(img, pt3, pt1, delaunay_color, 1, cv2.CV_AA, 0)
 
 
# Draw voronoi diagram
def draw_voronoi(img, subdiv) :
 
 ( facets, centers) = subdiv.getVoronoiFacetList([])
 
 for i in xrange(0,len(facets)) :
 ifacet_arr = []
 for f in facets[i] :
 ifacet_arr.append(f)
 
 ifacet = np.array(ifacet_arr, np.int)
 color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
 
 cv2.fillConvexPoly(img, ifacet, color, cv2.CV_AA, 0);
 ifacets = np.array([ifacet])
 cv2.polylines(img, ifacets, True, (0, 0, 0), 1, cv2.CV_AA, 0)
 cv2.circle(img, (centers[i][0], centers[i][1]), 3, (0, 0, 0), cv2.cv.CV_FILLED, cv2.CV_AA, 0)
 
 
if __name__ == '__main__':
 
 # Define window names
 win_delaunay = "Delaunay Triangulation"
 win_voronoi = "Voronoi Diagram"
 
 # Turn on animation while drawing triangles
 animate = True
 
 # Define colors for drawing.
 delaunay_color = (255,255,255)
 points_color = (0, 0, 255)
 
 # Read in the image.
 img = cv2.imread("image.jpg");
 
 # Keep a copy around
 img_orig = img.copy();
 
 # Rectangle to be used with Subdiv2D
 size = img.shape
 rect = (0, 0, size[1], size[0])
 
 # Create an instance of Subdiv2D
 subdiv = cv2.Subdiv2D(rect);
 
 # Create an array of points.
 points = [];
 
 # Read in the points from a text file
 with open("points.txt") as file :
 for line in file :
 x, y = line.split()
 points.append((int(x), int(y)))
 
 # Insert points into subdiv
 for p in points :
 subdiv.insert(p)
 
 # Show animation
 if animate :
 img_copy = img_orig.copy()
 # Draw delaunay triangles
 draw_delaunay( img_copy, subdiv, (255, 255, 255) );
 cv2.imshow(win_delaunay, img_copy)
 cv2.waitKey(100)
 
 # Draw delaunay triangles
 draw_delaunay( img, subdiv, (255, 255, 255) );
 
 # Draw points
 for p in points :
 draw_point(img, p, (0,0,255))
 
 # Allocate space for Voronoi Diagram
 img_voronoi = np.zeros(img.shape, dtype = img.dtype)
 
 # Draw Voronoi diagram
 draw_voronoi(img_voronoi,subdiv)
 
 # Show results
 cv2.imshow(win_delaunay,img)
 cv2.imshow(win_voronoi,img_voronoi)
 cv2.waitKey(0)

实现效果

代码下载:

相关推荐

得物可观测平台架构升级:基于GreptimeDB的全新监控体系实践

一、摘要在前端可观测分析场景中,需要实时观测并处理多地、多环境的运行情况,以保障Web应用和移动端的可用性与性能。传统方案往往依赖代理Agent→消息队列→流计算引擎→OLAP存储...

warm-flow新春版:网关直连和流程图重构

本期主要解决了网关直连和流程图重构,可以自此之后可支持各种复杂的网关混合、多网关直连使用。-新增Ruoyi-Vue-Plus优秀开源集成案例更新日志[feat]导入、导出和保存等新增json格式支持...

扣子空间体验报告

在数字化时代,智能工具的应用正不断拓展到我们工作和生活的各个角落。从任务规划到项目执行,再到任务管理,作者深入探讨了这款工具在不同场景下的表现和潜力。通过具体的应用实例,文章展示了扣子空间如何帮助用户...

spider-flow:开源的可视化方式定义爬虫方案

spider-flow简介spider-flow是一个爬虫平台,以可视化推拽方式定义爬取流程,无需代码即可实现一个爬虫服务。spider-flow特性支持css选择器、正则提取支持JSON/XML格式...

solon-flow 你好世界!

solon-flow是一个基础级的流处理引擎(可用于业务规则、决策处理、计算编排、流程审批等......)。提供有“开放式”驱动定制支持,像jdbc有mysql或pgsql等驱动,可...

新一代开源爬虫平台:SpiderFlow

SpiderFlow:新一代爬虫平台,以图形化方式定义爬虫流程,不写代码即可完成爬虫。-精选真开源,释放新价值。概览Spider-Flow是一个开源的、面向所有用户的Web端爬虫构建平台,它使用Ja...

通过 SQL 训练机器学习模型的引擎

关注薪资待遇的同学应该知道,机器学习相关的岗位工资普遍偏高啊。同时随着各种通用机器学习框架的出现,机器学习的门槛也在逐渐降低,训练一个简单的机器学习模型变得不那么难。但是不得不承认对于一些数据相关的工...

鼠须管输入法rime for Mac

鼠须管输入法forMac是一款十分新颖的跨平台输入法软件,全名是中州韵输入法引擎,鼠须管输入法mac版不仅仅是一个输入法,而是一个输入法算法框架。Rime的基础架构十分精良,一套算法支持了拼音、...

Go语言 1.20 版本正式发布:新版详细介绍

Go1.20简介最新的Go版本1.20在Go1.19发布六个月后发布。它的大部分更改都在工具链、运行时和库的实现中。一如既往,该版本保持了Go1的兼容性承诺。我们期望几乎所...

iOS 10平台SpriteKit新特性之Tile Maps(上)

简介苹果公司在WWDC2016大会上向人们展示了一大批新的好东西。其中之一就是SpriteKitTileEditor。这款工具易于上手,而且看起来速度特别快。在本教程中,你将了解关于TileE...

程序员简历例句—范例Java、Python、C++模板

个人简介通用简介:有良好的代码风格,通过添加注释提高代码可读性,注重代码质量,研读过XXX,XXX等多个开源项目源码从而学习增强代码的健壮性与扩展性。具备良好的代码编程习惯及文档编写能力,参与多个高...

Telerik UI for iOS Q3 2015正式发布

近日,TelerikUIforiOS正式发布了Q32015。新版本新增对XCode7、Swift2.0和iOS9的支持,同时还新增了对数轴、不连续的日期时间轴等;改进TKDataPoin...

ios使用ijkplayer+nginx进行视频直播

上两节,我们讲到使用nginx和ngixn的rtmp模块搭建直播的服务器,接着我们讲解了在Android使用ijkplayer来作为我们的视频直播播放器,整个过程中,需要注意的就是ijlplayer编...

IOS技术分享|iOS快速生成开发文档(一)

前言对于开发人员而言,文档的作用不言而喻。文档不仅可以提高软件开发效率,还能便于以后的软件开发、使用和维护。本文主要讲述Objective-C快速生成开发文档工具appledoc。简介apple...

macOS下配置VS Code C++开发环境

本文介绍在苹果macOS操作系统下,配置VisualStudioCode的C/C++开发环境的过程,本环境使用Clang/LLVM编译器和调试器。一、前置条件本文默认前置条件是,您的开发设备已...