Попытка заставить Affectiva работать на Node.js с PhantomJS - PullRequest
0 голосов
/ 30 июня 2018

Affectiva имеет JavaScript SDK для Интернета , но нет модуля для Node.js. Я пытаюсь заставить их работать вместе с безголовым браузером, таким как PhantomJS. (Примечание: на npm есть сторонний модуль Affectiva, но это для их REST API , а не SDK, который я хочу использовать.)

Я настроил тестовую страницу таким образом, чтобы я мог тестировать один и тот же код как в PhantomJS, так и в Chrome 67. Но я сталкиваюсь с ошибкой на стороне PhantomJS, которую я не могу отладить. Является ли ответ столь же простым, как "PhantomJS не имеет полной поддержки классов Image, ImageData или Uint8ClampedArray?" Единственные подсказки, которые мне удалось найти, - это то, что Object.keys () дает разные результаты для этих классов между Chrome и PhantomJS. Возможно SDK Affectiva полагается на Object.keys (), и ошибка является результатом этого?

Заранее благодарим за любые полезные идеи.

на стороне клиента:

<!DOCTYPE html>
<html>
<body>
  <canvas id='canvas' width='640' height='800'></canvas>

  <script src='https://download.affectiva.com/js/3.2/affdex.js'></script>

  <script>

    function startProcessing() {
      console.log('initialized');
      if (typeof window.callPhantom === 'function') {
        window.callPhantom('initialized');
      }
    }

    function saveResults(faces, image, timestamp) {
      console.log('timestamp:', timestamp);
      if (typeof window.callPhantom === 'function') {
        window.callPhantom(faces);
      }
    }

    function catchError(image, timestamp, err_detail) {
      if (typeof window.callPhantom === 'function') {
        window.callPhantom({error: err_detail});
      }
    }

    function processFrame(imgUrl) {

      var img = new Image();   // Create new img element
      img.addEventListener('load', function() {
        console.log('img:', img);
        // In Chrome:      img: object <img crossorigin scr="https://example.com/face.jpg">
        // In PhantomJS:   img: [object HTMLImageElement]

        context.drawImage(img, 0, 0);

        // Get imageData object.
        var imageData = context.getImageData(0, 0, 640, 800);
        console.log('imageData:', typeof(imageData), imageData, Object.keys(imageData));
        // In Chrome:      imageData: object ImageData{data: Uint8ClampedArray(2048000), width: 640, height: 800} ["data"]
        // In PhantomJS:   imageData: object [object ImageData] height,width,data

        // Remove prototype attributes that PhantomJS includes in Object.keys() (DOESN'T HELP)
        delete imageData.data.length;
        delete imageData.data.byteOffset;
        delete imageData.data.byteLength;
        delete imageData.data.buffer;
        var uint8caKeys = Object.keys(imageData.data).sort().reverse();

        console.log('imageData.data:', typeof(imageData.data), imageData.data, uint8caKeys.length, uint8caKeys.slice(0,6), imageData.data[1985161]);
        // In Chrome:      imageData.data: object Uint8ClampedArray(2048000) [42, 36, 25, 255...] 2048000 ["999999", "999998", "999997", "999996", "999995", "999994"]                  232
        // In PhantomJS:   imageData.data: object [object Uint8ClampedArray]                      2048004 length,byteOffset,byteLength,buffer,999999,999998,999997,999996,999995,999994 230
        // Extra whitespace added by me

        //Process the frame
        detector.process(imageData, 0);
        // In Chrome:    saveResults() triggered with expected data
        // In PhantomJS: catchError() triggered with CALLBACK: "worker code reported an exceptionTypeError: Cannot convert \"undefined\" to int"

      }, false);
      img.setAttribute('crossOrigin', '');
      img.src = imgUrl;

    }

    var aCanvas = document.getElementById("canvas");
    var context = aCanvas.getContext('2d');
    var detector = new affdex.PhotoDetector(affdex.FaceDetectorMode.LARGE_FACES);
    detector.detectAllEmotions();
    detector.detectAllAppearance();
    detector.addEventListener('onInitializeSuccess',   startProcessing);
    detector.addEventListener('onInitializeFailure',   catchError);
    detector.addEventListener('onImageResultsSuccess', saveResults);
    detector.addEventListener('onImageResultsFailure', catchError);
    detector.start();

  </script>

