思考: 从曲线中提取出近似直线的一段
这个问题也是别人问我的, 我思考了一些时间, 希望抛砖引玉, 得到更好的方法.
问题是这样的: 有一些离散的点, 在坐标系中把它们拟合成一条曲线, 其中有一段看上去很像是直线, 现在要求出这段"直线"的起始坐标和结束坐标, 并把这条线的方程求出来. 如下图:
从图中可以用肉眼看出标注的那一段近似一条直线, 问题是如果通过程序求出来.
我的想法
我的想法是这样的, 假设这条线是存在的, 那么这条线里的点, 它们相邻两两之间Y之差应该是"相似"的, 具体相似要什么程度, 需要有一个"允许误差值". 在这个允许误差值下, 尽可能多的把这条曲线上连续的点拉拢到一条直线上, 最后根据这些点用最小二乘法就可以求得直线方程了.
获取这些连续的点:
static List<Point> GetSequential(List<Point> points, double allowLapse) { var resultList = new List<List<Point>>(); var currentResult = new List<Point>(); currentResult.Add(points[0]); for (int i = 1; i < points.Count; i++) { //如果这个点与前一个点的高度差在一个合理的范围内 if ((points[i].Y - points[i - 1].Y) < allowLapse) currentResult.Add(points[i]); else { resultList.Add(currentResult); currentResult = new List<Point>(); currentResult.Add(points[i]); } } resultList.Add(currentResult); //从集合中选出点的数量最多的那组数据 List<Point> result = resultList.OrderByDescending(list => list.Count).First(); return result; }
Point类的定义, 很简单:
internal class Point { public double X { get; private set; } public double Y { get; private set; } public Point(double x, double y) { X = x; Y = y; } }
根据一些点, 用最小二乘法求出直线:
static void LeastSquare(List<Point> points) { double avgX = points.Average(point => point.X); double avgY = points.Average(point => point.Y); double numerator = points.Sum(point => (point.X - avgX) * (point.Y - avgY)); double denominator = points.Sum(point => (point.X - avgX) * (point.X - avgX)); double K = numerator / denominator; //斜率 double X0 = avgY - K * avgX; //为了方便测试, 我直接在这里输出结果 Console.WriteLine("Y={0} + {1}X\nStart ({2}, {3})\tEnd ({4}, {5})\nPoints Count: {6}", X0, K, points.First().X, points.First().Y, points.Last().X, points.Last().Y, points.Count); }
测试结果
允许Y差值 | 方程 | 起点 | 终点 | 包含点数 |
---|---|---|---|---|
0.005 | Y=0.27 + 0.45X | (0.13, 0.328201) | (0.15, 0.337173) | 3 |
0.010 | Y=0.25 + 0.60X | (0.12, 0.318606) | (0.17, 0.350823) | 6 |
0.015 | Y=0.48 + 1.08X | (0.54, 1.060612) | (0.77, 1.318348) | 24 |
0.020 | Y=0.23 + 1.46X | (0.28, 0.611221) | (0.77, 1.318348) | 50 |
0.025 | Y=0.22 + 1.48X | (0.26, 0.56426) | (0.77, 1.318348) | 52 |
0.030 | Y=0.13 + 1.64X | (0.09, 0.255596) | (0.77, 1.318348) | 69 |
0.035 | Y=0.13 + 1.64X | (0.08, 0.222174) | (0.77, 1.318348) | 70 |
0.040 | Y=0.12 + 1.65X | (0.07, 0.183729) | (0.77, 1.318348) | 71 |
对比文中开头的插图, 可以看出"允许Y差值"在0.020和0.025, 得到的数据还是比较靠谱的. 但这样的做法还有一点问题, 如果每个点都比前一个点高一点(在允许Y差值范围内), 等到点积累多了, 从整体看, 这些点组成的线就是一条向上的抛物线了, 那么这个算法就存在问题.
如果您有什么想法, 欢迎交流, 附上图中数据下载(数据只含Y坐标值, X从0.0开始, 相邻两点X间隔0.01).
作者:Create Chen
出处:http://technology.cnblogs.com
说明:文章为作者平时里的思考和练习,可能有不当之处,请博客园的园友们多提宝贵意见。
本作品采用知识共享署名-非商业性使用-相同方式共享 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· SQL Server 2025 AI相关能力初探
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库