Форум Flasher.ru
Ближайшие курсы в Школе RealTime
Список интенсивных курсов: [см.]  
  
Специальные предложения: [см.]  
  
 
Регистрация Блоги Правила Справка Пользователи Календарь Поиск рулит! Сообщения за день Все разделы прочитаны
 

Вернуться   Форум Flasher.ru > Блоги > elder_Nosferatu

Рейтинг: 5.00. Голосов: 2.

Жестикуляция. Первые шаги.

Запись от elder_Nosferatu размещена 18.10.2015 в 00:58

Всякие новомодные тачскрин-девайсы познакомили нас с жестами, как способом взаимодействия с приложениями, но... Предложенное нам определение жеста слегка обрезано. Никаких тебе спиралей, звездочек и прочих крякозябр, которые можно изобразить единственным взмахом пальца (манипулятора). А были же и хорошие времена!

Не знаю, как у кого, а моему первому знакомству с жестами я обязан замечательной игре Black&White славно/печально известного Питера Молинье. В ней нужно выполнять роль бестелесного божества. ГУИ позволял кликами по иконкам создавать чудеса, но так же была возможность чудесить и жестами. Такой способ был сложноват, так как без должной сноровки было трудно воспроизводить нужные жесты. Само собой управление мышкой не такое уж и удобное, но больше всего мешало то, что фигуру отображаемого жеста можно было увидеть лишь после удачного выполнения.

Был еще один отличный пример внедрения жестикуляций но теперь уже в качестве львиной доли взаимодействия с миром игры. Речь идет об игре Тургор (eng: [COLOR="rgb(255, 140, 0)"]Tension[/color]) от московской студии Ice-Pick Lodge. Здесь жестикуляция была безальтернативным методом управления, но и реализация была более удобной. Все взмахи мышью имитровали рисование кистью, так что след нанесенной краски четко давал понять, что я рисую, а значит и рисовать то, что нужно стало немного легче. Трудности управления мышкой это не убрало, но тем не менее.

Эти примеры вроде бы сильно меня вдохновляли, но я не знал куда можно прикрутить такую фичу, да и задачу "распознания жестов" я воспринимал слишком буквально, чтобы представить, с какой стороны к ней подобраться. Но все же я нашел лазейку, о которой стоит предупредить.

