描画された線の真直度をどのように定量化できますか?


37

Androidデバイスの画面上で、プレイヤーがポイントA(x1、y1)から他のポイントB(x2、y2)に線を引く必要があるゲームに取り組んでいます。

その図面が直線にどれだけ適合するかを見つけたい。たとえば、90%の結果は、描画がほぼ完全に線に合うことを意味します。プレーヤーがAからBに曲線を描くと、低いスコアが得られます。

エンドポイントは事前にわかりません。これどうやってするの?


1
あなたの2つのエンドポイントを事前に知っていますか?または、ユーザーが画面へのタッチを停止した時点で決定されますか?
ベイランクール

私の説明が明確でない場合は申し訳ありません。さて、開始点A(x、y)は最初のタッチであり、終了点B(x、y)はあなたが言ったようにタッチスクリーンから離したときです。
user3637362

プレイヤーが描いた文字のマッチングに関する質問があります。
アンコ

3
将来、ソースコードの画像を投稿しないでください。
ジョシュ

1
@ user3637362私はあなたが始めていることを理解しj=1、あなたが比較できるようにtouchList[j]してtouchList[j-1]、しかしときtouch.phase == TouchPhase.Begantouch.phase == TouchPhase.Ended位置が追加されていないtouchListと、その後には含まれませんsumLength。このバグはすべての場合に存在しますが、ラインにセグメントがほとんどない場合はより明確になります。
ケリートーマス

回答:


52

完全に直線の線は、全長がの最短の線でもありますsqrt((x1-x2)² + (y1-y2)²)。落書き線は理想的な接続ではないため、必然的に長くなります。

ユーザーが描いたパスのすべての個別のポイントを取得し、それらの間の距離を合計すると、全長と理想的な長さを比較できます。全体の長さを理想的な長さで除算するほど、ラインは良くなります。

これが視覚化です。黒い点がジェスチャーの終点であり、青い点がジェスチャー中に測定した点である場合、緑の線の長さを計算して加算し、赤の線の長さで除算します。

ここに画像の説明を入力してください

スコアまたはsinuosityインデックスが1の場合は完璧になり、高いものは完璧ではなくなり、1未満の場合はバグになります。スコアをパーセントで表示する場合は、100%をその数値で割ります。


34
このアプローチには、長さが等しいポリラインが等しく「直線」ではないという小さな問題があります。直線の周りで低い偏差で(しかし何回も)揺れ動く線は、単一の点に外に出てから戻る同じ長さの線よりも「まっすぐ」です。
ダンクラム

@Dancrumbsのコメントに十分な+1を付けることはできません。これは、ユーザーが直線を描いているように見えるため、この方法ではかなり重要な制限です。
T.カイリー

@Dancrumbは、ラインからの平均距離を考慮するか、任意のポイントがラインからの「最大距離」を考慮する。次に、アルゴリズムを、より小さな偏差振幅でより不安定なラインに向けて、予想されるパスから遠く離れたラインから離れるように重み付けできます。
Superdoggy

2
@Dancrumbは、これがOPのユースケースに利益をもたらす可能性があるように思えます。もちろん、手書きの線にはわずかな偏差があります。このアプローチは、実際にこれらの予想される違いの効果を弱めるのに役立つかもしれません。

2
@ user3637362コードにバグがあります。考えられる説明は、開始点と最初の点または終了点と最後の点の間の距離を考慮するのを忘れたが、コードを見ずに間違いが何であるかを伝えることは不可能だということです。
フィリップ

31

これもこれを実装する最良の方法ではないかもしれませんが、ダンクラムが言及した場合、RMSD(二乗平均平方根)は単なる距離法よりも優れている可能性があります(下の最初の2行を参照)。

RMSD = sqrt(mean(deviation^2))

注意:

  • 絶対偏差(積分のような)の合計は、正の誤差と負の誤差を平均化しないため、より良い場合があります。(=sum(abs(deviation))
  • 垂線を落とすよりも短い距離を作成する方法がある場合は、おそらく、直線までの最短距離を検索する必要があります。

図

(私の図面の低品質を許してください)

ご覧のように、

  1. 線に直交するベクトルを見つけますドット積が0に等しい)。
    (1, 3) が希望する方向を指している場合(3, -1)(それぞれの原点をたどる)
  2. hそのベクトルに平行な、理想的な線からユーザーの線までの距離測定します。
  3. RMSDまたは絶対差の合計を計算します。

Joel Bosveldの答えは興味深い事例を示しています。最初と最後に角があるほぼ完全に直線です。ユーザーが自由に線を引く場合、これは確かに問題です。それでも、この方法はそのシナリオをカバーできると思います。実際に、最小化された値としてRMSDまたは絶対積分を使用してフィットを実行できます。開始値は開始点と終了点である可能性があります。長さは重要ではないので、最適化がポイントを移動して理想的な線がさらに伸びたり短くなったりする場合は関係ありません(高さはそのニベーに合わせて計算する必要があります)。
gr4nt3d

1
これがカバーしていない別のケース:すべての測定点がx軸上にあるが、線が方向を数回反転するとします。これは、0のエラーを返します
Daveはmankoff

23

既存の回答では、エンドポイントが(与えられているのではなく)arbitrary意的であることを考慮していません。したがって、曲線の真直度を測定する場合、終点を使用することは(たとえば、予想される長さ、角度、位置を計算するために)意味がありません。簡単な例は、両端がねじれている直線です。曲線からの距離と端点間の直線を使用して測定すると、これは非常に大きくなります。これは、描画した直線が端点間の直線からオフセットされるためです。

