0


vue使用tracking实现人脸识别/人脸侦测

1、安装依赖

npm install tracking.js --save

2、完整代码(人脸识别功能)

以下代码实现打开摄像头识别人脸

注:

1、安卓设备的人脸识别实现规则: 打开设备摄像机后,在相机的拍摄下实时进行人脸识别,如果识别到人脸后,1.5秒后自动拍照(可自行调整拍照时间)。

2、IOS设备的人脸识别实现规则:是利用input file的方式来达到打开IOS设备摄像机的目的,此时IOS设备的相机是全屏状态,所以只能在相机拍摄以后,再利用input的change(changePic)事件来得到拍摄的照片,然后再对照片进行人脸识别(检测是否存在人脸),如果检测通过,会保留该图片并且绘制在页面上,如果未通过人脸检测,则会提示未检测到人脸。

<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>

该项目在调试时,tracking.js相关依赖会在检测人脸时,会有两个警告频繁出现,但不会影响项目的运行

第一个问题:[Violation] 'requestAnimationFrame' handler took <N>ms

这个警告通常是因为你的canvas或者video渲染过于复杂或者数据量过大,导致在浏览器的一帧内渲染超时。但本人目前没有太好的解决方法,如广大网友有了解或者是解决办法,希望可以在评论区讨论一下

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

这个警告大概的意思是 使用getImageData的多次读回操作会更快,建议将willReadFrequency属性设置为true。目前的解决方法是将 node_modules 依赖中的 tracking.js 和 tracking-min.js 这两个文件中的 getContext("2d") 和 getContext('2d') 整体替换为 getContext("2d",{ willReadFrequently: true })

另外如果不使用npm方式下载,也可以使用本地文件导入的方式 提供tracking.js相关文件 链接: https://pan.baidu.com/s/1oxJ2z6m5g4T9EUZG2XGgdg 提取码: emqn

3、调试代码(无人脸识别,可自行拍照和上传任意图片)

<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>

本文转载自: https://blog.csdn.net/jiangzhihao0515/article/details/130603601
版权归原作者 jiangzhihao0515 所有, 如有侵权,请联系我们删除。

“vue使用tracking实现人脸识别/人脸侦测”的评论:

还没有评论