clmtrackr
Version:
Javascript library for precise tracking of facial features via Constrained Local Models
467 lines (407 loc) • 14.2 kB
HTML
<html lang="en">
<head>
<title>Face deformation</title>
<meta charset="utf-8">
<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, #webgl {
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 {
position : relative;
width : 370px;
}
#content {
margin-top : 50px;
margin-left : auto;
margin-right : auto;
max-width: 600px;
}
h2 {
font-weight : 400;
}
.btn {
font-family: 'Lato';
font-size: 16px;
}
#controls {
text-align : center;
}
.sublabel {
text-align : center;
font-size: 10px;
margin-top : 0px;
}
</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>
<script src="./js/libs/jquery.min.js"></script>
<script src="./js/libs/poisson_new.js"></script>
<div id="content">
<h2>Face deformation</h2>
<div id="container">
<video id="videoel" width="400" height="300" preload="auto" loop playsinline autoplay>
</video>
<canvas id="overlay" width="400" height="300"></canvas>
<canvas id="webgl" width="400" height="300"></canvas>
</div>
<br/>
<div id="controls">
<select name="deformation" id="deform">
<option value="unwell">Unwell</option>
<option value="inca">Inca</option>
<option value="cheery">Cheery</option>
<option value="dopey">Dopey</option>
<option value="longface">Longface</option>
<option value="lucky">Lucky</option>
<option value="overcute">Overcute</option>
<option value="aloof">Aloof</option>
<option value="evil">Evil</option>
<option value="artificial">Artificial</option>
<option value="none">None</option>
</select>
<p class="sublabel">Facedeform preset</p>
<input class="btn" type="button" value="wait, loading video & images" disabled="disabled" onclick="startVideo()" id="startbutton"></input>
</div>
<div id="text">
<p>This is a demo of real-time <em>face deformation</em> with a parametric model using the javascript library <a href="https://github.com/auduno/clmtrackr"><em>clmtrackr</em></a>. Click start, keep your face still until the facemodel has fitted and try selecting some presets or adjusting the parameters on the right hand side.</p>
<p>Note that this demo works best with good, even lighting. The demo also needs support for WebGL, and works best in Google Chrome.</p>
<p>If you liked this demo, you should <a href="http://www.twitter.com/matsiyatzy">follow me on twitter</a>!</p>
</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>
<script>
// when everything is ready, automatically start everything ?
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');
var webgl_overlay = document.getElementById('webgl');
// canvas for copying videoframes to
var videocanvas = document.createElement('CANVAS');
videocanvas.width = vid_width;
videocanvas.height = vid_height;
/*********** Setup of video/webcam and checking for webGL support *********/
var videoReady = false;
var imagesReady = false;
function enablestart() {
if (videoReady && imagesReady) {
var startbutton = document.getElementById('startbutton');
startbutton.value = "start";
startbutton.disabled = null;
}
}
$(window).load(function() {
imagesReady = true;
enablestart();
});
var insertAltVideo = function(video) {
if (supports_video()) {
if (supports_webm_video()) {
video.src = "./media/cap13_edit2.webm";
} else if (supports_h264_baseline_video()) {
video.src = "./media/cap13_edit2.mp4";
} else {
return false;
}
fd.init(webgl_overlay);
return true;
} else return false;
}
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;
webgl_overlay.width = vid_width;
videocanvas.width = vid_width;
webGLContext.viewport(0,0,webGLContext.canvas.width,webGLContext.canvas.height);
}
// check whether browser supports webGL
var webGLContext;
if (window.WebGLRenderingContext) {
webGLContext = webgl_overlay.getContext('webgl') || webgl_overlay.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 mask 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(webgl_overlay);
vid.play();
}
vid.onresize = function() {
adjustVideoProportions();
fd.init(webgl_overlay);
if (trackingStarted) {
ctrack.stop();
ctrack.reset();
ctrack.start(vid);
}
}
}
function gumFail() {
// fall back to video if getUserMedia failed
insertAltVideo(vid);
alert("There was some problem trying to fetch video from your webcam, using a fallback video 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 {
insertAltVideo(vid);
alert("Your browser does not seem to support getUserMedia, using a fallback video instead.");
}
vid.addEventListener('canplay', function() {videoReady = true;enablestart();}, false);
/*********** Code for face substitution *********/
var animationRequest;
var positions;
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 face grid
drawGridLoop();
}
var fd = new faceDeformer();
var mouth_vertices = [
[44,45,61,44],
[45,46,61,45],
[46,60,61,46],
[46,47,60,46],
[47,48,60,47],
[48,59,60,48],
[48,49,59,48],
[49,50,59,49],
[50,51,58,50],
[51,52,58,51],
[52,57,58,52],
[52,53,57,52],
[53,54,57,53],
[54,56,57,54],
[54,55,56,54],
[55,44,56,55],
[44,61,56,44],
[61,60,56,61],
[56,57,60,56],
[57,59,60,57],
[57,58,59,57],
[50,58,59,50],
];
var extendVertices = [
[0,71,72,0],
[0,72,1,0],
[1,72,73,1],
[1,73,2,1],
[2,73,74,2],
[2,74,3,2],
[3,74,75,3],
[3,75,4,3],
[4,75,76,4],
[4,76,5,4],
[5,76,77,5],
[5,77,6,5],
[6,77,78,6],
[6,78,7,6],
[7,78,79,7],
[7,79,8,7],
[8,79,80,8],
[8,80,9,8],
[9,80,81,9],
[9,81,10,9],
[10,81,82,10],
[10,82,11,10],
[11,82,83,11],
[11,83,12,11],
[12,83,84,12],
[12,84,13,12],
[13,84,85,13],
[13,85,14,13],
[14,85,86,14],
[14,86,15,14],
[15,86,87,15],
[15,87,16,15],
[16,87,88,16],
[16,88,17,16],
[17,88,89,17],
[17,89,18,17],
[18,89,93,18],
[18,93,22,18],
[22,93,21,22],
[93,92,21,93],
[21,92,20,21],
[92,91,20,92],
[20,91,19,20],
[91,90,19,91],
[19,90,71,19],
[19,71,0,19]
]
function drawGridLoop() {
// get position of face
positions = ctrack.getCurrentPosition();
overlayCC.clearRect(0, 0, vid_width, vid_height);
if (positions) {
// draw current grid
ctrack.draw(overlay);
}
// check whether mask has converged
var pn = ctrack.getConvergence();
if (pn < 0.4) {
drawMaskLoop();
} else {
requestAnimFrame(drawGridLoop);
}
}
function drawMaskLoop() {
videocanvas.getContext('2d').drawImage(vid,0,0,videocanvas.width,videocanvas.height);
var pos = ctrack.getCurrentPosition();
if (pos) {
// create additional points around face
var tempPos;
var addPos = [];
for (var i = 0;i < 23;i++) {
tempPos = [];
tempPos[0] = (pos[i][0] - pos[62][0])*1.3 + pos[62][0];
tempPos[1] = (pos[i][1] - pos[62][1])*1.3 + pos[62][1];
addPos.push(tempPos);
}
// merge with pos
var newPos = pos.concat(addPos);
var newVertices = pModel.path.vertices.concat(mouth_vertices);
// merge with newVertices
newVertices = newVertices.concat(extendVertices);
fd.load(videocanvas, newPos, pModel, newVertices);
var parameters = ctrack.getCurrentParameters();
for (var i = 6;i < parameters.length;i++) {
parameters[i] += ph['component '+(i-3)];
}
positions = ctrack.calculatePositions(parameters);
overlayCC.clearRect(0, 0, vid_width, vid_height);
if (positions) {
// add positions from extended boundary, unmodified
newPos = positions.concat(addPos);
// draw mask on top of face
fd.draw(newPos);
}
}
animationRequest = requestAnimFrame(drawMaskLoop);
}
/*********** 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);
/********** parameter code *********/
var pnums = pModel.shapeModel.eigenValues.length-2;
var parameterHolder = function() {
for (var i = 0;i < pnums;i++) {
this['component '+(i+3)] = 0;
}
this.presets = 0;
};
var ph = new parameterHolder();
var gui = new dat.GUI();
var presets = {
"unwell" : [0, 0, 0, 0, 0, 0, 22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"inca" : [0, 0, -9, 0, -11, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0],
"cheery" : [0, 0, -9, 9, -11, 0, 0, 0, 0, 0, 0, 0, -9, 0, 0, 0, 0, 0],
"dopey" : [0, 0, 0, 0, 0, 0, 0, -11, 0, 0, 0, 0, 0, 0, 20, 0, 0, 0],
"longface" : [0, 0, 0, 0, -15, 0, 0, -12, 0, 0, 0, 0, 0, 0, -7, 0, 0, 5],
"lucky" : [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -4, 0, -6, 12, 0, 0],
"overcute" : [0, 0, 0, 0, 16, 0, -14, 0, 0, 0, 0, 0, -7, 0, 0, 0, 0, 0],
"aloof" : [0, 0, 0, 0, 0, 0, 0, -8, 0, 0, 0, 0, 0, 0, -2, 0, 0, 10],
"evil" : [0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, -8],
"artificial" : [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 0, -16, 0, 0, 0, 0, 0],
"none" : [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
};
var control = {};
var eig = 0;
for (var i = 0;i < pnums;i++) {
eig = Math.sqrt(pModel.shapeModel.eigenValues[i+2])*3
control['c'+(i+3)] = gui.add(ph, 'component '+(i+3), -5*eig, 5*eig).listen();
}
/********** defaults code **********/
function switchDeformedFace(e) {
//var split = ph.presets.split(",");
for (var i = 0;i < pnums;i++) {
ph['component '+(i+3)] = presets[e.target.value][i];
}
}
document.getElementById('deform').addEventListener('change', switchDeformedFace, false);
for (var i = 0;i < pnums;i++) {
ph['component '+(i+3)] = presets['unwell'][i];
}
</script>
</div>
</body>
</html>