0
点赞
收藏
分享

微信扫一扫

Spring Boot + Vue + TrackingJs实现人脸识别功能

人脸检测库: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();
    }

}

检测结果(渐渐露一下脸)

Spring Boot + Vue + TrackingJs实现人脸识别功能_人脸识别

程序员最擅长什么?当然是在代码中寻找美。

举报

相关推荐

0 条评论