Come posso quantificare la rettilineità di una linea disegnata?


37

Sto lavorando a un gioco che richiede ai giocatori di tracciare una linea da un punto A (x1, y1) all'altro punto B (x2, y2) sullo schermo di un dispositivo Android.

Voglio scoprire quanto bene quel disegno si adatta a una linea retta. Ad esempio, un risultato del 90% significherebbe che il disegno si adatta quasi perfettamente alla linea. Se i giocatori tracciano una linea curva da A a B, dovrebbe ottenere un punteggio basso.

I punti finali non sono noti in anticipo. Come posso fare questo?


1
Sai in anticipo quali sono i tuoi due punti finali? O è determinato nel momento in cui l'utente smette di toccare lo schermo?
Vaillancourt

Scusa se la mia descrizione non ti è chiara. Bene, il punto iniziale A (x, y) è il primo tocco e il punto finale B (x, y) è quando abbiamo rilasciato dal touchscreen come hai detto.
user3637362

Abbiamo una domanda correlata sulla corrispondenza delle lettere disegnate dal giocatore .
Anko

3
Si prega di non pubblicare immagini per il codice sorgente in futuro.
Josh

1
@ user3637362 Capisco che si stanno iniziando j=1in modo da poter confrontare touchList[j]con touchList[j-1], ma quando touch.phase == TouchPhase.Begano touch.phase == TouchPhase.Endedle posizioni non vengono aggiunti touchListe, successivamente, non inclusi nel sumLength. Questo bug sarebbe presente in tutti i casi ma sarebbe più evidente quando la linea ha pochi segmenti.
Kelly Thomas,

Risposte:


52

Una linea perfettamente dritta sarebbe anche la linea più corta possibile con una lunghezza totale di sqrt((x1-x2)² + (y1-y2)²). Una linea più scarabocchiata sarà una connessione meno ideale e quindi inevitabilmente più lunga.

Quando prendi tutti i singoli punti del percorso tracciati dall'utente e sommi le distanze tra loro, puoi confrontare la lunghezza totale con la lunghezza ideale. Più piccola è la lunghezza totale divisa per la lunghezza ideale, migliore è la linea.

Ecco una visualizzazione. Quando i punti neri sono i punti finali del gesto e i punti blu sono i punti misurati durante il gesto, calcoleresti e sommeresti le lunghezze delle linee verdi e le divideresti per la lunghezza della linea rossa:

enter image description here

Un punteggio o indice di sinuosità di 1 sarebbe perfetto, qualsiasi cosa più alta sarebbe meno perfetta, qualsiasi cosa al di sotto di 1 sarebbe un bug. Quando preferisci avere il punteggio in percentuale, dividi il 100% per quel numero.


34
C'è un piccolo problema con questo approccio in quanto le polilinee di uguale lunghezza non sono ugualmente "dritte". Una linea che traballa con una bassa deviazione (ma molte volte) attorno alla linea retta è "più dritta" di una linea di uguale lunghezza che devia verso un singolo punto e poi indietro.
Dancrumb,

Non posso fare abbastanza +1 su @Dancrumbs per commentare - questa è una limitazione piuttosto chiave con questo metodo come se l'utente stesse disegnando una linea retta traballi un po ', quindi sembra un caso d'uso comune.
T. Kiley,

@Dancrumb solo il fattore nella distanza media dalla linea o il fattore nella "distanza massima" qualsiasi punto proviene dalla linea. Quindi è possibile ponderare l'algoritmo verso linee più traballanti con ampiezze di deviazione minori e lontano da linee che si allontanano dal percorso previsto.
Superdoggy

2
@Dancrumb mi sembra che questo potrebbe costituire un vantaggio per il caso d'uso dell'OP. Le linee disegnate a mano avranno, ovviamente, piccole deviazioni. Questo approccio potrebbe effettivamente funzionare per smorzare l'effetto di queste differenze previste.

2
@ user3637362 hai un bug nel tuo codice. Una possibile spiegazione è che hai dimenticato di tenere conto della distanza tra il punto iniziale e il primo o il punto finale e l'ultimo punto, ma senza guardare il tuo codice è impossibile dire quale potrebbe essere il tuo errore.
Philipp,

31

Questo potrebbe non essere il modo migliore per implementarlo, ma suggerisco che un RMSD (deviazione quadrata media radice) potrebbe essere migliore, piuttosto che semplicemente il metodo della distanza, nei casi menzionati da Dancrumb (vedi le prime due righe di seguito).

RMSD = sqrt(mean(deviation^2))

Nota:

  • La somma delle deviazioni assolute (di tipo integrale) potrebbe essere migliore, in quanto non calcola la media degli errori positivi con quelli negativi. ( =sum(abs(deviation)))
  • Probabilmente dovresti cercare la distanza più breve dalla linea lineare se c'è un modo che crea distanze più brevi rispetto alla caduta della perpendicolare.

drawing

