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


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

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


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


    Как пишут авторы на странице проекта, 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>
    
    



    Скачаем библиотеку и подключим к нашему проекту:
    <script src="js/headtrackr.js"></script>
    


    Если мы сейчас откроем наш пример в браузере, то увидим следующую картинку:


    И в дебаг-режиме:


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

    Добавим в «facetrackingEvent” следующий код:
    document.getElementById('ang').innerHTML=Number(event.angle *(180/ Math.PI)-90);
    


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

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


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


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


    Небольшое улучшение присутствует, но это совсем не то, чего я ожидал.
    Вспомнив диплом ( в котором я ставил на датчик тока целую кучу всяких фильтров), решил попробовать фильтр Калмана, который открыл для меня Курносов Д.А. (преподаватель в моём университете), ( также, позднее, во многом понять принцип его работы с точки зрения программного кода, в свое время мне помогла статья justserega на хабре, который тогда мне очень помог, ответив на множество глупых и не очень вопросов в личке):


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


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


    Такая картинка нам подходит.
    Вот код самого фильтра:
    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

    Испытание


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


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

    Код, рисующий на 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();
    	}
    
    


    Результат:


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

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

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

    UPD: Бесплатный хостинг накрылся, страница на github: http://paulsmith220.github.com/htrack/
    Поделиться публикацией
    Ой, у вас баннер убежал!

    Ну. И что?
    Реклама
    Комментарии 38
      +1
      А как с поддержкой мобильных браузеров?
        +3
        Как только они начнут поддерживать getUserMedia :)
          +2
            0
            Слушайте, а у вас работает caniuse.com? У меня он просто много месяцев не открывается. Вот прямо сейчас у вас он работает?
              0
              Да, конечно. Странности, попробуйте пропинговать и взять wget'ом. Или зайти через анонимайзер.
                0
                Чудесаааа… «Сафари» (моим основным браузером) не открывается!
          0
          Ваша ссылка пишет «Лимит Процессорной Памяти Превышен». Я бы в личку это написал, да уж больно смешная формулировка.
            0
            Хм, бесплатный хостинг себя исчерпал.
            Попробую решить проблему
              +7
              Если узнаете, что такое «лимит процессорной памяти», обязательно сообщите :)
                0
                Да, заставляет задуматься :)
                  0
                  URL тоже странный prevushenie_resursov_processora
              0
              Если там серверной части нету, то, может, стоило куда-нибудь на народ положить? :) А то ваш хостинг, очевидно, не справился
                +2
                Дык
                > Архив с рабочим примером можно скачать тут — dl.dropbox.com/u/29076539/headtrack/htrack.zip
                  0
                  Народ не дает создавать сайты (уехал на ucoz), по-крайней мере меня он не пускает. jsfiddle не позволяет подключить камеру.
                    0
                    Что-то на локалхосте у меня ничего не заработало :( Проверял в ФФ 17.0.1 и Chrome. Под линуксом
                      0
                      Внимание, пример может не работать, если запускать его с локального компьютера, поэтому тут можно посмотреть вживую.

                      Да, браузер не подключает камеру, если стартовать с диска — кидаю в денвер и все работает.
                        –1
                        Все равно не работает ничего :) (запускаю через апач, естественно)
                        Пример с гитхаба тоже никак :( Эх…
                          +2
                          В семерочке в хроме все заработало :) Насладился, спасибо!
                      0
                      Если подскажите бесплатный хостинг html, который выдержит хабраэффект — буду очень благодарен. Не сомневался, что так случится рано или поздно, поэтому и приложил архив.
                        +1
                        github.com, тудаже и исходники
                        0
                        Если подскажите бесплатный хостинг

                        appfog.com

                        Не совсем привычный способ аплоада (чрез их консольную утилку). но если притыкаться то весьма удобно.
                      0
                      А это будет работать, если лицо на фоне ковра?
                        +2
                        А вы попробуйте.
                        Вообще, ничего этому не мешает, только если ковер не с вышитыми на нём лицами.
                        +1
                        Сделал «чебурашку», приставив ладони к ушам — задетектило что харя у меня очень широкая :) Вцелом интересная статья, спасибо большое автору. Пока моя бедная фантазия предложила лишь одно применение для браузера — это горизонтальный склолл страницы головой (при вертикальном на большой странице можно вывих шеи получить).
                        Какие ещё есть идеи для применения?
                          0
                          Игры сразу напрашиваются. Псевдо 3D. Я подумывал о меню, управляемом наклоном головы для домашнего сервера — (рраз — и выключил свет в ванной )

                          Чебурашка это да, более того, если достаточно правдоподобно нарисовать лицо ручкой на бумаге, затенив глаза, и вырезать эту голову — система кряхтя, но опознает в ней лицо :)
                          +3
                          Отличный вариант для игры. В обед разминать шею. Полезное с приятным (поиграть/физические упражнения).
                            –3
                            щщи сложнейшие
                              0
                              Алгоритм определения лица пока достаточно сырой. Сравниваю с теми, что вшиты в большинство современных камер.

                              Даже если в фокусе ничего не шевелится, рамка периодически «прыгает». Лицо, судя по всему, определяет не столько по форме, сколько ориентируется на «светлое пятно в центре кадра». Захватывает шею, руки и любые другие светлые объекты, из-за этого страдает точность. Когда поворачивается голова, шея остается на месте, из-за этого угол поворота определяется с большими искажениями. И это на близкой к идеалу картинке: без шума и размытия.

                              Зато алгоритм фильтрации понравился, хорошо сглаживает ошибки.

                              Почему-то напрочь отказалось работать в ФФ, только в Хроме работает.
                                +2
                                Замечу, что библиотеку для распознавания я не реализовывал, а только использовал (кстати, это единственное работающее JavaScript, а значит Client-Side решение, которое я видел, поэтому и взял её). Я реализовывал именно фильтрацию данных, т.к. взгляд зацепило это подрагивание рамки. Быть может авторам имеет смысл включить подобный моему фильтр непосредственно перед выводом данных.
                                0
                                Если сделать так, чтобы эта штука распознавала взмах рукой (что она и так делает, но заточена все равно под лицо, поэтому нестабильно), то можно сделать слайдшоу и листать фотографии/слайды жестами как на всяких устройствах с тачскрином, только без тачскрина.
                                  +3
                                  Решение очень сырое, но все же посмотрите.
                                    0
                                    Мне нравится!
                                      0
                                      О, смотрю на аппфоге захостили. Поделитесь потом, пожалуйста, информацией о том как «хабраэффект» перенесли…
                                        0
                                        Мопед не мой
                                    0
                                    Я спи… взял код из этой статьи и сдлал игру
                                    https://app.blastorq.pp.ua/WebCamSpacecraft/
                                    Нудно наклонять голову чтоб управлять кораблем)

                                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                    Самое читаемое