实训日记4:情绪分析

本周实现通过用户的面部图像来分析该用户的情绪的功能,分析的方法是调用了Face++旷世的api。
Face++的文档在https://console.faceplusplus.com.cn/documents/4888373该api可以对传入图片进行人脸检测和人脸分析。
需要注意的是,使用Face++的API需要进行注册等操作

图片要求

图片格式:JPG(JPEG),PNG
图片像素尺寸:最小 4848 像素,最大 40964096 像素
图片文件大小:2 MB
最小人脸像素尺寸: 系统能够检测到的人脸框为一个正方形,正方形边长的最小值为图像短边长度的 48 分之一,最小值不低于 48 像素。 例如图片为 4096*3200 像素,则最小人脸像素尺寸为 66*66 像素。

调用URL

https://api-cn.faceplusplus.com/facepp/v3/detect

调用方法

POST

请求参数

我们需要的请求参数如下:
实训日记4:情绪分析
其中最为需要的是emotion参数,emotion是情绪识别结果。返回值包含以下字段。每个字段的值都是一个浮点数,范围 [0,100],小数点后 3 位有效数字。每个字段的返回值越大,则该字段代表的状态的置信度越高。字段值的总和等于 100。

  • anger:愤怒
  • disgust:厌恶
  • fear:恐惧
  • happiness:高兴
  • neutral:平静
  • sadness:伤心
  • surprise:惊讶

测试

参考https://github.com/FacePlusPlus/facepp-javascript-sdk?tdsourcetag=s_pctim_aiomsg

  • 首先把facepp_sdk这个文件夹拖入到自己的项目
  • 在项目中引用
<script type="text/javascript" src="facepp_sdk/jquery.min.js"></script>
<script type="text/javascript" src="facepp_sdk/exif.js"></script>
<script type="text/javascript" src="facepp_sdk/facepp_sdk.js"></script>

在使用文件选择框选择图片后,使用如下代码以二进制的方式上传图片,并显示回调结果

