likes
comments
collection
share

H5人脸识别项目,代码免费开源啦,可以不写但一定要看会,万一未来用上了呢

作者站长头像
站长
· 阅读数 3

用处广泛的人脸识别成了当今大型项目实名认证的标配,利用webRTC技术在移动端人脸识别轻松搞定,废话不多说,直接上干货,可以不写但一定要看会,先收藏下万一未来用上了呢,下面我将介绍一下H5人脸识别前端技术实现步骤:

  • 1.调取手机摄像头并成功打开摄像头

  • 2.针对不同机型的手机做好UI兼容,调取摄像头时因为安卓手机和苹果手机会出现兼容问题,比如苹果手机成像出现镜像问题,而且部分配置较低的安卓手机无法直接根据后置摄像头id打开后置摄像头,需要动态切换才能打开

  • 3.对不同机型的成像图做出特俗处理。因为考虑到UI设计场景,需要有框覆盖在摄像头画面上(调取本地原生摄像头是不支持这种UI设计的,所以采用图层的方式一层一层覆盖来实现这种UI方案,成像照根据获取到的stream在video层自动播放,点击按钮自动截图video,根据截取的图片动态生成照片,后面根据安卓手机和苹果手机机型的成像特点进行区分处理图片。需要获取录取人脸识别视屏的也可以通过这种方式,然后获取每一帧图片,最后合成录制的视频)。

    下面我门来一步一步实现代码:

  • 1.调取本地设备的摄像头id列表,然后根据这些摄像头列表默认打开后置摄像头,打开摄像头后把获取到的stream给到dom的video中,并自动播放video,生成实时摄像头拍摄的画面。

getUserMediaId() {
    let self = this
    let sUserAgent = navigator.userAgent.toLowerCase();
    let isAndroid = sUserAgent.includes('android');
    let isRedmi = sUserAgent.includes('redmi');
    let isXiaomi = sUserAgent.includes('xiaomi');
    let isVivo = sUserAgent.includes('vivo');
    if (!isAndroid) {
      //苹果手机可以直接打开后置摄像头,无需自动切换
      const mediaConstraints = { video: { facingMode: { exact: "environment" } }, audio: false };//打开后置摄像头
      self.openDevice(mediaConstraints)
      return
    }
    let videoDeviceArray = []
    navigator.mediaDevices.getUserMedia({ video: true }).then((stream) => {
      if (stream) {
        stream.getTracks().forEach(track => {
          track.stop();
        });
      }
    })
    if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) {
      console.log("不支持 enumerateDevices() .");
    }
    navigator.mediaDevices.enumerateDevices().then(function (devices) {
      //寻找摄像头id
      devices.forEach(device => {
        if (device.kind === 'videoinput') {
          videoDeviceArray.push(device.deviceId);
        }
      });
      self.setState({
        videoDeviceArray: videoDeviceArray
      }, () => {
        if(self.state.videoDeviceArray.length < 1){
          console.log("无可用摄像头")
        }
      })
      if (videoDeviceArray.length > 0) {
      //部分安卓手机机型无法直接打开后置摄像头,需要自动切换至前置摄像头
        self.setState({
          currentId: ((videoDeviceArray.length === 2 && !(isRedmi || isXiaomi || isVivo)) ? videoDeviceArray[1] : videoDeviceArray[0])
        }, () => {
          let constraints = {
            video: { deviceId: { exact: self.state.currentId } },
            audio: false
          }
          self.openDevice(constraints)
        })
      }
    })
  }

打开设备,开启摄像头

