【ROS知识】Graham-Scan算法计算凸包
创始人
2025-05-30 04:19:53

一、说明

        其实实现凸包问题至少有五个方法,这里只介绍Graham-Scan经典算法。本文就该算法用代码实现,为了增加有趣性,我们用python实现。

二、算法描述

2.1 假设问题

        假如您在楼下的A处,在G处有个电话亭,问您从哪个路径到达电话亭路程最短?

        分析:(如上图)您从A,G以及楼的边角,可以抽象出(右图)绿色路径构成一个多边形。从A无论顺时针和逆时针都可以到达G,其中最短者就是最短路径。

2.2 引出凸包络问题

        对于给定的若干个点,求覆盖所有点的多边形凸包络 。如图:

2.3 这里有必要先介绍凸包的概念

        在空间上的集合Ω,如果集合内任意两个点的连线段,该线段上所有的点均在集合Ω内部,那么Ω就是一个凸集。读者按照这个定义,不难判定下图中,谁是凸集。

         进一步说,什么是凸多边形?就是既是凸集,又是多边形的图形区域。

2.4 什么是Graham-Scan算法

Graham-Scan算法是一种灵活的凸包算法,时间复杂度是O(nlogn)
基本理念

  • 初始化将包含凸包点的空堆栈。
  • 选择一个起点并将其添加到堆栈中。(选出最左下角的点(排序:x最小,其次是y最小))
  • 围绕起点到其余点做极角,按逆时针顺序对其余点进行排序。(在极角相等的情况下距离极点(p[0])最近的优先)
  • 扫描排序的点。用叉积判断新点和栈顶头两个点形成的拐向。顺时针就弹出栈顶元素,继续判断。否则压入新点p[i]
  • 最初将每个新点添加到堆栈中,然后检查以确保新点的外壳仍然是凸的。
  • 删除任何创建凹角的点 - 这些点位于船体内部。(最终栈内元素就是凸包点。)

三、Graham-Scan算法实现

import random"""
Computes the Convex Hull with the Graham Scan algorithm
Use:h = ConvexHull()print(h.hull)
"""class ConvexHull:def __init__(self, points):if not points:self.points = [(random.randint(0,100),random.randint(0,100)) for i in range(50)]else:self.points = pointsself.hull = self.compute_convex_hull()def get_cross_product(self,p1, p2, p3):return ((p2[0] - p1[0])*(p3[1] - p1[1])) - ((p2[1] - p1[1])*(p3[0] - p1[0]))def get_slope(self,p1, p2):if p1[0] == p2[0]:return float('inf')else:return 1.0*(p1[1]-p2[1])/(p1[0]-p2[0])def compute_convex_hull(self):hull = []self.points.sort(key=lambda x:[x[0],x[1]])start = self.points.pop(0)hull.append(start)self.points.sort(key=lambda p: (self.get_slope(p,start), -p[1],p[0]))for pt in self.points:hull.append(pt)while len(hull) > 2 and self.get_cross_product(hull[-3],hull[-2],hull[-1]) < 0:hull.pop(-2)return hullimport numpy as np
import matplotlib.pyplot as pltfig = plt.figure()
ax1 = fig.add_subplot(111)
#设置标题
ax1.set_title('Scatter Plot')
#设置X轴标签
plt.xlabel('X')
#设置Y轴标签
plt.ylabel('Y')
#画散点图
for pt in MakePoint.points:ax1.scatter(pt[0],pt[1],c = 'r',marker = 'o')
for pt in sidePoint:ax1.scatter(pt[0],pt[1],c = 'b',marker = '*')
#设置图标
plt.legend('x1')
#显示所画的图
plt.show()

测试图:

 

 

四、其它实现

4.1 用openCV进行

        范例调用opencv库

