使用picoJs在vue中的实现人脸识别


外圆内方 2019-8-1 Vue picoJs 人脸识别

需求初衷: 在一个餐饮项目中,有一个需求是员工吃饭需要自动扫脸识别出员工信息,员工点菜确认后出菜。并记录员工用餐信息。

注意问题: 在 chrome 46 之后,getUserMedia 只能在 localhost,https 中使用。解决方法有三种

  1. 使用 chrome 46 之前的浏览器
  2. 使用 https
  3. 给浏览器设置 unsafely-treat-insecure-origin-as-secure, 具体方法打开 chrome 在地址栏填入 chrome://flags 回车进入后查询 unsafely-treat-insecure-origin-as-secure,查询到后设置为 Enadled 并在方框中填写访问的地址重启。
  1. 在 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