微信小程序 | 人脸识别的最终解决方案
📌个人主页:个人主页
🧀 推荐专栏:小程序开发成神之路 --(这是一个为想要入门和进阶小程序开发专门开启的精品专栏!从个人到商业的全套开发教程,实打实的干货分享,确定不来看看? 😻😻)
📝作者简介:一个读研中创业、打工中学习的能搞全栈、也搞算法、目前在搞大数据的奋斗者。
⭐️您的小小关注是我持续输出的动力!⭐️
干货内容推荐
🥇入门和进阶小程序开发,不可错误的精彩内容🥇 :
- 《吐血整理的几十款小程序登陆界面【附完整代码】》
- 《你真的会做小程序按钮吗?看了字节35K前端的样式设计,悟了》
- 《来接私活吧?玩转小程序开发之丝滑拆红包【附完整代码】》
- 《来接私活吧?小程序接私活必备功能-婚恋交友【附完整代码】》
一、人脸识别功能现状
1.1 微信原生接口篇
微信原生的人脸识别接口可以完美的结合小程序所采集到的图像,可以达到实时帧级别的识别,是高效开发人脸识别功能的首选!
但是,由于人脸数据是个人的敏感数据,微信在开放该接口出来时,就是为了方便各行各业的业务开展。所以,我们要用它,就必须具备相应的资质。后续才能通过审核并发布上线。
资质说明链接:微信开放能力文档
1.2 百度接口篇
百度人脸识别SDK的服务模式是用户在平台开通好相应的权限以及获取到appId等一系列操作之后,再到我们开发端是采用对百度人脸验证平台的相应接口进行发送验证请求才能实现服务的调用。
也就是说:百度已经人脸验证服务帮你全部搭建好了,你只需要发起请求进行调即可。
百度人脸识别服务地址
可以按量收费,目前根据你的业务量的QPS进行计费统计。
如果您的业务并发支持要求较高,免费测试 QPS 不能满足,您可以随时购买扩充 QPS ,QPS 可包月购买,也可按天购买,灵活多样,适应多场景需求。
1.3 虹软接口篇
平台链接地址:虹软接口平台
虹软 和 百度 这两者之间的区别在于:
- 百度直接替你部署好了识别用的服务,你只需要按照文档指引向其特定的 接口发送数据即可获得你的结果,在某些主前端开发轻后端服务的应用来说还是很有优势的。
- 虹软所走的模式是不管你是离线还是在线都能让你运行,他把搭建服务端的工作留给你自己去做。在这样的模式下,你可以实时把控用户的人脸数据,对其进行自定义操作,从而构造更为灵活的人脸设别方式,对业务场景的开发也可以有更多操作的空间!
二、人脸识别功能流程
三、微信接口解析
3.1 微信摄像头组件的使用
- 小程序中要使用到摄像头的功能在于使用标签:
对于标签核心的参数如下:
属性 类型 必填 说明 可选值 默认值 mode string 否 应用模式,只在初始化时有效,不能动态变更 【 normal: 相机模式 】【scanCode:扫码模式】 normal 属性 类型 必填 说明 可选值 默认值 resolution string 否 分辨率,不支持动态修改 【 low 低 】【medium 中】【high 高】 medium 属性 类型 必填 说明 可选值 默认值 device-position string 否 摄像头朝向 【 front 前置 】【back 后置】 back 属性 类型 必填 说明 可选值 默认值 flash string 否 闪光灯,值为 auto , on, off 【 auto 自动 】【on 打开】【off 关闭】【torch 常亮】 auto 3.2 微信小程序实时视频帧获取
对如何调用和开启微信小程序中的摄像头摄像和拍照功能进行学习:微信小程序–摄像头接口详解
- 首先需要调用CameraContext wx.createCameraContext()方法创建camera操作对象。
- 然后在camera对象之后调用onCameraFrame(function callback),该方法用于实时获取摄像头所捕捉的相片帧,从而用于与后端人脸识别引擎进行交互。
const context = wx.createCameraContext() const listener = context.onCameraFrame((frame) => { console.log(frame.data instanceof ArrayBuffer, frame.width, frame.height) }) listener.start()四、工具包的准备
4.1 将帧数据转为Base64
- 针对onCameraFrame()方法,获取到的是相机所捕捉的图片帧数据,这个时候我们需要将其转为Base64格式的图片数据,这是为了让其传输到后端能够被人脸识别引擎所使用!
- 在微信所开放的用于将帧数据转化为base 64接口中wx.arrayBufferToBase64(已弃用),项目中需要用摄像头获取人脸并将获取的ArrayBuffer数据转化为base64,就需要经过一下流程:
代码如下:
let pngData = ToPNG.encode([frame.data], frame.width, frame.height), base64 = Base64Util.arrayBufferToBase64(pngData)
- 所以先准备ArrayBuffer数据转化为PNG数据的工具包ToPNG.js
import pako from 'pako' var UPNG = {}; UPNG.toRGBA8 = function (out) { var w = out.width, h = out.height; if (out.tabs.acTL == null) return [UPNG.toRGBA8.decodeImage(out.data, w, h, out).buffer]; var frms = []; if (out.frames[0].data == null) out.frames[0].data = out.data; var len = w * h * 4, img = new Uint8Array(len), empty = new Uint8Array(len), prev = new Uint8Array(len); for (var i = 0; i >> 8 }; H.H.I = function (N, W, R) { R = R >> 3; N[V] |= R; N[V + 1] |= R >>> 8; N[V + 2] |= R >>> 16 }; H.H.e = function (N, W, R) { return (N[W >>> 3] | N[(W >>> 3) + 1] >> (W & 7) & (1 return (N[W >> 3] | N[(W >>> 3) + 1] >> 3) + 2] >> (W & 7) & (1 return (N[W >> 3] | N[(W >>> 3) + 1] >> 3) + 2] >> (W & 7) }; H.H.i = function (N, W) { return (N[W >>> 3] | N[(W >>> 3) + 1] >> 3) + 2] >> 3) + 3] >> (W & 7) }; H.H.m = function () { var N = Uint16Array, W = Uint32Array; return { K: new N(16), j: new N(16), X: [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15], S: [3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 999, 999, 999], T: [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 0, 0, 0], q: new N(32), p: [1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577, 65535, 65535], z: [0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 0, 0], c: new W(32), J: new N(512), _: [], h: new N(32), $: [], w: new N(32768), C: [], v: [], d: new N(32768), D: [], u: new N(512), Q: [], r: new N(1 var N = H.H.m, W = 1 var V = R; V = (V & 2863311530) 1 | (V & 1431655765) > 2 | (V & 858993459) >> 4 | (V & 252645135) >> 8 | (V & 16711935) >> 16 | V >> 17 } function n(A, l, M) { while (l-- != 0) A.push(0, M) } for (var R = 0; R 3]; val = (val >> (7 - (cdi & 7))) & 1; img[row * bpl + (col >> 3)] |= (val var val = data[cdi 3]; val = (val >> (6 - (cdi & 7))) & 3; img[row * bpl + (col >> 2)] |= (val var val = data[cdi 3]; val = (val >> (4 - (cdi & 7))) & 15; img[row * bpl + (col >> 1)] |= (val var ii = row * bpl + col * cbpp; for (var j = 0; j 3) + j]; } cdi += bpp; col += ci; } y++; row += ri; } if (sw * sh != 0) di += sh * (1 + bpll); pass = pass + 1; } return img; } UPNG.decode._getBPP = function (out) { var noc = [1, null, 3, 1, 2, null, 4][out.ctype]; return noc * out.depth; } UPNG.decode._filterZero = function (data, out, off, w, h) { var bpp = UPNG.decode._getBPP(out), bpl = Math.ceil(w * bpp / 8), paeth = UPNG.decode._paeth; bpp = Math.ceil(bpp / 8); var i, di, type = data[off], x = 0; if (type > 1) data[off] = [0, 0, 1][type - 2]; if (type == 3) for (x = bpp; x >> 1)) & 255; for (var y = 0; y >> 1)); for (; x >> 1)); } else { for (; x > 8) & 255; buff[p + 3] = n & 255; }, readASCII: function (buff, p, l) { var s = ""; for (var i = 0; i = 0 && yoff >= 0) { si = (y * sw + x) si = ((-yoff + y) * sw - xoff + x) tb[ti] = sb[si]; tb[ti + 1] = sb[si + 1]; tb[ti + 2] = sb[si + 2]; tb[ti + 3] = sb[si + 3]; } else if (mode == 1) { var fa = sb[si + 3] * (1 / 255), fr = sb[si] * fa, fg = sb[si + 1] * fa, fb = sb[si + 2] * fa; var ba = tb[ti + 3] * (1 / 255), br = tb[ti] * ba, bg = tb[ti + 1] * ba, bb = tb[ti + 2] * ba; var ifa = 1 - fa, oa = fa + ba * ifa, ioa = (oa == 0 ? 0 : 1 / oa); tb[ti + 3] = 255 * oa; tb[ti + 0] = (fr + br * ifa) * ioa; tb[ti + 1] = (fg + bg * ifa) * ioa; tb[ti + 2] = (fb + bb * ifa) * ioa; } else if (mode == 2) { // copy only differences, otherwise zero var fa = sb[si + 3], fr = sb[si], fg = sb[si + 1], fb = sb[si + 2]; var ba = tb[ti + 3], br = tb[ti], bg = tb[ti + 1], bb = tb[ti + 2]; if (fa == ba && fr == br && fg == bg && fb == bb) { tb[ti] = 0; tb[ti + 1] = 0; tb[ti + 2] = 0; tb[ti + 3] = 0; } else { tb[ti] = fr; tb[ti + 1] = fg; tb[ti + 2] = fb; tb[ti + 3] = fa; } } else if (mode == 3) { // check if can be blended var fa = sb[si + 3], fr = sb[si], fg = sb[si + 1], fb = sb[si + 2]; var ba = tb[ti + 3], br = tb[ti], bg = tb[ti + 1], bb = tb[ti + 2]; if (fa == ba && fr == br && fg == bg && fb == bb) continue; //if(fa!=255 && ba!=0) return false; if (fa 24) != 255) pltAlpha = true; leng += (8 + dl * 3 + 4) + (pltAlpha ? (8 + dl * 1 + 4) : 0); } for (var j = 0; j >> 8) & 255, b = (c >>> 16) & 255; data[offset + ti + 0] = r; data[offset + ti + 1] = g; data[offset + ti + 2] = b; } offset += dl * 3; wUi(data, offset, crc(data, offset - dl * 3 - 4, dl * 3 + 4)); offset += 4; // crc if (pltAlpha) { wUi(data, offset, dl); offset += 4; wAs(data, offset, "tRNS"); offset += 4; for (var i = 0; i >> 24) & 255; offset += dl; wUi(data, offset, crc(data, offset - dl - 4, dl + 4)); offset += 4; // crc } } var fi = 0; for (var j = 0; j > 2, bln >> 2); inds.push(ind); var bb = new Uint8Array(qres.abuf, cof, bln); //console.log(frm.img, frm.width, frm.height); //var time = Date.now(); if (dither) UPNG.encode.dither(frm.img, frm.rect.width, frm.rect.height, plte, bb, ind); //console.log(Date.now()-time); frm.img.set(bb); cof += bln; } //console.log("quantize", Date.now()-time); time = Date.now(); } else { // what if ps==0, but there are // when not quantized, other frames can contain colors, that are not in an initial frame var frm = frms[j], img32 = new Uint32Array(frm.img.buffer), nw = frm.rect.width, ilen = img32.length; var ind = new Uint8Array(ilen); inds.push(ind); for (var i = 0; i = 300) break; } ind[i] = cmc; } } } //console.log("make palette", Date.now()-time); time = Date.now(); } var cc = plte.length; //console.log("colors:",cc); if (cc if (cc var frm = frms[j], nx = frm.rect.x, ny = frm.rect.y, nw = frm.rect.width, nh = frm.rect.height; var cimg = frm.img, cimg32 = new Uint32Array(cimg.buffer); var bpl = 4 * nw, bpp = 4; if (cc bpl = Math.ceil(depth * nw / 8); var nimg = new Uint8Array(bpl * nh); var inj = inds[j]; for (var y = 0; y may) may = y; } } if (max == -1) mix = miy = max = may = 0; if (evenCrd) { if ((mix & 1) == 1) mix--; if ((miy & 1) == 1) miy--; } var sarea = (max - mix + 1) * (may - miy + 1); if (sarea max) max = cx; if (cy may) may = cy; } } if (max == -1) mix = miy = max = may = 0; if (evenCrd) { if ((mix & 1) == 1) mix--; if ((miy & 1) == 1) miy--; } r = { x: mix, y: miy, width: max - mix + 1, height: may - miy + 1 }; var fr = frms[i]; fr.rect = r; fr.blend = 1; fr.img = new Uint8Array(r.width * r.height * 4); if (frms[i - 1].dispose == 0) { UPNG._copyTile(pimg, w, h, fr.img, r.width, r.height, -r.x, -r.y, 0); UPNG.encode._prepareDiff(cimg, w, h, fr.img, r); //UPNG._copyTile(cimg,w,h, fr.img,r.width,r.height, -r.x,-r.y, 2); } else UPNG._copyTile(cimg, w, h, fr.img, r.width, r.height, -r.x, -r.y, 0); } UPNG.encode._prepareDiff = function (cimg, w, h, nimg, rec) { UPNG._copyTile(cimg, w, h, nimg, rec.width, rec.height, -rec.x, -rec.y, 2); /* var n32 = new Uint32Array(nimg.buffer); var og = new Uint8Array(rec.width*rec.height*4), o32 = new Uint32Array(og.buffer); UPNG._copyTile(cimg,w,h, og,rec.width,rec.height, -rec.x,-rec.y, 0); for(var i=4; i>>2]==o32[(i>>>2)-1]) { n32[i>>>2]=o32[i>>>2]; //var j = i, c=p32[(i>>>2)-1]; //while(p32[j>>>2]==c) { n32[j>>>2]=c; j+=4; } } } for(var i=nimg.length-8; i>0; i-=4) { if(nimg[i+7]!=0 && nimg[i+3]==0 && o32[i>>>2]==o32[(i>>>2)+1]) { n32[i>>>2]=o32[i>>>2]; //var j = i, c=p32[(i>>>2)-1]; //while(p32[j>>>2]==c) { n32[j>>>2]=c; j+=4; } } }*/ } UPNG.encode._filterZero = function (img, h, bpp, bpl, data, filter, levelZero) { var fls = [], ftry = [0, 1, 2, 3, 4]; if (filter != -1) ftry = [filter]; else if (h * bpl > 500000 || bpp == 1) ftry = [0]; var opts; if (levelZero) opts = { level: 0 }; var CMPR = (data.length > 10e6 && UZIP != null) ? UZIP : pako; var time = Date.now(); for (var i = 0; i 1); else c = c >>> 1; } tab[n] = c; } return tab; })(), update: function (c, buf, off, len) { for (var i = 0; i >> 8); return c; }, crc: function (b, o, l) { return UPNG.crc.update(0xffffffff, b, o, l) ^ 0xffffffff; } } UPNG.quantize = function (abuf, ps) { var sb = new Uint8Array(abuf), tb = sb.slice(0), tb32 = new Uint32Array(tb.buffer); var KD = UPNG.quantize.getKDtree(tb, ps); var root = KD[0], leafs = KD[1]; var planeDst = UPNG.quantize.planeDst; var len = sb.length; var inds = new Uint8Array(len >> 2), nd; if (sb.length > 2] = nd.ind; tb32[i >> 2] = nd.est.rgba; } else for (var i = 0; i 2] = nd.ind; tb32[i >> 2] = nd.est.rgba; } return { abuf: tb.buffer, inds: inds, plte: leafs }; } UPNG.quantize.getKDtree = function (nimg, ps, err) { if (err == null) err = 0.0001; var nimg32 = new Uint32Array(nimg.buffer); var root = { i0: 0, i1: nimg.length, bst: null, est: null, tdst: 0, left: null, right: null }; // basic statistic, extra statistic root.bst = UPNG.quantize.stats(nimg, root.i0, root.i1); root.est = UPNG.quantize.estats(root.bst); var leafs = [root]; while (leafs.length maxL) { maxL = leafs[i].est.L; mi = i; } if (maxL = s0 || node.i1 node.est.L = 0; continue; } var ln = { i0: node.i0, i1: s0, bst: null, est: null, tdst: 0, left: null, right: null }; ln.bst = UPNG.quantize.stats(nimg, ln.i0, ln.i1); ln.est = UPNG.quantize.estats(ln.bst); var rn = { i0: s0, i1: node.i1, bst: null, est: null, tdst: 0, left: null, right: null }; rn.bst = { R: [], m: [], N: node.bst.N - ln.bst.N }; for (var i = 0; i > 2]; nimg32[i0 >> 2] = nimg32[i1 >> 2]; nimg32[i1 >> 2] = t; i0 += 4; i1 -= 4; } while (vecDot(nimg, i0, e) > eMq) i0 -= 4; return i0 + 4; } UPNG.quantize.vecDot = function (nimg, i, e) { return nimg[i] * e[0] + nimg[i + 1] * e[1] + nimg[i + 2] * e[2] + nimg[i + 3] * e[3]; } UPNG.quantize.stats = function (nimg, i0, i1) { var R = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; var m = [0, 0, 0, 0]; var N = (i1 - i0) >> 2; for (var i = i0; i 4; tg[ti + 1] += (er[1] * f) >> 4; tg[ti + 2] += (er[2] * f) >> 4; tg[ti + 3] += (er[3] * f) >> 4; } function N(x) { return Math.max(0, Math.min(255, x)); } function D(a, b) { var dr = a[0] - b[0], dg = a[1] - b[1], db = a[2] - b[2], da = a[3] - b[3]; return (dr * dr + dg * dg + db * db + da * da); } var pc = plte.length, nplt = [], rads = []; for (var i = 0; i >> 0) & 255), ((c >>> 8) & 255), ((c >>> 16) & 255), ((c >>> 24) & 255)]); } for (var i = 0; i >2]; var nc = nplt[ni]; var er = [cc[0] - nc[0], cc[1] - nc[1], cc[2] - nc[2], cc[3] - nc[3]]; //addErr(er, err, i+4, 16); //* if (x != w - 1) addErr(er, err, i + 4, 7); if (y != h - 1) { if (x != 0) addErr(er, err, i + 4 * w - 4, 3); addErr(er, err, i + 4 * w, 5); if (x != w - 1) addErr(er, err, i + 4 * w + 4, 1); //*/ } oind[i >> 2] = ni; tb32[i >> 2] = plte[ni]; } } } module.exports = UPNG- 再将png数据转化为Base64数据,所使用的工具包Base64Util:
export default { arrayBufferToBase64(buffer) { var binary = ''; var bytes = new Uint8Array(buffer); var len = bytes.byteLength; for (var i = 0; i 255 || (b = string.charCodeAt(i++)) > 255 || (c = string.charCodeAt(i++)) > 255) throw new TypeError("Failed to execute 'btoa' on 'Window': The string to be encoded contains characters outside of the Latin1 range."); bitmap = (a 18 & 63) + b64.charAt(bitmap >> 12 & 63) + b64.charAt(bitmap >> 6 & 63) + b64.charAt(bitmap & 63); } // If there's need of padding, replace the last 'A's with equal signs return rest ? result.slice(0, rest - 3) + "===".substring(rest) : result; } return "data:image/png;base64," + btoa(binary); } }4.2 控制帧数据采集频率
- 特别注意:对于微信的这个数据帧采集接口onCameraFrame,它是处于一个以60ms采集一次用户的数据的频率运行的,很多时候我们用不到这么高的频率!我们可以通过setInteerval定时器来控制摄像头采集用户数据的频率。
let task = setInterval(function() { var timeStart = Date.now(); //在此处处理store[0](图像的数据); // store.shift(); var frame = that.frameQueue.shift() console.log("开始运行===",frame,that.flag); that.flag = true; if(frame != undefined){ let pngData = UPNG.encode([frame.data], frame.width, frame.height), base64 = Base64Util.arrayBufferToBase64(pngData) uni.request({ url: 'http://127.0.0.1:8000/miniapp/faceEngine/faceLogin' , method: 'post', data: {openId:uni.getStorageSync("openId"),base64Img:base64} , dataType:'json', header: { 'content-type':'application/json'//自定义请求头信息 }, success:(res)=>{ console.log("====执行成功===",res) if(res.statusCode != undefined){ clearInterval(task) that.isAuthCamera = false uni.navigateTo({ url:'./login' }) } }, fail:(err)=>{ console.log("====执行失败===",err) clearInterval(task) that.isAuthCamera = false uni.navigateTo({ url:'./login' }) } }) }五、实时人脸采集功能实现
- 特别注意:对于微信的这个数据帧采集接口onCameraFrame,它是处于一个以60ms采集一次用户的数据的频率运行的,很多时候我们用不到这么高的频率!我们可以通过setInteerval定时器来控制摄像头采集用户数据的频率。
- 再将png数据转化为Base64数据,所使用的工具包Base64Util:
- 所以先准备ArrayBuffer数据转化为PNG数据的工具包ToPNG.js
- 小程序中要使用到摄像头的功能在于使用标签:
- 《来接私活吧?小程序接私活必备功能-婚恋交友【附完整代码】》
- 《来接私活吧?玩转小程序开发之丝滑拆红包【附完整代码】》
- 《你真的会做小程序按钮吗?看了字节35K前端的样式设计,悟了》
免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们,邮箱:ciyunidc@ciyunshuju.com。本站只作为美观性配图使用,无任何非法侵犯第三方意图,一切解释权归图片著作权方,本站不承担任何责任。如有恶意碰瓷者,必当奉陪到底严惩不贷!








