曲线平滑算法

由于项目开发需要对等值线进行平滑处理,所以研究了线条的平滑算法,经研究查阅资料,可以使用三次B样条曲线方程对线条进行平滑处理,而平滑处理可分为近似拟合和插值拟合两种,两种拟合处理各有其优缺点,以下会做说明,可根据实际业务需要进行选取。本次项目开发最终选用了近似拟合算法处理。

一、三次B样条曲线方程

B样条曲线的总方程为:

其中Pi是控制曲线的特征点,Fi,k(t)则是K阶B样条基函数。

三次B样条曲线方程中基函数为:

其中\binom{m}{k+1}表示阶乘,化成看的明白的式子就是:

将基函数带入总方程可换算出三次B样条曲线方程:

基于上述的三次B样条曲线方程可对曲线进行平滑处理。

二、三次B样条曲线近似拟合

近似拟合,平滑后的线条不一定过原始特征点,平滑处理的核心算法如下,算法中对闭合曲线和非闭合曲线分别做了处理:

// 闭合曲线
if (line[0] == line[len - 2] && line[1] == line[len - 1]) {
	for (var n = 0; n < pntLen; n++) {
		if (n <= pntLen - 4) {
			for (var t = 0.0; t <= 1.0; t += 0.1) {
				var a1 =  Math.pow((1 - t), 3) / 6;
				var a2 =  (3 * Math.pow(t, 3) - 6 * Math.pow(t, 2) + 4) / 6;
				var a3 = (-3 * Math.pow(t, 3) + 3 * Math.pow(t, 2) + 3 * t + 1) / 6;
				var a4 =  Math.pow(t, 3) / 6;
				
				var x = a1*points[n].x + a2*points[n + 1].x + a3*points[n + 2].x + a4*points[n + 3].x;
				var y = a1*points[n].y + a2*points[n + 1].y + a3*points[n + 2].y + a4*points[n + 3].y;
				newPnts.push({x: x, y: y});
			}
		} else if (n == pntLen - 3) {
			for (var t = 0.0; t <= 1.0; t += 0.1) {
				var a1 =  Math.pow((1 - t), 3) / 6;
				var a2 =  (3 * Math.pow(t, 3) - 6 * Math.pow(t, 2) + 4) / 6;
				var a3 = (-3 * Math.pow(t, 3) + 3 * Math.pow(t, 2) + 3 * t + 1) / 6;
				var a4 =  Math.pow(t, 3) / 6;
				
				var x = a1*points[n].x + a2*points[n + 1].x + a3*points[n + 2].x + a4*points[0].x;
				var y = a1*points[n].y + a2*points[n + 1].y + a3*points[n + 2].y + a4*points[0].y;
				newPnts.push({x: x, y: y});
			}
		} else if (n == pntLen - 2) {
			for (var t = 0.0; t <= 1.0; t += 0.1) {
				var a1 =  Math.pow((1 - t), 3) / 6;
				var a2 =  (3 * Math.pow(t, 3) - 6 * Math.pow(t, 2) + 4) / 6;
				var a3 = (-3 * Math.pow(t, 3) + 3 * Math.pow(t, 2) + 3 * t + 1) / 6;
				var a4 =  Math.pow(t, 3) / 6;
				
				var x = a1*points[n].x + a2*points[n + 1].x + a3*points[0].x + a4*points[1].x;
				var y = a1*points[n].y + a2*points[n + 1].y + a3*points[0].y + a4*points[1].y;
				newPnts.push({x: x, y: y});
			}
		} else if (n == pntLen - 1) {
			for (var t = 0.0; t <= 1.0; t += 0.1) {
				var a1 =  Math.pow((1 - t), 3) / 6;
				var a2 =  (3 * Math.pow(t, 3) - 6 * Math.pow(t, 2) + 4) / 6;
				var a3 = (-3 * Math.pow(t, 3) + 3 * Math.pow(t, 2) + 3 * t + 1) / 6;
				var a4 =  Math.pow(t, 3) / 6;
				
				var x = a1*points[n].x + a2*points[0].x + a3*points[1].x + a4*points[2].x;
				var y = a1*points[n].y + a2*points[0].y + a3*points[1].y + a4*points[2].y;
				newPnts.push({x: x, y: y});
			}
		}
	}
	// 不闭合
} else {
	newPnts.push({x: points[0].x, y: points[0].y});
	for (var n = 0; n < pntLen; n++) {
		if (n <= pntLen - 4) {
			for (var t = 0.0; t <= 1.0; t += 0.1) {
				var a1 =  Math.pow((1 - t), 3) / 6;
				var a2 =  (3 * Math.pow(t, 3) - 6 * Math.pow(t, 2) + 4) / 6;
				var a3 = (-3 * Math.pow(t, 3) + 3 * Math.pow(t, 2) + 3 * t + 1) / 6;
				var a4 =  Math.pow(t, 3) / 6;
				
				var x = a1*points[n].x + a2*points[n + 1].x + a3*points[n + 2].x + a4*points[n + 3].x;
				var y = a1*points[n].y + a2*points[n + 1].y + a3*points[n + 2].y + a4*points[n + 3].y;
				newPnts.push({x: x, y: y});
			}
		}
	}
	newPnts.push({x: points[pntLen - 1].x, y: points[pntLen - 1].y});
}

