道格拉斯-普克算法 - 把一堆复杂的线条变得简单,同时尽量保持原来的样子

道格拉斯-普克算法 - 把一堆复杂的线条变得简单,同时尽量保持原来的样子

flyfish

道格拉斯-普克算法(Douglas-Peucker Algorithm解决的问题其实很日常:把一堆复杂的线条(比如地图上的道路、河流,或者GPS记录的轨迹)变得简单,同时尽量保持原来的样子。

举个例子,假设用GPS记录了一条徒步路线,每走1米就记一个点,最后生成了1000个点的折线。但其实很多相邻的点几乎在一条直线上,完全没必要都保留——存起来占空间,画出来也累赘。这时候这个算法就派上用场了:它能自动删掉那些“多余”的点,比如直线段中间的点,只留下关键的拐点,让线条变简单,但看起来还是你走的那条路。

大概是上世纪70年代初。1972年有个叫乌尔斯·拉默的人先提出了类似思路,1973年道格拉斯和普克两个人又完善了这个方法,所以后来就用他们的名字命名了。

在这里插入图片描述

import numpy as np
import matplotlib.pyplot as plt
from PIL import Image
import io# 设置中文字体,确保中文正常显示
plt.rcParams["font.family"] = ["SimHei", "sans-serif"]
plt.rcParams['axes.unicode_minus'] = False  # 解决负号显示问题def point_to_segment_dist(point, start, end):"""计算点到线段的垂直距离"""if np.allclose(start, end):return np.linalg.norm(point - start)# 计算线段的单位向量line_vec = end - startline_len = np.linalg.norm(line_vec)unit_line_vec = line_vec / line_len# 计算从起点到点的向量point_vec = point - start# 计算投影长度projection_length = np.dot(point_vec, unit_line_vec)# 如果投影长度超出线段范围,则计算到端点的距离if projection_length < 0:return np.linalg.norm(point - start)elif projection_length > line_len:return np.linalg.norm(point - end)# 计算投影点projection = start + projection_length * unit_line_vec# 计算点到投影点的距离return np.linalg.norm(point - projection)def douglas_peucker(points, epsilon, frames=None):"""道格拉斯-普克算法实现,并记录每一步的处理过程用于可视化参数:points: 待简化的点集epsilon: 距离阈值frames: 存储每一步处理结果的列表"""if len(points) < 3:if frames is not None:frames.append({'points': points.copy(),'start_idx': 0,'end_idx': len(points) - 1,'max_dist_idx': None,'is_terminal': True})return pointsstart_point = points[0]end_point = points[-1]# 计算所有中间点到线段的距离distances = []for i in range(1, len(points) - 1):dist = point_to_segment_dist(points[i], start_point, end_point)distances.append((dist, i))if not distances:if frames is not None:frames.append({'points': points.copy(),'start_idx': 0,'end_idx': len(points) - 1,'max_dist_idx': None,'is_terminal': True})return np.array([start_point, end_point])# 找到最大距离的点max_dist, max_dist_idx = max(distances, key=lambda x: x[0])# 记录当前步骤if frames is not None:frames.append({'points': points.copy(),'start_idx': 0,'end_idx': len(points) - 1,'max_dist_idx': max_dist_idx if max_dist > epsilon else None,'is_terminal': max_dist <= epsilon})# 如果最大距离大于阈值,则递归处理if max_dist > epsilon:left_points = points[:max_dist_idx + 1]right_points = points[max_dist_idx:]left_simplified = douglas_peucker(left_points, epsilon, frames)right_simplified = douglas_peucker(right_points, epsilon, frames)# 合并结果(去掉重复的分割点)return np.vstack([left_simplified[:-1], right_simplified])else:# 所有点都足够接近线段,直接返回起点和终点return np.array([start_point, end_point])def create_frames(points, epsilon):"""创建算法执行过程的帧列表"""frames = []douglas_peucker(points, epsilon, frames)return framesdef draw_frame(frame, frames, points, epsilon, step_num, total_steps, figsize=(10, 6)):"""绘制单个帧"""fig, (ax1, ax2) = plt.subplots(1, 2, figsize=figsize)# 左侧子图:当前处理的线段ax1.set_title("当前处理的线段")ax1.set_xlabel("X")ax1.set_ylabel("Y")ax1.set_xlim(points[:, 0].min() - 1, points[:, 0].max() + 1)ax1.set_ylim(points[:, 1].min() - 1, points[:, 1].max() + 1)# 绘制原始曲线(半透明)ax1.plot(points[:, 0], points[:, 1], 'b-', alpha=0.3, label='原始曲线')# 绘制当前处理的线段current_points = frame['points']start_idx = frame['start_idx']end_idx = frame['end_idx']start_point = current_points[start_idx]end_point = current_points[end_idx]ax1.plot([start_point[0], end_point[0]], [start_point[1], end_point[1]], 'r-', linewidth=2, label='当前线段')# 绘制最大距离点if frame['max_dist_idx'] is not None:max_dist_idx = frame['max_dist_idx']max_point = current_points[max_dist_idx]# 绘制最大距离点ax1.plot(max_point[0], max_point[1], 'go', markersize=8, label='最远点')# 计算并绘制垂直线projection = compute_projection(max_point, start_point, end_point)ax1.plot([max_point[0], projection[0]], [max_point[1], projection[1]], 'g--', linewidth=1, label='垂直距离')# 显示距离值dist = point_to_segment_dist(max_point, start_point, end_point)mid_x = (max_point[0] + projection[0]) / 2mid_y = (max_point[1] + projection[1]) / 2ax1.text(mid_x, mid_y, f'd={dist:.2f}', ha='center', va='bottom', bbox=dict(facecolor='white', alpha=0.8))ax1.legend()# 右侧子图:累积简化结果ax2.set_title("累积简化结果")ax2.set_xlabel("X")ax2.set_ylabel("Y")ax2.set_xlim(points[:, 0].min() - 1, points[:, 0].max() + 1)ax2.set_ylim(points[:, 1].min() - 1, points[:, 1].max() + 1)# 绘制原始曲线(半透明)ax2.plot(points[:, 0], points[:, 1], 'b-', alpha=0.3, label='原始曲线')# 收集所有已处理的线段simplified_points = []# 从第一步到当前步,收集所有终端节点(即已处理完的线段)for f in frames[:step_num + 1]:if f['is_terminal']:p = f['points']simplified_points.append(p[0])simplified_points.append(p[-1])# 去重并排序(按X坐标)if simplified_points:simplified_points = np.array(simplified_points)_, idx = np.unique(simplified_points[:, 0], return_index=True)simplified_points = simplified_points[np.sort(idx)]# 绘制简化后的曲线ax2.plot(simplified_points[:, 0], simplified_points[:, 1], 'r-', linewidth=2, label='简化曲线')ax2.plot(simplified_points[:, 0], simplified_points[:, 1], 'ro', markersize=5)ax2.legend()# 添加标题和步骤信息if frame['is_terminal']:step_text = f"步骤 {step_num + 1}/{total_steps}: 所有点距离 ≤ ε,保留首尾点"else:step_text = f"步骤 {step_num + 1}/{total_steps}: 保留最远点,分割曲线"fig.suptitle(f"道格拉斯-普克算法 (阈值 ε = {epsilon})", fontsize=14)plt.figtext(0.5, 0.01, step_text, ha="center", fontsize=12)# 保存当前帧为图像buf = io.BytesIO()plt.savefig(buf, format='png', bbox_inches='tight')buf.seek(0)image = Image.open(buf)plt.close(fig)return imagedef compute_projection(point, start, end):"""计算点在直线上的投影"""if np.allclose(start, end):return startline_vec = end - startpoint_vec = point - startline_len_sq = np.sum(line_vec ** 2)# 计算投影系数t = np.dot(point_vec, line_vec) / line_len_sq# 限制投影在端点之间t = max(0, min(1, t))return start + t * line_vecdef create_gif(points, epsilon, output_path='douglas_peucker.gif', duration=1000):"""创建道格拉斯-普克算法执行过程的GIF动画"""# 生成所有帧frames = create_frames(points, epsilon)# 绘制每一帧并保存为GIFimages = []for i, frame in enumerate(frames):image = draw_frame(frame, frames, points, epsilon, i, len(frames))images.append(image)# 保存为GIFimages[0].save(output_path,save_all=True,append_images=images[1:],duration=duration,loop=0  # 0表示无限循环)print(f"GIF动画已保存至: {output_path}")return output_path# 示例:创建一个带噪声的正弦曲线并生成GIF
if __name__ == "__main__":# 生成示例数据np.random.seed(42)  # 设置随机种子,确保结果可重现x = np.linspace(0, 10, 100)y = np.sin(x) + np.random.normal(0, 0.3, size=len(x))  # 添加随机噪声points = np.column_stack([x, y])# 设置阈值epsilon = 0.5# 创建GIF动画create_gif(points, epsilon, output_path='douglas_peucker.gif', duration=1000)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/94234.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/94234.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

团购商城 app 系统架构分析

一、引言 团购商城 APP 作为一种融合了电子商务与团购模式的应用程序&#xff0c;近年来在市场上取得了显著的发展。它为用户提供了便捷的购物体验&#xff0c;同时也为商家创造了更多的销售机会。一个完善且高效的系统架构是保障团购商城 APP 稳定运行、提供优质服务的基础。本…

【AI平台】n8n入门7:本地n8n更新

✅0、前言 目标&#xff1a;本地n8n部署后&#xff0c;有新版本&#xff0c;然后进行更新。官方文档&#xff1a;Docker | n8n Docs特别说明&#xff1a; n8n镜像更新后&#xff0c;容器重建&#xff0c;所以之前在n8n配置的东西&#xff0c;就莫有了&#xff0c;工作流提前导…

还在使用Milvus向量库?2025-AI智能体选型架构防坑指南

前言说明&#xff1a;数据来源&#xff1a;主要基于 Milvus&#xff08;v2.3&#xff09;和 Qdrant&#xff08;v1.8&#xff09;的最新稳定版&#xff0c;参考官方文档、GitHub Issues、CNCF报告、以及第三方评测&#xff08;如DB-Engines、TechEmpower&#xff09;。评估原则…

3-verilog的使用-1

verilog的使用-1 1.判断上升沿 reg s_d0; reg s_d1; wire signal_up ; //判断信号的上升沿 assign signal_up (~touch_key_d1) & touch_key_d0; always (posedge clk or negedge rst_n) beginif(rst_n 1b0) begins_d0< 1b0;s_d1< 1b0;endelse begins_d0&…

ESXI虚拟交换机 + H3C S5120交换机 + GR5200路由器组网笔记

文章目录一、组网拓扑与核心逻辑1. 拓扑结构2. 核心逻辑二、详细规划方案1. VLAN 与 IP 地址规划2. 设备连接规划三、配置步骤1. H3C S5120 交换机配置&#xff08;VLAN 与端口&#xff09;2. H3C GR5200 路由器配置&#xff08;路由、网关、NAT&#xff09;3. ESXi 虚拟交换机…

python的驾校培训预约管理系统

前端开发框架:vue.js 数据库 mysql 版本不限 后端语言框架支持&#xff1a; 1 java(SSM/springboot)-idea/eclipse 2.NodejsVue.js -vscode 3.python(flask/django)–pycharm/vscode 4.php(thinkphp/laravel)-hbuilderx 数据库工具&#xff1a;Navicat/SQLyog等都可以 该系统通…

webrtc弱网-QualityScaler 源码分析与算法原理

一. 核心功能QualityScaler 是 WebRTC 中用于动态调整视频编码质量的模块&#xff0c;主要功能包括&#xff1a;QP 监控&#xff1a;持续监测编码器输出的量化参数&#xff08;QP&#xff09;丢帧率分析&#xff1a;跟踪媒体优化和编码器导致的丢帧情况自适应决策&#xff1a;根…

Maven 快照(SNAPSHOT)

Maven 快照(SNAPSHOT) 引言 Maven 快照(SNAPSHOT)是 Maven 中的一个重要概念,主要用于版本管理。它允许开发者在构建过程中使用尚未发布的版本。本文将详细介绍 Maven 快照的原理、用途以及如何在项目中配置和使用快照。 Maven 快照原理 Maven 快照是版本号的一部分,…

2025-0803学习记录20——毕业论文快速整理成小论文

本科毕业论文写好啦&#xff0c;但是C导要我整理成一篇约8000字的小论文&#xff0c;准备投稿。毕业论文到投稿的小论文&#xff0c;这其实是从“全景展示”到“聚焦精炼”的过程。目前我已经有完整的大论文&#xff08;约6万字&#xff09;&#xff0c;材料是充足的&#xff0…

VUE2 学习笔记16 插槽、Vuex

插槽在编写组件时&#xff0c;可能存在这种情况&#xff0c;页面需要显示不同的内容&#xff0c;但是页面结构是类似的&#xff0c;在这种情况下&#xff0c;虽然也可以使用传参来进行&#xff0c;但传参时&#xff0c;还需要编写props等逻辑&#xff0c;略显重复&#xff0c;而…

IntelliJ IDEA开发编辑器摸鱼看股票数据

在IDEA的插件市场中心搜索stock&#xff0c;检索结果里面的插件&#xff0c;点击安装即可安装后的效果

Linux Deepin深度操作系统应用商店加载失败,安装星火应用商店

Linux Deepin国产操作系统优点 Deepin&#xff08;原名Linux Deepin&#xff09;是一款由中国团队开发的Linux发行版&#xff0c;基于Debian stable分支&#xff0c;以美观易用的界面和本土化体验著称。以下是其核心优点总结&#xff1a; 1. 极致美观的界面设计 Deepin Deskt…

postgresql创建只读用户并授权

postgresql创建只读用户并授权 CREATE USER yk WITH ENCRYPTED PASSWORD <your_password>;GRANT USAGE ON SCHEMA public to yk; GRANT SELECT ON ALL TABLES IN SCHEMA public TO yk;根据以上创建的用户&#xff0c;出现一个问题&#xff0c;对新建的表没有查询权限&am…

pytest vs unittest: 区别与优缺点比较

主要区别特性pytestunittest起源第三方库Python标准库语法风格更简洁的Pythonic语法基于Java风格的JUnit测试发现自动发现测试需要继承TestCase类断言方式使用Python原生assert使用各种assert方法(assertEqual等)夹具系统强大的fixture系统简单的setUp/tearDown方法参数化测试内…

Boost.Asio学习(5):c++的协程

协程是什么&#xff1f;协程就是可以“暂停”和“继续”的函数&#xff0c;像在函数里打个断点&#xff0c;然后以后可以从断点继续运行&#xff0c;而不是重新开始。线程 vs 协程&#xff1a;类比想象你在写小说&#xff1a;线程&#xff1a;你开了 3 个作者&#xff08;线程&…

Linux 中,命令查看系统版本和内核信息

在 Linux 中&#xff0c;可以通过以下命令查看系统版本和内核信息&#xff1a;1. 查看内核版本uname -a或精简显示&#xff1a;uname -r # 只显示内核版本示例输出&#xff1a;Linux ubuntu 5.4.0-135-generic #152-Ubuntu SMP Tue Nov 15 08:12:21 UTC 2022 x86_64 x86_64 x8…

数据结构总纲以及单向链表详解:

以下是基于笔记更详细的知识梳理&#xff0c;从概念到细节逐层拆解&#xff0c;帮你吃透数据结构核心要点&#xff1a; 数据结构部分的重点内容&#xff1a;一、数据结构基础框架 &#xff08;一&#xff09;逻辑结构&#xff08;关注元素间“逻辑关系”&#xff09; 笔记里提到…

模型学习系列之参数

背景 “GLM-4.5拥有 3550 亿总参数量&#xff0c;其中 320 亿活跃参数&#xff1b;GLM-4.5-Air 采用更紧凑的设计&#xff0c;拥有 1060 亿总参数量&#xff0c;其中 120 亿活跃参数。” 定义与关系 总参数量&#xff1a;模型中所有可训练参数的总和&#xff08;包括嵌入层、注…

[创业之路-535]:软件需要原型验证、产品需要原型验证、商业模式也需要原型验证

原型验证在软件、产品开发以及商业模式探索中均扮演着至关重要的角色&#xff0c;它通过低成本、快速迭代的方式&#xff0c;帮助团队验证核心假设、降低风险并优化方案。以下是针对这三个领域的具体分析&#xff1a;一、软件原型验证&#xff1a;从概念到可交互的模型核心目的…

sublime text2配置

sublime text2配置背景配置其他背景 之前下载了就把它当记事本在使用。但是&#xff0c;在使用过程中&#xff0c;有些场景很痛苦。如果说找一个字符串中的某一部分&#xff0c;虽然它通过了这个功能&#xff0c;但是不够明显&#xff0c;看瞎了。。。 配置 下面是我改的一些选…