- PVSM.RU - https://www.pvsm.ru -

Мини-игра с отслеживанием положения головы или как я встретил headtrackr.js

Мини игра с отслеживанием положения головы или как я встретил headtrackr.js
11.02.2013 г. читатель omfg [1] опубликовал статью [2], с которой началось мое знакомство с headtrackr.js [3].
В этом топике я расскажу, как средствами браузера с поддержкой getUserMedia [4] получить координаты и угол наклона головы пользователя перед монитором, как учесть дефекты изображения, принимаемого с веб-камеры и отфильтровать их, и как использовать данную технологию в своих пректах, задействовав лишь html + JavaScript.

Применений этому можно придумать огромное количество. Для простоты, в данном топике мы сделаем мини-игру, в которой змейка будет ползти сверху вниз и менять направление в зависимости от положения головы играющего.
Самым нетерпеливым: результат тут [5].

Знакомство с Headtrack.js

Как пишут авторы на странице проекта [3], headtrack.js является библиотекой для распознавания лица и головы в реальном времени, отслеживания позиции головы и её положения, относительно экрана, использую веб-камеру и стандарт webRTC/getUserMedia.

Попробуем сделать сделать небольшой Hello World:

1) Создадим html файл.

Содержание:

<!doctype html>
<html lang="en">
	<head>
		<title>Детекцийа</title>
		<meta http-equiv="X-UA-Compatible" content="IE=Edge"/>
		<meta charset="utf-8">
		<style>
			body {
				background-color: #f0f0f0;
				margin-left: 10%;
				margin-right: 10%;
				margin-top: 5%;
				width: 40%;
				overflow: hidden;
				font-family: "Helvetica", Arial, Serif;
				position: relative;
			}

		</style>
	<script type="text/javascript" src="js/jquery.js"></script>

				
	</head>
	<body>
			<script src="js/headtrackr.js"></script>
		
		<canvas id="compare" width="320" height="240" style="display:none;"></canvas>
		<video id="vid" autoplay loop width="320" height="240"></video>
		<canvas id="overlay" width="320" height="240"></canvas>
		<canvas id="debug" width="320" height="240"></canvas>
		
		<p id='gUMMessage'></p>
		<p>Что происходит : <span id='headtrackerMessage'></span></p>
		<br>
			<p><input type="button" onclick="htracker.stop();htracker.start();" value="Перезапуск"></input>
		<br/><br/>
		<input type="checkbox" onclick="showProbabilityCanvas()" value=""></input>Матрица вероятностей</p>
		<button id='stop_ang'>Стоп</button>
		<div id='tab_p' style='height:100px; overflow:scroll;'>
		<table id='angles' border=1 cellspacing=0>
		
		</table>
		</div>
		<div id='slider_wrap'>
			<div id='slider'></div>
		</div>
				<script>
		
		  // Получаем элементы video и canvas
		
			var videoInput = document.getElementById('vid');
			var canvasInput = document.getElementById('compare');
			var canvasOverlay = document.getElementById('overlay')
			var debugOverlay = document.getElementById('debug');
			var overlayContext = canvasOverlay.getContext('2d');
			canvasOverlay.style.position = "absolute";
			canvasOverlay.style.top = '0px';
			canvasOverlay.style.zIndex = '100001';
			canvasOverlay.style.display = 'block';
			debugOverlay.style.position = "absolute";
			debugOverlay.style.top = '0px';
			debugOverlay.style.zIndex = '100002';
			debugOverlay.style.display = 'none';
			
			// Определяем сообщения, выдаваемые библиотекой
			
			statusMessages = {
				"whitebalance" : "Проверка камеры или баланса белого",
				"detecting" : "Обнаружено лицо",
				"hints" : "Что-то не так, обнаружение затянулось",
				"redetecting" : "Лицо потеряно, поиск..",
				"lost" : "Лицо потеряно",
				"found" : "Слежение за лицом"
			};
			
			supportMessages = {
				"no getUserMedia" : "Браузер не поддерживает getUserMedia",
				"no camera" : "Не обнаружена камера."
			};
			
			document.addEventListener("headtrackrStatus", function(event) {
				if (event.status in supportMessages) {
					var messagep = document.getElementById('gUMMessage');
					messagep.innerHTML = supportMessages[event.status];
				} else if (event.status in statusMessages) {
					var messagep = document.getElementById('headtrackerMessage');
					messagep.innerHTML = statusMessages[event.status];
				}
			}, true);
			
			// Установка отслеживания
			
			var htracker = new headtrackr.Tracker({altVideo : {ogv : "", mp4 : ""}, calcAngles : true, ui : false, headPosition : false, debug : debugOverlay});
			htracker.init(videoInput, canvasInput);
			htracker.start();
			
			// Рисуем прямоугольник вокруг «пойманного» лица
			
			document.addEventListener("facetrackingEvent", function( event ) {
				// clear canvas
				overlayContext.clearRect(0,0,320,240);
				// once we have stable tracking, draw rectangle
				if (event.detection == "CS") {
					overlayContext.translate(event.x, event.y)
					overlayContext.rotate(event.angle-(Math.PI/2));
					overlayContext.strokeStyle = "#CC0000";
					overlayContext.strokeRect((-(event.width/2)) >> 0, (-(event.height/2)) >> 0, event.width, event.height);
					overlayContext.rotate((Math.PI/2)-event.angle);
					overlayContext.translate(-event.x, -event.y);
					 document.getElementById('ang').innerHTML=Number(event.angle *(180/ Math.PI)-90);
					
					 
				}
			});
			
			// Включениевыключение показа дебаг режима (вероятности)
			function showProbabilityCanvas() {
				var debugCanvas = document.getElementById('debug');
				if (debugCanvas.style.display == 'none') {
					debugCanvas.style.display = 'block';
				} else {
					debugCanvas.style.display = 'none';
				}
			}
		</script>
	</body>
