vue uses tracking to implement face recognition/face detection

1. Install dependencies

npm install tracking.js --save

2. Complete code (face recognition function)

The following code implements opening the camera to recognize faces

Note:

1. Implementation rules of face recognition for Android devices: After turning on the device camera, face recognition is performed in real time under the camera's shooting. If the face is recognized, the photo will be automatically taken after 1.5 seconds (the photo time can be adjusted by yourself).

2. Face recognition implementation rules for IOS devices: The purpose of opening the camera of the IOS device is to use the input file method. At this time, the camera of the IOS device is in full-screen state, so you can only use the change of input after the camera shoots ( changePic) event to get the taken photo, and then perform face recognition on the photo (detect whether there is a face). If the detection passes, the picture will be retained and drawn on the page. If it fails the face detection, it will prompt that it has not passed the face detection. Face detected.

<template>
    <div class="camera_outer">
        <!--
      此处代码请勿随意删除:
      input兼容ios无法调用摄像头的问题
      accept属性兼容某些华为手机调用摄像头,打开的是文件管理器的问题
      capture="user" 调用前置摄像头 camera 调用后置摄像头
      如果使用 style="display: none" 隐藏input后,可能会出现无法吊起相册等问题
    -->
        <input
            type="file"
            id="file"
            accept="image/*"
            capture="user"
            style="opacity: 0;"
            @change="changePic"
        />
        <video
            id="videoCamera"
            :width="videoWidth"
            :height="videoHeight"
            autoplay
            class="img_bg_camera"
        />
        <!-- 
            如果使用 style="display: none" 隐藏canvas后,将不会显示出人脸检测的识别框
            如需要人脸识别框显示 video与canvas 样式是需要相同(目的是保持同一位置)
        -->
        <canvas
            id="canvasCamera"
            :width="videoWidth"
            :height="videoHeight"
            class="img_bg_camera"
        />
        <div v-if="imgSrc" class="img_bg_camera" :class="[isDisplay ? 'displayBlock' : 'displayNone']">
            <img id="imgId" :src="imgSrc" alt class="tx_img" />
        </div>
        <div class="bottomButton">
            <van-button id="open" type="warning" @click="getCamers()" class="marginRight10" >打开摄像机</van-button>
        </div>
    </div>
</template>

