JS不全记录 - 使用tracking进行人脸追踪

283 阅读3分钟

前言

在进行人脸追踪的学习过程中,网上大部分的分享的技术点都是face-api。在大致学习了face-api有关文档之后,感觉它的作用主要是人脸识别(并没有体会到有进行人脸的追踪过程),所以后续挖掘到了有人分享了使用tracking去实现人脸的追踪。

安装依赖: npm i tracking
项目导入:
  require('tracking/build/tracking-min.js')
  require('tracking/build/data/face-min.js')

之前有使用过 import的导入方式,没有获取到对应的依赖,没有发现原因是什么

或者直接下载依赖包,进行导入

简单说一下以下代码实现的业务需求

1. 开启设备摄像头(某些原因导致设备不支持,也提供了图片上传的功能)
2. 进行人脸追踪
3. 获取人脸照片,并转换成base64传给后端进行人脸校验

以下代码使用react进行分享

---------------- DOM结构 ----------------
reader(
    <div>
        { showContainer ?
        <div>
          <video id='video' ref={video} style={{width: 200, height: 200, borderRadius: '50%'}} />
          <canvas id="refCanvas" style={{width: 250, height: 250, position: 'absolute', left: 0}}></canvas>
        </div> :
        <img style={{width: 200, height: 200, borderRadius: isRadius}} src={imgUrl} />}
        {
          upImg ?
          <div style={{ width: '200px' }}>
            <input type='file' accept='image/*' onChange={upImage} name='file' id='file'/>  
          </div> : ''
        }
        <BodyContainer>
    </div>
)
const [imgUrl, setImgUrl] = useState(IconFace)
const [tipFlag, setTipFlag] = useState(false) // 提示用户已经检测到
const [removePhotoID, setRemovePhotoID] = useState(null)
const [showContainer, setShowContainer] = useState(true)
const [upImg, setUpImg] = useState(false)
const [isRadius, setIsRadius] = useState('50%') // 图片是否圆角

let trackertask = null
let streamIns = null  // 视频流
let isTakePhoto = false // 判断是否已经拍照

useEffect(() => {
    if (video) {
      playVideo()
    }
},[video])
 
const playVideo = () =>{
  getUserMedia({
      //摄像头拍摄的区域
      video: {
        width: 200,
        height: 200,
        facingMode: "user",/* 前置优先 */,
      } 
    },
    success, error
  )
}

const getUserMedia = (constrains, success, error) => {
    if (navigator.mediaDevices.getUserMedia) {
      //最新标准API
      navigator.mediaDevices.getUserMedia(constrains)
      .then(success)
      .catch(error)
    } else if (navigator.webkitGetUserMedia) {
      //webkit内核浏览器
      navigator.webkitGetUserMedia(constrains).then(success).catch(error)
    } else if (navigator.mozGetUserMedia) {
      //Firefox浏览器
      navagator.mozGetUserMedia(constrains).then(success).catch(error)
    } else if (navigator.getUserMedia) {
      //旧版API
      navigator.getUserMedia(constrains).then(success).catch(error)
    } else {
      Toast.show('你的浏览器不支持访问用户媒体设备')
    }
}
  
const success = (stream) => {
    streamIns = stream
    const video = document.getElementById("video")
    try {
      video.srcObject = stream
    } catch {
      Toast.show('你的浏览器不支持访问用户媒体设备')
    }
    // 苹果手机的系统弹框会阻止js的线程的继续执行 手动0.1秒之后自动执行代码
    setTimeout(() => {
      video.play()
      initTracker()// 人脸捕捉
    }, 100)
}

const error = (err) => {
    Toast.show('访问用户媒体失败,请进行人像图像上传')
    setIsRadius('0')
    setUpImg(true) // 调用图片上传
    setShowContainer(false)
}

const initTracker = () => {
    const context = document.getElementById("refCanvas").getContext("2d")
    const canvas = document.getElementById("refCanvas")
    const video = document.getElementById("video")
    const tracker1 = new window.tracking.ObjectTracker("face") // 使用监听人脸的包
    
    tracker1.setInitialScale(4)
    tracker1.setStepSize(2)
    tracker1.setEdgesDensity(0.1)
    
    try {
      trackertask = tracking.track("#video", tracker1) // 开始追踪
    } catch (e) {
      Toast.show('访问用户媒体失败,请重试')
    }
    
    //开始捕捉方法 一直不停的检测人脸直到检测到人脸
    tracker1.on("track", (e) => {
      //画布描绘之前清空画布
      context.clearRect(0, 0, canvas.width, canvas.height)
      if (e.data.length === 0) {
        Toast.show('未检测到人脸,请将人脸置于框中间或调整一下姿势')
      } else {
        e.data.forEach((rect) => {
          //设置canvas 方框的颜色大小
          context.strokeStyle = '#42e365'
          context.lineWidth = 2
          context.strokeRect(rect.x, rect.y, rect.width, rect.height)
        })
        if (!tipFlag) {
          Toast.show('检测成功,正在拍照,请保持不动3秒')
        }
        // 2秒后拍照,仅拍一次 给用户一个准备时间
        // flag 限制一直捕捉人脸,只要拍照之后就停止检测
        if (!isTakePhoto) {
          isTakePhoto = true
          setRemovePhotoID(setTimeout(() => {
            tackPhoto()
            video.pause()
            setTipFlag(true)
          }, 2000))
        }
      }
    })
}

const tackPhoto = () => {
    // 在画布上面绘制拍到的照片
    const context = document.getElementById("refCanvas").getContext("2d")
    context.drawImage(document.getElementById("video"), 10, 0, 160, 160)
    setImgUrl(saveAsPNG(document.getElementById("refCanvas")))
    close()
}
  
// 保存为jpg,base64格式图片
const saveAsPNG = (c) => {
    return c.toDataURL("image/jgp", 0.4)
}
   
const close = () => {
    const video = document.getElementById("video")
    isTakePhoto = false
    setTipFlag(false)
    setShowContainer(false)
    // setContext(null)
    clearTimeout(removePhotoID)
    setRemovePhotoID(null)
    if (streamIns) {
      streamIns.enabled = false
      streamIns.getTracks()[0].stop()
      streamIns.getVideoTracks()[0].stop()
    }
    streamIns = null
    if (trackertask != null) {
      trackertask.stop()
    }
    video.pause()
}

// 图片上传
const upImage = () => {
    var file = document.getElementById('file');
    const reads = new FileReader()
    var f = file.files[0]
    reads.readAsDataURL(f)
    reads.onload = (e) => {
      setImgUrl(e.target.result)
    }
 }