注
需求初衷: 在一个餐饮项目中,有一个需求是员工吃饭需要自动扫脸识别出员工信息,员工点菜确认后出菜。并记录员工用餐信息。
注意问题: 在 chrome 46 之后,getUserMedia 只能在 localhost,https 中使用。解决方法有三种
- 使用 chrome 46 之前的浏览器
- 使用 https
- 给浏览器设置 unsafely-treat-insecure-origin-as-secure, 具体方法打开 chrome 在地址栏填入 chrome://flags 回车进入后查询 unsafely-treat-insecure-origin-as-secure,查询到后设置为 Enadled 并在方框中填写访问的地址重启。
- 在 vue 文件中
<template>
<video id="video" style="display: none;"></video>
<canvas id="canvas" width="320" height="240"></canvas>
</template>
<script>
import video from "SRC/minixs/video.js"
export default {
mixins: [video],
mounted() {
this.initCamera("submitFacePic“)
},
methods: {
// fd是包含提取面部的formData
submitFacePic(fd) {}
}
}
</script>
import pico from "./pico.js"
export default {
data() {
return {
// 存储画布实例
camvas: null,
// 拍照开关
photographic: true
}
},
methods: {
// 初始化面部识别
initCamera(funName) {
var _this = this
// 使用最后5帧的检测
var updateMemory = pico.instantiate_detection_memory(5)
var facefinderClassifyRegion = function(r, c, s, pixels, ldim) {
return -1.0
}
// 获取facefinder用于处理面部的算法二进制文件
var cascadeurl = `./facefinder`
fetch(cascadeurl).then(function(response) {
response.arrayBuffer().then(function(buffer) {
var bytes = new Int8Array(buffer)
facefinderClassifyRegion = pico.unpack_cascade(bytes)
})
})
// 在画布上获取绘图上下文并定义一个函数将RGBA图像转换为灰度
var ctx = document.getElementById("canvas").getContext("2d")
function rgbaToGrayscale(rgba, nrows, ncols) {
var gray = new Uint8Array(nrows * ncols)
for (var r = 0; r < nrows; ++r) {
for (
var c = 0;
c < ncols;
++c // gray = 0.2*red + 0.7*green + 0.1*blue
) {
gray[r * ncols + c] =
(2 * rgba[r * 4 * ncols + 4 * c + 0] +
7 * rgba[r * 4 * ncols + 4 * c + 1] +
1 * rgba[r * 4 * ncols + 4 * c + 2]) /
10
}
}
return gray
}
// 每当视频帧可用时调用该函数
var processfn = function(video, dt) {
// 将视频帧渲染到canvas元素并提取RGBA像素数据
ctx.drawImage(video, 0, 0, 320, 240)
var rgba = ctx.getImageData(0, 0, 320, 240).data
const image = {
pixels: rgbaToGrayscale(rgba, 240, 320),
nrows: 240,
ncols: 320,
ldim: 320
}
const params = {
shiftfactor: 0.1, //将检测窗口移动其大小的10%
minsize: 100, //脸的最小尺寸
maxsize: 1000, //脸的最大尺寸
scalefactor: 1.1 //用于多尺度处理:当移动到更高比例时,将检测窗口的大小调整10%
}
// 在帧上运行级联并对获得的检测进行聚类
// dets是一个包含(r,c,s,q)四元组的数组
//(表示行,列,比例和检测分数)
let dets = pico.run_cascade(
image,
facefinderClassifyRegion,
params
)
dets = updateMemory(dets)
dets = pico.cluster_detections(dets, 0.2)
// 绘制检测
for (let i = 0; i < dets.length; ++i) {
// 检查检测分数 //如果它高于阈值,则绘制它 //(常数50.0是经验的:其他级联可能需要不同的级联)
if (dets[i][3] > 50.0) {
// 截取到人脸在video中的参数
// 左上角x坐标
const x = dets[i][1] - dets[i][2] / 2
// 左上角y坐标
const y = dets[i][0] - dets[i][2] / 2
// 长
const xWidth = dets[i][2] * 1.2
// 高
const yWidth = dets[i][2] * 0.9
// 绘制红色方框
ctx.beginPath()
ctx.rect(x, y, xWidth, yWidth)
ctx.lineWidth = 3
ctx.strokeStyle = "red"
ctx.stroke()
// 如果允许拍照,则拍照生成image,append到formData并返回
if (_this.photographic) {
_this.photographic = false
setTimeout(() => {
const canvas = document.createElement("canvas")
canvas.width = 640
canvas.height = 480
canvas
.getContext("2d")
.drawImage(
document.getElementById("video"),
x * 2,
y * 2,
xWidth * 2,
yWidth * 2,
0,
0,
640,
480
)
canvas.toBlob(
blob => {
var fd = new FormData()
fd.append("file", blob, "face.png")
_this[funName](fd)
},
"image/png",
1
)
}, 500)
}
}
}
}
// 实例化摄像机处理(参见https://github.com/cbrandolino/camvas)
this.camvas = new this.Camvas(ctx, processfn)
},
// 初始化画布
Camvas(ctx, callback) {
this.ctx = ctx
this.callback = callback
this.video = document.getElementById("video")
//如果我们不这样做,则不播放流。
//顺便说一句,播放和暂停控件照常工作
//用于流式视频。
this.video.setAttribute("autoplay", "1")
this.video.setAttribute("playsinline", "1") //对iPhone很重要
//当我们开始流式传输视频时,会发生回调。
navigator.mediaDevices
.getUserMedia({ video: true, audio: false })
.then(
stream => {
//是的,现在我们的网络摄像头输入被视为普通视频和
//我们可以开始玩得开心
this.video.srcObject = stream
//让我们开始画画布
this.update()
},
function(err) {
throw err
}
)
//一旦我们可以在画布上绘制一个新帧,我们就会调用`draw`函数
//我们作为参数传递
this.update = function() {
var self = this
var last = Date.now()
var loop = function() {
// 对于某些效果,您可能想知道传递了多少时间
// 变量(以毫秒表示)
var dt = Date.now() - last
self.callback(self.video, dt)
last = Date.now()
requestAnimationFrame(loop)
}
requestAnimationFrame(loop)
}
},
// 关闭摄像头
closeCamera() {
this.camvas && this.camvas.video.srcObject.getTracks()[0].stop()
}
}
}
具体的详情参考https://github.com/tehnokv/picojs