# ArcSoft3.0NodeJs 版本实现 **Repository Path**: BlacksMoon/arc_soft_node_js_3.0 ## Basic Information - **Project Name**: ArcSoft3.0NodeJs 版本实现 - **Description**: 采用ffi调用虹软3.0免费版本动态库,node 调用 C++ 实例。 - **Primary Language**: JavaScript - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 19 - **Forks**: 0 - **Created**: 2020-11-29 - **Last Updated**: 2025-01-06 ## Categories & Tags **Categories**: cv **Tags**: None ## README # ArcSoft3.0_Nodejs ### 一、项目说明 [虹软官网传送门](https://www.arcsoft.com.cn/) ,有不同平台和不同版本的sdk,有需要的根据业务下载。 此次项目支持windows和linux系统,mac 由于 禁用electron打包的应用。 1. 项目由electron-vue脚手架创建。 2. 在主线程中实现与虹软SDK交互,如果还需要和render层交互,还需使用app.on来接收render层传过来的业务。 3. vue打包时,需要在package.json中配置包含路径。 4. 采用ffi库调用C++库,[ref资料1](https://zhuanlan.zhihu.com/p/40526242) [ref资料2](http://www.voidcn.com/article/p-gogmvomv-brw.html) [ref资料3](https://www.jb51.net/article/147574.htm) 其它的请自行百度,此处就不细细阐述了。 5. 图片处理有两种,一种是[opencv4nodejs](https://www.npmjs.com/package/opencv4nodejs)(node版本大于10.x,我用的是12的最后一版本,最新版本的用的python3.3,编译问题太多),一种是jimp,都有方法实现,我推荐opencv,处理速度快,但是配置环境略复杂。 资料看官方的npm即可,其余资料参考python调用opencv的方法类似。 6. 此次主要针对window版本的dll,linux调用方法一样,将路径换位自己的路径即可,文件名为.so ### 二、ffi安装及环境安装 ``` bash 1、安装 Visual Studio 15 生成工具 2017  利用 微软自带的安装exe  【vs_BuildTools.exe】 目录:C:\Users\Administrator\.windows-build-tools,脚本安装时,会因为安装包过大和网络问题,导致安装失败。 2、指定编译MSBuild.exe位置,因为2019和上述装的2017都有这个exe,最好使用2017的,2019我安装失败了。 npm config set msbuild_path "C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\MSBuild\15.0\Bin\MSBuild.exe" 3、指定python版本,还是建议2.7,其他高版本问题太多,我的3.8.5(不推荐) npm config set python C:/Python27 npm config set python C:/python3.8.5 3、安装gyp npm install node-gyp -g 或者 npm install --global node-gyp@latest 4、安装ffi ref npm install ffi -g npm install ref -g 【20201208重要说明】 node.js调用C++动态库依赖node-ffi库,因node-ffi支持的node版本版本过低,在electron高版本中无法使用; 有一位国外作者提供了 node-ffi-napi 的库来支持高版本的node.js,推荐大家使用。 即在package.json中替换为: { "ffi-napi": "^3.1.0", "ref-array-napi": "^1.2.1", "ref-napi": "^3.0.1", "ref-struct-napi": "^1.1.1", } 成功编译 ``` ### 三、opencv4nodejs的安装 ``` bash 手动安装opencv的环境,设置环境变量 OPENCV_BIN_DIR=E:\commonsoft\opencv\build\x64\vc14\bin OPENCV_INCLUDE_DIR=E:\commonsoft\opencv\build\include OPENCV_LIB_DIR=E:\commonsoft\opencv\build\x64\vc14\lib 【20201208】 在package.json中添加 ``` bash "opencv4nodejs": { "disableAutoBuild": 1, "opencvIncludeDir": "E:\\commonsoft\\opencv\\build\\include", "opencvLibDir": "E:\\commonsoft\\opencv\\build\\x64\\vc14\\lib", "opencvBinDir": "E:\\commonsoft\\opencv\\build\\x64\\vc14\\bin" } ``` 此处这么安装,成功率90% ``` bash set OPENCV4NODEJS_DISABLE_AUTOBUILD=1 npm i -g opencv4nodejs ``` 【20201208重要说明】 此处很重要,不然 很多报错,比如 无法解析外部参数 ffi-napi 等其它包不要rebuild ![electron-rebuild编译opencv4nodejs](https://images.gitee.com/uploads/images/2020/1218/143553_59c5dafd_1983982.png "重要说明.png") 【20201208重要说明】 如果要单独用node xxx.js执行,则npm i 的版本的就可以,否则报node version against。 ![只能求求其一](https://images.gitee.com/uploads/images/2020/1218/145855_f973ca31_1983982.png "版本错误.png") ### 四、代码结构 ``` bash ├─.electron-vue ├─.idea ├─build │ └─icons ├─dist │ ├─electron │ └─web ├─images ├─src │ ├─main │ │ ├─inc │ │ ├─lib │ │ │ ├─X64 │ │ │ └─X86 │ │ └─modual │ │ ├─img │ │ │ ├─faces │ │ │ └─test │ │ └─log │ └─renderer │ ├─assets │ ├─components │ │ └─LandingPage │ ├─router │ └─store │ └─modules ├─static └─test ├─e2e │ └─specs └─unit modual文件夹为人脸识别工作目录 lib文件夹为放置dll文件路径的地方,x86 x64 为对应的版本 img 图片路径(faces 为 drawFace 的操作路径 test 是 cvImages 的操作路径) ``` ### 五、代码调用实例及说明 ``` bash const arc_face = require('./face_engine'); const path = require('path'); const config = require('./config'); const m = require('./logger'); const client = require('./clients'); const imageHelper = require('./face_cv_image'); const fsUtil = require('./fileUtils'); // 设置dll路径 let dllPath = ""; if (process.env.NODE_ENV !== 'development') { dllPath = require('path').join(__dirname, '../../main/lib/X64').replace(/\\/g, '\\\\') }else{ dllPath = require('path').join(__dirname, '../lib/X64').replace(/\\/g, '\\\\') } // 添加DLL所在目录到环境变量 process.env['PATH'] = `${process.env.PATH};${dllPath}`; // 初始化接口 const arc = new arc_face(config.lib_win64); Object.prototype.toString = function(){ return JSON.stringify(this); }; Date.prototype.toLocaleString = function() { return this.getFullYear() + "-" + (this.getMonth() + 1) + "-" + this.getDate() + " " + this.getHours() + ":" + this.getMinutes() + ":" + this.getSeconds(); }; this.test_arc = async () => { try{ let dataInfo = arc.getActiveFileInfo(); if (null != dataInfo){ let start = new Date(parseInt(dataInfo.startTime) * 1000); let end = new Date(parseInt(dataInfo.endTime) * 1000); m.logger.info("expire date is from %s to %s, please check the date for expired.thank you.", start.toLocaleString(), end.toLocaleString() ); if (new Date().getTime() > end){ m.logger.warn('your arc soft is expired,please change the file to continue use it,thank you.'); return; } } client.get('active', (err, res) => { // 激活 if (!res || parseInt(res) !== 1){ let ib = arc.activeFaceEngine(config.appId,config.appKey); client.set('active',ib?1:0); }else{ m.logger.info("engine has already initialed, do not repeat active"); } }); let version = arc.getVersion(); m.logger.info("current verson is %s",version.Version); // 初始化引擎 arc.initialFaceEngine(16,50); // 设置可信度 arc.setLiveParam(0.5,0.7); let img_path1 = path.join(__dirname, './img/1.jpg'); let img_path2 = path.join(__dirname, './img/2.jpg'); let img_path3 = path.join(__dirname, './img/3.jpg'); // jimp 处理图片 // let asvl1 = await imageHelper.parseImage(img_path1,false); // let asvl2 = await imageHelper.parseImage(img_path2,false); // let asvl3 = await imageHelper.parseImage(img_path3,false); // opencv 处理图片 let asvl1 = await imageHelper.cvImages(img_path1,false,path.join(__dirname, './img/test')); let asvl2 = await imageHelper.cvImages(img_path2,false,path.join(__dirname, './img/test')); let asvl3 = await imageHelper.cvImages(img_path3,false,path.join(__dirname, './img/test')); // 检测人脸 let data1 = arc.detectFacesEx(asvl1.imageData); let data2 = arc.detectFacesEx(asvl2.imageData); // 提取特征值 let feature1 = arc.extractFeature(asvl1.imageData, data1.multi.faceRect[0], data1.multi.faceOrient[0]); let feature2 = arc.extractFeature(asvl2.imageData, data2.multi.faceRect[0], data2.multi.faceOrient[0]); if (!feature1 || !feature2){ m.logger.error("feature1 or feature2 is null. please check."); return; } // console.log("feature1",feature1) // console.log("feature2",feature2) // 比较 let sim = arc.compareFeature(feature1.nav,feature2.nav); if (sim < 0){ m.logger.info("similar is %d,is not the same person.",sim); return; } if (sim > 0.8){ m.logger.info("similar is %d,is the same person.",sim); }else{ m.logger.info("similar is %d,is not the same person.",sim); } // base64 转 feature 测试 ,实际效果会差0.02 let f1 = arc.base64ToFeature(feature1.nab.featureBuffer); let f2 = arc.base64ToFeature(feature2.nab.featureBuffer); let ff = arc.compareFeature(f1,f2); if (ff > 0.8){ m.logger.info("similar is %d,is the same person.",ff); }else{ m.logger.info("similar is %d,is not the same person.",ff); } // 释放人脸指针 arc.release(f1.feature); arc.release(f2.feature); // 单体检测 let ib = arc.processEx(asvl3.imageData,false); if (!ib){ m.logger.error("single process checked failed."); return; } arc.getSexInfo(); arc.getAgeInfo(); arc.getAngleInfo(); arc.getLivenessScore(false); // 测试画框 let isIR = false; let asvl5 = await imageHelper.cvImages(img_path1,isIR); let infos = arc.detectFacesEx(asvl5.imageData); let filePath = path.join(__dirname, './img/faces'); await fsUtil.dirExists(filePath); imageHelper.drawFace(img_path1,infos.multi,true,filePath); // detect 和 process 系列 let fileCv = path.join(__dirname, './img/test'); await fsUtil.dirExists(fileCv); let asvl6 = await imageHelper.cvImages(img_path1,isIR,fileCv); let faces = arc.detectFaces(asvl6,false); let info = arc.processNone(asvl6,false); arc.getLivenessScore(false); // console.log('info',info); // 释放 函数中已经释放调用的指针 arc.unInitialEngine(); client.quit(); }catch (e) { m.logger.info("some error happened.%s",e.toString()); arc.unInitialEngine(); client.quit() } }; this.test_arc(); ``` ### 六、安装及使用 ``` bash # install dependencies npm install # serve with hot reload at localhost:9080 npm run dev # build electron application for production npm run build # run unit & end-to-end tests npm test # 测试人脸部分 node face_test.js ``` ### 七、常见问题 ``` bash 1、Dynamic Linking Error: Win32 error 126 这个错误有三种原因 (1)通常是传入的 DLL 路径错误,找不到 Dll 文件,推荐使用绝对路径。 (2)如果是在 x64 的node/electron下引用 32 位的 DLL,也会报这个错,反之亦然。要确保 DLL 要求的 CPU 架构和你的运行环境相同。 DLL 还有引用其他 DLL 文件,但是找不到引用的 DLL 文件,可能是 VC 依赖库或者多个 DLL 之间存在依赖关系。 设置dll的工作的环境变量 process.env['PATH'] = `${process.env.PATH};${'E:/arface_node/arface_nodejs/src/main/lib/X64'}` //添加DLL所在目录到环境变量 Dynamic Linking Error: Win32 error 127:DLL (3)没有找到对应名称的函数,需要检查头文件定义的函数名是否与 DLL 调用时写的函数名是否相同。 2、electron-vue 运行问题,如果提示app of undefined 类似的问题,在创建窗体时加入如下模块 enableRemoteModule:true, // 加入此句就可以获取app模块,就不会报错了 3、在node里面,指针 -> 缓存(buffer), 如 let a = new (typedef.MInt32.size) 表示 a*, a.deref() 可以取出指针中的值, 普通变量 b, 那 b.ref() 表示 b* .size 相当于C++ 里面的 size_t 4、调用C++ 第一步就是对应 数据类型 // ref提供了C++的基本数据类型和定义,nodejs参数类型和C语言参数类型转换 const ref = require('ref'); typedef.MLong = ref.types.long; typedef.UMLong = ref.types.uint32; typedef.MFloat = ref.types.float; // 提供 C++ 结构体定义方法 const StructType = require('ref-struct'); typedef.__tag_rect = StructType({ left: typedef.MInt32, top: typedef.MInt32, right: typedef.MInt32, bottom: typedef.MInt32 }); // 提供 C++ 数组定义方法 const ArrayType = require('ref-array'); ppu8Plane: ArrayType(ref.refType(typedef.MUInt8), 4), ref.refType(XXX)表示指针 5、C++接口映射 // 一种是不带回调函数的,如下: ffi.Library(libFile, { // 映射的接口名称 C++接口,对应的数据类型 ASFGetActiveFileInfo: [TypeDef.MRESULT, [ TypeDef.LPASF_ActiveFileInfo // [out] 激活文件信息 ] ] } // 一种是带回调函数的调用 let libname = ffi.Library('./libname', { 'setCallback': ['void', ['pointer']] }); let callback = ffi.Callback('void', ['int', 'string'], function(id, name) { console.log("id: ", id); console.log("name: ", name); }); libname.setCallback(callback); // 退出时引用回调指针以避免GC(垃圾回收) process.on('exit', function() { callback }); 6、指针拷贝操作,用到 linux 中的 libc 操作 分配指定大小的空间指针: libc.malloc(size); 初始化指针:libc.memset(*p,offset,size); 指针拷贝:libc.memcpy(*to, *from ,size); 7、结合node中的buffer,将指针拷贝至buffer const arr = new Buffer(TypeDef.MByte.size); for (let i = 0; i < _feature.featureSize; i++) { libc.memcpy(arr.address(), feature.feature.address() + i * TypeDef.MByte.size, TypeDef.MByte.size); this.pointers.push(feature.feature); _normal.feature.push(ref.get(arr, 0, TypeDef.MByte)); } arr.deref()即取出指针中的值 8、分配的指针空间一定要释放 libc.free(*p) ``` ### 八、开源说明 + 开源不易,请使用代码时,注明引用,谢谢~ + 如果你觉得我的代码帮到了你,请捐赠以表示支持,谢谢~ ![输入图片说明](https://images.gitee.com/uploads/images/2020/1130/104841_d4277490_1983982.jpeg "1.jpg")