<script>

    var facepp = new FACEPP(APIKEY,APISERET,1);



    // 选择照片
    function selectImage(input){

        let imageView = document.getElementById('preview');

        const reader = new FileReader();

        reader.readAsDataURL(input.files[0]);

        reader.onload = function (e) {

            //移除之前的人脸框
            $("#facesContainer div").remove();

            //图片的base64数据
            const base64Image = e.target.result;

            //显示图片
            //修复显示方向不对问题
            fixOrientention(base64Image,imageView);

            /*
            //base64方式上传图片
            let dic = {'image_base64' : base64Image};

            facepp.detectFace(dic,success,failed);

            */

            // 以二进制的方式上传图片
            // 将base64转为二进制
            let imageData = facepp.dataURItoBlob(base64Image);
            //根据个人需求填写的参数,这里全部写上了,包括年龄性别等,详情看官方文档
            let attributes = 'gender,age,smiling,headpose,facequality,blur,eyestatus,emotion,ethnicity,beauty,mouthstatus,eyegaze,skinstatus';
            //上传图片,获取结果
            let dataDic = {'image_file':imageData,'return_landmark':2,'return_attributes':attributes};


            //调用接口,检测人脸
            facepp.detectFace(dataDic,success,failed);
        }
    }

    //成功的回调
    function success(e){
        //显示结果
        console.log(e);
        let textView = document.getElementById('text');
        textView.innerText = JSON.stringify(e,null,"\t");

        // 画上人脸框,男女颜色不一样
        let imageView = document.getElementById('preview');

        const faces = e.faces;

        for (const index in faces){
            const face = faces[index];
            const face_rectangle = face.face_rectangle;

            //脸部旋转角度
            var roll = face.attributes.headpose.roll_angle;

            //性别
            var gender = face.attributes.gender.value;

            const borderColor = (gender == 'Male') ? 'blue' : 'red';

            console.log('人脸框颜色:' + borderColor);

            //脸部坐标
            var faceX = face_rectangle.left;
            var faceY = face_rectangle.top;
            var faceW = face_rectangle.width;
            var faceH = face_rectangle.height;

            //faceContainer尺寸
            var width = 320;
            var height = 320;

            //img尺寸
            var imageW = imageView.width;
            var imageH = imageView.height;

            //图片实际尺寸
            var naturalWidth = imageView.naturalWidth;
            var naturalHeight = imageView.naturalHeight;

            console.log('container尺寸' + width + '----' +  height);
            console.log('img尺寸' + imageW + '----' + imageH);
            console.log('图片实际尺寸' + naturalWidth + '----' + imageH);

            const scale = imageW / naturalWidth;

            console.log('scale > ' + scale);

            const offsetX = (width - imageW)*0.5;
            const offsetY = (height - imageH)*0.5;

            console.log('offsetX:' + offsetX + 'offsetY' + offsetY);

            //添加人脸框
            $('<div/>').css({
                position: 'absolute',
                top: faceY * scale + offsetY,
                left: faceX * scale + offsetX,
                height: faceH * scale,
                width: faceW * scale,
                border: '2px solid ' + borderColor,
                transform: 'rotate(' + roll + 'deg)'
            }).appendTo($("#facesContainer"));
        }
    }

    //失败的回调
    function failed(e){
        console.log(e);
        let textView = document.getElementById('text');
        textView.innerText = JSON.stringify(e);
    }

    //图片方向矫正
    function fixOrientention(base64Image,imageView) {
        const image = new Image();

        image.onload = () => {
            const canvas = document.createElement('canvas');

            const initSize = image.src.length;

            let width = image.naturalWidth;
            let height = image.naturalHeight;

            const ctx = canvas.getContext('2d');
            ctx.clearRect(0, 0, canvas.width, canvas.height);

            // 旋转图片操作
            EXIF.getData(image, function () {
                const orientation = EXIF.getTag(this, 'Orientation');
                console.log(`orientation:${orientation}`);
                switch (orientation) {
                    // 正常状态
                    case 1:
                        console.log('旋转0°');
                        canvas.height = height;
                        canvas.width = width;
                        ctx.drawImage(image, 0, 0, width, height);
                        break;
                    // 旋转90度
                    case 6:
                        console.log('旋转90°');
                        canvas.height = width;
                        canvas.width = height;
                        ctx.rotate(Math.PI / 2);
                        ctx.translate(0, -height);
                        ctx.drawImage(image, 0, 0, width, height);
                        break;
                    // 旋转180°
                    case 3:
                        console.log('旋转180°');
                        canvas.height = height;
                        canvas.width = width;
                        ctx.rotate(Math.PI);
                        ctx.translate(-width, -height);
                        ctx.drawImage(image, 0, 0, width, height);
                        break;
                    // 旋转270°
                    case 8:
                        console.log('旋转270°');
                        canvas.height = width;
                        canvas.width = height;
                        ctx.rotate(-Math.PI / 2);
                        ctx.translate(-width, 0);
                        ctx.drawImage(image, 0, 0, width, height);
                        break;

                    default:
                        console.log('default 旋转0°');
                        canvas.height = height;
                        canvas.width = width;
                        ctx.drawImage(image, 0, 0, width, height);
                        break;
                }
            });

            var newBase64 = canvas.toDataURL('image/jpeg', 1.0);
            imageView.src = newBase64;
        };
        image.src = base64Image;
    }

</script>

基本思路是把图片转为base64,再转为二进制;接下来根据个人需求填写的参数,这里全部写上;然后上传图片,调用接口;最后获取结果,结果是json格式的数据,把它展示在文本框中即可。

测试结果

测试结果如下
实训日记4:情绪分析
这里用到了个人练习生蔡徐坤先生用标准姿势手拿篮球的照片,可以看到准确地框出了人脸的位置,并在下方文本框中写出了返回参数。
看一下我们最关心的emotion参数:
实训日记4:情绪分析
情绪分析的结果为:

  • anger:愤怒——14.67%
  • disgust:厌恶——0.163%
  • fear:恐惧——0.163%
  • happiness:高兴——1.936%
  • neutral:平静——68.429%
  • sadness:伤心——14.67%
  • surprise:惊讶——11.633%