Search code examples
javascripthtmltensorflow.jsface-api

face-api and tensorflow.js not working in browser


I'm trying to run this example in the browser

https://justadudewhohacks.github.io/face-api.js/docs/index.html#getting-started-browser

Specifically this code here

<!DOCTYPE html>
<html>
<head>
  <script src="assets/face-api.js"></script>
  <script src="assets/commons.js"></script>
  <script src="assets/faceDetectionControls.js"></script>
  <link rel="stylesheet" href="assets/styles.css">
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.100.2/css/materialize.css">
  <script type="text/javascript" src="https://code.jquery.com/jquery-2.1.1.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.100.2/js/materialize.min.js"></script>
</head>
<body>
  <div id="navbar"></div>
  <div class="center-content page-container">

    <div class="progress" id="loader">
      <div class="indeterminate"></div>
    </div>
    <div style="position: relative" class="margin">
      <video onloadedmetadata="onPlay(this)" id="inputVideo" autoplay muted playsinline></video>
      <canvas id="overlay" />
    </div>

    <div class="row side-by-side">

      <!-- face_detector_selection_control -->
      <div id="face_detector_selection_control" class="row input-field" style="margin-right: 20px;">
        <select id="selectFaceDetector">
          <option value="ssd_mobilenetv1">SSD Mobilenet V1</option>
          <option value="tiny_face_detector">Tiny Face Detector</option>
        </select>
        <label>Select Face Detector</label>
      </div>
      <!-- face_detector_selection_control -->

      <!-- check boxes -->
      <div class="row" style="width: 220px;">
        <input type="checkbox" id="hideBoundingBoxesCheckbox" onchange="onChangeHideBoundingBoxes(event)" />
        <label for="hideBoundingBoxesCheckbox">Hide Bounding Boxes</label>
      </div>
      <!-- check boxes -->

      <!-- fps_meter -->
      <div id="fps_meter" class="row side-by-side">
        <div>
          <label for="time">Time:</label>
          <input disabled value="-" id="time" type="text" class="bold">
          <label for="fps">Estimated Fps:</label>
          <input disabled value="-" id="fps" type="text" class="bold">
        </div>
      </div>
      <!-- fps_meter -->

    </div>


    <!-- ssd_mobilenetv1_controls -->
    <span id="ssd_mobilenetv1_controls">
      <div class="row side-by-side">
        <div class="row">
          <label for="minConfidence">Min Confidence:</label>
          <input disabled value="0.5" id="minConfidence" type="text" class="bold">
        </div>
        <button
          class="waves-effect waves-light btn"
          onclick="onDecreaseMinConfidence()"
        >
          <i class="material-icons left">-</i>
        </button>
        <button
          class="waves-effect waves-light btn"
          onclick="onIncreaseMinConfidence()"
        >
          <i class="material-icons left">+</i>
        </button>
      </div>
    </span>
    <!-- ssd_mobilenetv1_controls -->

    <!-- tiny_face_detector_controls -->
    <span id="tiny_face_detector_controls">
      <div class="row side-by-side">
        <div class="row input-field" style="margin-right: 20px;">
          <select id="inputSize">
            <option value="" disabled selected>Input Size:</option>
            <option value="128">128 x 128</option>
            <option value="160">160 x 160</option>
            <option value="224">224 x 224</option>
            <option value="320">320 x 320</option>
            <option value="416">416 x 416</option>
            <option value="512">512 x 512</option>
            <option value="608">608 x 608</option>
          </select>
          <label>Input Size</label>
        </div>
        <div class="row">
          <label for="scoreThreshold">Score Threshold:</label>
          <input disabled value="0.5" id="scoreThreshold" type="text" class="bold">
        </div>
        <button
          class="waves-effect waves-light btn"
          onclick="onDecreaseScoreThreshold()"
        >
          <i class="material-icons left">-</i>
        </button>
        <button
          class="waves-effect waves-light btn"
          onclick="onIncreaseScoreThreshold()"
        >
          <i class="material-icons left">+</i>
        </button>
      </div>
    </span>
    <!-- tiny_face_detector_controls -->

  </body>

  <script>
    let forwardTimes = []
    let withBoxes = true

    function onChangeHideBoundingBoxes(e) {
      withBoxes = !$(e.target).prop('checked')
    }

    function updateTimeStats(timeInMs) {
      forwardTimes = [timeInMs].concat(forwardTimes).slice(0, 30)
      const avgTimeInMs = forwardTimes.reduce((total, t) => total + t) / forwardTimes.length
      $('#time').val(`${Math.round(avgTimeInMs)} ms`)
      $('#fps').val(`${faceapi.utils.round(1000 / avgTimeInMs)}`)
    }

    async function onPlay() {
      const videoEl = $('#inputVideo').get(0)

      if(videoEl.paused || videoEl.ended || !isFaceDetectionModelLoaded())
        return setTimeout(() => onPlay())


      const options = getFaceDetectorOptions()

      const ts = Date.now()

      const result = await faceapi.detectSingleFace(videoEl, options).withFaceExpressions()

      updateTimeStats(Date.now() - ts)

      if (result) {
        const canvas = $('#overlay').get(0)
        const dims = faceapi.matchDimensions(canvas, videoEl, true)

        const resizedResult = faceapi.resizeResults(result, dims)
        const minConfidence = 0.05
        if (withBoxes) {
          faceapi.draw.drawDetections(canvas, resizedResult)
        }
        faceapi.draw.drawFaceExpressions(canvas, resizedResult, minConfidence)
      }

      setTimeout(() => onPlay())
    }

    async function run() {
      // load face detection and face expression recognition models
      await changeFaceDetector(TINY_FACE_DETECTOR)
      await faceapi.loadFaceExpressionModel('/')
      changeInputSize(224)

      // try to access users webcam and stream the images
      // to the video element
      const stream = await navigator.mediaDevices.getUserMedia({ video: {} })
      const videoEl = $('#inputVideo').get(0)
      videoEl.srcObject = stream
    }

    function updateResults() {}

    $(document).ready(function() {
      renderNavBar('#navbar', 'webcam_face_expression_recognition')
      initFaceDetectionControls()
      run()
    })
  </script>