</body>
</html>

на стороне сервера:

const instance = await phantom.create();
const page = await instance.createPage();
await page.on('onResourceRequested', (requestData) => {
  console.info('Requesting', requestData.url);
  // PhantomJS seems to be downloading all the correct scripts
});

page.on('onConsoleMessage', msg => {
  console.log('CONSOLE: ' + msg);
});

page.on('onCallback', data => {
  console.log('CALLBACK: ' + JSON.stringify(data));

  if (data==='initialized') {

    page.evaluate(function(imgUrl){
      processFrame(imgUrl);
    }, 'https://example.com/face.jpg');

  }
});

const openStatus = await page.open('http://localhost:1337/affectiva-test');
if (openStatus==='success') {
  exits.success();
} else {
  exits.error(openStatus);
}

1 Ответ

0 голосов
/ 03 июля 2018

Поиск альтернативного безголового браузера, кажется, ответ. Как предложил @Vaviloff, я переключился на Puppeteer . Самым большим изменением является то, что данные не могут быть переданы со страницы клиента обратно в приложение node.js, так просто как window.callPhantom(). В этом примере я отправляю stringified-объекты через console.log(). @Vaviloff предложил, чтобы на странице клиента вместо этого был AJAX-вызов. Мне пока не ясно, что будет лучше.

на стороне клиента:

<!DOCTYPE html>
<html>
<body>
  <canvas id='canvas' width='640' height='800'></canvas>
  <script src='https://download.affectiva.com/js/3.2/affdex.js'></script>

  <script>

    function startProcessing() {
      console.log('initialized');
    }

    function saveResults(faces, image, timestamp) {
      delete faces[0].emojis;
      console.log('result:', JSON.stringify(faces[0]));
    }

    function catchError(image, timestamp, err_detail) {
      console.log(err_detail);
    }

    function processFrame(imgUrl, timestamp) {
      var img = new Image();
      img.addEventListener('load', function() {
        context.drawImage(img, 0, 0);
        var imageData = context.getImageData(0, 0, 640, 800);
        detector.process(imageData, timestamp);

      }, false);
      img.setAttribute('crossOrigin', '');
      img.src = imgUrl;
    }

    console.log('initializing');
    var aCanvas = document.getElementById("canvas");
    var context = aCanvas.getContext('2d');
    var detector = new affdex.PhotoDetector(affdex.FaceDetectorMode.LARGE_FACES);
    detector.detectAllEmotions();
    detector.detectAllAppearance();
    detector.addEventListener('onInitializeSuccess',   startProcessing);
    detector.addEventListener('onInitializeFailure',   catchError);
    detector.addEventListener('onImageResultsSuccess', saveResults);
    detector.addEventListener('onImageResultsFailure', catchError);
    detector.start();

  </script>

</body>
</html>

Серверные:

// If you are analyzing video (instead of images), avoid the default, bundled Chromium browser, since it doesn't support MP4
// const browser = await puppeteer.launch({executablePath: '/path/to/Chrome'});

const browser = await puppeteer.launch();
const page = await browser.newPage();

page.on('console', msg => {
  if (msg.text()==='initialized') {
    page.evaluate(
      (imgUrl, timestamp) => processFrame(imgUrl, timestamp),
      'https://example.com/face.jpg',
       0
    );
  }
  console.log('PAGE LOG:', msg.text());
});
await page.goto('http://localhost:1337/affectiva-test');

exits.success();
...