// console.log('p5 version:', p5); // console.log('ml5 version:', ml5); // console.log(location.origin); let assets = {}; var draw = function () { // Begin met het tekenen van de video // plaats hem op position x = 0, y = 0. // vul de hele breedte en hoogte image(lastFrame, 0,0, width, height); for(let detection of detections) { push(); let transformed = transformDetection(detection); translate(transformed.origin.x, transformed.origin.y); rotate(transformed.angle); try { if( overlayGrid ) { drawGridOverlay(transformed) } if( overlayLandmarks ) { drawLandmarkOverlay(transformed); } drawMask(transformed); } catch (error) { console.error(error); } pop(); } }; function drawLandmarkOverlay(detection) { for(let nr in detection.points) { p = detection.points[nr] stroke('red') strokeWeight(5) point(p.x, p.y) noStroke(); textSize(12); fill('white'); text(nr, p.x, p.y); } noFill(); } function drawGridOverlay(detection) { textSize(20) stroke(100,100,100) strokeWeight(1) for (let y = 0; y < detection.height; y+=10) { if(y%100 === 0) { strokeWeight(3) text(y, detection.x - 10, y); } else { strokeWeight(1) } line(detection.x, detection.y + y, detection.x+detection.width, detection.y + y); } for (let x = 0; x < detection.width; x+=10) { if(x != 0 && x%100 === 0) { // 0 already drawn for y strokeWeight(3) text(x, x, detection.y - 10); } else { strokeWeight(1) } line(detection.x + x, detection.y, detection.x + x, detection.y + detection.height); } } var drawMask = function(detection) { }; // var gotResults = function(err, result) { // if (err) { // console.log(err) // return // } // }; // function code_error(type, error) { // window.parent.postMessage({ // 'type': type, // 'error': error.message, // 'name': error.name, // 'line': error.lineNumber - 2, // seems it giveswrong line numbers // 'column': error.columnNumber // }, '*'); // } // function no_code_error(type){ // window.parent.postMessage({ // 'type': type, // 'error': null // }, '*'); // } // window.addEventListener("message", function (e) { // if (event.origin !== window.location.origin) { // console.error("Invalid origin of message. Ignored"); // return; // } // console.debug("receive", e.data); // switch (e.data.action) { // case 'asset': // if(e.data.content === null){ // delete assets[e.data.id]; // } else { // assets[e.data.id] = loadImage(e.data.content); // } // break; // case 'code': // let f = new Function(""); // try { // f = new Function(e.data.draw); // no_code_error('syntax'); // } catch (error) { // code_error('syntax', error); // // window.parent.postMessage({'syntax': error.lineNumber}); // } // handleResults = f; // break; // default: // console.error("Invalid action", e.data.action); // break; // } // }); let faceapi; var video; var lastFrame; var frameToDetect; var detections = []; var factor_x, factor_y; var flip = false; // mirror mode, disabled, because it's tricky to enable with faceApi // function pause() { // if (running) // running = false; // else { // running = true; // faceapi.detect(gotResults); // } // } // by default all options are set to true const detection_options = { withLandmarks: true, withDescriptors: false, minConfidence: 0.5, Mobilenetv1Model: window.parent.location.origin + '/assets/faceapi', FaceLandmarkModel: window.parent.location.origin + '/assets/faceapi', FaceLandmark68TinyNet: window.parent.location.origin + '/assets/faceapi', FaceRecognitionModel: window.parent.location.origin + '/assets/faceapi', TinyFaceDetectorModel: window.parent.location.origin + '/assets/faceapi', } function setupAssets(){ // placeholder. Override in patch... } let images = {}; function preload() { const req = new Request('/assets/images.json'); fetch(req).then( response => response.json() ).then(data => { for(let id in data) { images[id] = loadImage(data[id]); } // console.log('images', data, images); }); // console.log(images); } var overlayLandmarks = false; var overlayGrid = false; function setup() { // createCanvas(1280,720, WEBGL); createCanvas(540,420); smooth(); noFill(); push(); translate(-width/2, -height/2); let constraints = { video: { width: { min: 720 }, height: { min: 540 } }, audio: false }; video = createCapture(constraints); lastFrame = createGraphics(video.width, video.height); frameToDetect = createGraphics(video.width, video.height); // console.log(video); // HeadGazeSetup(video); // video.size(width, height); video.hide(); // Hide the video element, and just show the canvas faceapi = ml5.faceApi(video, detection_options, modelReady); textAlign(RIGHT); setupAssets(); let controlEl = document.createElement('div'); controlEl.classList.add('controls'); let label1El = document.createElement('label'); let check1El = document.createElement('input'); check1El.type = 'checkbox'; check1El.addEventListener('change', (e) => overlayLandmarks = e.target.checked); let text1Node = document.createTextNode("Show points") label1El.appendChild(check1El); label1El.appendChild(text1Node); controlEl.appendChild(label1El); let label2El = document.createElement('label'); let check2El = document.createElement('input'); check2El.type = 'checkbox'; check2El.addEventListener('change', (e) => overlayGrid = e.target.checked); let text2Node = document.createTextNode("Show coordinates") label2El.appendChild(check2El); label2El.appendChild(text2Node); controlEl.appendChild(label2El); let downloadBtn = document.createElement('button'); downloadBtn.innerHTML = 'screenshot'; downloadBtn.style.float = 'right'; // Convert canvas to image downloadBtn.addEventListener("click", function(e) { const canvas = document.querySelector('canvas'); const dataURL = canvas.toDataURL("image/png", 1.0); let a = document.createElement('a'); a.href = dataURL; a.download = 'screenshot.png'; document.body.appendChild(a); a.click(); }); controlEl.appendChild(downloadBtn); document.body.appendChild(controlEl); } function modelReady() { frameToDetect.image(video, 0,0, video.width, video.height); faceapi.detect(gotResults); } // var handleResults = function(){ // // background(parseInt(Math.random()*255),parseInt(Math.random()*255),parseInt(Math.random()*255)); // background((millis()/100)%255,0,0); // image(video, -width/2 + 10, -height/2 + 10, width - 20, height -20); // }; gotResults = function(err, result) { if (err) { console.error(err) return } // store data for async draw function // TODO results to more compatible format // translate(width,0); // move to far corner if (flip) { lastFrame.push(); lastFrame.scale(-1.0,1.0); // flip x-axis backwards lastFrame.image(frameToDetect, -lastFrame.width, 0, lastFrame.width, lastFrame.height); lastFrame.pop(); } else { lastFrame.image(frameToDetect, 0, 0, lastFrame.width, lastFrame.height); } detections = parseDetectionResults(result, flip, width); // size of video becomes known only after camera approval if(lastFrame.width != video.width || lastFrame.height != video.height){ // console.log('Resizing canvas'); lastFrame.resizeCanvas(video.width, video.height); frameToDetect.resizeCanvas(video.width, video.height); } // lastFrame.background('red'); frameToDetect.image(video, 0,0, video.width, video.height); factor_x = width / video.width; factor_y = height / video.height; faceapi.detect(gotResults); } function drawBox(detections) { for (let i = 0; i < detections.length; i++) { const alignedRect = detections[i].alignedRect; const x = alignedRect._box._x const y = alignedRect._box._y const boxWidth = alignedRect._box._width const boxHeight = alignedRect._box._height noFill(); stroke(161, 95, 251); strokeWeight(2); rect(x, y, boxWidth, boxHeight); } } function drawLandmarks(detection) { // for (let i = 0; i < detections.length; i++) { const mouth = detection.parts.mouth; const nose = detection.parts.nose; const leftEye = detection.parts.leftEye; const rightEye = detection.parts.rightEye; const rightEyeBrow = detection.parts.rightEyeBrow; const leftEyeBrow = detection.parts.leftEyeBrow; const jawOutline = detection.parts.jawOutline; strokePoints(mouth, CLOSE); strokePoints(nose, CLOSE); strokePoints(leftEye, CLOSE); strokePoints(leftEyeBrow, OPEN); strokePoints(rightEye, CLOSE); strokePoints(rightEyeBrow, OPEN); strokePoints(jawOutline, OPEN); // } } function strokePoints(points, closed) { beginShape(); for (let i = 0; i < points.length; i++) { const x = points[i].x; const y = points[i].y; vertex(x, y) } if(typeof closed === 'undefined') { closed = CLOSE; } endShape(closed) } function drawPoints(points, radius) { if(typeof radius === 'undefined') { radius = 2; } for (let i = 0; i < points.length; i++) { const x = points[i].x; const y = points[i].y; circle(x, y, radius); } } function faceDistance(face1, face2){ // distance between faces, in pixels, not meters.. for now // we cheat a little: take centers, visualise circle with r = max(width, height) // and find distance between these circles box1 = (face1.x, face1.x + face1.width) box2 = (face2.x, face2.x + face2.width) c1 = { x: face1.x + face1.width / 2, y: face1.y + face1.height / 2, } c2 = { x: face2.x + face2.width / 2, y: face2.y + face2.height / 2, } r1 = Math.max(face1.width, face1.height) / 2; r2 = Math.max(face2.width, face2.height) / 2; dx = c1.x - c2.x; dy = c1.y - c2.y; return Math.sqrt( Math.pow(dx, 2) + Math.pow(dy, 2) ) - r1 - r2; } function mergePoints() { // a points should be {x: , y: } // collect all points in the arguments: let points = []; for(let arg of arguments) { if(Array.isArray(arg)) { points.push(...arg); } else { points.push(arg); } } return points; } function getBoundingBox(){ // arguments contains points, or sets of points. Find bbox const points = mergePoints(...arguments); const xs = points.map((point) => point.x); const ys = points.map((point) => point.y); const minx = Math.min(...xs); const miny = Math.min(...ys); return { x: minx, y: miny, width: Math.max(...xs) - minx, height: Math.max(...ys) - miny, } } function parseDetectionResults(results, flip, frameWidth) { let detections = []; for(let result of results) { const landmarks = result.landmarks._positions.map((pos) => parseCoordinate(pos, flip, frameWidth)); let x = result.alignedRect._box._x * factor_x; if(flip) { x *= -1; x += frameWidth; } let detection = { 'points': landmarks, // TODO: rotation 'parts': {}, x: x, y: result.alignedRect._box._y * factor_y, width: result.alignedRect._box._width * factor_x, height: result.alignedRect._box._height * factor_y, } // for(let idx in result.parts) { // detection.parts[idx] = result.parts[idx].map((pos) => parseCoordinate(pos)); // } detection['center'] = { x: detection.x + detection.width / 2, y: detection.y + detection.height / 2, } detections.push(detection); } return detections; } /** * face api detector returns coordinates with _x and _y attributes. * We convert this to the canvas's coordinates * @param Object {_x: , _y: } */ function parseCoordinate(position, flip, frameWidth) { let x = position._x * factor_x; if (flip) { x *= -1; x += frameWidth; } return { x: x, y: position._y * factor_y, } } function transformDetection(original) { const b = original.points[36]; // outer point on left eye const a = original.points[45]; // outer point on right eye const angle = atan2(a.y - b.y, a.x - b.x); // let cx =a.x/2 + b.x/2 // let cy = a.y/2 + b.y/2 const cx = original.x const cy = original.y let detection = { 'points': original.points.map(p => transformPoint(p, cx, cy, angle)), 'origin': {x:cx, y:cy}, 'angle': angle, 'original': original } const bbox = getBoundingBox(detection.points); padding_x = bbox.width * .1; padding_y = bbox.height * .1; detection['x'] = bbox.x - padding_x, detection['y'] = bbox.y - padding_y, detection['width'] = bbox.width * 1.2, detection['height'] = bbox.height * 1.2 // detection['x'] = original.x - cx // detection['y'] = original.y - cy // detection['width'] = original.width // detection['height'] = original.height return detection; } function transformPoint(p, cx, cy, angle) { const px = p.x-cx; const py = p.y-cy; return { x: px * cos(-angle) - py * sin(-angle), y: px * sin(-angle) + py * cos(-angle) } } // error handling from consoleUtils.js::hijackConsoleErrorsScript function getScriptOff(line) { var offs = 0; var l = 0; var file = ''; for (var i=0; i l) { l = n; file = offs[i][1]; } } return [line - l, file]; } // catch reference errors, via http://stackoverflow.com/a/12747364/2994108 window.onerror = function (msg, url, lineNumber, columnNo, error) { var string = msg.toLowerCase(); var substring = "script error"; var data = {}; // if (url.match(${EXTERNAL_LINK_REGEX}) !== null && error.stack){ // var errorNum = error.stack.split('about:srcdoc:')[1].split(':')[0]; // var fileInfo = getScriptOff(errorNum); // data = msg + ' (' + fileInfo[1] + ': line ' + fileInfo[0] + ')'; // } else { // var fileInfo = getScriptOff(lineNumber); data = msg + ' (' + error.fileName + ': line ' + error.lineNumber + ')'; // } window.parent.postMessage([{ log: [{ method: 'error', data: [data], id: Date.now().toString() }], source: error.fileName }], '*'); return false; }; // catch rejected promises window.onunhandledrejection = function (event) { if (event.reason && event.reason.message && event.reason.stack){ // var errorNum = event.reason.stack.split('about:srcdoc:')[1].split(':')[0]; // var fileInfo = getScriptOff(errorNum); var data = event.reason.message + ' (' + event.reason.stack + ': line ' + event.reason.stack.split("\n")[0] + ')'; window.parent.postMessage([{ log: [{ method: 'error', data: [data], id: Date.now().toString() }], source: event.reason.stack.split("\n")[0] }], '*'); } };