С недавним прибытием моего Apple Pencil мои мысли превратились в почерк и поглаживание распознавания жестов. В Swift, DBPathRecognizer , уже есть отличная среда распознавания рукописного ввода от Didier Brun , но в моей версии используется другая техника. В то время как Дидье использует распознавание пути, мое больше похоже на сопоставление двух изображений.
Основным преимуществом моего подхода является то, что пользователь может построить письмо в любом порядке. Например, буква «Е» может быть сформирована путем рисования трех горизонтальных полос и вертикальной полосы в качестве отдельного элемента, она может быть сформирована путем рисования формы «С» и добавления отсутствующей центральной горизонтальной полосы, или она может быть сформирована путем рисования форма «L» и добавление двух других горизонтальных полос в виде буквы «C» с вертикальной частью, перекрывающей существующую вертикальную полосу.
С другой стороны, эта свобода требует большего обучения. В моей структуре Patterns вы можете увидеть количество сэмплов, которые я взял, чтобы получить демо-версию.
Кодирование пользовательского жеста
Мой компонент ScribeView расширяет UIView и отвечает за кодирование штрихов пользователя и вызов метода для его ScribeViewDelegate после завершения жеста. Я использую стандартные функции touchesBegan () и touchesMoved (), чтобы вычислить ограничивающую рамку жеста и сохранить все местоположения касания (конечно, я использую объединенные касания ):
for touch in coalescedTouches
{
let locationInView = touch.locationInView(self)
strokePoints.append(locationInView)
bezierPath.addLineToPoint(locationInView)
minX = min(locationInView.x, minX)
minY = min(locationInView.y, minY)
maxX = max(locationInView.x, maxX)
maxY = max(locationInView.y, maxY)
}
Поскольку один жест может состоять из более чем одного касания, в touchesEnded () я планирую таймер на 0,3 «. Если в течение этого периода начинается новое касание, этот таймер становится недействительным, и данные из нового касания добавляются к существующий жест.
Однако, если таймер завершает работу, я вызываю handleGesture (), и именно здесь происходят интересные вещи.
Прежде всего, используя минимальные и максимальные координаты касания, я рассчитываю ширину и высоту ограничительной рамки:
let gestureWidth = abs(minX - maxX)
let gestureHeight = abs(minY - maxY)
Я хочу, чтобы жест был определен в сетке 8 x 8, поэтому, когда cellCount определен как 8, я вычисляю размер ячеек на экране:
let cellWidth = max(gestureWidth / CGFloat(cellCount - 1), 1)
let cellHeight = max(gestureHeight / CGFloat(cellCount - 1), 1)
Затем создайте двумерный массив логических значений для хранения моих данных:
var cells = [[Bool]](count: cellCount, repeatedValue: [Bool](count: cellCount, repeatedValue: false))
Теперь я могу перебирать сохраненные точки обводки и, основываясь на положении верхнего левого угла ограничительной рамки и размерах ячеек, я могу установить значения в моем массиве ячеек равными true, если местоположение касания прошло через ячейку. Если эффект, я превращаю жест, независимо от его размера и соотношения сторон, в нормализованный квадрат 8 х 8.
Причина, по которой я выбрал 8 x 8, состоит в том, что эти 64 булевых значения идеально вписываются в UInt64, который в основном является хешем для жеста и используется для сопоставления с моей библиотекой известных жестов в шаблонах . Таким образом, поскольку клетка представляет собой массив массивов, я использую flatMap () , чтобы сгладить это тем уменьшить () с битовым сдвигом , чтобы создать что большое целое число без знака , которое представляет массив в 8 х 8:
let strokeResult = cells.flatMap({ return $0 }).reduce(UInt64(0))
{
($0 << 1 | ($1 ? 1 : 0))
}
Следующий шаг — сопоставить этот результат с моей библиотекой. Чтобы сделать это, я выполняю логическое «и» в зависимости от результата удара пользователя и UInt64 моих известных ударов и получаю подсчет населения, или подсчет этого. Использование уменьшения () снова, я выбираю элемент с самой высокой popcount :
let bestMatch = patterns.reduce((UInt64(0), UInt64(0), ""))
{
let popcount = ($1.0 & strokeResult).popcount()
return popcount > $0.0 ? (popcount, $1.0, $1.1) : $0
}
Чтобы помочь мне создать библиотеку, у меня есть функция printToConsole (), которая печатает ASCII-версию массива и его значение UInt64 :
Вывод
Это было всего несколько часов работы, но я думаю, что это хорошее основание для простого распознавания жестов с помощью Swift.
Как всегда, исходный код этого проекта доступен в моем репозитории GitHib здесь . Наслаждайтесь!