1、项目背景
微信公开课公众号在2019年7月5日宣布了微信小程序实现Web AR的基础API,该API能实时从摄像头获取每帧图像。但是,微信小程序没有提供图像识别和人脸检测的功能。Tacking.js是一个轻量型的计算机视觉库,能实现颜色跟踪、人脸检测等功能。JSFeat是一个计算机视觉算法库,类似OpenCV的图像算法。
本演示项目的源代码在GitHub上,地址请见文章末尾。
2、准备步骤
用微信开发者工具导入源代码,点击工具栏上的“预览“按钮,微信扫一扫会启动微信小程序。如果“预览”按钮是灰色不可点击,则需要为小程序项目填写一个测试ID。
微信小程序会请求使用摄像头权限,选择“允许“。如果选择“拒绝”了,在小程序右上角的设置按钮中重新打开即可。
因为是新的小程序API,iOS版微信需要7.0.4版,Android版微信需要7.0.5版。目前,大多数人没有安装新版微信,可以选择“take a photo”模式体验,而不是“access a camera”模式。因为“take a photo”模式没有使用新API,所以在任何版本的微信都可用。
3、使用方法
使用Demo小程序,扫描这张图片即可。您也可以扫描身边红色、黄色、蓝色的东西。
扫描成功后的效果如下:
“take a photo”模式的截图
操作方法:点击“take a photo”按钮拍照,点击“retry”重新拍照。
“access a camera”模式的截图
操作方法:页面会自动检测摄像头画面中是否存在紫色、黄色、蓝色,没有手动操作。
4、图像颜色跟踪的用途
虽然根据颜色不能判断是什么物体,但能在已知颜色的一堆物体中,区分是什么物体。颜色分析比轮廓分析等图像技术的速度要快很多,但准确率要低。适用于对准确度要求不高的场合。
5、人脸检测
使用Demo小程序,扫描人脸或图片即可。
识别成功后,人脸会显示一个方框,并且显示了眉毛、眼睛、鼻子、嘴巴、脸的轮廓等31个特征点。
点击“change camera direction”按钮,切换后置和前置摄像头。
2019.8.1更新:实现了透视变换的人脸。
透视变换的人脸:当人脸特征点不准确时,2D贴纸的位置也不准确。
调整人脸识别的参数后,特征点变得贴近人脸,2D贴纸的位置变得准确。
6、图像识别
图像识别分为2个部分,一个是被识别的图片,另一个是拍照或摄像头实时获取的图片。
需要在拍照或摄像头图像中,计算出被识别图片的位置、大小、旋转等变换。
被识别图片如下:
扫描一个旋转的图片
期望结果如下:
半透明的黑色遮罩层覆盖了被识别的图片。
扫描一个斜切的图片
期望结果如下:
扫描一个平移和缩放的图片
期望结果如下:
功能限制:目前支持识别图片的平移、旋转、斜切、缩放等2D变换。
2019.8.1更新:实现了识别图片的透视变换。
扫描一个透视变换的图片
期望结果如下:
7、对象跟踪(不推荐)
在人脸、眼睛、嘴巴等位置显示一个方框,该功能的速度比人脸识别慢,准确度也差。这个是tracking.js的一个例子。
8、技术问题和处理方法
8.1 将原生组件camera和canvas叠加,camera显示摄像头视频,canvas显示信息,第一次打开小程序时,下方的camera组件会遮挡上方的canvas组件。
处理方法:为camera组件和canvas组件分别设置CSS的z-index属性。
8.2 实时获取摄像头图像,方法context.onCameraFrame的frame.data值是ArrayBuffer类型,怎么转化成图像的数据类型?
处理方法:图像对象 = new Uint8ClampedArray(frame.data);
8.3 计算机视觉库tracking.js移植到微信小程序,需要做哪些修改?
处理方法:微信小程序中不存在浏览器对象的window和navigator,需替换掉。tracking.js库在小程序中无法访问摄像头,需将小程序的摄像头图像数据送给tracking.js库。
8.4 实时获取摄像头图像,对象CameraFrameListener的stop()方法无法停止侦听摄像头,即使退出小程序页面也没有用。
重现步骤:CameraFrameListener的start()方法启用侦听摄像头后,在小程序调试日志中看见不停输出console.log记录。接着,调用CameraFrameListener的stop()方法通知CameraFrameListener停止,小程序调试日志仍然不停地输出console.log记录。最后,点击左上角返回退出页面,在新页面的小程序调试日志仍然不停地输出console.log记录。
const
处理方法:不在context.onCameraFrame的回调函数中调用tracking.js库,改成用定时器调用tracking.js库。
const
8.5 除了用定时器执行循环,还有其他循环执行方法吗?
一般使用requestAnimationFrame方法,以前在小程序中没有提供,只在小游戏中提供。
现在小程序的canvas增加了webgl的支持,也增加了canvas.requestAnimationFrame方法。
9、图像识别的优化方法
9.1 准备计时器
在要优化的方法调用前和调用后的位置放置计时器,记录方法的耗用时间。一些优化方法可能没有预期效果,通过计时器能观察出来。
9.2 提高识别速度
1、一般要对图像预处理,比如灰度化、模糊化、边缘检测等。测试了tracking.js自带的Image.blur()、Image.grayscale()、Image.sobel()等方法,效果不理想。预处理后,还增加了整体的识别时间。
测试结果:没有预处理时,耗时1600ms。灰度化后,耗时约1660ms。模糊化后,耗时约2450ms。
2、通过减少图像的尺寸大小,效果明显。
测试结果:
图像宽度375像素,耗时1600ms;
图像宽度180像素,耗时350ms;
图像宽度90像素,耗时160ms;
图像宽度50像素,耗时50ms。
3、微信小程序拍照的图像尺寸压缩方法
将图像绘制到画布上,利用画布调整图像尺寸。
ctx
4、微信小程序实时获取摄像头的图像尺寸压缩方法
2019.8.6更新:因为频繁调用wx.canvasToTempFilePath会让Android版微信闪退,“Access a camera”模式不再使用该压缩方法。
获取的摄像头图像是一个Arraybuffer类型,不是文件路径,画布的ctx.drawImage方法无法绘制。
非官方思路:先将Arraybuffer类型的图像用wx.canvasPutImageData方法绘制到画布上,接着用wx.canvasToTempFilePath方法生成临时文件,然后把临时文件用画布的ctx.drawImage方法绘制。剩下的事情和拍照的图像压缩方法一样。
wx
注:该方法会重复创建临时文件,若有其他图像压缩方法更好。
9.3 提高识别精度
1、识别的图像应该和原始的图像比例一致。例如,原始图像长宽比4:3,为了在程序上显示好看,图像被拉伸为长宽比4:3.5,被拉伸的图像无法被正确识别。
2、在图像尺寸固定时,tracking.js库中人脸检测的initialScale值和颜色检测的minDimension值越大,检测速度越快但识别精度越低。两个值越小,检测速度越慢但识别精度越高。
3、在图像尺寸发生几倍的变化时,tracking.js库中人脸检测的initialScale值和颜色检测的minDimension值要分别地配合图像尺寸调整。例如,图像尺寸从宽375像素和高458像素,变成宽50像素和高61像素。这时几乎无法检测到人脸了,但将initialScale值和minDimension值设置的足够小,又可以检测到人脸。
9.4 根据场景选择优化方案
提高识别精度时,识别速度会降低;而提高识别速度时,识别精度又会降低。在用户点击按钮拍照的场景,识别精度可以设置的高一些。在实时地从摄像头获取图像的场景,识别精度可以设置的低一些,以换取更快的识别速度。
9.5 微信小程序无法优化的地方
微信小程序的宿主环境分为2层:逻辑层运行在JavaScript引擎上,渲染层运行在WebView上。小程序的JavaScript计算速度比手机网页快了一点。但是,遇到用JavaScript进行视觉计算的情况,运算时间会很长。
例如,PC浏览器上JavaScript计算一个图像变换,花费300毫秒,而在手机浏览器上计算这个图像变换,花费3秒。目测,PC浏览器比手机浏览器约快10倍。在微信小程序中,既无法使用WASM加速,也无法直接使用硬件加速。另外,小程序还有2MB的大小限制。
提示:人脸检测那个演示需要的人脸训练模型(Regressor.js)原始大小有4MB,包含4层模型。为了符合微信小程序的大小限制,改为了1层模型。