? 博文地址 mitscherlich.me/tech/2018/n…
早在去年,中科院的山世光老师团队就开源了其论文中实现的人脸识别框架 SeetaFaceEngine
,而在今年早些时候,中科视拓团队又为我们带来了性能更为优越的 SeetaFaceEngine2
。我在随后便封装了一个 Node.js 插件版的模块 node-seeta
,以便于在我的项目中使用它。
经过了半年左右的修改与重构,现在这个模块也还算是稳定了,所以现在也想来分享一下这个模块的开发心得,以及如何使用这个模块在你的 Node.js 应用中引入 SeetaFaceEngine。
现在 node-seeta
的版本是 0.2.4
,如果你使用的是 0.2.2 <=
的版本,暴露的 API 则全部是同步操作 (不需要 async
, await
关键字),当然 >= 0.2.3
也保留了同步的版本:detectSync
, compareSync
和 recognizeSync
。
⚠️ 请不要使用
0.1.x
,他们是已经废弃的版本。
下载安装
源码地址
- node-seeta: github.com/Mitscherlic…
- SeetaFaceEngine2: github.com/seetaface/S…
系统需求
Ubuntu | Windows | |
---|---|---|
版本 | xenial/boinc | 7 with sp1/8/8.1/10 |
体系结构 | x64 | x86/x86_64 |
编译工具 | gcc | Visual Studio 2013/2015 |
最小配置 | 1G1C | 1G1C |
构建状态 |
编译依赖
这个模块的编译依赖于 OpenCV
,使用 OpenCV 是由于 SeetaFaceEngine2 自身的历史包袱 (延伸阅读: 为什么 Node.js 读取图片这么难)。不过幸好 SeetaFaceEngine2 对于 OpenCV 的依赖仅仅是读取图片,所以理论上 OpenCV >= 2.4
的版本都可以使用,但要注意预编译的库文件使用了 gcc 6.20
和 protobuf 2.6
,而这两个库在 Ubuntu boinc 上不是默认的版本,你可以从源码编译安装,也可以参考我的安装指南来购配置构建环境。
参考下面的命令来配置你的安装环境,可能会有所帮助:
- For Ubuntu xenial
$ sudo apt update
$ sudo apt install build-essential dpkg-dev
$ sudo apt install cmake git libopenblas-dev libprotobuf-dev libssl-dev libgtk2.0-dev pkg-config libavcodec-dev libavformat-dev libswscale-dev
复制代码
准备好之后,你可以参考我的的安装脚本来一键配置 OpenCV 和 protobuf。
哦对了,使用这个模块依然需要 SeetaFaceEngine 预训练的模型文件:
开发实践
光说不练假把式,现在我们就从一个简易的人脸检测/对齐/识别项目入手,来简单了解一下这个模块如何将 SeetaFaceEngine 带到 Node.js 的世界。
下面我编写了一个使用了 koa/seeta 的人脸识别后端服务,并设计前端页面用来测试它的功能。
? 你可以从以下地址获取示例代码:github.com/Mitscherlic…
后端接口
首先在你的项目目录下初始化项目所需要的依赖:
# 使用 npm
$ npm i -S koa koa-logger koa-router koa-multer koa-static-cache seeta
# 当然你也可以使用 yarn
$ yarn add koa koa-logger koa-router koa-multer koa-static-cache seeta
复制代码
使用 koa 作为后端 Web 框架,koa-logger 用于打印请求日志,koa-multer 用于处理上传文件,koa-static-cache 用于托管静态文件,最后自然是项目核心 node-seeta 模块。
接着创建工程目录结构:
.
├── app.js
├── router.js
├── models
│ ├── SeetaFaceDetector2.0.ats
│ ├── SeetaFaceRecognizer2.0.ats
│ └── SeetaPointDetector2.0.pts5.ats
├── package.json
└── public
├── uploads
├── js
| └── main.js
└── index.html
复制代码
编写入口文件:
// app.js
const static = require('koa-static')
const logger = require('koa-logger')
const seeta = require('seeta')
const path = require('path')
const fs = require('fs')
const Koa = require('koa')
const app = new Koa()
const fdModel = path.join(__dirname, 'models/SeetaFaceDetector2.0.ats')
const faModel = path.join(__dirname, 'models/SeetaPointDetector2.0.pts5.ats')
const frModel = path.join(__dirname, 'models/SeetaFaceRecognizer2.0.ats')
const port = process.env.PORT || 3000
const dev = process.env.NODE_ENV === 'development'
// 由于 `SeetaFaceEngine2` 的限制,所有的 seeta 对象都只能为单例
// 这里使用了 koa 框架的反模式,将 seeta 对象挂载到 context 下
// 在路由中这样使用:ctx.detector/pointer/recognizer
app.context.detector = new seeta.FaceDetector(fdModel)
app.context.pointer = new seeta.PointDetector(faModel)
app.context.recognizer = new seeta.FaceRecognizer(frModel)
app.use(static(path.join(__dirname, 'public'), {
maxAge: 14 * 24 * 60 * 60 // 两周
}))
if (dev) app.use(logger())
require('./router')(app)
app.listen(port)
console.log(`app running on http://127.0.0.1:${port}`)
复制代码
编写路由文件:
// router.js
const Multer = require('koa-multer')
const Router = require('koa-router')
const { Image } = require('seeta')
const fs = require('fs')
const store = Multer.diskStorage({
// 保存路径
destination: (req, file, cb) => cb(null, 'public/uploads/'),
// 修改文件名称
filename: (req, file, cb) => {
const suffix = (file.originalname).split('.').pop()
const timestamp = Date.now()
cb(null, timestamp + '.' + suffix)
}
})
const upload = Multer({ storage: store })
const router = new Router({ prefix: '/api' })
/**
* /apit/seeta/face 人脸识别请求接口
* method: POST
* files: avatar
* 接受客户端发送的图片并进行人脸检测与识别,返回检测结果与相似度结果
*/
router.post('/seeta/face', upload.single('avatar'), async ctx => {
// 读取上传的图片
const image = new Image(__dirname + '/public/uploads/' + ctx.req.file.filename)
// 检测/对齐/识别
try {
if (ctx.recognizer.opertaional) {
const { count, faces } = await ctx.pointer.detect(image, ctx.detector)
if (count < 1) {
ctx.body = { success: 0, msg: '您上传的图片好像不包含人脸' }
return
}
const similars = await ctx.recognizer.recognize(image, ctx.detector, ctx.pointer)
ctx.body = { success:1, faces, similars }
} else ctx.body = { success: 0, msg: '请等待服务端初始化完成' }
} catch (err) {
console.error(err)
}
})
/**
* /apit/seeta/faces 获取人脸数据库 (入库的图片)
* method: GET
* 返回已经入库的图片路径
*/
router.get('/seeta/faces', async ctx => {
// 遍历整个上传文件夹
const avatars = []
const walk = path => {
fs.readdirSync(path).forEach(file => {
const newPath = path + '/' + file
const stat = fs.statSync(newPath)
if (stat.isFile()) {
if (/(.*)\.(png|jpg)/.test(file))
avatars.push(newPath)
} else if (stat.isDirectory()) walk(newPath)
})
}
walk('public/uploads')
ctx.body = { success: 1, avatars }
})
/**
* /apit/seeta/faces 清空人脸数据库
* method: DELETE
* 清空人脸数据库
*/
router.delete('/seeta/faces', async ctx => {
if (ctx.recognizer.operational) {
const walk = path => {
fs.readdirSync(path).forEach(file => {
const newPath = path + '/' + file
const stat = fs.statSync(newPath)
if (stat.isFile()) {
if (/(.*)\.(png|jpg)/.test(file))
fs.unlinkSync(newPath)
} else if (stat.isDirectory()) walk(newPath)
})
}
walk('public/uploads')
ctx.recognizer.clear()
ctx.body = { success: 1, msg: '清除成功' }
} else ctx.body = { success: 0, msg: '请等待服务端初始化完成' }
})
module.exports = app => app.use(router.routes(), router.allowedMethods())
复制代码
简单吧,后端的逻辑就这么多。
前端页面
前端页面的代码我就不贴了,大家可以到示例仓库里自取,这里就只放几张演示截图:
小结
本来编写这个模块的初衷只是为了完成我在实验室遗留的一个项目,后来觉得有必要好好学习一下 node.js 的 c++ 插件开发,现在经过了反复的修改成为了一个小有规模的项目。如果你对这个项目感兴趣,也欢迎联系我加入到本项目的开发中来;本文写作时间仓促,如有不慎遗漏或者有谬误之处,还请不吝指正。
参考链接
- Mitscherlich/node-seeta: github.com/Mitscherlic…
- seetaface/SeetaFaceEngine2: github.com/seetaface/S…
- koajs/koa: github.com/koajs/koa
- koajs/static: github.com/koajs/stati…
- koa-modules/multer: github.com/koa-modules…
- alexmingoia/koa-Routre: github.com/alexmingoia…