(Per favore, scusa la bassa qualità del mio disegno)

Come vedi, devi

  1. trova un vettore ortogonale alla tua linea ( punto-prodotto è uguale a 0 ).
    Se la tua linea punta verso ciò (1, 3) che vorresti (3, -1)(attraverso l'origine ciascuno)
  2. Misura le distanze h dalla linea ideale a quella dell'utente, parallela a quel vettore.
  3. Calcola l'RMSD o la somma delle differenze assolute.

La risposta di Joel Bosveld indica un caso interessante: una linea quasi perfettamente dritta con angoli all'inizio e alla fine. Se la linea deve essere tracciata liberamente dall'utente, questo è effettivamente un problema. Tuttavia, penso che questo metodo potrebbe coprire quello scenario. Si potrebbe effettivamente eseguire un accoppiamento con RMSD o Integrale assoluto come valore top-be-minimized. I valori iniziali potrebbero essere i punti iniziale e finale. Poiché la lunghezza non ha importanza, è anche irrilevante se l'ottimizzazione sposta i punti in modo che la linea ideale si estenda ulteriormente o sia più corta (l'altezza deve essere calcolata a quel niveau).
gr4nt3d,

1
Un altro caso che non sembra coprire: supponiamo che ogni punto misurato si trovi sull'asse x, ma la linea inverte la direzione più volte. Questo restituirà un errore di 0.
dave mankoff il

23