<script>
// npm install tracking.js --save
require("tracking/build/tracking-min.js");
require("tracking/build/data/face-min.js");
require("tracking/build/data/eye-min.js");
require("tracking/build/data/mouth-min.js");
require("tracking/examples/assets/stats.min.js");
export default {
    data() {
        return {
            videoWidth: 300, //摄像机宽度
            videoHeight: 300, //摄像机高度
            imgSrc: "", //生成图片链接
            canvas:  null, //canvas
            context: null, //context
            video: null, //video
            isFlag: false, //非正常拍照
            isDisplay: false, //生成的照片是否显示
        }
    },
    mounted() {
        // this.getCompetence();
    },
    destroyed() {
        this.stopNavigator();
    },
    methods: {
        //调用权限(打开摄像头功能)
        getCompetence() {
            var _this = this;
            //得到canvasCamera的元素
            this.canvas = document.getElementById("canvasCamera");
            this.context = this.canvas.getContext("2d"); // 画布
            this.video = document.getElementById("videoCamera");
            // 旧版本浏览器可能根本不支持mediaDevices,我们首先设置一个空对象
            if (navigator.mediaDevices === undefined) {
                Object.defineProperty(navigator, "mediaDevices", {
                    value: {},
                    writable: true,
                    configurable: true,
                    enumerable: true,
                });
            }
            // 一些浏览器实现了部分mediaDevices,我们不能只分配一个对象,如果使用getUserMedia,因为它会覆盖现有的属性。如果缺少getUserMedia属性,就添加它。
            if (navigator.mediaDevices.getUserMedia === undefined) {
                navigator.mediaDevices.getUserMedia = function (constraints) {
                    // 首先获取现存的getUserMedia(如果存在)
                    var getUserMedia = navigator.getUserMedia || navigator.mediaDevices.getUserMedia;
                    // 有些浏览器不支持,会返回错误信息
                    if (!getUserMedia) {
                        this.$toast("getUserMedia is not implemented in this browser");
                        return Promise.reject(
                            new Error(
                                "getUserMedia is not implemented in this browser"
                            )
                        );
                    }
                    // 否则,使用Promise将调用包装到旧的navigator.getUserMedia
                    return new Promise(function (resolve, reject) {
                        getUserMedia.call(
                            navigator,
                            constraints,
                            resolve,
                            reject
                        );
                    });
                };
            }
            var constraints = {
                audio: false,
                video: {
                    width: this.videoWidth,
                    height: this.videoHeight,
                    transform: "scaleX(-1)",
                    facingMode: "user", // user 安卓前置摄像头  {exact: 'environment} 后置摄像头
                },
            }
            //使苹果手机和苹果ipad支持打开摄像机
            if (
                navigator.userAgent.toLowerCase().indexOf("iphone") != -1 ||
                navigator.userAgent.toLowerCase().indexOf("ipad") != -1
            ) {
                //使得file一定可以获取到
                document.getElementById("file").click();
                
            } else { // (安卓/浏览器(除safari浏览器)) 在用户允许的情况下,打开相机,得到相关的流
                navigator.mediaDevices
                    .getUserMedia(constraints)
                    .then(function (stream) {
                        // 旧的浏览器可能没有srcObject
                        if (!_this.video) {
                            _this.video = {};
                        }
                        try {
                            _this.video.srcObject = stream;
                        } catch (err) {
                            _this.video.src = window.URL.createObjectURL(stream); // window.URL || window.webkitURL
                        }
                        _this.isFlag = true;
                        _this.video.onloadedmetadata = () => {
                            _this.video.play();
                            _this.initTracker();// 人脸捕捉
                        }
                    })
                    .catch((err) => {
                        this.$toast('访问用户媒体权限失败,请重试');
                    });
            }
        },
        // 人脸捕捉 设置各种参数 实例化人脸捕捉实例对象,注意canvas上面的动画效果。
        // 使用 domId 来控制监听那个容器 Android 使用容器 #videoCamera,IOS使用容器 #imgId
        initTracker(domId) {
            // this.tracker = new window.tracking.ObjectTracker("face"); // tracker实例
            this.tracker = new window.tracking.ObjectTracker(['face', 'eye', 'mouth']); // tracker实例
            this.tracker.setInitialScale(4);
            this.tracker.setStepSize(2); // 设置步长
            this.tracker.setEdgesDensity(0.1);
            try {
                this.trackertask = window.tracking.track(domId ? domId :"#videoCamera", this.tracker); // 开始追踪
            } catch (e) {
                this.$toast("访问用户媒体失败,请重试")
            }
            //开始捕捉方法 一直不停的检测人脸直到检测到人脸
            this.tracker.on("track", (e) => {
                //画布描绘之前清空画布
                this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
                if (e.data.length === 0) {
                    if(domId) this.$toast("未检测到人脸,请重新拍照或上传")
                } else {
                    if(!domId){ // 安卓设备
                        e.data.forEach((rect) => { //设置canvas 方框的颜色大小
                            this.context.strokeStyle = "#42e365";
                            this.context.lineWidth = 2;
                            this.context.strokeRect(rect.x, rect.y, rect.width, rect.height);
                        });
                        if (!this.tipFlag) {
                            this.$toast("检测成功,正在拍照,请保持不动2秒")
                        }
                    }else{ // IOS设备或safari浏览器
                        if (!this.tipFlag) {
                            this.$toast("检测成功,正在生成,请稍等")
                        }
                    }
                    
                    // 1.5秒后拍照,仅拍一次 给用户一个准备时间
                    // falg 限制一直捕捉人脸,只要拍照之后就停止检测
                    if (!this.flag) {
                        this.tipFlag = true
                        this.flag = true;
                        this.removePhotoID = setTimeout(() => {
                            this.$toast("图像生成中···")
                            this.setImage(domId ? true:false);
                            this.stopNavigator() // 关闭摄像头
                            this.flag = false
                            this.tipFlag = false;
                            clearInterval(this.removePhotoID)
                            this.removePhotoID = null
                        }, 1500);
                    }
                }
            });
        },
        //苹果手机获取图片并且保存图片
        changePic(event) {
            this.isDisplay = false; // 隐藏已拍照片的展示
            
            var reader = new FileReader();
            var f = (document.getElementById("file")).files;
            reader.readAsDataURL(f[0]);
            reader.onload = () => {
                var re = reader.result;
                this.canvasDataURL(re, { quality: 1 }, (base64Codes) => {
                    if (base64Codes) {
                        this.imgSrc = base64Codes;
                        
                        // 此方式是为了检测图片中是否有人脸
                        this.$nextTick(()=>{
                            this.isFlag = true;
                            this.initTracker('#imgId')
                        })

                        // 如果不需要检验拍照或上传图片中是否有人脸,可注释上方人脸检测代码,使用此种方式
                        // this.isDisplay = true;
                        // this.submitCollectInfo();
                        
                    } else {
                        this.$toast('拍照失败');
                    }
                    event.target.value = ""; // 解决上传相同文件不触发change事件问题
                });
            };
        },
        //压缩图片
        canvasDataURL(path, obj, callback) {
            let img = new Image();
            img.src = path;
            const that = this;
            img.onload = () => {
                // 默认按比例压缩
                var w = that.videoWidth, h = that.videoHeight, scale = w / h;
                // 使用人脸识别video高度
                // w = obj.width || w;
                // h = obj.height || w / scale;

                // 使用图片真实高度(像素)图像更加清晰
                w = img.width;
                h = img.height;
                
                var quality = 0.5; // 默认图片质量为0.5
                //生成canvas
                var canvas = document.createElement("canvas");
                // canvas 设置宽高 使用默认宽高图片会变形、失真
                canvas.width = w
                canvas.height = h
                var ctx = canvas.getContext("2d");
                // 创建属性节点
                ctx.drawImage(img, 0, 0, w, h);
                // 图像质量 数值范围(0 ~ 1) 1表示最好品质,0基本不被辨析但有比较小的文件大小;
                if (obj.quality && obj.quality <= 1 && obj.quality > 0) {
                    quality = obj.quality;
                }
                // quality值越小,所绘制出的图像越模糊
                var base64 = canvas.toDataURL("image/jpeg", quality);
                // 回调函数返回base64的值
                callback(base64);
            };
        },
        //绘制图片(拍照功能)
        setImage(flag) {
            if(!this.context){
                this.$toast('请打开摄像机')
                return;
            }
            this.context.drawImage(
                flag ? document.getElementById('imgId') : this.video,
                0,
                0,
                this.videoWidth,
                this.videoHeight
            );
            // 获取图片base64链接
            var image = this.canvas.toDataURL("image/png", 0.5);
            if (this.isFlag) {
                if (image) {
                    this.imgSrc = image;
                    this.isDisplay = true;
                    this.submitCollectInfo();
                } else {
                    this.$toast("图像生成失败");
                }
            } else {
                this.$toast("图像生成失败");
            }
        },
        //保存图片
        async submitCollectInfo() { //其中可以和后端做一些交互
            console.log('与后端交互');
        },
        // 关闭摄像头 并且停止人脸检测
        stopNavigator() {
            if (this.video && this.video.srcObject) {
                this.video.srcObject.getTracks()[0].stop();
            }
            if(this.trackertask) this.trackertask.stop();
            this.tracker = null;
            this.isFlag = false
        },
        //打开摄像机
        getCamers() {
            this.isDisplay = false; // 隐藏已拍照片的展示
            this.getCompetence();
        },
        // 以下是提供的几种可能在优化或者与后端交互时需要使用的方法
        // //返回
        // goBack() {
        //     this.stopNavigator();
        //     //可以写相应的返回的一些操作,携带一些需要携带的参数
        // },
        // // 访问用户媒体设备
        // getUserMedias(constrains, success, error) {
        //     if (navigator && navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { //最新标准API
        //         navigator.mediaDevices.getUserMedia(constrains).then(success).catch(error);
        //     } else if (navigator && navigator.webkitGetUserMedia) { //webkit内核浏览器
        //         navigator.webkitGetUserMedia(constrains).then(success).catch(error);
        //     } else if (navigator && navigator.mozGetUserMedia) { //Firefox浏览器
        //         navagator.mozGetUserMedia(constrains).then(success).catch(error);
        //     } else if (navigator && navigator.getUserMedia) { //旧版API
        //         navigator.getUserMedia(constrains).then(success).catch(error);
        //     } else {
        //         this.$toast("你的浏览器不支持访问用户媒体设备")
        //         // error("访问用户媒体失败")
        //     }
        // },
        // // Base64转文件
        // getBlobBydataURI(dataURI, type) {
        //     var binary = window.atob(dataURI.split(",")[1]);
        //     var array = [];
        //     for (var i = 0; i < binary.length; i++) {
        //         array.push(binary.charCodeAt(i));
        //     }
        //     return new Blob([new Uint8Array(array)], {
        //         type: type,
        //     });
        // },
        // compare(url) {
        //     let blob = this.getBlobBydataURI(url, 'image/png')
        //     let formData = new FormData()
        //     formData.append("file", blob, "file_" + Date.parse(new Date()) + ".png")
        //     // TODO 得到文件后进行人脸识别
        // },
    }
}
</script>

