Affectiva 有一个用于 Web 的 JavaScript SDK,但没有用于 Node.js 的模块。 我试图让它们与无头浏览器一起工作,比如PhantomJS。(注意:npm 上有一个第三方 Affectiva 模块,但那是针对他们的 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);
}
寻找替代的无头浏览器似乎是答案。 按照@Vaviloff的建议,我改用了木偶师。最大的变化是数据不能像window.callPhantom()
那样简单地从客户端页面传递回节点.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();