clmtrackr
Version:
Javascript library for precise tracking of facial features via Constrained Local Models
504 lines (457 loc) • 17.6 kB
HTML
<html lang="en">
<head>
<title>Face deformation</title>
<meta charset="utf-8">
<link href="./styles/bootstrap.min.css" rel="stylesheet" type="text/css">
<style>
@import url(https://fonts.googleapis.com/css?family=Lato:300italic,700italic,300,700);
body {
font-family: 'Lato';
background-color: #f0f0f0;
margin: 0px auto;
max-width: 1150px;
}
#overlay, #canvasel {
position: absolute;
top: 0px;
left: 0px;
-o-transform : scaleX(-1);
-webkit-transform : scaleX(-1);
transform : scaleX(-1);
-ms-filter : fliph; /*IE*/
filter : fliph; /*IE*/
}
#videoel {
-o-transform : scaleX(-1);
-webkit-transform : scaleX(-1);
transform : scaleX(-1);
-ms-filter : fliph; /*IE*/
filter : fliph; /*IE*/
}
#container, #webglcontainer {
position : relative;
width : 370px;
/*margin : 0px auto;*/
}
#content {
margin-top : 70px;
margin-left : 100px;
margin-right : 100px;
max-width: 950px;
}
h2 {
font-weight : 400;
}
.nogum {
display : none;
}
.btn {
font-family: 'Lato';
font-size: 16px;
}
.hide {
display : none;
}
.nohide {
display : block;
}
</style>
<script>
// getUserMedia only works over https in Chrome 47+, so we redirect to https. Also notify user if running from file.
if (window.location.protocol == "file:") {
alert("You seem to be running this example directly from a file. Note that these examples only work when served from a server or localhost due to canvas cross-domain restrictions.");
} else if (window.location.hostname !== "localhost" && window.location.protocol !== "https:"){
window.location.protocol = "https";
}
</script>
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-32642923-1']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
</head>
<body>
<script src="./js/libs/dat.gui.min.js"></script>
<script src="./js/libs/utils.js"></script>
<script src="./js/libs/webgl-utils.js"></script>
<script src="../build/clmtrackr.js"></script>
<script src="../models/model_pca_20_svm.js"></script>
<script src="./js/libs/Stats.js"></script>
<script src="./js/face_deformer.js"></script>
<div id="content">
<h2>Face deformation</h2>
<div id="container" class="nohide">
<video id="videoel" width="370" height="288" preload="auto" playsinline autoplay></video>
<canvas id="canvasel" width="370" height="288" class="hide"></canvas>
<canvas id="overlay" width="370" height="288"></canvas>
</div>
<div id="webglcontainer" class="hide">
<canvas id="webgl" width="306" height="342"></canvas>
</div>
<br/>
<div id="buttons">
<input class="btn" type="button" value="wait, loading video" disabled="disabled" onclick="startVideo()" id="startbutton"></input>
</div>
<p id="score"></p>
<div id="text">
<p>This is an example of face deformation using the javascript library <a href="https://github.com/auduno/clmtrackr"><em>clmtrackr</em></a>.</p>
<p>Note that this example needs support for WebGL, and works best in Google Chrome.</p>
<div id="gum" class="nohide">
<p>To try it out:
<ol>
<li>allow the page to your use webcamera</li>
<li>make sure that your face is clearly visible in the video, and click start</li>
<li>keep your head still until the model fits your face properly</li>
<li>choose a preset or fiddle with the parameters on the right hand side, to deform the captured face</li>
<ol>
</p>
</div>
<div id="nogum" class="hide">
<p>
There was some problem trying to capture your webcamera, please check that your browser supports WebRTC. Instead we'll use a static image. To try it out:
<ol>
<li>click start</li>
<li>wait till the model fits the face</li>
<li>choose a preset or fiddle with the parameters on the right hand side, to deform the captured face</li>
</ol>
</p>
</div>
</div>
<a href="https://github.com/auduno/clmtrackr"><img style="position: absolute; top: 0; left: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_left_green_007200.png" alt="Fork me on GitHub"></a>
<p id='gUMMessage'></p>
<script>
var vid = document.getElementById('videoel');
var vid_width = vid.width;
var vid_height = vid.height;
var overlay = document.getElementById('overlay');
var overlayCC = overlay.getContext('2d');
/*********** Setup of video/webcam and checking for webGL support *********/
function enablestart() {
var startbutton = document.getElementById('startbutton');
startbutton.value = "start";
startbutton.disabled = null;
}
var insertAltVideo = function(video) {
// insert alternate video if getUserMedia not available
if (supports_video()) {
if (supports_webm_video()) {
video.src = "./media/franck.webm";
} else if (supports_h264_baseline_video()) {
video.src = "./media/franck.mp4";
} else {
return false;
}
fd.init(document.getElementById('webgl'));
return true;
} else return false;
}
var insertAltImage = function() {
var canvas = document.getElementById('canvasel');
var cc = canvas.getContext('2d');
var img = new Image();
img.onload = function() {
cc.drawImage(img, 0, 0, vid_width, vid_height);
};
img.src = './media/franck_02221.jpg';
vid.className = "hide";
vid = canvas;
canvas.className = "nohide";
var startbutton = document.getElementById('startbutton');
startbutton.onclick = function() {
ctrack.start(vid);
drawLoop();
}
startbutton.value = "start";
startbutton.disabled = null;
}
function adjustVideoProportions() {
// resize overlay and video if proportions are not 4:3
// keep same height, just change width
var proportion = vid.videoWidth/vid.videoHeight;
vid_width = Math.round(vid_height * proportion);
vid.width = vid_width;
overlay.width = vid_width;
}
// check whether browser supports webGL
var webGLContext;
var webGLTestCanvas = document.createElement('canvas');
if (window.WebGLRenderingContext) {
webGLContext = webGLTestCanvas.getContext('webgl') || webGLTestCanvas.getContext('experimental-webgl');
if (!webGLContext || !webGLContext.getExtension('OES_texture_float')) {
webGLContext = null;
}
}
if (webGLContext == null) {
alert("Your browser does not seem to support WebGL. Unfortunately this face deformation example depends on WebGL, so you'll have to try it in another browser. :(");
}
function gumSuccess( stream ) {
// add camera stream if getUserMedia succeeded
if ("srcObject" in vid) {
vid.srcObject = stream;
} else {
vid.src = (window.URL && window.URL.createObjectURL(stream));
}
vid.onloadedmetadata = function() {
adjustVideoProportions();
fd.init(document.getElementById('webgl'));
vid.play();
}
vid.onresize = function() {
adjustVideoProportions();
fd.init(document.getElementById('webgl'));
if (trackingStarted) {
ctrack.stop();
ctrack.reset();
ctrack.start(vid);
}
}
}
function gumFail() {
// fall back to video if getUserMedia failed
insertAltVideo(vid);
document.getElementById('gum').className = "hide";
document.getElementById('nogum').className = "nohide";
alert("There was some problem trying to fetch video from your webcam, using a static image instead.");
}
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
window.URL = window.URL || window.webkitURL || window.msURL || window.mozURL;
// check for camerasupport
if (navigator.mediaDevices) {
navigator.mediaDevices.getUserMedia({video : true}).then(gumSuccess).catch(gumFail);
} else if (navigator.getUserMedia) {
navigator.getUserMedia({video : true}, gumSuccess, gumFail);
} else {
insertAltImage();
//insertAltVideo(vid);
document.getElementById('gum').className = "hide";
document.getElementById('nogum').className = "nohide";
alert("Your browser does not seem to support getUserMedia, using a static image instead.");
}
vid.addEventListener('canplay', enablestart, false);
/*********** Code for face tracking and face deformation *********/
var ctrack = new clm.tracker();
ctrack.init(pModel);
var trackingStarted = false;
function startVideo() {
// start video
vid.play();
// start tracking
ctrack.start(vid);
trackingStarted = true;
//start drawing faces
drawLoop();
}
var positions;
var fd = new faceDeformer();
function drawLoop() {
// track in video
positions = ctrack.getCurrentPosition();
overlayCC.clearRect(0, 0, vid_width, vid_height);
if (positions) {
ctrack.draw(overlay);
}
// hide buttons
elem = document.getElementById('buttons');
elem.setAttribute('class', 'hide');
// show message
var scoreel = document.getElementById('score');
scoreel.innerHTML = "Please keep head still while model fits";
var pn = ctrack.getConvergence();
if (pn < 1.8) {
requestAnimFrame(setupFaceDeformation);
scoreel.innerHTML = "";
if (vid.tagName == "VIDEO") {
vid.pause();
}
} else {
requestAnimFrame(drawLoop);
}
}
var ph, gui;
var rotation = 0;
var scale = 3;
var xOffset = -10;
var yOffset = 0;
function setupFaceDeformation() {
// draw face deformation model
positions = ctrack.getCurrentPosition();
overlayCC.clearRect(0, 0, vid_width, vid_height);
ctrack.draw(overlay);
fd.load(vid, positions, pModel);
fd.draw(positions);
// hide video
var elem = document.getElementById('container');
elem.setAttribute('class', 'hide');
// show facial deformation element
elem = document.getElementById('webglcontainer');
elem.setAttribute('class', 'nohide');
// hide message element
document.getElementById('score').setAttribute('class', 'hide');
// set up controls
ph = new parameterHolder();
gui = new dat.GUI();
var guiSelect = gui.add(ph, 'presets', presets);
var gui1 = gui.add(ph, 'param1', -50, 50).listen();
var gui2 = gui.add(ph, 'param2', -20, 20).listen();
var gui3 = gui.add(ph, 'param3', -20, 20).listen();
var gui4 = gui.add(ph, 'param4', -20, 20).listen();
var gui5 = gui.add(ph, 'param5', -20, 20).listen();
var gui6 = gui.add(ph, 'param6', -20, 20).listen();
var gui7 = gui.add(ph, 'param7', -20, 20).listen();
var gui8 = gui.add(ph, 'param8', -20, 20).listen();
var gui9 = gui.add(ph, 'param9', -20, 20).listen();
var gui10 = gui.add(ph, 'param10', -20, 20).listen();
var gui11 = gui.add(ph, 'param11', -20, 20).listen();
var gui12 = gui.add(ph, 'param12', -20, 20).listen();
var gui13 = gui.add(ph, 'param13', -20, 20).listen();
var gui14 = gui.add(ph, 'param14', -20, 20).listen();
var gui15 = gui.add(ph, 'param15', -20, 20).listen();
var gui16 = gui.add(ph, 'param16', -20, 20).listen();
var gui17 = gui.add(ph, 'param17', -20, 20).listen();
var gui18 = gui.add(ph, 'param18', -20, 20).listen();
var gui19 = gui.add(ph, 'param19', -20, 20).listen();
var gui20 = gui.add(ph, 'param20', -20, 20).listen();
var guiGrid = gui.add(ph, 'draw_grid', false);
var guiFace = gui.add(ph, 'draw_face', true);
gui1.onChange(drawDeformedFace);
gui2.onChange(drawDeformedFace);
gui3.onChange(drawDeformedFace);
gui4.onChange(drawDeformedFace);
gui5.onChange(drawDeformedFace);
gui6.onChange(drawDeformedFace);
gui7.onChange(drawDeformedFace);
gui8.onChange(drawDeformedFace);
gui9.onChange(drawDeformedFace);
gui10.onChange(drawDeformedFace);
gui11.onChange(drawDeformedFace);
gui12.onChange(drawDeformedFace);
gui13.onChange(drawDeformedFace);
gui14.onChange(drawDeformedFace);
gui15.onChange(drawDeformedFace);
gui16.onChange(drawDeformedFace);
gui17.onChange(drawDeformedFace);
gui18.onChange(drawDeformedFace);
gui19.onChange(drawDeformedFace);
gui20.onChange(drawDeformedFace);
guiSelect.onChange(switchDeformedFace);
guiGrid.onChange(drawDeformedFace);
guiFace.onChange(drawDeformedFace);
drawDeformedFace();
}
function drawDeformedFace() {
// draw model
var parameters = ctrack.getCurrentParameters();
parameters[0] = scale*Math.cos(rotation)-1;
parameters[1] = scale*Math.sin(rotation);
parameters[2] = xOffset;
parameters[3] = yOffset;
parameters[0+4] = ph.param1;
parameters[1+4] = ph.param2;
parameters[2+4] = ph.param3;
parameters[3+4] = ph.param4;
parameters[4+4] = ph.param5;
parameters[5+4] = ph.param6;
parameters[6+4] = ph.param7;
parameters[7+4] = ph.param8;
parameters[8+4] = ph.param9;
parameters[9+4] = ph.param10;
parameters[10+4] = ph.param11;
parameters[11+4] = ph.param12;
parameters[12+4] = ph.param13;
parameters[13+4] = ph.param14;
parameters[14+4] = ph.param15;
parameters[15+4] = ph.param16;
parameters[16+4] = ph.param17;
parameters[17+4] = ph.param18;
parameters[18+4] = ph.param19;
parameters[19+4] = ph.param20;
positions = fd.calculatePositions(parameters, true);
fd.clear();
if (ph.draw_face) fd.draw(positions);
if (ph.draw_grid) fd.drawGrid(positions);
}
function switchDeformedFace() {
// draw model
var parameters = ctrack.getCurrentParameters();
parameters[0] = scale*Math.cos(rotation)-1;
parameters[1] = scale*Math.sin(rotation);
parameters[2] = xOffset;
parameters[3] = yOffset;
var split = ph.presets.split(",");
parameters[0+4] = ph.param1 = parseInt(split[0],10);
parameters[1+4] = ph.param2 = parseInt(split[1],10);
parameters[2+4] = ph.param3 = parseInt(split[2],10);
parameters[3+4] = ph.param4 = parseInt(split[3],10);
parameters[4+4] = ph.param5 = parseInt(split[4],10);
parameters[5+4] = ph.param6 = parseInt(split[5],10);
parameters[6+4] = ph.param7 = parseInt(split[6],10);
parameters[7+4] = ph.param8 = parseInt(split[7],10);
parameters[8+4] = ph.param9 = parseInt(split[8],10);
parameters[9+4] = ph.param10 = parseInt(split[9],10);
parameters[10+4] = ph.param11 = parseInt(split[10],10);
parameters[11+4] = ph.param12 = parseInt(split[11],10);
parameters[12+4] = ph.param13 = parseInt(split[12],10);
parameters[13+4] = ph.param14 = parseInt(split[13],10);
parameters[14+4] = ph.param15 = parseInt(split[14],10);
parameters[15+4] = ph.param16 = parseInt(split[15],10);
parameters[16+4] = ph.param17 = parseInt(split[16],10);
parameters[17+4] = ph.param18 = parseInt(split[17],10);
parameters[18+4] = ph.param19 = parseInt(split[18],10);
parameters[19+4] = ph.param20 = parseInt(split[19],10);
positions = fd.calculatePositions(parameters, true);
if (ph.draw_face) fd.draw(positions);
if (ph.draw_grid) fd.drawGrid(positions);
}
var parameterHolder = function() {
this.param1 = 0.0001;
this.param2 = 0.0001;
this.param3 = 0.0001;
this.param4 = 0.0001;
this.param5 = 0.0001;
this.param6 = 0.0001;
this.param7 = 0.0001;
this.param8 = 0.0001;
this.param9 = 0.0001;
this.param10 = 0.0001;
this.param11 = 0.0001;
this.param12 = 0.0001;
this.param13 = 0.0001;
this.param14 = 0.0001;
this.param15 = 0.0001;
this.param16 = 0.0001;
this.param17 = 0.0001;
this.param18 = 0.0001;
this.param19 = 0.0001;
this.param20 = 0.0001;
this.presets = 0;
this.draw_face = true;
this.draw_grid = false;
}
var presets = {
"default" : [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"oi mate" : [0, 0, 13, 1.2, 0, -15, 0, 1, 8, -5, 0, 0, 0, 0, 0, 0, 0, 11.6, 0, -7],
"unhappy" : [0, 0, 0, 0, 0, 0, 0, 0, 0, -13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"greek" : [0, 0, 0, 1.6, 0, -6, 0, 0, 0, -13, 0, 4.7, 1, 0, 11, -1, 8, 8, 0, 0],
"cheery" : [0, 0, 0, 0, 10.7, 0, 16.8, 0, 0, -5, 0, -4, 13, 0, 0, 0, 0, 0, 0, 0],
"luke" : [0, 0, -1.7, -8.7, -8, -4.8, 12.5, -1, 14.6, -11, 0, -2, -13, 0, 0, 0, 0, 7, 0, -3],
"chum" : [0, 0, 13, 1.2, 0, 2.5, 0, 1, 16.8, -5, 0, 0, 0, 0, 0, 0, 0, 11.6, 0, -7],
"disgust" : [-4, -14, 8, 2, 3, -5.6, -2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -10, 0, -5],
};
/*********** Code for stats **********/
stats = new Stats();
stats.domElement.style.position = 'absolute';
stats.domElement.style.top = '0px';
document.getElementById('container').appendChild( stats.domElement );
document.addEventListener("clmtrackrIteration", function(event) {
stats.update();
}, false);
</script>
</div>
</body>
</html>