<style lang="scss" scoped>
.camera_outer {
    position: relative;
    overflow: hidden;
    background-size: cover;
    background: white;
    width: 100%;
    height: 100%;
}
video,
canvas,
.tx_img {
    -moz-transform: scaleX(-1);
    -webkit-transform: scaleX(-1);
    -ms-transform: scaleX(-1);
    -o-transform: scaleX(-1);
    transform: scaleX(-1);
}
.img_bg_camera {
    position: absolute;
    bottom: 25%;
    top: 25%;
    left: 50%;
    margin-left: -150px;
    border-radius: 50%;
    -webkit-border-radius: 50%;
    -moz-border-radius: 50%;
    -ms-border-radius: 50%;
    -o-border-radius: 50%;
}
.img_bg_camera img {
    width: 300px;
    height: 300px;
    border-radius: 50%;
    -webkit-border-radius: 50%;
    -moz-border-radius: 50%;
    -ms-border-radius: 50%;
    -o-border-radius: 50%;
}

.displayNone {
    // display: none;
    opacity: 0;
}

.displayBlock {
    // display: block;
    opacity: 1;
}

.marginRight10 {
    margin-right: 20px;
}

.bottomButton {
    position: fixed;
    bottom: 20px;
    width: 100%;
    text-align: center;
}
</style>

