如何量化画线的直线度?


37

我正在开发一款游戏,要求玩家在Android设备的屏幕上从点A(x1,y1)到另一点B(x2,y2)画一条线。

我想找出该绘图与一条直线的拟合程度。例如,如果结果为90%,则表示工程图几乎完全适合线条。如果玩家从A到B绘制一条曲线,则得分会较低。

终点未知。我怎样才能做到这一点?


1
您事先知道您的两个终点是什么吗?还是确定在用户停止触摸屏幕的那一刻?
Vaillancourt

抱歉,如果您不清楚我的描述。好吧,起点A(x,y)是第一次触摸,终点B(x,y)是我们从您所说的触摸屏上释放时。
user3637362 2015年

关于匹配玩家画出的字母,我们有一个相关的问题。
Anko 2015年

3
请不要在以后发布图片作为源代码。
乔许

1
@ user3637362我了解您已经开始使用,j=1可以与进行比较,但是何时或未将职位添加到,随后不将其包含在中。在所有情况下都将存在此错误,但是当该行的分段很少时,该错误将更加明显。touchList[j]touchList[j-1]touch.phase == TouchPhase.Begantouch.phase == TouchPhase.EndedtouchListsumLength
凯利·托马斯

Answers:


52

一条完美的直线也将是最短的直线,总长度为sqrt((x1-x2)² + (y1-y2)²)。较粗糙的线条将是不太理想的连接,因此不可避免地会更长。

当用户绘制路径的所有单个点并总结它们之间的距离时,可以将总长度与理想长度进行比较。总长度除以理想长度越小,线条越好。

这是一个可视化。当黑点是手势的终点,而蓝点是您在手势过程中测量的点时,您将计算并累加绿线的长度,然后将其除以红线的长度:

在此处输入图片说明

分数或正弦指数为1将是完美的,更高的分数或更低的完美度,低于1的任何分数都是错误。如果您希望分数以百分比表示,请将100%除以该数字。


34
这种方法存在一个问题,即等长的折线不一样地“直线”。一条绕直线以低偏差(但多次)摆动的直线比等长的直线“直”,该等长的直线偏离到单个点然后返回。
Dancrumb

我无法对@Dancrumbs的注释进行+1的充分解释-这是此方法的一个关键限制,就好像用户画一条直线时,他们会稍微摆动一样,所以这就像一个普通的用例。
T. Kiley 2015年

@Dancrumb只是考虑到线的平均距离,或者考虑任何点到线的“最大距离”。然后,您可以将算法权重移向偏差幅度较小的更多摆动线,并远离偏离预期路径的线。
Superdoggy 2015年

2
@Dancrumb在我看来,这可能最终会为OP的用例带来好处。手绘线当然会有小的偏差。这种方法可能实际上可以减轻这些预期差异的影响。

2
@ user3637362您的代码中有错误。可能的解释是,您忘记考虑起点和第一个点之间或终点和最后一个点之间的距离,但是如果不查看代码,就无法分辨出错误所在。
菲利普

31

这可能也不是实现此目标的最佳方法,但是我建议在Dancrumb提到的情况下,RMSD(均方根偏差)可能比仅使用距离方法更好(请参见下面的前两行)。

RMSD = sqrt(mean(deviation^2))

注意:

  • 绝对偏差的总和(类积分)可能会更好,因为它不能将负误差平均化为正误差。(=sum(abs(deviation))
  • 如果有一种方法可以创建比直线垂直线更短的距离,则可能必须搜索到直线的最短距离。

画画

(请原谅我的绘画质量低下)

如您所见,您必须

  1. 找到与您的线正交的矢量点积等于0)。
    如果您的路线指向(1, 3) 您想要的位置(3, -1)(每个都穿过原点)
  2. 测量 h从理想线到用户线的平行于该矢量的距离
  3. 计算 RMSD或绝对差之和。

乔尔·博斯维尔德(Joel Bosveld)的答案表明了一个有趣的例子:一条几乎完美的直线,起点和终点都有拐角。如果使用者自由地画线,那确实是一个问题。不过,我认为这种方法可以解决这种情况。实际上,可以将RMSD绝对积分作为最小值来进行拟合。起始值可以是起点和终点。由于长度无关紧要,因此优化是否移动点以使理想线更远或更短(必须将高度计算为该niveau)也无关紧要。
gr4nt3d 2015年

1
似乎没有涵盖的另一种情况:说每个测量点都在x轴上,但是该线将方向反转了几次。这将返回0错误
戴夫mankoff

23

现有答案没有考虑端点是任意的(而不是给定的)。因此,在测量曲线的直线度时,使用端点(例如,计算预期的长度,角度,位置)是没有意义的。一个简单的例子是两端弯曲的直线。如果我们使用距曲线的距离和端点之间的直线的距离进行测量,这将非常大,因为绘制的直线与端点之间的直线偏移。

我们如何知道曲线的笔直度?假设曲线足够平滑,我们想知道曲线的切线平均变化了多少。对于一条线,它将为​​零(因为切线是常数)。

如果我们将时间t的位置设为(x(t),y(t)),则切线为(Dx(t),Dy(t)),其中Dx(t)是时间t处x的导数(该站点似乎缺少TeX支持)。如果未通过弧长对曲线进行参数化,则通过除以||(Dx(t),Dy(t))||进行归一化。因此,我们在时间t处具有与曲线相切的单位矢量(或角度)。因此,角度为a(t)=(Dx(t),Dy(t))/ ||(Dx(t),Dy(t))||

然后,我们对沿曲线积分的|| Da(t)|| ^ 2感兴趣。

