人脸检测库:TrackingJs
人脸比对能力:百度人脸识别
最近做一个培训系统,需要检测护理人员是否正在观看视频和统计离开次数。时间紧任务重,作为新一代的”天才程序员“这个活我肯定是要主动请功,然后搜了一下文献看到有这么一个人脸检测的插件,经过一番了解之后发现原理也很简单,而且不需要写大量的代码去调用。开造!!!!!!!
首先跟各位”像我一样长得非常帅“的大帅们简单介绍一下这个插件,TrackingJs是基于浏览器端的人脸检测库,用于实时追踪来自相机捕获的图像。追踪的数据对象可以是颜色或者人脸。若追踪的数据对象为人脸时,可以设置追踪检测目标为眼睛、鼻子、嘴巴,当检测到目标移动或者出现时会触发JavaScript事件,最终根据JavaScript的回调函数来完成业务操作。
刚刚也提到了如果检测对象类型是人脸的话可以设置检测目标为:眼睛、鼻子、嘴巴,但是这三个包是需要单独引入
<template>
<el-dialog v-if="dialogVisible" :center="true" :title="message" :visible.sync="dialogVisible" :show-close="false"
width="25%" :before-close="handleClose" :destroy-on-close="true">
<div style="width:100%;height: auto;background: rgb(12, 29, 63);">
<!-- 时间 -->
<div class="_left"><span class="_time">{{ TIME }}</span><br />{{ WEEK }}<br />{{ YTD }}</div>
<!-- 视频 -->
<video id="videoCamera-face" style="width:100%" preload autoplay loop muted />
<!-- 人脸画像 -->
<canvas id="canvas-face" v-show="false" class="_face" />
</div>
</el-dialog>
</template>
<script>
// tracking引用
require('tracking/build/tracking-min.js')
// 人脸
require('tracking/build/data/face-min.js')
// 鼻子
require('tracking/build/data/eye-min.js')
// 嘴巴
require('tracking/build/data/mouth-min.js')
export default {
name: 'faceUpload',
data() {
return {
dialogVisible: false,
YTD: null,
TIME: null,
WEEK: null,
// 人脸抓拍
takeFace: false,
// 识别次数
count: 0,
// 提示消息
message: '人脸识别',
// 人脸识别配置
trackerTask: null,
mediaStreamTrack: null,
videoWidth: 100,
videoHeight: 100,
// 视频标签
video: undefined,
// 本次人脸识别标记,回调父组件时需要传输
UUID: undefined,
type: undefined
}
},
props: {
// 完成/失败后自动关闭窗口
autoClose: {
type: Boolean,
default: false
}
},
created() {
this.FormatTime()
},
methods: {
/**
* 重置
*/
rest() {
this.UUID = undefined
this.type = undefined
},
/***
* 开始人脸识别,type:insert/update,注册/更新
*/
openHandler(UUID, type) {
this.rest()
this.UUID = UUID
this.type = type
this.dialogVisible = true
this.takeFace = true
this.getCompetence()
},
getCompetence() {
var that = this
this.$nextTick(() => {
const video = this.mediaStreamTrack = document.getElementById('videoCamera-face')
that.video = video
const canvas = document.getElementById('canvas-face')
const context = canvas.getContext('2d')
const tracker = new window.tracking.ObjectTracker(['face', 'eye', 'mouth'])
// 识别灵敏度 值越小灵敏度越高
tracker.setInitialScale(1.2)
// 转头角度影响识别率
tracker.setStepSize(14)
tracker.setEdgesDensity(0.1)
// 消息提示
that.message = '正在初始化摄像头'
// 打开摄像头
that.openCamera(video, canvas)
// tracker对象和标签关联
that.trackerTask = window.tracking.track('#videoCamera-face', tracker)
var i = 0
tracker.on('track', function (event) {
i++;
if (i >= 200) {
that.$emit('failure', '人脸识别异常!')
that.closeHandler()
}
if (event.data.length === 0 && that.takeFace) {
that.message = '未检测到人脸'
}
else {
// 会不停的去检测人脸,所以这里需要做个锁
if (that.takeFace) {
// 关闭人脸抓拍
that.takeFace = false
// 裁剪出人脸并绘制下来
context.drawImage(video, 0, 0, 300, 150)
// 人脸的basa64
const dataURL = canvas.toDataURL('image/jpeg')
// 向父组件回调人脸识别成功函数
that.$emit('success', {
'base64': dataURL,
'type': that.type
})
// 关闭组件
that.closeHandler()
}
}
})
})
},
// 打开摄像头
openCamera(video, canvas) {
navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ||
navigator.msGetUserMedia || null;
const that = this
// 获取媒体属性,旧版本浏览器可能不支持mediaDevices,我们首先设置一个空对象
if (navigator.mediaDevices === undefined) {
console.log(navigator)
navigator.mediaDevices = {}
}
// 一些浏览器实现了部分mediaDevices,我们不能只分配一个对象
// 使用getUserMedia,因为它会覆盖现有的属性。
// 这里,如果缺少getUserMedia属性,就添加它。
if (navigator.mediaDevices.getUserMedia === undefined) {
console.log(navigator)
navigator.mediaDevices.getUserMedia = function (constraints) {
// 首先获取现存的getUserMedia(如果存在)
const getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia
// 一些浏览器根本没实现它 - 那么就返回一个error到promise的reject来保持一个统一的接口
if (!getUserMedia) {
that.message = 'getUserMedia 浏览器不支持摄像头'
return Promise.reject(new Error('getUserMedia 浏览器不支持摄像头'))
}
// 否则,为老的navigator.getUserMedia方法包裹一个Promise
return new Promise(function (resolve, reject) {
getUserMedia.call(navigator, constraints, resolve, reject)
})
}
}
this.message = JSON.stringify(navigator)
var constraints = {
audio: false,
video: {
width: this.videoWidth,
height: this.videoHeight,
transform: 'scaleX(-1)'
}
}
if (navigator.getUserMedia) { // Standard 如果用户允许打开摄像头
// stream为读取的视频流
const promise = navigator.mediaDevices.getUserMedia(constraints)
promise.then(stream => {
this.mediaStreamTrack = stream.getTracks()[0]
window.stream = stream
// 旧的浏览器可能没有srcObject
if ('srcObject' in video) {
video.srcObject = stream
} else {
video.src = window.URL.createObjectURL(stream)
}
video.onloadedmetadata = function (e) {
video.play()
}
}).catch(err => {
this.message = JSON.stringify(err)
})
} else if (navigator.webkitGetUserMedia) { // WebKit-prefixed 根据不同的浏览器写法不同
navigator.webkitGetUserMedia(constraints, (stream) => {
video.src = window.webkitURL.createObjectURL(stream)
video.play()
}, this.errBack)
} else if (navigator.mozGetUserMedia) { // Firefox-prefixed
navigator.mozGetUserMedia(constraints, (stream) => {
video.src = window.URL.createObjectURL(stream)
video.play()
}, this.errBack)
} else {
}
},
// 关闭摄像头
closeCamera(video) {
if (typeof window.stream === 'object') {
if ('srcObject' in video) {
video = null
}
window.stream.getTracks().forEach(track => track.stop())
}
},
/**
* 销毁组件和关闭摄像头
*/
closeHandler() {
this.closeCamera(this.video)
// 停止检测
this.trackerTask.stop()
// 关闭弹窗
this.dialogVisible = false
},
// 时间
FormatTime() {
//设置返回显示的日期时间格式
var date = new Date();
var year = this.formatTime(date.getFullYear());
var month = this.formatTime(date.getMonth() + 1);
var day = this.formatTime(date.getDate());
var hour = this.formatTime(date.getHours());
var minute = this.formatTime(date.getMinutes());
var second = this.formatTime(date.getSeconds());
var weekday = date.getDay();
var weeks = new Array(
"星期日",
"星期一",
"星期二",
"星期三",
"星期四",
"星期五",
"星期六"
);
var week = weeks[weekday];
// 时间
this.TIME = `${hour}:${minute}:${second}`
// 日期
this.YTD = `${year}-${month}-${day}`
// 星期
this.WEEK = `${week}`
// 回调时间函数
setTimeout(this.FormatTime, 1000)
},
formatTime(n) {
//判断时间是否需要加0
if (n < 10) {
return "0" + n;
} else {
return n;
}
},
handleClose() { }
},
// 组件销毁前关闭摄像头
beforeDestroy() {
this.closeCamera(this.mediaStreamTrack)
}
}
</script>
<style lang="less" scoped>
/deep/ .el-dialog__body {
/* 去掉弹窗的内边距 */
padding: 0px !important;
}
.bottom {
width: 100%;
padding: 8px;
color: white;
display: flex;
justify-content: space-between;
}
._left {
background-color: rgb(6, 36, 74);
width: 25%;
text-align: center;
padding: 5px 0px 5px 0px;
z-index: 99999;
position: absolute;
background: none;
color: white;
}
._left ._time {
font-size: 26px;
}
._right {
font-size: 14px;
background-color: rgb(6, 36, 74);
width: 73%;
padding: 5px 5px 5px 5px;
float: right;
display: flex;
justify-content: space-around;
._content {
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column;
justify-content: space-between;
width: 70%;
h3 {
color: white;
margin: 0px !important;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
._face {
width: 100%;
height: 100%;
// width: 475px;
// height: 475px;
float: right;
}
}
</style>
前端检测到人脸之后将人脸画像转为base64的格式请求后端服务器,由服务器去调用百度人脸识别能力,百度检测后会返回一个相似结果,你可以设置一个阈值,相似率大于 > n 则代表人脸识别成功
这部分的代码也没有什么技术含量,全部贴出来奉献给大帅们吧
/**
* @author 水货程序员
* @date 2024-11-02
* Description: 操作远程人脸库信息
*/
@Component
public class RemoteFaceClient {
@Autowired
private ISysUserService sysUserService;
private static final String APP_ID = "123456";
private static final String API_KEY = "abc123";
private static final String SECRET_KEY = "abc123";
private static final String GROUP_ID = "user_01";
private static final String IMAGE_TYPE = "BASE64";
private AipFace aipFace;
@EventListener(ContextRefreshedEvent.class)
public void initApiFace(){
this.aipFace=new AipFace(APP_ID,API_KEY,SECRET_KEY);
}
/**
* 向人脸库插入一张人脸
* @param image
* @param userId
* @param options
* @return
*/
public boolean addUser(String image, String userId, HashMap<String,String> options){
JSONObject responseBody = aipFace.addUser(image.split(",")[1], IMAGE_TYPE, GROUP_ID, userId, options);
if(responseBody.getInt("error_code")==0){
return true;
}
return false;
}
/**
* 删除人脸库人脸信息
* @param userId
* @param options
* @return
*/
public boolean delUser(String userId,HashMap<String,String> options){
JSONObject responseBody = aipFace.deleteUser(GROUP_ID, userId, options);
if(responseBody.getInt("error_code")==0){
return true;
}
return false;
}
/**
* 搜索用户
* @param image
* @param options
* @return
*/
public SysUser searchUser(String image,HashMap<String,String> options){
JSONObject responseBody = aipFace.search(image.split(",")[1], IMAGE_TYPE, GROUP_ID, options);
if(responseBody.getInt("error_code")==0){
JSONArray userList = responseBody.getJSONObject("result").getJSONArray("user_list");
//不为空,且只有一个
if (userList != null && userList.length() == 1) {
JSONObject userInfo = new JSONObject(userList.get(0).toString());
//相似度大于等于80
if (userInfo.getDouble("score") >= 80) {
Long userId = Long.valueOf(userInfo.get("user_id").toString());
//用户信息返回
return sysUserService.selectUserById(userId);
}
}
}
return null;
}
/**
* 人脸对比
* @param matchRequestList
* @return
*/
public boolean matchUser(List<MatchRequest> matchRequestList){
JSONObject responseBody = aipFace.match(matchRequestList);
if(responseBody.getInt("error_code")==0){
//判断相似度
if(responseBody.getJSONObject("result").getDouble("score") >= 80){
return true;
}
}
return false;
}
}
/**
* 用户人脸信息Service业务层处理
*
* @author 水货程序员
* @date 2024-11-02
* Description: 用户画像信息管理
*/
@Service
public class FaceInfoServiceImpl implements FaceInfoService {
@Autowired
private FaceInfoMapper faceInfoMapper;
@Autowired
private TokenService tokenService;
@Autowired
private RemoteFaceClient remoteFaceClient;
/**
* 查询用户人脸信息
*
* @param faceId 用户人脸信息ID
* @return 用户人脸信息
*/
@Override
public FaceInfo selectFaceInfoById(Long faceId) {
return faceInfoMapper.selectFaceInfoById(faceId);
}
/**
* 查询用户人脸信息列表
*
* @param faceInfo 用户人脸信息
* @return 用户人脸信息
*/
@Override
public List<FaceInfo> selectFaceInfoList(FaceInfo faceInfo) {
return faceInfoMapper.selectFaceInfoList(faceInfo);
}
/**
* @param faceInfo
* @param base64
* @param user
* @return
*/
public int insertFaceInfo(FaceInfo faceInfo, String base64, SysUser user) {
//附加信息
HashMap<String, String> options = new HashMap<>();
options.put("userId", user.getUserId().toString());
options.put("userName", user.getUserName());
options.put("nickName", user.getNickName());
// 添加到人脸库
boolean addUser =remoteFaceClient.addUser(base64,user.getUserId().toString(),options);
if (addUser) {
faceInfo.setFaceBase64(base64);
faceInfo.setNickName(user.getNickName());
faceInfo.setUserGroup("user_01");
faceInfo.setUserId(user.getUserId());
faceInfo.setUserName(user.getUserName());
faceInfo.setCreateBy(user.getUserName());
faceInfo.setCreateTime(new Date());
//插入到数据库
return faceInfoMapper.insertFaceInfo(faceInfo);
}
return 0;
}
/**
* 修改用户人脸信息
*
* @param faceInfo 用户人脸信息
* @return 结果
*/
@Override
public int updateFaceInfo(FaceInfo faceInfo) {
faceInfo.setUpdateTime(DateUtils.getNowDate());
return faceInfoMapper.updateFaceInfo(faceInfo);
}
/**
* 批量删除用户人脸信息
*
* @param userIds 需要删除的用户人脸信息ID
* @return 结果
*/
@Override
public int deleteFaceInfoByUserIds(Long[] userIds) {
//删除远程库
Arrays.stream(userIds).forEach(userId->remoteFaceClient.delUser(userId.toString(),new HashMap<String,String>()));
return faceInfoMapper.deleteFaceInfoByUserIds(userIds);
}
/**
* 删除用户人脸信息信息
*
* @param userId 用户人脸信息ID
* @return 结果
*/
public int deleteFaceInfoByUserId(Long userId) {
//删除远程库
if (remoteFaceClient.delUser(userId.toString(),new HashMap<String, String>())) {
return faceInfoMapper.deleteFaceInfoByUserId(userId);
}
return 0;
}
/**
* 人脸识别认证(人脸比对)
*
* @param faceBase
* @return
*/
@Override
public AjaxResult authentication(String faceBase) {
/*当前登录用户*/
SysUser user = tokenService.getLoginUser(ServletUtils.getRequest()).getUser();
/*根据用户ID查询人脸信息*/
FaceInfo faceInfo = faceInfoMapper.findFaceInfoByUserId(user.getUserId());
if (Objects.isNull(faceInfo)) {
return AjaxResult.success("人脸识别失败,未录入人脸信息!",2);
}
/*人脸比对*/
List<MatchRequest> matchRequests = new ArrayList<>();
/*前端传入人脸画像*/
matchRequests.add(new MatchRequest(faceBase.split(",")[1], "BASE64"));
/*数据库人脸画像*/
matchRequests.add(new MatchRequest(faceInfo.getFaceBase64().split(",")[1], "BASE64"));
//人脸对比
if (remoteFaceClient.matchUser(matchRequests)) {
/*返回用户信息*/
Map resultMap = new HashMap<>();
Map<String, Object> userInfo = new HashMap<>();
userInfo.put("userId", user.getUserId());
userInfo.put("nickName", user.getNickName());
userInfo.put("dept", user.getDept().getDeptName());
userInfo.put("phonenumber", user.getPhonenumber());
resultMap.put("userInfo", userInfo);
resultMap.put("message", "恭喜您,人脸识别成功!");
return AjaxResult.success(resultMap);
}
return AjaxResult.success("人脸识别认证失败,请重试!",2);
}
/**
* 人脸搜索
* @param faceBase
* @return
*/
@Override
public AjaxResult faceSearch(String faceBase) {
SysUser user = remoteFaceClient.searchUser(faceBase,new HashMap<String, String>());
if (user != null) {
Map<String, Object> resultMap = new HashMap<>();
resultMap.put("userId", user.getUserId());
resultMap.put("nickName", user.getNickName());
resultMap.put("dept", user.getDept().getDeptName());
resultMap.put("phonenumber", user.getPhonenumber());
return AjaxResult.success(resultMap);
}
return AjaxResult.success("未查询到相关信息,请重试!",2);
}
/**
* 获取登录用户的人脸信息
*
* @return
*/
@Override
public AjaxResult getLoginFaceInfo() {
Long userId = tokenService.getLoginUser(ServletUtils.getRequest()).getUser().getUserId();
return AjaxResult.success(faceInfoMapper.findFaceInfoByUserId(userId));
}
/**
* 上传人脸信息
* @param params
* @return
*/
@Override
public AjaxResult faceUpload(Map<String, String> params) {
//查询人脸是否被注册过
SysUser resultUser = remoteFaceClient.searchUser(params.get("base64"), new HashMap<String, String>());
if (params.get("type").equals("insert")&&!Objects.isNull(resultUser)) {
return AjaxResult.error("抱歉,您的人脸信息已被注册,用户昵称:"+resultUser.getNickName()+",请联系系统管理员!");
}
SysUser user = tokenService.getLoginUser(ServletUtils.getRequest()).getUser();
//查询用户是否上传过人脸信息
FaceInfo faceInfoByUserId = faceInfoMapper.findFaceInfoByUserId(user.getUserId());
if (!Objects.isNull(faceInfoByUserId)) {
//删除人脸库和数据库
if (deleteFaceInfoByUserId(user.getUserId())==0) {
return AjaxResult.error("更新人脸数据时发生了替换异常!");
}
}
//添加人脸信息
if (insertFaceInfo(new FaceInfo(), params.get("base64"), user)==0) {
return AjaxResult.error("添加人脸信息失败!");
}
return AjaxResult.success();
}
}
检测结果(渐渐露一下脸)
程序员最擅长什么?当然是在代码中寻找美。