When debugging this project, tracking.js-related dependencies will cause two warnings to appear frequently when detecting faces, but they will not affect the operation of the project.

First question: [Violation] 'requestAnimationFrame' handler took <N>ms 

This warning is usually because your canvas or video rendering is too complex or the amount of data is too large, causing the rendering to timeout within one frame of the browser. But I don’t have a good solution at the moment. If netizens know anything about it or have a solution, I hope they can discuss it in the comment area.

第二个问题:Canvas2D: Multiple readback operations using getImageData are faster with the willReadFrequently attribute set to true.

This warning probably means that using getImageData's multiple readback operations will be faster. It is recommended to set the willReadFrequency attribute to true. The current solution is to replace getContext("2d") and getContext('2d') in the two files tracking.js and tracking-min.js in the node_modules dependency with getContext("2d",{ willReadFrequently: true })

In addition, if you do not use npm to download, you can also use local file import to provide tracking.js related files. Link: https://pan.baidu.com/s/1oxJ2z6m5g4T9EUZG2XGgdg Extraction code: emqn 

3. Debugging code (no face recognition, you can take photos and upload any pictures by yourself)

<template>
    <div class="camera_outer">
        <!--
      此处代码请勿随意删除:
      input兼容ios无法调用摄像头的问题
      accept属性兼容某些华为手机调用摄像头,打开的是文件管理器的问题
    -->
        <input
            type="file"
            id="file"
            accept="image/*"
            capture="camera"
            style="display: none"
            @change="changePic"
        />
        <video
            id="videoCamera"
            :width="videoWidth"
            :height="videoHeight"
            autoplay
            class="img_bg_camera"
        />
        <canvas
            style="display: none"
            id="canvasCamera"
            :width="videoWidth"
            :height="videoHeight"
            class="img_bg_camera"
        />
        <div
            v-if="imgSrc"
            class="img_bg_camera"
            :class="[isDisplay ? 'displayBlock' : 'displayNone']"
        >
            <img :src="imgSrc" alt class="tx_img" />
        </div>
        <div class="bottomButton">
            <van-button color="#aaaaaa" @click="stopNavigator()" class="marginRight10"
                >关闭摄像头</van-button
            >
            <van-button
                id="open"
                type="warning"
                @click="getCamers()"
                class="marginRight10"
                >打开摄像机</van-button
            >
            <van-button type="warning" @click="setImage()">拍照</van-button>
        </div>
    </div>
