赞
踩
1、安装依赖
npm install tracking.js --save
以下代码实现打开摄像头识别人脸
注:
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
- <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>
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。