import cv2
import numpy as np# 新建512*512的空白图片
img = np.zeros((512,512,3), np.uint8)
# 平面点集
pts = np.array([[200,250], [250,300], [300, 270], [270,200], [120, 240]], np.int32)
pts = pts.reshape((-1,1,2))
# 绘制填充的多边形
cv2.fillPoly(img, [pts], (255,255,255))
# 保存图片
cv2.imwrite('F://polygon.png', img)
import cv2# 读取图片并转至灰度模式
imagepath = 'F://convex.png'
img = cv2.imread(imagepath, 1)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 二值化
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
# 图片轮廓
image, contours, hierarchy = cv2.findContours(thresh, 2, 1)
cnt = contours[0]
# 寻找凸包并绘制凸包(轮廓)
hull = cv2.convexHull(cnt)
print(hull)length = len(hull)
for i in range(len(hull)):cv2.line(img, tuple(hull[i][0]), tuple(hull[(i+1)%length][0]), (0,255,0), 2)# 显示图片
cv2.imshow('line', img)
cv2.waitKey()

4.2 用网上一个代码,也很有趣!


import sys
import random
import matplotlibmatplotlib.use('Qt5Agg')
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QApplication, QMainWindow, QMenu, QVBoxLayout, QHBoxLayout, QSizePolicy, QWidget, \QTextBrowser, QLineEdit, QPushButton, QMessageBox
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
from matplotlib.animation import FuncAnimation
import matplotlib.pyplot as pltxx = [random.randint(1, 10) for i in range(10)]
yy = [random.randint(1, 10) for i in range(10)]class MyMplCanvas(FigureCanvas):def __init__(self, parent=None, width=12, height=6, dpi=100):fig = Figure(figsize=(9, 7), dpi=100)self.ax = fig.add_subplot(1, 1, 1)FigureCanvas.__init__(self, fig)self.setParent(parent)FigureCanvas.setSizePolicy(self,QtWidgets.QSizePolicy.Expanding,QtWidgets.QSizePolicy.Expanding)FigureCanvas.updateGeometry(self)class ApplicationWindow(QtWidgets.QMainWindow):def __init__(self):QtWidgets.QMainWindow.__init__(self)self.index = 0self.dic = {}self.pointsSize = 30self.upper = 100self.lower = -100self.xx = [random.randint(self.lower, self.upper) for i in range(self.pointsSize)]self.yy = [random.randint(self.lower, self.upper) for i in range(self.pointsSize)]self.points = [[self.xx[i], self.yy[i]] for i in range(self.pointsSize)]self.points.sort(key=lambda x: (x[0], x[1]))self.setAttribute(QtCore.Qt.WA_DeleteOnClose)self.setWindowTitle("分治法生成凸包演示by18070542孙泽坤")self.main_widget = QtWidgets.QWidget(self)self.xs = []self.ys = []self.pointers = 0self.game_start()vbox = QtWidgets.QVBoxLayout(self.main_widget)self.canvas = MyMplCanvas(self.main_widget, width=6, height=6, dpi=100)  ###attention###vbox.addWidget(self.canvas)hbox = QtWidgets.QHBoxLayout(self.main_widget)self.textBrowser = QTextBrowser(self)self.button = QPushButton("下一步")self.quitButton = QPushButton("退出")self.introduceButton = QPushButton("查看凸包问题原理介绍")self.introduceButton.clicked.connect(self.introduce)self.button.clicked.connect(self.choice_point)self.quitButton.clicked.connect(self.exit_pragram)vbox.addWidget(self.button)vbox.addWidget(self.quitButton)vbox.addWidget(self.introduceButton)vbox.addWidget(self.textBrowser)self.setLayout(vbox)self.main_widget.setFocus()self.setCentralWidget(self.main_widget)self.ani = FuncAnimation(self.canvas.figure, self.update_line, interval=15)self.textBrowser.append("先把最左边和最右边的两个边界点({},{})和({},{})连接起来".format(self.points[0][0], self.points[0][1], self.points[self.pointsSize - 1][0], self.points[self.pointsSize - 1][1]))def caculate(self, x1, y1, x2, y2, x3, y3):return x1 * y2 + x3 * y1 + x2 * y3 - x1 * y3 - x2 * y1 - x3 * y2def judge(self, x1, x2, y1, y2):pta = tuple([x1, y1])ptb = tuple([x2, y2])numa = self.dic.get(pta, [])numb = self.dic.get(ptb, [])if ptb in numa or pta in numb:return Falsenuma.append(ptb)numb.append(pta)self.dic[pta] = numaself.dic[ptb] = numbreturn Truedef findUpperMax(self, x1, y1, x2, y2):flag = FalseupperMax = 0upperx = uppery = 0for i in self.points:if i[0] == x1 and i[1] == y1 or i[0] == x2 and i[1] == y2:continueareaData = self.caculate(x1, y1, x2, y2, i[0], i[1])if areaData > 0:if areaData > upperMax:flag = TrueupperMax = areaDataupperx = i[0]uppery = i[1]if flag == False:if self.judge(x1, x2, y1, y2) == False:returnself.xs.append([x1, x2])self.xs.append([y1, y2])returnif self.judge(x1, upperx, y1, uppery) == False:returnself.xs.append([x1, upperx])self.xs.append([y1, uppery])if self.judge(x2, upperx, y2, uppery) == False:returnself.xs.append([x2, upperx])self.xs.append([y2, uppery])self.findUpperMax(x1, y1, upperx, uppery)self.findUpperMax(upperx, uppery, x2, y2)def findBottomMin(self, x1, y1, x2, y2):flag = FalselowerMax = 0lowerx = lowery = 0for i in self.points:if i[0] == x1 and i[1] == y1 or i[0] == x2 and i[1] == y2:continueareaData = self.caculate(x1, y1, x2, y2, i[0], i[1])if areaData < 0:if areaData < lowerMax:flag = TruelowerMax = areaDatalowerx = i[0]lowery = i[1]if flag == False:if self.judge(x1, x2, y1, y2) == False:returnself.xs.append([x1, x2])self.xs.append([y1, y2])returnif self.judge(x1, lowerx, y1, lowery) == False:returnself.xs.append([x1, lowerx])self.xs.append([y1, lowery])if self.judge(x2, lowerx, y2, lowery) == False:returnself.xs.append([x2, lowerx])self.xs.append([y2, lowery])self.findBottomMin(x1, y1, lowerx, lowery)self.findBottomMin(lowerx, lowery, x2, y2)def scatter_points(self):self.canvas.ax.plot([self.points[0][0], self.points[self.pointsSize - 1][0]],[self.points[0][1], self.points[self.pointsSize - 1][1]], color='black')for i in range(0, self.pointsSize):self.canvas.ax.scatter(self.xx[i], self.yy[i], color='k', s=20)def game_start(self):self.findUpperMax(self.points[0][0], self.points[0][1], self.points[self.pointsSize - 1][0],self.points[self.pointsSize - 1][1])self.findBottomMin(self.points[0][0], self.points[0][1], self.points[self.pointsSize - 1][0],self.points[self.pointsSize - 1][1])def choice_point(self):self.textBrowser.clear()if self.pointers == len(self.xs):self.textBrowser.append("凸包生成完毕!点击退出键退出")returnself.ys.append(self.xs[self.pointers])self.ys.append(self.xs[self.pointers + 1])# print(self.xs[self.pointers][0], self.xs[self.pointers + 1][0],#             self.xs[self.pointers][1], self.xs[self.pointers + 1][1])text = "选择点({0},{1})和点({2},{3}),把它们连接起来" \.format(self.xs[self.pointers][0], self.xs[self.pointers + 1][0],self.xs[self.pointers][1], self.xs[self.pointers + 1][1])self.textBrowser.append(text)self.pointers += 2def update_line(self, i):self.canvas.ax.clear()self.scatter_points()for i in range(0, len(self.ys), 2):a = self.ys[i]b = self.ys[i + 1]if i == len(self.ys) - 2:self.canvas.ax.plot(a, b, color='red')else:self.canvas.ax.plot(a, b, color='black')if i == len(self.ys) - 2:self.canvas.ax.scatter(self.ys[i][0], self.ys[i + 1][0], color='red')self.canvas.ax.scatter(self.ys[i][1], self.ys[i + 1][1], color='red')def introduce(self):text = "第一步:把所有点在横坐标方向上按从小到大顺序排列,左右两个端点必定是凸包上的点\n第二步:在上凸包集合点中找一个距离直线最远的点,将它与左右端点连接起来\n第三步:重复递归求解到上半凸包的边\n第四步:下半凸包同理,通过递归求解,最终得到整个凸包"reply = QMessageBox.information(self, '分治法凸包问题原理介绍', text, QMessageBox.Yes)def exit_pragram(self):sys.exit(0)if __name__ == "__main__":App = QApplication(sys.argv)aw = ApplicationWindow()aw.show()App.exit()sys.exit(App.exec_())