三次B样条曲线近似拟合处理的效果图如下,其中红色为原始未平滑处理的曲线,而蓝色为平滑后的曲线。可以看出近似拟合后的曲线平滑的效果较为满意,但是缺点是曲线不过原始特征点 。

三、三次B样条曲线插值拟合 

插值拟合,平滑后的曲线过原始特征点,平滑的核心算法如下:

function getCtrlPoint(pointsArr, index, scaleA, scaleB) {
	if (!scaleA || !scaleB) {
		scaleA = 0.15;
		scaleB = 0.15;
	}

	// 处理两种极端情形
	if (index < 1) {
		var pAx = pointsArr[0].x + (pointsArr[1].x - pointsArr[0].x) * scaleA;
		var pAy = pointsArr[0].y + (pointsArr[1].y - pointsArr[0].y) * scaleA;
	} else {
		var pAx = pointsArr[index].x + (pointsArr[index + 1].x - pointsArr[index - 1].x) * scaleA;
		var pAy = pointsArr[index].y + (pointsArr[index + 1].y - pointsArr[index - 1].y) * scaleA;
	}

	if (index > pointsArr.length - 3) {
		var last = pointsArr.length - 1
		var pBx = pointsArr[last].x - (pointsArr[last].x - pointsArr[last - 1].x) * scaleB;
		var pBy = pointsArr[last].y - (pointsArr[last].y - pointsArr[last - 1].y) * scaleB;
	} else {
		var pBx = pointsArr[index + 1].x - (pointsArr[index + 2].x - pointsArr[index].x) * scaleB;
		var pBy = pointsArr[index + 1].y - (pointsArr[index + 2].y - pointsArr[index].y) * scaleB;
	}

	return {
		pA: {x: pAx, y: pAy},
		pB: {x: pBx, y: pBy}
	}
}

 三次B样条曲线插值拟合处理的效果图如下,其中红色为原始未平滑处理的曲线,而蓝色为平滑后的曲线。可以看出插值拟合后的曲线平滑的效果不尽如人意,有出现线条打结的情况,但是优点是曲线过原始特征点 。

参考资料:https://blog.csdn.net/liumangmao1314/article/details/54588155 

  • 20
    点赞
  • 125
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
贝塞尔曲线是一种平滑曲线,通过控制点来定义曲线的形状。在Java中,可以通过使用Java 2D API的Path2D类来绘制和操作贝塞尔曲线。 下面是一个简单的贝塞尔曲线平滑算法的实现: ```java import java.awt.geom.Path2D; public class BezierCurve { public static Path2D smoothCurve(double[] x, double[] y) { Path2D path = new Path2D.Double(); path.moveTo(x[0], y[0]); // 为了形成闭合曲线,最后一个点需要在两个端点之间插值 double[] x0 = new double[x.length + 2]; double[] y0 = new double[y.length + 2]; System.arraycopy(x, 0, x0, 1, x.length); System.arraycopy(y, 0, y0, 1, y.length); x0[0] = x[0] * 2 - x[1]; y0[0] = y[0] * 2 - y[1]; x0[x0.length - 1] = x[x.length - 1] * 2 - x[x.length - 2]; y0[y0.length - 1] = y[y.length - 1] * 2 - y[y.length - 2]; for (int i = 1; i < x0.length - 2; i++) { double x1 = x0[i]; double y1 = y0[i]; double x2 = x0[i + 1]; double y2 = y0[i + 1]; double xc = (x1 + x2) / 2; double yc = (y1 + y2) / 2; path.quadTo(x1, y1, xc, yc); } // 为了能够在Path2D上使用close方法,需要添加一个结束点 path.lineTo(x[x.length - 1], y[y.length - 1]); path.closePath(); return path; } } ``` 这个算法使用了二次贝塞尔曲线(Path2D.quadTo)来连接所有的点。在连接相邻的点之间,算法会通过计算中点来计算控制点。为了形成闭合曲线,最后一个点需要在两个端点之间插值。最后,为了能够在Path2D上使用close方法,需要添加一个结束点。 使用这个算法可以将一组点平滑地连接成一个贝塞尔曲线: ```java import javax.swing.*; import java.awt.*; public class BezierCurveTest extends JPanel { private static final int WIDTH = 400; private static final int HEIGHT = 400; private static final double[] X = { 50, 100, 150, 200, 250, 300, 350 }; private static final double[] Y = { 200, 250, 150, 300, 100, 350, 50 }; public BezierCurveTest() { setPreferredSize(new Dimension(WIDTH, HEIGHT)); } @Override protected void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2d = (Graphics2D) g.create(); // 绘制原始点 g2d.setColor(Color.BLUE); for (int i = 0; i < X.length; i++) { g2d.fillOval((int) X[i] - 3, (int) Y[i] - 3, 6, 6); } // 绘制贝塞尔曲线 g2d.setColor(Color.RED); Path2D path = BezierCurve.smoothCurve(X, Y); g2d.draw(path); g2d.dispose(); } public static void main(String[] args) { JFrame frame = new JFrame("Bezier Curve Test"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.getContentPane().add(new BezierCurveTest()); frame.pack(); frame.setLocationRelativeTo(null); frame.setVisible(true); } } ``` 这个示例程序中,我们使用了一组随机生成的点来绘制贝塞尔曲线。可以看到,通过使用贝塞尔曲线平滑算法,我们可以将这些点连接成一个平滑曲线

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值