Як я можу оцінити прямолінійність намальованої лінії?


37

Я працюю над грою, яка вимагає від гравців провести лінію від точки A (x1, y1) до іншої точки B (x2, y2) на екрані пристрою Android.

Я хочу знайти, як добре цей малюнок підходить до прямої лінії. Наприклад, результат 90% означав би, що малюнок майже ідеально підходить до лінії. Якщо гравці намалюють вигнуту лінію від А до В, вона повинна отримати низький бал.

Кінцеві точки не відомі заздалегідь. Як я можу це зробити?


1
Чи знаєте ви заздалегідь, які ваші дві кінцеві точки? Або визначається в момент, коли користувач перестає торкатися екрана?
Vaillancourt

Вибачте, якщо мій опис вам не зрозумілий. Ну, початкова точка A (x, y) - це перший дотик, а кінцева точка B (x, y) - це коли ми звільнилися від сенсорного екрану, як ви сказали.
користувач3637362

У нас пов'язане питання щодо відповідності букв, намальованих гравцем .
Анко

3
Будь ласка, не публікуйте зображення для вихідного коду в майбутньому.
Джош

1
@ User3637362 Я розумію , що ви починаєте , j=1так що ви можете порівняти touchList[j]з touchList[j-1], але коли touch.phase == TouchPhase.Beganі touch.phase == TouchPhase.Endedпозицій не будуть додані touchListі згодом не включені в sumLength. Ця помилка буде присутня у всіх випадках, але вона буде більш очевидною, коли рядок має мало сегментів.
Келлі Томас

Відповіді:


52

Ідеально пряма також була б найкоротшою можливою лінією загальною довжиною sqrt((x1-x2)² + (y1-y2)²). Більш чітка лінія буде менш ідеальним з'єднанням і, отже, неминуче довше.

Коли ви берете всі окремі точки шляху, який користувач намалював, і підсумуйте відстані між ними, ви можете порівняти загальну довжину з ідеальною довжиною. Чим менша загальна довжина, поділена на ідеальну довжину, тим краще лінія.

Ось візуалізація. Коли чорні точки є кінцевими точками жесту, а сині - це точки, які ви вимірювали під час жесту, ви обчислили б і склали довжини зелених ліній і розділили їх на довжину червоної лінії:

enter image description here

Оцінка або індекс sinuosity 1 буде ідеальним, все вище, було б менш досконалим, все, що нижче 1, було б помилкою. Коли ви вважаєте за краще мати рахунок у відсотках, поділіть 100% на це число.


34
Існує невелика проблема такого підходу в тому, що полілінії однакової довжини не є однаково «прямими». Лінія, яка коливається з низьким відхиленням (але багато разів) щодо прямої, є «прямішою», ніж лінія однакової довжини, яка відхиляється до однієї точки, а потім назад.
Dancrumb

Я не можу +1 @Dancrumbs прокоментувати достатньо - це досить ключове обмеження для цього методу, так як якщо користувач малює пряму лінію, вони будуть коливатися трохи, так що це виглядає як загальний випадок використання.
Т. Кілі

@Dancrumb просто коефіцієнт середньої відстані від лінії, або коефіцієнт "максимальної відстані" будь-якої точки є від лінії. Тоді ви можете зважувати алгоритм до більш хитких ліній з меншими амплітудами відхилень і від ліній, що відхиляються далеко від очікуваного шляху.
Супердоггі

2
@Dancrumb мені здається, що це може спричинити користь для використання ОП. Ручні намальовані лінії, звичайно, матимуть невеликі відхилення. Цей підхід насправді може придушити ефект цих очікуваних відмінностей.

2
@ user3637362 у вас є помилка в коді. Можливе пояснення полягає в тому, що ви забули врахувати відстань між початковою і першою точкою або кінцевою і останньою точкою, але, не дивлячись на ваш код, неможливо сказати, у чому може бути ваша помилка.
Філіп

31

Це може бути і не найкращим способом реалізації цього, але я пропоную, що RMSD (середнє квадратне відхилення кореня) може бути кращим, ніж просто метод відстані, у випадках, згаданих Dancrumb (див. Перші два рядки нижче).

RMSD = sqrt(mean(deviation^2))

Примітка:

  • Сума абсолютних відхилень (як інтеграл) може бути кращою, оскільки вона не оцінює позитивні помилки з негативними. ( =sum(abs(deviation)))
  • Ймовірно, вам доведеться шукати найкоротшу відстань до лінійної лінії, якщо є спосіб, який створює більш короткі відстані, ніж опускання перпендикуляра.

drawing

(Вибачте про низьку якість мого малюнка)

