UNPKG

clmtrackr

Version:

Javascript library for precise tracking of facial features via Constrained Local Models

467 lines (407 loc) 14.2 kB
<!doctype 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>