openDevice(constraints) {
    let self = this
    //开启摄像头
    if (navigator.mediaDevices === undefined) {
      navigator.mediaDevices = {};
    }
    if (navigator.mediaDevices.getUserMedia === undefined) {
      navigator.mediaDevices.getUserMedia = function (constraints) {
        var getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia || navigator.oGetUserMedia;
        if (!getUserMedia) {
          return Promise.reject(new Error('getUserMedia is not implemented in this browser'));
        }
        return new Promise(function (resolve, reject) {
          getUserMedia.call(navigator, constraints, resolve, reject);
        });
      }
    }
    //如果发现有流存在,要先关闭,再去打开流
    if (window.stream) {
      window.stream.getTracks().forEach(track => {
        track.stop();
      });
    }
    if (self.localMediaStream) {
      self.localMediaStream.getTracks().forEach(track => {
        track.stop();
      });
    }
    try {
      navigator.mediaDevices.getUserMedia(constraints)
        .then(
          (stream) => {
            var video1 = this.video.current;
            video1.style.display = 'block'
            try {
              window.stream = stream;
              video1.srcObject = stream;
            } catch (error) {
              video1.src = window.URL.createObjectURL(stream);//如果srcObject无法给到值,则给src,做好兼容
            }
            self.localMediaStream = stream;
          })
        .catch(()=>{
          if(!this.state.isOpenVideo){
            if(self.state.openNumber>10){
              console.log('打开摄像头失败')
              return
            }
            self.changeDevice();
            return
          }
        });
    } catch (e) {
      console.log('打开摄像头失败',e)
    }
  }
  • 2.因为安卓手机部分机型兼容性问题无法默认打开后置摄像头(这种现象出现在一些配置较低的安卓手机机型上),根据获取到的摄像头id列表,进行动态切换前后置摄像头:

changeDevice = (e) => {
    //切换摄像头设备
    let constraints = {
      video: { deviceId: { exact: this.state.currentId } },
      audio: false
    }
    let currentId = this.state.currentId;
    let videoDeviceArray = this.state.videoDeviceArray;
    videoDeviceArray.forEach(id => {
      if (id !== currentId) {
        this.setState({
          currentId: id
        }, () => {
          constraints = {
            video: { deviceId: { exact: this.state.currentId } },
            audio: false
          }
          this.openDevice(constraints);
        })
      }
    })
  }
  • 3.对video截图并生成图片,根据安卓苹果手机成像的特点对截图图片做出处理:


stopRecording() {
    let self = this;
    setTimeout(() => {
      try {
        var video = this.video.current;
        if (self.localMediaStream) {
          var canvas = document.getElementById('canvas1');
          let ctx = canvas.getContext("2d")
          //安卓手机调整角度,苹果手机因为镜像需要特殊处理
          var sUserAgent = navigator.userAgent.toLowerCase();
          console.log(navigator.userAgent.toLowerCase())
          var isAndroid = sUserAgent.includes('android');
          if (!isAndroid) {
            ctx.rotate(90 * Math.PI / 180);
            ctx.drawImage(video, 0, -canvas.width, canvas.height, canvas.width);
          } else {
            ctx.scale = (2, 2)
            ctx.rotate(-270 * Math.PI / 180);
            ctx.drawImage(video, 0, 0, canvas.height, -canvas.width);
          }
          var firstFrame = canvas.toDataURL("image/png", 1.0);
          // 停止摄像机
          self.stopCapture();
          if (self.state.picType === 'people') {
            self.props.history.push({ pathname: "/ocrStart", PeopleUrl: firstFrame })
          } else if (self.state.picType === 'nation') {
            self.props.history.push({ pathname: "/ocrStart", NationUrl: firstFrame })
          } else {
            console.log("拍照失败请重新拍照")
          }
        }
      } catch (e) {
        console.log("停止异常,进入到catch",e)
      }
    }, 10)
  }
  • 4.生成图后记得要关闭流:

stopCapture() {
    var video = this.video.current;
    if (video.srcObject) {
      let stream = video.srcObject
      if (stream) {
        let tracks = stream.getTracks();
        tracks.forEach(track => {track.stop()})
      }
    }
    if (video.src) {
      let stream1 = video.src
      let tracks1 = stream1.getTracks();
      if (tracks1) {
        tracks1.forEach(track => {track.stop()})
      }
    }
  }

总结:本项目难点在于,

  • 一方面需要兼容不同的机型设备,因为不同机型会遇到不同的问题,比如:每个设备的拍照像素和视频的体积大小是不同的,所以为了保持数据传输稳定性跟后端联调时用到了分段逐帧传输。

  • 同时为了按照原先的UI设计方案研发要在拍照的同时有提示知道脸部位置的指示在摄像屏幕上,让项目看起来更高大上和赋予科技感,一方面框住人脸进行更精准的识别,所以没发说直接调取手机的原声摄像头去拍照录视频,因此加大了难度,最后在技术上就是不断的找突破,用到了webRTC技术。

最后给出的这种方案就满足所有机型的兼容性强的技术实现方案,已在线上持续运营了一年多,经实践测试,失败率最终从5%降至0.3%,如果有感兴趣的朋友可以关注我私下联系交流,希望能帮到码友们。