3.多边形曲线简化之Douglas-Peucker算法

Douglas-Peucker算法

        根据具体情况,减少表示多边形曲线的点,可以减少内存,同时对曲线进行操作的时间。这里介绍经典的Douglas–Peucker算法,相关文献:Algorithms for the reduction of the number of points
required to represent a digitized line or its caricature(下载链接)。看完论文,算法的基本思想总结如下:
将曲线的第一个点(作为anchor)和最后一个点(作为floater)连成一条直线,计算所有中间点到该直线的最大距离Dmax。
如果Dmax<D(预先设置的阈值),则该段只保留第一个和最后一个点,该段处理结束;反之,用Dmax对应的中间点将曲线分为两段,对这两段重复第一步和第二步。
直到递归结束后,将保留的点依次连接,组成简化后的曲线。
在这里插入图片描述
        我们常说的Douglas-Peucker算法就是论文中的Method 2,论文中还介绍了Method1。Method 2的点数会比 Method 1多一点,同时运行时间时间只有Method 1的5%左右,所以Method 1很少用。下面将简单介绍下论文中Method 1。

  1. 将曲线的第一个点(作为anchor)和最后一个点(作为floater)连成一条直线,计算中间所有点到该直线的最大距离Dmax。
  2. 如果Dmax<D(预先设置的阈值),则只保留第一个和最后一个点,中间所有点全部删除,该曲线处理结束;如果Dmax>=D,则将用Dmax对应的点做新的floater。
  3. 重复第一步和第二步,floater慢慢移向anchor点,直到最大距离Dmax满足条件。这时将floater点作为新的anchor,又将原始曲线的最后一个点作为floater,重复第一步、第二步和第三步。
  4. 最后将作过anchor的点组成简化后的曲线。

示例演示

实现一个对二维曲线的简化。

#include <iostream>
#include <cmath>
#include <utility>
#include <vector>
#include <stdexcept>
using namespace std;

typedef std::pair<double, double> Point;

double PerpendicularDistance(const Point &pt, const Point &lineStart, const Point &lineEnd)
{
	double dx = lineEnd.first - lineStart.first;
	double dy = lineEnd.second - lineStart.second;

	//Normalize
	double mag = pow(pow(dx, 2.0) + pow(dy, 2.0), 0.5);
	if (mag > 0.0)
	{
		dx /= mag; dy /= mag;
	}

	double pvx = pt.first - lineStart.first;
	double pvy = pt.second - lineStart.second;

	//Get dot product (project pv onto normalized direction)
	double pvdot = dx * pvx + dy * pvy;

	//Scale line direction vector
	double dsx = pvdot * dx;
	double dsy = pvdot * dy;

	//Subtract this from pv
	double ax = pvx - dsx;
	double ay = pvy - dsy;

	return pow(pow(ax, 2.0) + pow(ay, 2.0), 0.5);
}

void RamerDouglasPeucker(const vector<Point> &pointList, double epsilon, vector<Point> &out)
{
	if (pointList.size() < 2)
		throw invalid_argument("Not enough points to simplify");

	// Find the point with the maximum distance from line between start and end
	double dmax = 0.0;
	size_t index = 0;
	size_t end = pointList.size() - 1;
	for (size_t i = 1; i < end; i++)
	{
		double d = PerpendicularDistance(pointList[i], pointList[0], pointList[end]);
		if (d > dmax)
		{
			index = i;
			dmax = d;
		}
	}

	// If max distance is greater than epsilon, recursively simplify
	if (dmax > epsilon)
	{
		// Recursive call
		vector<Point> recResults1;
		vector<Point> recResults2;
		vector<Point> firstLine(pointList.begin(), pointList.begin() + index + 1);
		vector<Point> lastLine(pointList.begin() + index, pointList.end());
		RamerDouglasPeucker(firstLine, epsilon, recResults1);
		RamerDouglasPeucker(lastLine, epsilon, recResults2);

		// Build the result list
		out.assign(recResults1.begin(), recResults1.end() - 1);
		out.insert(out.end(), recResults2.begin(), recResults2.end());
		if (out.size() < 2)
			throw runtime_error("Problem assembling output");
	}
	else
	{
		//Just return start and end points
		out.clear();
		out.push_back(pointList[0]);
		out.push_back(pointList[end]);
	}
}

int main()
{
	vector<Point> pointList;
	vector<Point> pointListOut;

	pointList.push_back(Point(0.0, 0.0));
	pointList.push_back(Point(1.0, 0.1));
	pointList.push_back(Point(2.0, -0.1));
	pointList.push_back(Point(3.0, 5.0));
	pointList.push_back(Point(4.0, 6.0));
	pointList.push_back(Point(5.0, 7.0));
	pointList.push_back(Point(6.0, 8.1));
	pointList.push_back(Point(7.0, 9.0));
	pointList.push_back(Point(8.0, 9.0));
	pointList.push_back(Point(9.0, 9.0));

	RamerDouglasPeucker(pointList, 1.0, pointListOut);

	cout << "result" << endl;
	for (size_t i = 0; i < pointListOut.size(); i++)
	{
		cout << pointListOut[i].first << "," << pointListOut[i].second << endl;
	}
	system("pause");
	return 0;
}

运行结果

在这里插入图片描述

代码段一是使用matplotlib库可视化一个三维Convex Hull,代码段二是使用Douglas-Peucker算法对点集进行多边形折线简化。这两段代码需要的依赖库是numpy、matplotlib和scipy.spatial中的distance模块。 合并这两段代码需要在代码段一中添加导入ConvexHull类的代码,并将ConvexHull(points)替换为douglas_peucker(points, tolerance)。 合并后的代码如下: ```python import numpy as np from mpl_toolkits.mplot3d import Axes3D import matplotlib.pyplot as plt from scipy.spatial import distance def douglas_peucker(points, tolerance): """ 使用Douglas-Peucker算法多边形折线进行简化 :param points: 多边形点集,numpy数组,每行表示一个三维点 :param tolerance: 简化误差容忍度 :return: 简化后的多边形点集 """ if len(points) < 3: return points # 找到距离最远的点作为分割点 dmax = 0 index = 0 end = len(points) - 1 for i in range(1, end): d = distance.point_line_distance(points[i], points[0], points[end]) if d > dmax: index = i dmax = d # 如果距离最远点小于容忍度,则直接返回两个端点 if dmax < tolerance: return np.array([points[0], points[end]]) # 递归地对两个子区间进行简化 left_points = douglas_peucker(points[:index+1], tolerance) right_points = douglas_peucker(points[index:], tolerance) # 返回合并后的结果 return np.vstack((left_points[:-1], right_points)) # 生成随机三维数据点 np.random.seed(0) points = np.random.rand(8, 3) # 计算多边形折线简化后的点集 tolerance = 0.1 simplified_points = douglas_peucker(points, tolerance) # 计算Convex Hull hull = ConvexHull(simplified_points) # 可视化Convex Hull fig = plt.figure() ax = fig.add_subplot(111, projection="3d") for s in hull.simplices: ax.plot(simplified_points[s, 0], simplified_points[s, 2], simplified_points[s, 1], "b-") plt.show() ``` 这段代码的运行结果是生成一个简化后的Convex Hull的三维可视化图形。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值