前言
最近整理资料的时候,才发现之前写的一个智慧猫眼的爱智场景没有给大家写文章介绍,可能有的小伙伴之前也看到爱智官方引用我的这个代码给大家进行了一个直播演示,我个人还是觉得自己也有必要简单介绍一下(其实是代码被自己搞丢了,最近才找官方要回来)。整个场景所需要的嵌入式设备以及相关代码看看看我之前写的arduino 天下第一(暴论) – 智能猫眼与 SDDC 连接器移植到 arduino 上和从零开始的DIY智能家居 – 智能红外接近传感器,这里就不过多啰嗦了,直接来进入这次的正题。
界面&描述
在讲解应用代码之前,先给大家看一下实现的界面以及介绍下整个场景的流程。
上图就是我们的这个应用的主页面,其实也就一个页面,为了方便把所有内容都放在一个页面里面了(就是懒!o(´^`)o)。不过不影响我接下来的表演。
从上到下,最上面黑色区域是摄像头拍摄到的一张张图片的显示区域,再往下就是关于人脸识别的一些信息和识别度等数据,这里还添加了一个根据灯关色温进行切换的表情显示。可以说说以上都是整个场景的一些数据展示。
中间部分我们可以看到有四个设备通过虚线连接到到爱智的一个演示图,我们通过点击每个图片对应的类型设备进行场景设备的选择,当设备被添加成功后,场景的业务逻辑会自动的运行。
主要业务逻辑:红外接近设备检测到物体靠近,会通知应用进行摄像头设备的拍照,再拿到返回的照片信息后,会通过人脸识别功能进行识别匹配,如果是授权人员,门锁设备将会进行开锁操作,同归灯泡会个根据当前人脸的表情进行色温调节。
除了可以自动识别开锁,应用也提供了手动开锁,那个大大的圆形的中间有个锁的按钮就是开锁按钮了。
最下面的授权成员,其实就是我们配置的人员人脸信息,主要还是通过调用手机摄像头进行拍照,然后通过人脸分析模块将得到的数据和个人信息进行持久化存储。
关于应用的大致描述就以上这些了。(/ω\)
代码分析
下面我们根据上面说的场景业务逻辑给大家简单过一遍代码。
-
添加授权人员信息
... // 添加成员信息 const multer = require('middleware').multer; const upload = multer({ dest: 'uploads/' }); // const upload = multer({ storage: multer.memoryStorage() }); app.post('/upload-file', upload.any(), (req, res) => { // 检测用户名是否存在 const isExistName = lwir_camera_lock_light_scene.settings.faces.find((item) => { return item.name === req.body.name }); if (isExistName) { return res.send({ result: false, message: '当前用户名的用户已存在!' }); } const buffer = fs.readFile(path.join('./', req.files[0].path)); const features = getFaceFeature(buffer); if (!features) { return res.send({ result: false, message: '人脸识别出错!' }) } if (!features.length || features.length > 1) { return res.send({ result: false, message: '请确保图片中有且仅有单人头像!' }); } // 先检测数据库中是否存在该人脸的信息 const isExistFace = checkFaceAuthorization(features[0].keys); if (isExistFace) { return res.send({ result: false, message: '当前人脸信息已经在数据库中存在!' }); } // 将人脸信息key录入数据库中 lwir_camera_lock_light_scene.settings.faces.push({ name: req.body.name, keys: features[0].keys }) updateSceneDB(); res.send({ result: true, message: '人脸录入成功!' }); emitMembers() }) // 获取图片上的人脸特征信息 function getFaceFeature(picture) { if (!picture) { return } const bitmap = imagecodec.decode(picture, { components: imagecodec.COMPONENTS_RGB }); if (!bitmap || !bitmap.buffer) { return } const faces = facenn.detect(bitmap.buffer, { width: bitmap.width, height: bitmap.height, pixelFormat: facenn.PIX_FMT_RGB24 }, true); const faceFeatures = []; if (faces.length) { let feature; faces.map((item) => { feature = facenn.feature(bitmap.buffer, { width: bitmap.width, height: bitmap.height, pixelFormat: facenn.PIX_FMT_RGB24 }, item, { live: true, emotion: true }) faceFeatures.push(feature); }) } return faceFeatures; } // 检查人脸是否为授权成员 function checkFaceAuthorization(key) { for (const item of lwir_camera_lock_light_scene.settings.faces) { const res = facenn.compare(key, item.keys); if (res > 0) { socketIO.emit('similarity', Number((res * 100).toFixed(1))); } console.log("compare: ", res) if (res > 0.55) { socketIO.emit('face', { name: item[0] }); return true; } } socketIO.emit('face', { name: '' }); return false; } ...
-
场景自动化逻辑
// 构建设备控制对象 function generateDevController(devid) { return new Promise((resolve, reject) => { const dev = devManager.devMap.get(devid) || {}; const type = getDeviceType(dev); devManager.generateController(devid).then((controller) => { controller.on('message', (data) => { console.info('message:', data); if (type === 'lock') { // 门锁 socketIO.emit('lock', data.data.unlock); lockStatus = data.data.unlock; if (lockStatus === 'ON' && lightDev && lightDev.devid) { const lightController = devManager.controllerMap.get(lightDev.devid); if (lightController) { lightController.send({ channel0: true }, undefined, 3) } } } else if (type === 'lwir') { // 红外感应 if (data.data.proximity === 'ON' && cameraDev) { socketIO.emit('people') notify.push('智能猫眼', '检测到有人出现!'); t = setInterval(() => { // 检测到物体靠近后,开启500ms定时器拍照处理 recogniseCateyeView(devManager.controllerMap.get(cameraDev.devid)); }, 500); } else { clearInterval(t); } } else if (type === 'camera') { curImageSize = data.size; console.log('image size: ', curImageSize) } }); resolve(controller); }).catch(() => { reject(`应用缺少控制${dev.alias}的权限!`); }) }) } // 获取猫眼视图进行人脸识别分析开锁 function recogniseCateyeView(controller) { if (!controller) { return; } getCateyeData(controller) .then((chunk) => { // 人脸分析 const features = getFaceFeature(chunk); let isAuthorization = false; // 图片中是否存在授权用户 if (features && features.length) { let scenes = ''; switch (features[0].emotion) { case 'happy': scenes = 'relax' break; case 'sad': scenes = 'night' break; default: scenes = 'wakeup' break; } socketIO.emit('emotion', features[0].emotion); if (lightDev && lightDev.devid) { const lightController = devManager.controllerMap.get(lightDev.devid); if (lightController && lockStatus === 'ON') { // 开锁状态期间可以改变色温 lightController.send({ scenes }, undefined, 3) } } for (const item of features) { isAuthorization = checkFaceAuthorization(item.keys); if (isAuthorization) { break; } } } else { socketIO.emit('similarity', 0); socketIO.emit('face', { name: '' }); } socketIO.emit('image', chunk.toString('base64')); if (isAuthorization && lockStatus === 'OFF') { unlock(); } }).catch((err) => { console.error(err) }) } // 获取猫眼数据 function getCateyeData(controller) { return new Promise((resolve, reject) => { // 使用连接器进行分块传输 const camConnector = new Device.Connector(controller, true); let chunks = []; camConnector.on('connect', function () { console.info('connect'); }) camConnector.on('data', function (chunk) { console.info('data: ', chunk.length); chunks.push(chunk); }) camConnector.on('close', function () { console.info('close'); }) camConnector.on('error', function (error) { chunks = []; reject('connector error.') }) camConnector.on('finish', function () { console.info('finish', chunks.length, curImageSize); if (!curImageSize) { return reject('no image size.'); } if (!chunks.length) { return reject('no receive image.') } const chunk = Buffer.concat(chunks); console.log('chunk compare: ', chunk.length, curImageSize) if (chunk.length === curImageSize) { resolve(chunk); } else { reject('chunk lose.'); } }) camConnector.on('timeout', function (error) { chunks = []; const time = formatDate(new Date()) reject(`connector timeout: ${time}`) }) controller.send({ cam: 'image', connector: camConnector }, undefined, 0) }) }
场景演示
场景演示这里就不录制了,大家可以去看官网当时的一个直播演示视频
总结
这个场景整个实现相对于之间的来说,还是相对比较复杂的,如果你可以自己开发这样的一个场景出来的话,那我感觉其他的爱智设备控制应用开发应该也不会有难度了。最后,如果大家觉得哪里写的有问题的话可以直言不讳哈!