/* - DISCLAIMER - */
На офф-сайте игры Тургор я нашел флеш-рисовалку (http://www.tension-game.com/drawing_ru.php), которая демонстрирует основную фишку геймплея. В то время я уже был знаком с флешом и с... декомпиляторами. Так что я сразу взял себе эту рисовалку на прицел в качестве пособия по распознанию жестов. И вот прошло несколько лет и я таки совершил богомерзкий акт взлома этого продукта с целью познания чужой реализации решения этой интересной задачи. Тех, кто копит бонусные баллы для попадания в рай для флешеров, прошу покинуть мой блог, так как я собираюсь поделиться результатами флеш-декомпиляции.
/* - END OF DISCLAIMER - */


Короче, ковырнул я немного полученный код и увидел хитрую идею! Жесты? А зачем? Пусть игрок думает, что он имеет дело с жестами, а мы будем просто работать с набором точек (не обязательно упорядоченным), через которые может пройти этот жест. И тогда просто остается сравнить готовый (эталонный) набор точек с тем, который введет своим взмахом игрок. И не нужно следить за следованием от точки к точке. И нейросетей для распознания тоже ваять не придется. Да, решение далеко от идеала, но оно проверено в боевых условиях (игра вышла и в СНГ, и в Европе), а результаты оказались неплохими.

Суть метода:
* Рисуем жест и делим весь путь на множество точек.
* Сжимаем полученную фигуру до определенных габаритов (1х1), чтобы при сравнении отпали претензии типа "у меня выше", "у меня шире" или "а у меня еле на экране поместилась" - главное, чтобы было похоже.
* Определяем максимальную погрешность сравнения (допустимое расстояние между ближайшими точками сравниваемых жестов).
* Просим кого либо нарисовать одним росчерком похожую фигуру (регулярно хватаем точи по сигналам MOUSE_MOVE или ENTER_FRAME).
* Так же сжимаем вторую фигуру до оговоренных габаритов.
* Сравниваем две полученные фигуры, то есть два набора точек: для каждой точки одной фигуры пытаемся найти хотя бы одну точку другой фигуры, которая лежит не далее определенного допуска. Если это удается, то меняем фигуры местами и повторяем проверку. Две успешных проверки подсказывают, что оба набора точек могут лежать на похожих жестах (с заданной погрешностью конечно).
Вот и вся премудрость.

Зачем две проверки? Пример:
1. Шаблон - окружность, а введенный жест - дуга на 270 градусов. Каждая точка жеста найдет соседа при наложении, но жест не закончен;
2. Шаблон - дуга на 270 градусов, а введенный жест - окружность. Каждая точка шаблона найдет соседа при наложении, но жест немного мудренее (как сравнивать "0" и "9" - у обеих петля, но в "9" есть еще и хвостик);

Проанализируем же этот метод распознания:
-Достоинства-
* Такой простой, что и не стразу в голову приходит
-Недостатки-
* По сути не является распознанием именно ЖЕСТА (пути, который проходит через определенные точки).
* Хоть и игнорирует горизонтально-вертикальные искажения всей фигуры, но не учитывает поворот.
* Для удобства юзверя нужно много вариантов "почерка" (парни из Ice-Pick Lodge соорудили по 30-40 вариантов начертания для каждого жеста).
* Даже визуально совпадающие фигуры (хоть бы даже с нулевой погрешностью) могут не пройти проверку сравнения, если на одном участке сильно отличается плотность опорных точек у сравниваемых жестов. К примеру в шаблонном жесте точки равномерно распределены на расстоянии погрешности. И в этом же шаблоне есть почти прямой участок, который при попытке воспроизведения вручную можно пройти резким росчерком. Если ФПС будет не достаточно высок, то такой резкий росчерк зафиксирует не так уж и много точек, хоть и пройдет он тютилька в тютильку с шаблоном. Может оказаться, что между двумя ближайшими точками жеста, расстояние между которыми составляет скажем размер десятикратной погрешности лежит добрая сотня точек шаблона. А это значит, что мало какой из этих точек достанется на столько близкий сосед, чтобы она Станиславского цитировала. Это можно конечно обойти, если самостоятельно напихать точек между сильно удаленными, но в этом то и недостаток метода, что без лишних телодвижений ему не понять очевидных вещей.
* Вроде были еще претензии, но и так хватает.

Чтобы немного продемонстрировать описанную ересь предлагаю поюзать мой тестовый полигон, а потом и с самым важным классом познакомиться, который повторяет спионеренную идею, но в вольном пересказе (Инфу о жесте завернул в класс, отдельные задачки разбил на функции избавился от лишних объектов и т.д.). Класс как бы рабочий, что доказывает испытательный полигон, но он писался с прицелом на читабельность и понимабельность, так что есть еще куда выкручивать производябельность (если библиотека жестов будет внушительной, то лаги во время сравнения будут обеспечены).

-Тестовый полигон-
В приложении 2 окна - холст и библиотека шаблонных жестов.
С холстом все понятно, указал погрешность и айда рисовать. Получилось - увидишь шаблон, которому соответствует рисованное, а нет - просто сетку 10х10 натянутую на жест. Ну и название вверху будет светиться.
В библиотеке же рассортированы жесты за авторством Ice-Pick Lodge. Жесты сложены в группы, каждый элемент которой представляет разное начертание одного и того же символа. Клики по этих элементах нарисуют жест в окошке, а если окошко не очищать (автоочищать), то можно наложить несколько жестов из одной группы для наглядности различия.
Gestures.swf   (149.4 Кб)


-Собственно класс-
Код AS3:
package {
 
	/**
	 * Экземпляр класса умеет хранить инфу о точках жеста, а так же сравнивать себя с другим экземпляром.
	 * ...
	 * @author elder_Nosferatu
	 */
	public class GuestureData {
		/** X-координаты точек жеста. Модификатор internal нужен лишь для визуализации. */
		internal var pntsX:Array;
 
		/** Y-координаты точек жеста. Модификатор internal нужен лишь для визуализации. */
		internal var pntsY:Array;
 
		/** Количестов точек жеста. */
		private var _size:uint;
 
		/** Имя жеста */
		private var _name:String;
 
		/** Признак компрессии (нормализации) данных жеста. */
		private var _compressed:Boolean;
 
		/**
		 * Собственно конструктор...
		 * @param	data	готовая инфа о жесте в формате JSON или XML.
		 * 		JSON:
		 * 			{
		 * 				"guestures":[
		 * 					{
		 * 						"name":"guesture0",
		 *						"points":[
		 * 							[pnt0x, pnt0y],
		 * 							[pnt1x, pnt1y],
		 * 							...
		 * 						]
		 * 					},
		 * 					...
		 * 				]
		 * 			}
		 * 		XML:
		 * 			<guestures>
		 * 				<guesture name="guesture0" >
		 * 					<pnt "x"="pnt0x" "y"="pnt0y" />
		 * 					<pnt "x"="pnt1x" "y"="pnt1y" />
		 * 					...
		 * 				</guesture>
		 * 			</guestures>
		 */
		public function GuestureData(data:* = null) {
			_name = null;
			pntsX = [];
			pntsY = [];
 
			if (data is XML) parseXML(data);
			else if (data is Object) parseObject(data);
		}
 
		/**
		 * Парсинг данных о жесте в формате JSON.
		 * @param	data	обьект с инфой.
		 */
		public function parseObject(data:Object):void {
			var arr:Array = data["points"];
			var item:Array;
 
			_name = data["name"];
			_size = arr.length;
			pntsX.length = _size;
			pntsY.length = _size;
 
			for (var i:uint = 0, li:uint = arr.length; i < li; ++i) {
				item = arr[i];
				pntsX[i] = item[0];
				pntsY[i] = item[1];
			}
 
			compress();
		}
 
		/**
		 * Парсинг данных о жесте в формате XML (не проверял правильность ибо влом и не люблю XML).
		 * @param	data	XML-обьект с инфой.
		 */
		public function parseXML(data:XML):void {
			var arr:XMLList = data.children();
			var item:XML;
 
			_name = data.attribute("name");
			_size = arr.length;
			pntsX.length = _size;
			pntsY.length = _size;
 
			for (var i:uint = 0, li:uint = arr.length; i < li; ++i) {
				item = arr[i] as XML;
				pntsX[i] = item.attribute("x");
				pntsY[i] = item.attribute("y");
			}
 
			compress();
		}
 
		/**
		 * Сбрасывает инфу о жесте.
		 */
		public function clear():void {
			_name = null;
			_size = 0;
			_compressed = false;
			pntsX.length = 0;
			pntsY.length = 0;
		}
 
		/**
		 * Ручное добавление инфы о следующей точке жеста.
		 * @param	x	x-координата точки.
		 * @param	y	y-координата точки.
		 */
		public function addPoint(x:Number, y:Number):void {
			pntsX[_size] = x;
			pntsY[_size] = y;
			++_size;
		}
 
		/**
		 * Компрессия (нормализация) данных о жесте. Выполнять после полного заполнения инфы о жесте.
		 * Компрессия преобразует прямоугольник, в который вписан жест в квадрат со стороной, равной единице.
		 */
		public function compress():void {
			var minX:Number = Math.min.apply(null, pntsX);
			var minY:Number = Math.min.apply(null, pntsY);
			var maxX:Number = Math.max.apply(null, pntsX);
			var maxY:Number = Math.max.apply(null, pntsY);
			var dx:Number = (maxX - minX) || 1;
			var dy:Number = (maxY - minY) || 1;
 
			if (minX == 0 || minY == 0 || maxX == 1 || maxY == 1) return;
 
			for (var i:uint = 0; i < _size; ++i) {
				pntsX[i] = (pntsX[i] - minX) / dx;
				pntsY[i] = (pntsY[i] - minY) / dy;
			}
			_compressed = true;
		}
 
		/** Хвастаемся именем жеста. */
		public function get name():String {
			return _name;
		}
 
		/** Хвастаемся размером (количеством точек) жеста. */
		public function get size():uint {
			return _size;
		}
 
		/**
		 * Сравниваемся на другой жест с учетом допустимого отклонения.
		 * @param	instance	жест для сравнения.
		 * @param	accuracy	максимальное отклонение ближайших точек нормализованых жестов.
		 * @return	результат сравнения.
		 */
		public function compare(instance:GuestureData, accuracy:Number):Boolean {
			var sqr:Number = accuracy * accuracy;
			var res:Boolean;
 
			// А все ли наши точки "совпадают" с точками жеста instance.
			res = checkMatching(this, instance, sqr);
			// Если нет, то что мы ждесь забыли?!!
			if (!res) return false;
 
			// А что если в жесте instance, есть точки, которые не "совпадают" с нашими.
			res = checkMatching(instance, this, sqr);
			return res;
		}
 
		/**
		 * Проверяем существование собственной точки, квадрат расстояния от (x;y) которой не превышает sqrDistance.
		 * @param	pntX	x-координата точки для проверки.
		 * @param	pntY	y-координата точки для проверки.
		 * @param	sqrDistance	квадрат максимально допустимого расстояния.
		 * @return	результат поиска достаточно близкой точки.
		 */
		private function isCloseEnough(pntX:Number, pntY:Number, sqrDistance:Number):Boolean {
			for (var i:uint = 0; i < _size; ++i) {
				var dx:Number = pntX - pntsX[i];
				var dy:Number = pntY - pntsY[i];
				var sqr:Number = dx * dx + dy * dy;
				if (sqr <= sqrDistance) return true;
			}
			return false;
		}
 
		/**
		 * Проверка точек жеста sample, на "соответсвие" точкам жеста standart с учетом квадрата максимально допустимого расстояния.
		 * @param	standart	жест-эталон.
		 * @param	sample		жест-пробник.
		 * @param	sqrDistance	квадрат масимально допустимого рассояния между точками.
		 * @return	результат проверки.
		 */
		private static function checkMatching(standart:GuestureData, sample:GuestureData, sqrDistance:Number):Boolean {
			for (var i:uint = 0, li:uint = standart.size; i < li; ++i) {
				var stdX:Number = standart.pntsX[i];
				var stdY:Number = standart.pntsY[i];
				var isClose:Boolean = sample.isCloseEnough(stdX, stdY, sqrDistance);
				if (!isClose) return false;
			}
			return true;
		}
 
	}
 
}
Вложения
Тип файла: swf Gestures.swf (149.4 Кб, 441 просмотров)
Всего комментариев 4

Комментарии

Старый 18.10.2015 17:44 КорДум вне форума
КорДум
 
Аватар для КорДум
Не нашел в примере квадрата, он определяется как круг всегда? И получилось нарисовать AboutWorld только с десятого раза )
Старый 18.10.2015 18:28 MikroAcse вне форума
MikroAcse
 
Аватар для MikroAcse
Насчёт квадрата тоже интересно. А вот AboutWorld определяется нормально каждый раз, гы
Старый 18.10.2015 21:47 elder_Nosferatu вне форума
elder_Nosferatu
 
Аватар для elder_Nosferatu
Пошаманьте с точностью распознавания. Если допуск сильно маленький, то вряд ли вообще что то распознает, а если большой, то и круг и квадрат и звезда все равно под ближайший жест подойдет
Старый 18.10.2015 21:57 elder_Nosferatu вне форума
elder_Nosferatu
 
Аватар для elder_Nosferatu
К стати, я писал, что жесты сам не создавал, я просто скомуниздил хмл-файл с описанием 12 символов, которые использовались в игре. Авторы так старались угодить и каллиграфам и криворучкам, что настрочили почти 400 жестов. Я решил, что для демонстрации этого хватит
 
Последние записи от elder_Nosferatu

 


Часовой пояс GMT +4, время: 06:39.


Copyright © 1999-2008 Flasher.ru. All rights reserved.
Работает на vBulletin®. Copyright ©2000 - 2019, Jelsoft Enterprises Ltd. Перевод: zCarot
Администрация сайта не несёт ответственности за любую предоставленную посетителями информацию. Подробнее см. Правила.