鉴于我们最有可能具有离散的数据点而不是曲线,因此必须使用有限差分来近似导数。因此,Da(t)变为(a(t+h)-a(t))/h。并且,a(t)变为((x(t+h)-x(t))/h,(y(t+h)-y(t))/h)/||((x(t+h)-x(t))/h,(y(t+h)-y(t))/h)||。然后,我们通过对h||Da(t)||^2所有数据点求和并可能通过曲线的长度进行归一化来获得S。我们很可能使用h=1,但这实际上只是一个任意比例因子。

重申一下,对于一条线,S将为零,并且偏离一条线时,S越大。要转换为所需的格式,请使用1/(1+S)。假定比例尺是任意的,则可以将S乘以某个正数(或以其他方式对其进行变换,例如使用bS ^ c代替S)来调整某些曲线的笔直度。


2
这是直线度最明智的定义。
马克·托马斯

1
到目前为止,这是最明智的答案,我相信其他人会感到非常沮丧。不幸的是,该解决方案的呈现形式比其他形式更加晦涩难懂,但我建议OP仍然存在。
Dan Sheppard 2015年

通常我也认为这个答案确实是最好的。尽管有一个问题困扰我:如果生产线不够“光滑”会怎样?例如,如果您有两个角度为90°的完美直线段。我错了吗?或者与真正平滑的直线相比,结果会很低?(我认为Dancrumb的用户情况不稳定,这是一个类似的问题)...在本地,这肯定是最好的方法。
gr4nt3d 2015年

3

这是基于网格的系统,对吗?找到您自己的直线点并计算直线的斜率。现在,使用该计算,在精确值存在一定误差的情况下,确定直线将通过的有效点。

通过少量的反复试验测试,确定匹配点的好坏程度,并使用一个标尺为您的测试设置相同的结果。

即一条几乎水平的斜线可能有7个点可以绘制。如果您可以始终匹配确定为直线一部分的7个中的6个或更多,那么那将是最高分。评分的长度和准确性应该是评分的一部分。


3

一个非常简单直观的度量是最佳拟合直线和实际曲线之间的区域。确定这一点非常简单:

  1. 在所有点上使用最小二乘拟合(这可以防止Joel Bosveld提到的结结问题)。
  2. 对于曲线上的所有点,确定到直线的距离。这也是一个标准问题。(线性代数,基本变换。)
  3. 求和所有距离。

您能介意我问您一些文本编码(JS,C#)还是伪代码,因为以上大多数答案都是在理论上描述的,所以我不知道如何开始?
user3637362 2015年

1
@ user3637362:StackOverflow上具有实际的答案:stackoverflow.com/questions/6195335/... stackoverflow.com/questions/849211/...
MSalters

2

想法是保持用户触摸过的所有点,然后评估并汇总这些点之间的距离,以与用户释放屏幕时形成的线相加。

这是一些让您开始使用伪代码的方法:

bool mIsRecording = false;
point[] mTouchedPoints = new point[];

function onTouch
  mIsRecording = true

functon update
  if mIsRecording
    mTouchedPoints.append(currentlyTouchedLocation)

function onRelease
  mIsRecording = false

  cumulativeDistance = 0

  line = makeLine( mTouchedPoints.first, mTouchedPoints.last )

  for each point in mTouchedPoints:
    cumulativeDistance = distanceOfPointToLine(point, line)

  mTouchedPoints = new point[]

什么cumulativeDistance可以使您对配件有所了解。距离为零将意味着用户一直在直线上。现在,您必须进行一些测试以查看其在您的上下文中的行为。您可能想distanceOfPointToLine通过平方平方来放大返回的值,以惩罚距离线路较远的更多距离。

我不熟悉统一性,但是update这里的代码可能包含一个onDrag函数。

并且您可能希望在其中添加一些代码,以防止与最后一个注册点相同时注册该点。当用户不移动时,您不想注册内容。


5
将每个测量点的理想线和该点之间的距离相加时,需要考虑所采取的测量数量,否则,当用户绘制速度较慢或使用扫描速度较快的设备时,他们将注册更多分意味着他们将获得更差的分数。
菲利普

@Philipp是的!我必须承认,您的做法似乎比我的好:P
Vaillancourt

我认为通过采用平均距离而不是累积距离可以改进此方法。
Dancrumb 2015年

@Dancrumb确实,这取决于需求,但是,那是解决问题的一种方法。
Vaillancourt

2

您可以使用的一种方法是将线细分为段,并在表示段的每个矢量和表示第一个点与最后一个点之间的直线的矢量之间做一个矢量点积。这样的好处是可以让您轻松地找到“尖峰”段。

编辑:

另外,除点积外,我还将考虑使用段的长度。一个非常短但正交的向量的计数应少于一个偏差较小的长向量。


1

最简单,最快捷的方法可能就是找出要覆盖用户绘制线的所有点的线的粗细。

线必须越粗,用户绘制线的效果就越差。


0

以某种方式引用MSalters答案,这是一些更具体的信息。

使用最小二乘法为您的点拟合一条线。您基本上是在寻找最合适的函数y = f(x)。一旦有了它,就可以使用实际的y值求和求差的平方:

s =总和((yf(x))^ 2)

总和越小,直线越直。

此处介绍了如何获得最佳近似值:http : //math.mit.edu/~gs/linearalgebra/ila0403.pdf

只需阅读“拟合直线”即可。请注意,使用t代替x,使用b代替y。将C和D确定为近似值,则f(x)= C + Dx

附加说明:显然,您还必须考虑行的长度。每行包括2点将是完美的。我不知道确切的上下文,但是我想我将平方和除以点数作为等级。我还要增加最小长度,最小点数的要求。(也许是最大长度的75%)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.