</template>

<script>
export default {
    data() {
        return {
            videoWidth: 300, //摄像机宽度
            videoHeight: 300, //摄像机高度
            imgSrc: "", //生成图片链接
            canvas:  null, //canvas
            context: null, //context
            video: null, //video
            isFlag: false, //非正常拍照
            isDisplay: false, //生成的照片是否显示
        }
    },
    mounted() {
        // this.getCompetence();
    },
    destroyed() {
        this.stopNavigator();
    },
    methods: {
        //调用权限(打开摄像头功能)
        getCompetence() {
            var _this = this;
            //得到canvasCamera的元素
            this.canvas = document.getElementById("canvasCamera");
            this.context = this.canvas.getContext("2d");
            this.video = document.getElementById("videoCamera");
            // 旧版本浏览器可能根本不支持mediaDevices,我们首先设置一个空对象
            if (navigator.mediaDevices === undefined) {
                Object.defineProperty(navigator, "mediaDevices", {
                    value: {},
                    writable: true,
                    configurable: true,
                    enumerable: true,
                });
            }
            // 一些浏览器实现了部分mediaDevices,我们不能只分配一个对象,如果使用getUserMedia,因为它会覆盖现有的属性。如果缺少getUserMedia属性,就添加它。
            if (navigator.mediaDevices.getUserMedia === undefined) {
                navigator.mediaDevices.getUserMedia = function (constraints) {
                    // 首先获取现存的getUserMedia(如果存在)
                    var getUserMedia = navigator.getUserMedia || navigator.mediaDevices.getUserMedia;
                    // 有些浏览器不支持,会返回错误信息
                    if (!getUserMedia) {
                        console.log('getUserMedia is not implemented in this browser');
                        return Promise.reject(
                            new Error(
                                "getUserMedia is not implemented in this browser"
                            )
                        );
                    }
                    // 否则,使用Promise将调用包装到旧的navigator.getUserMedia
                    return new Promise(function (resolve, reject) {
                        getUserMedia.call(
                            navigator,
                            constraints,
                            resolve,
                            reject
                        );
                    });
                };
            }
            var constraints = {
                audio: false,
                video: {
                    width: this.videoWidth,
                    height: this.videoHeight,
                    transform: "scaleX(-1)",
                },
            }
            //使苹果手机和苹果ipad支持打开摄像机
            if (
                navigator.userAgent.toLowerCase().indexOf("iphone") != -1 ||
                navigator.userAgent.toLowerCase().indexOf("ipad") != -1
            ) {
                //使得file一定可以获取到
                document.getElementById("file").click();
                
            } else {
                //在用户允许的情况下,打开相机,得到相关的流
                navigator.mediaDevices
                    .getUserMedia(constraints)
                    .then(function (stream) {
                        // 旧的浏览器可能没有srcObject
                        if (!_this.video) {
                            _this.video = {};
                        }
                        try {
                            _this.video.srcObject = stream;
                        } catch (err) {
                            _this.video.src = window.URL.createObjectURL(stream);
                        }
                        _this.isFlag = true;
                        _this.video.onloadedmetadata = () => _this.video.play();
                    })
                    .catch((err) => {
                        console.log(err);
                    });
            }
        },
        //苹果手机获取图片并且保存图片
        changePic() {
            var reader = new FileReader();
            var f = (document.getElementById("file")).files;
            reader.readAsDataURL(f[0]);
            reader.onload = () => {
                var re = reader.result;
                this.canvasDataURL(re, { quality: 0.5 }, (base64Codes) => {
                    if (base64Codes) {
                        this.imgSrc = base64Codes;
                        this.isDisplay = true;
                        this.submitCollectInfo();
                    } else {
                        this.$toast('拍照失败');
                    }
                });
            };
        },
        //压缩图片
        canvasDataURL(path, obj, callback) {
            let img = new Image();
            img.src = path;
            const that = this;
            img.onload = () => {
                // 默认按比例压缩
                var w = that.videoWidth, h = that.videoHeight, scale = w / h;
                // w = obj.width || w;
                // h = obj.height || w / scale;
                w = img.width || w;
                h = img.height || w / scale;
                var quality = 0.5; // 默认图片质量为0.5
                //生成canvas
                var canvas = document.createElement("canvas");
                // canvas 设置宽高 使用默认宽高图片会变形、失真
                canvas.width = w
                canvas.height = h
                var ctx = canvas.getContext("2d");
                // 创建属性节点
                ctx.drawImage(img, 0, 0, w, h);
                // 图像质量
                if (obj.quality && obj.quality <= 1 && obj.quality > 0) {
                    quality = obj.quality;
                }
                // quality值越小,所绘制出的图像越模糊
                var base64 = canvas.toDataURL("image/jpeg", quality);
                // 回调函数返回base64的值
                callback(base64);
            };
        },
        //绘制图片(拍照功能)
        setImage() {
            this.context.drawImage(
                this.video,
                0,
                0,
                this.videoWidth,
                this.videoHeight
            );
            // 获取图片base64链接
            var image = this.canvas.toDataURL("image/png", 0.5);
            if (this.isFlag) {
                if (image) {
                    this.imgSrc = image;
                    this.isDisplay = true;
                    this.submitCollectInfo();
                } else {
                    console.log("拍照失败");
                }
            } else {
                console.log("拍照失败");
            }
        },
        //保存图片
        async submitCollectInfo() { //其中可以和后端做一些交互
            console.log('与后端交互');
        },
        // 关闭摄像头
        stopNavigator() {
            if (this.video && this.video.srcObject) {
                this.video.srcObject.getTracks()[0].stop();
            }
        },
        //打开摄像机
        getCamers() {
            this.isDisplay = false; // 隐藏已拍照片的展示
            this.getCompetence();
        },
    }
}
</script>