Як бачите, ви повинні

  1. знайдіть ортогональний вектор до своєї лінії ( крапка-добуток дорівнює 0 ).
    Якщо ваша лінія вказує на(1, 3) потрібне (3, -1)(через кожне джерело)
  2. Виміряйте відстані h від ідеальної лінії до користувальницької, паралельної цьому вектору.
  3. Обчисліть RMSD або суму абсолютних різниць.

Відповідь Джоеля Босвельда вказує на цікавий випадок: майже ідеально пряму лінію з кутами на початку та в кінці. Якщо користувач повинен малювати лінію вільно, це справді проблема. Тим не менш, я думаю, що цей метод може охопити цей сценарій. Насправді можна виконати пристосування з RMSD або абсолютним інтегралом як максимальне значення. Стартовими значеннями можуть бути початкова та кінцева точки. Оскільки довжина не має значення, вона також не має значення, якщо оптимізація переміщує точки, щоб ідеальна лінія простягалася далі або була коротшою (висота повинна бути розрахована на цю ниву).
gr4nt3d

1
Інший випадок, який, схоже, не охоплює: скажімо, кожна вимірювана точка знаходиться на осі x, але лінія кілька разів повертає напрямок. Це поверне помилку 0.
Дейв Манкофф

23

Існуючі відповіді не враховують, що кінцеві точки є довільними (а не даними). Таким чином, при вимірюванні прямолінійної кривої немає сенсу використовувати кінцеві точки (наприклад, для обчислення очікуваної довжини, кута, положення). Простим прикладом може бути пряма лінія з обома кінцями. Якщо ми вимірюємо, використовуючи відстань від кривої та пряму між кінцевими точками, це буде досить великим, оскільки пряма, яку ми провели, зміщується від прямої між кінцевими точками.

Як ми можемо сказати, наскільки пряма крива? Припускаючи, що крива досить гладка, ми хочемо знати, наскільки в середньому дотична до кривої змінюється. Для рядка це буде нуль (оскільки дотична постійна).

Якщо дозволити позиції в момент часу t бути (x (t), y (t)), тоді дотична дорівнює (Dx (t), Dy (t)), де Dx (t) - похідна від x в момент t (Здається, на цьому сайті відсутня підтримка 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)||. Потім ми отримуємо S шляхом підсумовування h||Da(t)||^2всіх точок даних і, можливо, нормалізації за довжиною кривої. Швидше за все, ми використовуємоh=1 , але це дійсно просто довільний масштабний фактор.

Щоб повторити, S буде нульовим для рядка і більшим, чим більше він відхилиться від рядка. Щоб конвертувати у потрібний формат, використовуйте 1/(1+S). Зважаючи на те, що шкала дещо довільна, можна помножити S на якесь додатне число (або перетворити його іншим способом, наприклад, використовувати bS ^ c замість S), щоб регулювати, наскільки прямі певні криві.


2
Це найрозумніше визначення прямолінійності.
Маркс Томас

1
Це, безумовно, найрозумніша відповідь, і я впевнений, що оточуючі стали дуже неприємними. На жаль, форма, в якій представлено рішення, є дещо незрозумілішою, ніж інші, але я б рекомендував, що ОП зберігається.
Ден Шеппард

Взагалі я також вважаю, що ця відповідь справді найкраща. Хоча проблема мене турбує: що станеться, якщо лінія недостатньо "гладка"? Наприклад, якщо у вас два ідеально прямих відрізка з кутом, скажімо, 90 °. Я помиляюся чи це призведе до досить низького результату порівняно з дійсно гладкою лінійною лінією? (Я думаю, що випадок користувача Dancrumb із хиткою лінією був подібною проблемою) ... Однак на місцевому рівні це, безумовно, найкращий спосіб.
gr4nt3d

3

Це система на основі сітки, правда? Знайдіть власні точки для прямої та обчисліть нахил лінії. Тепер, використовуючи цей обчислення, визначте дійсні точки, через які проходила б лінія, враховуючи деяку похибку від точного значення.

За допомогою невеликої кількості тестування проб і помилок визначте, яка хороша та погана кількість точок відповідності буде існувати, і встановіть гру, використовуючи шкалу для однакових результатів вашого тестування.

тобто коротка лінія з майже горизонтальним нахилом може мати 7 точок, які ви могли провести через. Якщо ви зможете послідовно порівнювати 6 чи більше із 7, які були визначені частиною прямої лінії, то це був би найвищий бал. Класифікація за довжиною та точністю повинна бути частиною оцінки.


3

Дуже легким та інтуїтивним вимірюванням є область між найкращою прямою лінією та фактичною кривою. Визначити це досить просто:

  1. Використовуйте квадратики з мінімальними розмірами для всіх точок (це запобігає проблемі кінцевих перетворень, згаданих Джоелем Босвельдом).
  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

@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% максимальної довжини)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.