</body>
</html>

Unfortunately this is not working ( I have loaded the associated libraries into assets, i.e., https://github.com/justadudewhohacks/face-api.js/tree/master/dist and moving the other files from this example here

https://github.com/justadudewhohacks/face-api.js/tree/master/examples/examples-browser

What am i doing wrong? I am loading this on a page on my site here

https://moodmap.app/webcamFaceExpressionRecognition.html in case you want to see what's happening in the browser.

Based on changes below,

Here is the node server where I am setting where things are held - is it possible to just change this instead? As it is coming up with a separate issue with the shard needed in the model as well now when making the changes suggested below.

Thanks!

const config = require('../../config');

const express = require('express');
const app = express();
const server = require('http').createServer(app);
const io = require('socket.io')(server, { wsEngine: 'ws' });
const mysql = require('mysql');
const expressSession = require('express-session');
const ExpressMysqlSessionStore = require('express-mysql-session')(expressSession);
const sharedsession = require('express-socket.io-session');
const path = require('path');

const utils = require('./utils');

// remove from header "X-Powered-By: Express"
app.disable('x-powered-by');

server.listen(config.serverParams.port, config.serverParams.address, () => {
    console.log(`Server running at http://${server.address().address}:${server.address().port}`);
});

/* DATABASE */
global.db = mysql.createConnection(config.db);
db.connect();
/* DATABASE */

/* SESSION */
const sessionStore = new ExpressMysqlSessionStore(config.sessionStore, db);
const session = expressSession({
    ...config.session,
    store: sessionStore,
});
app.use(session);
/* SESSION */

app.use(express.static(config.frontendDir));
app.use(express.static(path.join(__dirname, './src/assets')))
app.use(express.static(path.join(__dirname, './src/assets/weights')))

app.use((req,res,next)=>{
    //can reaplce * with website we want to allow access
    res.header('Access-Control-Allow-Origin', '*');
  next();
  });

app.get([
    '/signup',
    '/stats',
    '/pay',
], (req, res) => res.sendFile(path.join(`${config.frontendDir}${req.path}.html`)));

io.use(sharedsession(session, {
    autoSave: true
}));

io.on('connection', socket => {

    socket.use((packet, next) => {
        if (packet[0]) {
            console.log('METHOD:', packet[0]);
            const sessionData = socket.handshake.session.user;
            const noSessionNeed = [ 'login', 'signup', 'checkAuth' ].includes(packet[0]);
            let error;
            if ( ! sessionData && ! noSessionNeed) error = { code: -1, message: 'You need to login in extension!' };
            if (error) return next(new Error(JSON.stringify(error)));
            else next();
        }
    });

    const auth = require('./auth')(socket);
    socket.on('checkAuth', auth.checkAuth);
    socket.on('login', auth.login);
    socket.on('signup', auth.signup);
    socket.on('logout', auth.logout);

    const users = require('./users')(socket);
    socket.on('users.get', users.get);

    const sentiment = require('./sentiment')(socket);
    socket.on('sentiment.get', sentiment.get);
    socket.on('sentiment.set', sentiment.set);

    socket.on('disconnect', () => {

    });

});

Reason being still getting an error for some reason as below,?

fetchOrThrow.ts:11 Uncaught (in promise) Error: failed to fetch: (404) , from url: https://moodmap.app/assets/tiny_face_detector_model-weights_manifest.json
    at fetchOrThrow.ts:11
    at step (drawContour.ts:28)
    at Object.next (drawContour.ts:28)
    at fulfilled (drawContour.ts:28)
(anonymous) @   fetchOrThrow.ts:11
step    @   drawContour.ts:28
(anonymous) @   drawContour.ts:28
fulfilled   @   drawContour.ts:28
async function (async)      
run @   webcamFaceExpressionRecognition.html:142
(anonymous) @   webcamFaceExpressionRecognition.html:158
j   @   jquery-2.1.1.min.js:2
fireWith    @   jquery-2.1.1.min.js:2
ready   @   jquery-2.1.1.min.js:2
I   @   jquery-2.1.1.min.js:2

Thanks in advance!


Solution

  • So, the error you were talking about in the question is:

    Uncaught (in promise) Error: failed to fetch: (404) , from URL: 
    https://moodmap.app/tiny_face_detector_model-weights_manifest.json
    

    So, this error implies that the file tiny_face_detector_model-weights_manifest.json could not be found. And this is happening for all other manifest.json files as well, as I see in your website.

    You mentioned in the question that all your associated libraries are into assets folder. So, your tiny_face_detector_model-weights_manifest.json file and other manifest.json files also in assets folder and I am giving you the solution according to that but if you change location of files to another folder, just replace assets with that whatever folder where your file is in.

    face-api.js Line:1976 face-api.js Linke: 188-190

    Here, in line 1976 you see defaultModelName. This tells about the file to load.


    1. So if your app has only the issue with loading tiny_face_detector_model-weights_manifest.json [ For this case, It is showing 404 error for all manifest.json so jump below] then

    face-api.js Line: 5627 face-api.js Line: 470

    Go to line 5627 in face-api.js and change "tiny_face_detector_model" with "./assets/tiny_face_detector_model"


    2. If all your manifest files are showing 404 error while loading, which true for this case because all your files are in assets folder.

    So, in that case go to line number 1976 of face-api.js,

    just replace:

    var defaultManifestFilename=defaultModelName+"-weights_manifest.json";
    

    with:

    var defaultManifestFilename="./assets/"+defaultModelName+"-weights_manifest.json";
    

    That means just concatenate the folder name or path where the manifest file exists to the mentioned variable. And this will fix the path issue for all manifest.json files in your application.

    NOTE: If there is a problem finding the code with line number I mentioned then use search.