曲線の直線性をどのように確認しますか?曲線が十分に滑らかであると仮定して、曲線の接線が平均してどれだけ変化しているかを知りたいです。線の場合、これはゼロになります(接線が一定であるため)。

時間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はラインではゼロであり、ラインから逸脱するほど大きくなります。必要な形式に変換するには、を使用します1/(1+S)。スケールがいくぶんarbitrary意的であることを考えると、Sに正の数を掛ける(またはSの代わりにbS ^ cを使用するなど他の方法で変換する)ことにより、特定の曲線の直線性を調整することができます。


2
これは真直度の最も賢明な定義です。
マルクストーマス

1
これは間違いなく最も賢明な答えであり、他の人が非常にイライラするようになると確信しています。残念ながら、ソリューションが提示される形式は他の形式よりも少しあいまいですが、OPが持続することをお勧めします。
ダンシェパード

一般的に、この答えは本当に最高だと思います。問題が気になりますが、ラインが「十分に滑らか」でない場合はどうなりますか?たとえば、90°の角度を持つ2つの完全に直線の線分がある場合。私は間違っているのですか、それとも本当に滑らかな直線に比べてかなり低い結果になるのでしょうか?(私は、不安定な線を伴うDancrumbのユーザーケースも同様の問題であったと思います)...ローカルでは、これが最善の方法であることは確かです。
gr4nt3d

3

これはグリッドベースのシステムですよね?線の独自のポイントを見つけて、線の勾配を計算します。ここで、その計算を使用して、正確な値からある程度の誤差を与えて、ラインが通過する有効なポイントを決定します。

短い試行錯誤のテストを通して、一致するポイントがどれだけ良いか悪いかを判断し、テストの同じ結果のスケールを使用してゲームをセットアップします。

すなわち、ほぼ水平な勾配の短い線には、7つの点を通過させることができます。直線の一部であると判断された7つのうち6つ以上を一貫して一致させることができる場合、それが最高スコアになります。長さと精度の評価は、スコアリングの一部である必要があります。


3

非常に簡単で直感的な尺度は、最適な直線と実際の曲線の間の面積です。これを決定することはかなり簡単です:

  1. すべての点で最小二乗近似を使用します(これにより、Joel Bosveldが言及したエンドキンクの問題が回避されます)。
  2. 曲線上のすべての点について、線までの距離を決定します。これも標準的な問題です。(線形代数、基底変換。)
  3. すべての距離を合計します。

上記のほとんどの答えは理論的に説明されているので、テキストコーディング(JS、C#)または擬似コードを要求してもかまいませんか?
user3637362

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フィッティングのアイデアを与えてくれるでしょう。距離0は、ユーザーが常に直線上にいることを意味します。ここで、コンテキストでどのように動作するかを確認するために、いくつかのテストを行う必要があります。そしてdistanceOfPointToLine、直線から離れた距離が大きいほどペナルティを課すことで、返される値を増幅することができます。

私は団結に精通していませんが、updateここのコードはonDrag関数に入れられるかもしれません。

そして、ポイントが最後に登録されたポイントと同じ場合、ポイントの登録を防ぐためのコードをそこに追加することもできます。ユーザーが移動しない場合は、ものを登録したくないでしょう。


5
すべての測定ポイントの理想的なラインとポイントの間の距離を合計するときは、行った測定の数を考慮する必要があります。ポイントは、彼らがより悪いスコアを取得することを意味します。
フィリップ

@Philippはい、そうです!私はあなたのやり方が私のものよりも良いようだと認めなければなりません:P
Vaillancourt

このアプローチは、累積距離ではなく平均距離をとることで改善されると思います。
ダンクラム

@Dancrumb本当に、それはニーズに依存しますが、ええ、それはそれを行う方法です。
ベイランクール

2

使用できる方法の1つは、ラインをセグメントに分割し、セグメントを表す各ベクトルと最初と最後のポイント間の直線を表すベクトルの間でベクトルドット積を求めることです。これには、非常に「尖った」セグメントを簡単に見つけることができるという利点があります。

編集:

また、ドット積に加えてセグメントの長さを使用することを検討します。非常に短いが直交するベクトルは、偏差の少ない長いベクトルよりも数が少ないはずです。


1

最も簡単で迅速な方法は、ユーザーが描画した線のすべてのポイントをカバーするために、線の太さを単純に調べることです。

線が太くなればなるほど、ユーザーが線を描く際に悪化しました。


0

どういうわけかMSalters Answerを参照して、ここにいくつかのより具体的な情報があります。

最小二乗法を使用して、ポイントの線を近似します。基本的に、最適な関数y = f(x)を探しています。取得したら、実際のy値を使用して差の2乗を合計できます。

s =合計((yf(x))^ 2)

合計が小さいほど、直線が直線になります。

最適な近似値を取得する方法については、http//math.mit.edu/~gs/linearalgebra/ila0403.pdfで説明しています

「直線の当てはめ」から読んでください。xの代わりにtが、yの代わりにbが使用されることに注意してください。CおよびDは近似値として決定されるため、f(x)= C + Dx

追記:当然、行の長さも考慮する必要があります。2ポイントで構成されるすべての行が完璧になります。正確なコンテキストはわかりませんが、評価としてポイントの数で割った平方和を使用すると思います。また、最小の長さ、最小のポイント数の要件を追加します。(おそらく最大長の約75%)

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.