人脸识别技术一直备受追捧,但训练步骤是其生产就绪的一大障碍。为了绕过这个障碍,让向介绍face-api.js,这是一个基于TensorFlow.js实现的JavaScript人脸识别库。
face-api.js功能强大且易于使用,它只向展示了配置所需的内容。它实现了几种卷积神经网络(CNN),但隐藏了编写神经网络以解决面部检测、识别和地标检测的所有底层层。可以从这里获取库和模型。
在深入任何代码之前,需要设置一个服务器。如果一直在关注,会看到通过直接在浏览器中工作而保持了简单,而没有设置服务器。这种方法直到现在都有效,但如果尝试将face-api.js模型直接服务到浏览器,那么会遇到错误,因为HTML5要求从Web服务器提供网页和图像。
设置服务器的一个简单方法是设置Chrome的Web服务器。启动服务器并按照如下设置:
现在可以通过访问http://127.0.0.1:8887来访问应用程序。
一旦设置了服务器,创建一个HTML文档并导入face-api库:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8"/>
    <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
    <script type="application/x-javascript" src="face-api.js"></script>
</head>
<body>
    <h1>使用face-api.js进行实时情绪检测</h1>
    <video autoplay muted id="video" width="224" height="224" style="margin: auto;"></video>
    <div id="prediction">加载中</div>
    <script type="text/javascript" defer src="index.js"></script>
</body>
</html>
还需要包括一个视频标签和处理应用程序逻辑的JavaScript文件。这是文档的最终外观。
让继续到JavaScript文件并定义一些重要的变量:
const video = document.getElementById("video");
const text = document.getElementById("prediction");
现在设置函数来开始视频:
function startVideo() {
    navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
    if (navigator.getUserMedia) {
        navigator.getUserMedia({ video: true }, function(stream) {
            var video = document.querySelector('video');
            video.srcObject = stream;
            video.onloadedmetadata = function(e) {
                video.play();
            };
        }, function(err) {
            console.log(err.name);
        });
    } else {
        document.body.innerText = "getUserMedia not supported";
        console.log("getUserMedia not supported");
    }
}
使用face-api.js进行预测。与其下载所有模型,可以像这样从URI加载它们:
let url = "https://raw.githubusercontent.com/justadudewhohacks/face-api.js/master/weights/";
faceapi.nets.tinyFaceDetector.loadFromUri(url + 'tiny_face_detector_model-weights_manifest.json'),
faceapi.nets.faceLandmark68Net.loadFromUri(url + 'face_landmark_68_model-weights_manifest.json'),
faceapi.nets.faceRecognitionNet.loadFromUri(url + 'face_recognition_model-weights_manifest.json'),
faceapi.nets.faceExpressionNet.loadFromUri(url + 'face_expression_model-weights_manifest.json')
face-api.js将情绪分类为七个类别:快乐、悲伤、愤怒、厌恶、恐惧、中性和惊讶。由于对两个主要类别感兴趣,即中性和不高兴,将top_prediction设置为中性。一个人通常被认为是不高兴的,当他们是悲伤的、愤怒的或厌恶的,所以如果这些情绪中的任何一个的预测分数大于中性的预测分数,那么将显示预测的情绪为不高兴。让以字符串格式获取顶级预测:
function prediction_string(obj) {
    let top_prediction = "neutral";
    let maxVal = 0;
    var str = top_prediction;
    if (!obj) return str;
    obj = obj.expressions;
    for (var p in obj) {
        if (obj.hasOwnProperty(p)) {
            if (obj[p] > maxVal) {
                maxVal = obj[p];
                top_prediction = p;
                if (p === obj.sad || obj.disgusted || obj.angry){
                    top_prediction = "grumpy";
                }
            }
        }
    }
    return top_prediction;
}
最后,需要从face-api模型中获取预测:
const predictions = await faceapi.detectAllFaces(video, new faceapi.TinyFaceDetectorOptions())
    .withFaceLandmarks()
    .withFaceExpressions();
代码整合在一起看起来像这样:
const video = document.getElementById("video");
const canvas = document.getElementById("canvas");
const text = document.getElementById("prediction");
Promise.all([
    faceapi.nets.tinyFaceDetector.loadFromUri('/models'),
    faceapi.nets.faceLandmark68Net.loadFromUri('/models'),
    faceapi.nets.faceRecognitionNet.loadFromUri('/models'),
    faceapi.nets.faceExpressionNet.loadFromUri('/models'),
]).then(startVideo);
function prediction_string(obj) {
    let top_prediction = "neutral";
    let maxVal = 0;
    var str = top_prediction;
    if (!obj) return str;
    obj = obj.expressions;
    for (var p in obj) {
        if (obj.hasOwnProperty(p)) {
            if (obj[p] > maxVal) {
                maxVal = obj[p];
                top_prediction = p;
                if (p === obj.sad || obj.disgusted || obj.angry){
                    top_prediction = "grumpy";
                }
            }
        }
    }
    return top_prediction;
}
function startVideo() {
    navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
    if (navigator.getUserMedia) {
        navigator.getUserMedia({ video: true }, function(stream) {
            var video = document.querySelector('video');
            video.srcObject = stream;
            video.onloadedmetadata = function(e) {
                video.play();
            };
        }, function(err) {
            console.log(err.name);
        });
    } else {
        document.body.innerText = "getUserMedia not supported";
        console.log("getUserMedia not supported");
    }
}
video.addEventListener("play", () => {
    let visitedMsg = true;
    setInterval(async () => {
        const predictions = await faceapi.detectAllFaces(video, new faceapi.TinyFaceDetectorOptions())
            .withFaceLandmarks()
            .withFaceExpressions();
        if (visitedMsg) {
            text.innerText = "Your expression";
            visitedMsg = false;
        }
        text.innerHTML = prediction_string(predictions[0]);
    }, 100);
});