<style lang="scss" scoped>
.camera_outer {
    position: relative;
    overflow: hidden;
    background-size: cover;
    background: white;
    width: 100%;
    height: 100%;
}
video,
canvas,
.tx_img {
    -moz-transform: scaleX(-1);
    -webkit-transform: scaleX(-1);
    -ms-transform: scaleX(-1);
    -o-transform: scaleX(-1);
    transform: scaleX(-1);
}
.img_bg_camera {
    position: absolute;
    bottom: 25%;
    top: 25%;
    left: 50%;
    margin-left: -150px;
    border-radius: 50%;
    -webkit-border-radius: 50%;
    -moz-border-radius: 50%;
    -ms-border-radius: 50%;
    -o-border-radius: 50%;
}
.img_bg_camera img {
    width: 300px;
    height: 300px;
    border-radius: 50%;
    -webkit-border-radius: 50%;
    -moz-border-radius: 50%;
    -ms-border-radius: 50%;
    -o-border-radius: 50%;
}

.displayNone {
    display: none;
}

.displayBlock {
    display: block;
}

.marginRight10 {
    margin-right: 20px;
}

.bottomButton {
    position: fixed;
    bottom: 20px;
    width: 100%;
    text-align: center;
}
</style>

Guess you like

Origin blog.csdn.net/jiangzhihao0515/article/details/130603601