</html>

Скачаем [6] библиотеку и подключим к нашему проекту:

<script src="js/headtrackr.js"></script>

Если мы сейчас откроем наш пример в браузере, то увидим следующую картинку:
Мини игра с отслеживанием положения головы или как я встретил headtrackr.js

И в дебаг-режиме:
Мини игра с отслеживанием положения головы или как я встретил headtrackr.js

Как видим, наше приложение работает, лицо успешно определяется.
В данной статье рассматривается проблема снятия угла поворота головы вокруг нормали к плоскости экрана, поэтому обратим внимание на этот параметр.

Добавим в «facetrackingEvent” следующий код:

document.getElementById('ang').innerHTML=Number(event.angle *(180/ Math.PI)-90);

А в сам Html документа этот:

Угол: <span id='ang'></span>

Теперь наше страница умеет отображать отклонение головы от вертикали в градусах.
Однако, посмотрим на график значений угла, полученных за 20 секунд:
Мини игра с отслеживанием положения головы или как я встретил headtrackr.js

Такая огорчающая картина говорит как о шумах в снимаемом с камеры изображении, так и о погрешностях в вычислениях самой библиотеки.
Без паники, сейчас мы с этим справимся.
Первое что я сделал – попробовал прогнать данные через фильтр скользящего среднего [7], однако картина не обрадовала:
Мини игра с отслеживанием положения головы или как я встретил headtrackr.js

Небольшое улучшение присутствует, но это совсем не то, чего я ожидал.
Вспомнив диплом ( в котором я ставил на датчик тока целую кучу всяких фильтров), решил попробовать фильтр Калмана [8] (во многом понять принцип его работы, в свое время мне помогла статья [9] justserega [10] на хабре, который тогда мне очень помог, ответив на множество глупых и не очень вопросов в личке):
Мини игра с отслеживанием положения головы или как я встретил headtrackr.js

Уже значительно лучше. Однако это фильтр, взятый с произвольными настройками.
Подбираем ковариацию и настраиваем погрешность измерений и получаем:
Мини игра с отслеживанием положения головы или как я встретил headtrackr.js

Просто замечательно. Вот он же, но тут я двигал головой вправо-влево:
Мини игра с отслеживанием положения головы или как я встретил headtrackr.js

Такая картинка нам подходит.

Вот код самого фильтра:

var Q = 2;
var R = 85;
var F = 1;
var H = 1;
var X0;
var P0;
var State = 0;
var Covariance = 0.1;

