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>
版权归原作者 jiangzhihao0515 所有, 如有侵权,请联系我们删除。