Le risposte esistenti non tengono conto del fatto che i punti finali sono arbitrari (piuttosto che forniti). Pertanto, quando si misura la rettilineità della curva, non ha senso utilizzare i punti finali (ad esempio, per calcolare la lunghezza, l'angolo, la posizione previsti). Un semplice esempio potrebbe essere una linea retta con entrambe le estremità piegate. Se misuriamo usando la distanza dalla curva e la linea retta tra i punti finali, questa sarà piuttosto grande, poiché la linea retta che abbiamo disegnato è sfalsata dalla linea retta tra i punti finali.

Come diciamo quanto è diritta la curva? Supponendo che la curva sia abbastanza regolare, vogliamo sapere quanto, in media, sta cambiando la tangente alla curva. Per una linea, questo sarebbe zero (poiché la tangente è costante).

Se lasciamo che la posizione al momento t sia (x (t), y (t)), allora la tangente è (Dx (t), Dy (t)), dove Dx (t) è la derivata di x al tempo t (questo sito sembra mancare il supporto TeX). Se la curva non è parametrizzata per lunghezza d'arco, normalizziamo dividendo per || (Dx (t), Dy (t)) ||. Quindi abbiamo un vettore unitario (o angolo) della tangente alla curva al tempo t. Quindi, l'angolo è a (t) = (Dx (t), Dy (t)) / || (Dx (t), Dy (t)) ||

Siamo quindi interessati a || Da (t) || ^ 2 integrato lungo la curva.

Dato che molto probabilmente abbiamo punti dati discreti piuttosto che una curva, dobbiamo usare differenze finite per approssimare i derivati. Quindi, Da (t) diventa (a(t+h)-a(t))/h. E, diventa una (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)||. Quindi otteniamo S sommando h||Da(t)||^2tutti i punti dati e possibilmente normalizzandoci per la lunghezza della curva. Molto probabilmente, lo usiamo h=1, ma in realtà è solo un fattore di scala arbitrario.

Per ribadire, S sarà zero per una linea e più grande quanto più devia da una linea. Per convertire nel formato richiesto, utilizzare 1/(1+S). Dato che la scala è in qualche modo arbitraria, è possibile moltiplicare S per un numero positivo (o trasformarlo in qualche altro modo, ad esempio utilizzare bS ^ c invece di S) per regolare la rettilineità di alcune curve.


2
Questa è la definizione più sensata di rettilineità.
Marcks Thomas,

1
Questa è di gran lunga la risposta più sensata e sono sicuro che gli altri diventerebbero molto frustranti. Sfortunatamente, la forma in cui viene presentata la soluzione è un po 'più oscura delle altre, ma raccomanderei che il PO persista.
Dan Sheppard,

In generale, penso anche che questa risposta sia davvero la migliore. Anche se un problema mi dà fastidio: cosa succede se la linea non è "abbastanza liscia"? Ad esempio, se hai due segmenti perfettamente dritti con un angolo, diciamo, di 90 °. Sbaglio o questo comporterebbe un risultato piuttosto basso rispetto a una linea lineare davvero liscia? (Penso che il caso dell'utente di Dancrumb con una linea traballante fosse un problema simile) ... A livello locale questo è sicuramente il modo migliore.
gr4nt3d,

3

Questo è un sistema basato su griglia, giusto? Trova i tuoi punti per la linea e calcola la pendenza della linea. Ora, usando quel calcolo, determinare i punti validi che la linea dovrebbe attraversare, dato un certo margine di errore rispetto al valore esatto.

Attraverso una breve quantità di test di prova ed errore, determinare quale quantità buona e cattiva di punti di corrispondenza esisterebbero e impostare il gioco usando una scala per gli stessi risultati dei test.

vale a dire una linea corta con pendenza quasi orizzontale può avere 7 punti che è possibile tracciare. Se riesci a far corrispondere costantemente 6 o più dei 7 determinati a far parte della linea retta, sarebbe il punteggio più alto. La classificazione per lunghezza e precisione dovrebbe far parte del punteggio.


3

Una misura molto semplice e intuitiva è l'area tra la retta più adatta e la curva effettiva. Determinare questo è abbastanza semplice:

  1. Usa un adattamento dei minimi quadrati su tutti i punti (questo evita il problema del nodo finale menzionato da Joel Bosveld).
  2. Per tutti i punti sulla curva, determinare la distanza dalla linea. Anche questo è un problema standard. (algebra lineare, trasformazione di base.)
  3. Somma tutte le distanze.

Ti dispiacerebbe se ti chiedessi un po 'di codifica del testo (JS, C #) o pseudo-codice, dal momento che la maggior parte delle risposte sopra sono descritte in teoria, non so come iniziare?
user3637362


2

L'idea è di mantenere tutti i punti toccati dall'utente, quindi valutare e sommare la distanza tra ciascuno di quei punti rispetto alla linea formata quando l'utente rilascia lo schermo.

Ecco qualcosa per iniziare nello pseudo-codice:

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[]

Ciò che è cumulativeDistancepotrebbe darti un'idea sull'adattamento. Una distanza pari a 0 significherebbe che l'utente era sempre dritto sulla linea. Ora dovresti fare alcuni test per vedere come si comporta nel tuo contesto. E potresti voler amplificare il valore restituito distanceOfPointToLinequadrandolo per penalizzare maggiormente le grandi distanze dalla linea.

Non ho familiarità con l'unità, ma il codice updatequi potrebbe andare in una onDragfunzione.

E potresti voler aggiungere da qualche parte un po 'di codice per impedire la registrazione di un punto se è uguale all'ultimo registrato. Non si desidera registrare elementi quando l'utente non si sposta.


5
Quando sommi la distanza tra la linea ideale e il punto per ogni punto misurato, devi tenere conto del numero di misure che hai preso, altrimenti quando l'utente disegna più lentamente o usa un dispositivo con una velocità di scansione maggiore registrerà di più punti il ​​che significa che otterranno un punteggio peggiore.
Philipp

@Philipp Sì, lo fai! Devo ammettere che il tuo modo di farlo sembra migliore del mio: P
Vaillancourt

Penso che questo approccio sia migliorato prendendo la distanza media , piuttosto che la distanza cumulativa.
Dancrumb,

@Dancrumb Davvero, dipende dai bisogni, ma sì, sarebbe un modo per farlo.
Vaillancourt

2

Un metodo che è possibile utilizzare è quello di suddividere la linea in segmenti ed eseguire un prodotto punto vettoriale tra ciascun vettore che rappresenta il segmento e un vettore che rappresenta una linea retta tra il primo e l'ultimo punto. Questo ha il vantaggio di farti trovare facilmente segmenti estremamente "appuntiti".

Modificare:

Inoltre, prenderei in considerazione l'utilizzo della lunghezza del segmento oltre al prodotto punto. Un vettore molto corto ma ortogonale dovrebbe contare meno di un vettore lungo che ha una deviazione minore.


1

Il più semplice e veloce potrebbe essere semplicemente quello di scoprire quanto dovrebbe essere spessa la linea per coprire tutti i punti della linea disegnata dall'utente.

Più spessa deve essere la linea, peggio l'utente ha fatto nel tracciare la linea.


0

In qualche modo facendo riferimento a MSalters Answer, ecco alcune informazioni più specifiche.

Usa il metodo dei minimi quadrati per adattare una linea ai tuoi punti. Fondamentalmente stai cercando una funzione y = f (x) che si adatta meglio. Una volta ottenuto, puoi utilizzare i valori y effettivi per sommare il quadrato delle differenze:

s = somma sopra ((yf (x)) ^ 2)

Più piccola è la somma, più dritta è la linea.

Come ottenere la migliore approssimazione, è spiegato qui: http://math.mit.edu/~gs/linearalgebra/ila0403.pdf

Ho appena letto da "Inserire una linea retta". Si noti che t viene utilizzato invece di xeb anziché y. C e D devono essere determinati come approssimazione, quindi si ha f (x) = C + Dx

Nota aggiuntiva: ovviamente, devi anche tener conto della lunghezza della linea. Ogni linea composta da 2 punti sarà perfetta. Non conosco il contesto esatto, ma immagino che userei la somma dei quadrati divisa per il numero di punti come punteggio. Inoltre aggiungerei il requisito di una lunghezza minima, un numero minimo di punti. (Forse circa il 75% della lunghezza massima)

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.