凸包——Graham-Scan算法_凸包算法graham复杂度_theArcticOcean的博客-CSDN博客

相关内容

热门资讯

Python基础(十七):装饰... 闭包闭包(英语:Closure),又称词法闭...
计算机科学导论笔记(十四) 目录 十六、安全 16.1 引言 16.1.1 安全目标 16.1.2 攻击 16.1.2.1 威...
@Transactional导... 首先我有一个Class A和Class B,A和B存在循环依赖。 @Servi...
HTML5-表单 HTML5-表单 一、Form 1.action 属性 action 属性用于指定表单...
【小猫爪】AUTOSAR学习笔... 【小猫爪】AUTOSAR学习笔记05-Communication Stack之CanSM模块前言1 ...
c# 使用AutoResetE...         做项目时有一个需求。用一个线程去执行耗时操作。另一个线程需要使用第一个线程的操作结果...
在pycharm中使用chat... 目录 前言 一、插件安装 二、使用步骤 总结 前言 ChatGPT是目前最强大的AI,...
Codeforces Roun... G. Subsequence Addition 标签 规律、数学 链接 传送门、 结论 当前前缀和小...
算法leetcode|42. ... 文章目录42. 接雨水:样例 1:样例 2:提示ÿ...
【项目设计】负载均衡在线OJ 🎇Linux: 博客主页:一起去看日落吗分享博主的在L...
Java开发 | 重写 | 多... 前言 大家好,我是程序猿爱打拳,今天给大家带来的是面向对象之封装继承多...
【Unity】NavMesh ... 在Unity中,可以使用自带导航系统(Navigation System...
由文心一言发布会引发的思考,聊... 文章目录前言一. 文心一言的试用1.1 文心一言发布会1.2 文心一言图片生成功能试用1.3 文心一...
java线程之Thread类的... Thread类的基本用法1. Thread类的构造方法2. Thread的几个常见属性常见属性线程中...
css实现3D弹性按钮以及bo... box-shadow 在实现案例之前先了解css的阴影属性box-shadow,该属性...
【Linux】基础命令大全、实... 个人简介:Java领域新星创作者;阿里云技术博主、星级博主、专家博主&#...
R语言基础教程4:列表和数据框 文章目录列表数据帧表头 R语言系列:1 编程基础💎2 循环语句...
Git基础知识 Git基础知识前言一、Git基本概念1、分布式版本控制系统--Git2、Git配置命令3、Git原理...
【JavaWeb】MySQL 一、数据库的相关概念 1.数据库(DB) ==存储和管...
CPU 是如何执行程序的 代码写了那么多,你知道 a = 1 + 2 这条代码是怎么被 CPU ...
从产品的角度看缓存 文章目录 1. What——什么是缓存?2. Why——为什么需要使用缓存?2.1 什么是用户体验2...
vivado 开发过程中所遇错...  [Synth 8-4556] 开辟的数组内存空间大小问题 [Synth 8-4556] size...
1.4 K8S入门之POD和网... POD 分类 自主式POD控制器管理的POD 容器 每个容器独立存在,有自己的IP地址...
【二】一起算法---队列:ST... 纸上得来终觉浅,绝知此事要躬行。大家好!我是霜淮子,欢迎订...
在使用fastjson中遇到的... 一、在使用fastjson中遇到的问题 导论:最近在写一个JavaFx项目的时候使用...
HJ31 单词倒排 描述 对字符串中的所有单词进行倒排。 说明: 1、构成单词的字符只有26个大写或小写英...
普通插槽、具名插槽、作用域插槽 插槽 插槽就是子组件提供给父组件的占位符,用slot来表示,父组件可以在...
Go语言必知必会100问题-0... 减少代码的嵌套层数 软件开发中的“心智模型”用于描述开发人员在编码时心理活动,每段代码...
CSRF漏洞的概念、利用方式、... CSRF漏洞1.CSRF的概念1.1 什么是CSRF?1.2 基本攻击流程2.CSRF...
基于springboot开发的... 基于springboot开发的学生考勤管理系统 如需更多资料请前往文章底部获取联系方式 系统设计主要...