我收到一条路径 - 来自touchevent的x,y坐标列表。如何检测此路径形成圆形路径(不是完整或精确的圆)?是否有任何算法或方法来检测这个?
答案 0 :(得分:3)
以下是一篇论文,其中总结了各种方法,以便将圆圈拟合到数据中:http://www.cs.bsu.edu/homepages/kerryj/kjones/circles.pdf
答案 1 :(得分:2)
我会做这样的事情:
假设我有一个半圆点:
val angles180 = (0 to 180 by 45).map(_ / 180.0 * Pi)
val points = angles180.map { a => (math.cos(a), math.sin(a)) }
我可以将子集的组合与3个元素结合起来:
val pointsList = points.combinations(3).toList
locate the center and radius of a circle given only three points on the circle,pointsList
中的每个子集:
val circles = for {
p <- pointsList
} yield calculateCircle(p)
其中:
case class Circle(x: Double, y: Double, r: Double)
def calculateCircle(points: Seq[(Double, Double)]): Circle = {
val Seq((x1, y1), (x2, y2), (x3, y3)) = points
val mr = (y2 - y1) / (x2 - x1)
val mt = (y3 - y2) / (x3 - x2)
val x0 = (mr * mt * (y3 - y1) + mr * (x2 + x3) - mt * (x1 + x2)) / (2 * (mr - mt))
val y0 = -1.0 / mr * (x0 - (x1 + x2) / 2) + (y1 + y2) / 2
val r = math.sqrt((x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0))
Circle(x0, y0, r)
}
您应该检查无效的圈子(可能这些点是共线的,并给出无效的结果):
def isInvalid(circle: Circle) =
(circle.x.isNaN || circle.y.isNaN || circle.r.isNaN)
val validCircles = circles.filterNot(isInvalid)
虽然有效,但这些圈子可以(将)拥有不同的数据。您应该看看计算的圆圈是否彼此相似。
这样做的一种方法是查看他们的数据是否在(m - x . s, m + x . s)
的区间内,其中m
是平均值,s
是标准偏差。对于x == 2.58
,99%的数据位于区间内。
您可以检查每个圆圈数据是否位于内部,如果不存在,则不是圆圈。请记住,我们为圆心的x
和y
位置以及半径r
执行此操作,只有这三个都很好才有效。
def looksLikeACircle(circles: Seq[Circle]) = {
val resultsLists = circles.map { c => List(c.x, c.y, c.r) }.transpose
val cis = resultsLists.map(interval99)
val good = resultsLists.zip(cis).map { case (values, ci) => withinInterval(values, ci) }
val allGood = good.reduceLeft { (acc, v) => acc && v }
allGood
}
val allGood = looksLikeACircle(validCircles)
其中:
def mean(values: Seq[Double]) = values.sum / values.size
def standardDeviation(values: Seq[Double]) = {
val m = mean(values)
math.sqrt(values.map { v => (v - m) * (v - m) }.sum / (values.size - 1))
}
def interval99(values: Seq[Double]) = {
val m = mean(values)
val d = (2.58 * standardDeviation(values))
(m - d, m + d)
}
def withinInterval(values: Seq[Double], ci: (Double, Double)) =
values.forall { v => ci._1 <= v && v <= ci._2 }
嗯,这看起来像是一个圆圈,但也许只是数学。给定足够大的半径,真圆可以在窗口中显示为直线。问题是:用户是否可以在窗口中创建带有这些点的圆圈?
您可以检查平均圆是否在窗口的边界内:
case class Window(x: Double, y: Double, w: Double, h: Double)
def calculateMeanCircle(circles: Seq[Circle]) = {
val resultsLists = circles.map { c => List(c.x, c.y, c.r) }.transpose
val Seq(xm, ym, rm) = resultsLists.map(mean)
Circle(xm, ym, rm)
}
def isCircleFit(circle: Circle, window: Window) = {
def isWithinBounds(value: Double, bounds: (Double, Double)) = {
bounds._1 <= value && value <= bounds._2
}
val (xMin, xMax) = (circle.x - circle.r, circle.x + circle.r)
val (yMin, yMax) = (circle.y - circle.r, circle.y + circle.r)
val horizontalBounds = (window.x, window.x + window.w)
val verticalBounds = (window.y, window.y + window.h)
val isInside =
isWithinBounds(xMin, horizontalBounds) && isWithinBounds(xMax, horizontalBounds) &&
isWithinBounds(yMin, verticalBounds) && isWithinBounds(yMax, verticalBounds)
isInside
}
val meanCircle = calculateMeanCircle(validCircles)
最后,对于这篇长篇文章开头的points
(希望你还在这里)和window
:
val window = Window(0, 0, 600, 800)
println("Looks like a circle? " + allGood)
println(meanCircle)
println(window)
println("Is mean circle fit? " + isCircleFit(meanCircle, window))
我们得到:
Looks like a circle? true
Circle(-8.088567489053774E-18,4.4408920985006264E-17,1.0)
Window(0.0,0.0,600.0,800.0)
Is mean circle fit? false
这个窗口内的这些点只能绘制一个象限,所以不合适。
如果:
val points5 = angles180.map { a => (window.w / 2 + math.cos(a), window.h / 2 + math.sin(a)) }
现在好了(它在窗口的中央):
Looks like a circle? true
Circle(300.0,400.0,1.0000000000000144)
Window(0.0,0.0,600.0,800.0)
Is mean circle fit? true
这些点几乎形成一个中心位于(0, 0)
和半径r == 1
的圆圈怎么样:
val points = Seq(
( 1.0, 0.0),
( 0.0, 1.1),
(-0.9, 0.0),
(-0.1, -1.0)
)
嗯,它看起来像一个,但它当然不适合(但你明白了这一点):
Looks like a circle? true
Circle(0.05805243445692886,0.07674497786857344,1.0288200278337032)
Window(0.0,0.0,600.0,800.0)
Is mean circle fit? false
你需要彻底测试它,因为我没有,也许可以稍微调整一下(或者很多),但是我希望你可以从这里开始。
此外,我认为其中一个标记为java
,但我认为你不应该很难转换它。