检测圆形(非精确圆)路径算法?

时间:2015-01-30 13:26:49

标签: java algorithm math geometry

我收到一条路径 - 来自touchevent的x,y坐标列表。如何检测此路径形成圆形路径(不是完整或精确的圆)?是否有任何算法或方法来检测这个?

2 个答案:

答案 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 circlepointsList中的每个子集:

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%的数据位于区间内。 您可以检查每个圆圈数据是否位于内部,如果不存在,则不是圆圈。请记住,我们为圆​​心的xy位置以及半径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,但我认为你不应该很难转换它。