javascript - 尝试使用 PhantomJS 让 Affectiva 在 Node.js 上运行

标签 javascript node.js image image-processing phantomjs

Affectiva有a JavaScript SDK for the web ,但没有 Node.js 的模块。我试图让它们与 headless 浏览器一起工作,比如 PhantomJS。 (注意:有 a third-party Affectiva module on npm ,但那是针对 their REST API 的,而不是我要使用的 SDK。)

我已经设置了一个测试页面,这样我就可以在 PhantomJS 和 Chrome 67 中测试相同的代码。但是我在 PhantomJS 端遇到了一个错误,我似乎无法调试。 答案是否像“PhantomJS 不完全支持 Image、ImageData 或 Uint8ClampedArray 类?” 我能找到的唯一线索是 Object.keys() 在 Chrome 和 PhantomJS 之间为这些类提供了不同的结果。也许 Affectiva 的 SDK 依赖于 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);
}

最佳答案

寻找替代的 headless 浏览器似乎就是答案。正如@Vaviloff 建议的那样,我切换到 Puppeteer 。最大的变化是数据无法像 window.callPhantom() 那样简单地从客户端页面传递回 Node.js 应用程序。在此示例中,我使用 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();

关于javascript - 尝试使用 PhantomJS 让 Affectiva 在 Node.js 上运行,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51112509/

相关文章:

image - Docker - 从 docker repo 拉取失败 (EOF/403) 但从 RH repo 下载有效

ios - 保存高分辨率资源,以便应用程序知道它可以在 Retina 设备上使用它们

javascript - 如何在javascript中调用url?

javascript - Strapi Beta (3.0) 的自定义 Controller 代码

jquery 上的 javascript 数组变量

node.js - 如何在代理之前检索 POST 请求正文

node.js - 使用 Passport-facebook 策略的 2 个实例

python - 无法去除透明度,PIL getbbox() 和 Numpy 都不起作用

javascript - 在 Backbone View 中渲染 reCAPTCHA v2.0 小部件

javascript - 将字符串中的字符和数字分开