function SetState(state_s,covariance_s){
State = state_s;
Covariance = covariance_s;
}
function Correct(data)
{
X0 = F*State;
P0 = F*Covariance*F + Q;

var K = H*P0/(H*P0+R);
State = X0 + K*(data - H*X0);
Covariance = (1 - K*H)*P0;
}

SetState(0,0.1);

В финальном архиве он лежит отдельным файлом kalman.js

Испытание

Для испытания полученной системы, я сделал ползунок, двигающийся влево или вправо, в зависимости от наклона головы:
Мини игра с отслеживанием положения головы или как я встретил headtrackr.js

Вдохновленный результатами, решил набросать что-то более «визуально понятное», с точки зрения отображения плавности изменения координат:

Код, рисующий на canvas "змейку"

var angles = [0];
var canvas = document.getElementById("canvas");
var rc=document.getElementById("canvas").getContext('2d');
			
	
rc.clearRect(0, 0, canvas.width, canvas.height);
setInterval(function(){redraw(angles);},20);		
function redraw(angles){
rc.clearRect(0, 0, canvas.width, canvas.height);
rc.beginPath();
	for (var i=0;i<=angles.length-1;i++){
		
		rc.lineTo(angles[i]+150,i+0);
		rc.moveTo(angles[i]+150,i+0);

	
	}
	rc.arc(angles[angles.length-1]+153, 200, 6, 0 , 2 * Math.PI, false);
	rc.stroke();
	rc.moveTo(angles[angles.length-1]+150,200);
	rc.fillStyle = 'green';
      rc.fill();
		
}

Массив angles накапливает и хранит 200 последних значений угла, при получении новых значений делается сдвиг влево:

 angles[angles.length] = (angle*1.5);
 if (angles.length > 200){
		angles.shift();
	}

Результат:
Мини игра с отслеживанием положения головы или как я встретил headtrackr.js

Если змейка в примере начинает судорожно дергаться — попробуйте отодвинуться подальше от камеры и перезагрузить страницу.
Архив с рабочим примером можно скачать тут [11]
Запускать файл 1.html
Внимание, пример может не работать, если запускать его с локального компьютера, поэтому тут можно посмотреть вживую [5].
Эксель файл со снятыми значениями для чистого сигнала и пропущенного через фильтры и диаграммы для всего этого тут: http://goo.gl/FWMBE [12]

Дальше думаю либо развивать тему в сторону pseudo-3D [13], либо доработаю пример из статьи до чего-то более серьезного (меню, управляемое взглядом? Перемещение по карте наклонами головы? etc.)

Спасибо за внимание, хорошего дня.

Автор: Paul_Smith

Источник [14]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/javascript/27043

Ссылки в тексте:

[1] omfg: http://habrahabr.ru/users/omfg/

[2] статью: http://habrahabr.ru/post/169051/

[3] headtrackr.js: https://github.com/auduno/headtrackr/

[4] getUserMedia: http://dev.w3.org/2011/webrtc/editor/getusermedia.html

[5] тут: http://test.headtrack.p.ht/

[6] Скачаем: https://github.com/auduno/headtrackr/blob/master/headtrackr.js

[7] фильтр скользящего среднего: http://ru.wikipedia.org/wiki/%D0%A1%D0%BA%D0%BE%D0%BB%D1%8C%D0%B7%D1%8F%D1%89%D0%B0%D1%8F_%D1%81%D1%80%D0%B5%D0%B4%D0%BD%D1%8F%D1%8F_(%D1%84%D0%B8%D0%BB%D1%8C%D1%82%D1%80)

[8] фильтр Калмана: http://ru.wikipedia.org/wiki/%D0%A4%D0%B8%D0%BB%D1%8C%D1%82%D1%80_%D0%9A%D0%B0%D0%BB%D0%BC%D0%B0%D0%BD%D0%B0

[9] статья: http://habrahabr.ru/post/140274/

[10] justserega: http://habrahabr.ru/users/justserega/

[11] тут: http://dl.dropbox.com/u/29076539/headtrack/htrack.zip

[12] http://goo.gl/FWMBE: http://goo.gl/FWMBE

[13] pseudo-3D: http://en.wikipedia.org/wiki/Head-coupled_perspective

[14] Источник: http://habrahabr.ru/post/169249/