web前端通过video+canvas+MediaDevices.getUserMedia()的方式调用本地多媒体设备(不局限于摄像头)时。存在一个安全问题,为了用户的隐私安全,http协议无法使用多媒体设备。
https://blog.csdn.net/weixin_45408862/article/details/107865462
因为像摄像头和麦克风属于可能涉及重大隐私问题的API,getUserMedia()的规范提出了浏览器必须满足一系列隐私和安全要求。这个方法功能很强大,只能在安全的上下文中使用,在不安全的环境中为undefined。

安全上下文:

  1. HTTPS、
  2. file:///url方案加载的页面,
  3. 开发者本地测试使用localhost/127.0.0.1

在上面这些情况下才能使用多媒体设备。

想要通过ip访问多媒体设备,要么修改成https协议,要么就file:///url

修改成Https协议存在一个问题,证书的问题,还有就是一般情况下外网配个域名才采用https协议。

该案例中使用localhost || 127.0.0.1 是可以正常访问的,但是使用http://ip方式访问时因为没有证书会报错
这里需要把http请求换成https的方式进行请求
方法一:

  1. // vue.config.js
  2. devServer: {
  3. https: true
  4. } // 可以在本地开启 https 服务

方法二:
如何使用谷歌浏览器通过IP访问多媒体设备?

其实很简单,就是让谷歌浏览器开放某个地址的权限

1 打开谷歌浏览器,在地址栏输入:
chrome://flags/#unsafely-treat-insecure-origin-as-secure

image.png

https://developer.mozilla.org/zh-CN/docs/Web/API/MediaDevices/getUserMedia

video
poster: 视频封面
muted: 设置或返回视频是否应该被静音(关闭声音)
playsinline: 禁止使用进度条
onloadedmetadata: 事件在指定视频/音频(audio/video)的元数据加载后触发

码云项目
https://gitee.com/TsMask/face-api-demo-vue

npm install face-api.js -S

api:
https://justadudewhohacks.github.io/face-api.js/docs/globals.html

vue文件

训练模型文件,放置public/models 下
https://github.com/justadudewhohacks/face-api.js/tree/master/weights

实现代码

先加载模型

  1. async initFaceApiModel() {
  2. try {
  3. // 加载所有模型数据,models 是存放模型数据文件的目录
  4. // await faceapi.loadModels('/models');
  5. await faceapi.nets[this.nets].loadFromUri("/models"); // 算法模型
  6. await faceapi.loadFaceLandmarkModel("/models"); // 轮廓模型
  7. await faceapi.loadFaceExpressionModel("/models"); // 表情模型
  8. await faceapi.loadAgeGenderModel("/models"); // 年龄模型
  9. // 根据算法模型参数识别调整结果
  10. switch (this.nets) {
  11. case "ssdMobilenetv1":
  12. this.options = new faceapi.SsdMobilenetv1Options({
  13. // 最小置信值,大于等于这个值才被认为检测到了人脸,然后返回一个detection对象
  14. minConfidence: 0.5, // 0.1 ~ 0.9
  15. // 最多返回人脸的个数
  16. maxResults: 100 // default: 100
  17. });
  18. break;
  19. case "tinyFaceDetector":
  20. // 模型的配置参数
  21. // 实测下来,Tiny Face Detector 模型的性能非常好,检测的准确度也不错,只有人脸很小的时候,会有较大偏差,scoreThreshold 阈值为 0.6 时最佳
  22. this.options = new faceapi.TinyFaceDetectorOptions({
  23. // 输入的数据源大小,这个值越小,处理速度越快。常用值:128, 160, 224, 320, 416, 512, 608
  24. inputSize: 512, // default: 416
  25. // // 用于过滤边界的分数阈值,大于等于这个值才被认为检测到了人脸,然后返回一个detection对象, 0.1 ~ 0.9
  26. scoreThreshold: 0.6, // default: 0.5
  27. });
  28. break;
  29. case "mtcnn":
  30. // 多任务级联卷积神经网络
  31. this.options = new faceapi.MtcnnOptions({
  32. // 人脸尺寸的最小值,小于这个尺寸的人脸不会被检测到
  33. minFaceSize: 20, // default: 20
  34. // 比例因子用于计算图像的比例步长
  35. scaleFactor: 0.709, // 0.1 ~ 0.9 default: 0.709
  36. });
  37. break;
  38. }
  39. // 节点属性化
  40. this.videoEl = document.getElementById("myVideo");
  41. this.canvasEl = document.getElementById("myCanvas");
  42. } catch (err) {
  43. console.log(err)
  44. }
  45. },

唤起摄像设备
navigator.mediaDevices.getUserMedia

视频流用video标签在页面呈现

  1. <divclass="facePhoto__see">
  2. <video
  3. id="myVideo"
  4. class="facePhoto__video"
  5. @loadedmetadata="fnRun"
  6. ></video>
  7. <canvas
  8. id="myCanvas"
  9. class="facePhoto__canvas"
  10. style='margin: 0;padding: 0;'
  11. ></canvas>
  12. </div>

loadedmetadata 监听视频元数据已加载事件, 在这里用算法处理视频流

绘制到canvas上

  1. // 人脸面部勘探轮廓识别绘制
  2. async fnRunFaceLandmark() {
  3. console.log("RunFaceLandmark");
  4. if (this.videoEl.paused) return clearTimeout(this.timeout);
  5. // 识别绘制人脸信息
  6. const result = await faceapi[this.detectFace](
  7. this.videoEl,
  8. this.options
  9. ).withFaceLandmarks();
  10. console.log('result===', result)
  11. if (isArray(result) && result.length > 1) {
  12. this.$toast({
  13. message: '检测出多张人脸',
  14. duration: 800
  15. })
  16. } else if (isArray(result) && result.length === 1 && !this.videoEl.paused) {
  17. const dims = faceapi.matchDimensions(this.canvasEl, this.videoEl, true);
  18. const resizeResult = faceapi.resizeResults(result, dims);
  19. this.withBoxes
  20. ? faceapi.draw.drawDetections(this.canvasEl, resizeResult)
  21. : faceapi.draw.drawFaceLandmarks(this.canvasEl, resizeResult);
  22. } else {
  23. this.canvasEl
  24. .getContext("2d")
  25. .clearRect(0, 0, this.canvasEl.width, this.canvasEl.height);
  26. }
  27. this.timeout = setTimeout(() => this.fnRunFaceLandmark(), 1000); // 递归调用,实时检测
  28. },
  1. // 获取照片 base64 (扩展:相机拍照功能,保存本地相册)
  2. // 头像照片
  3. captureAvg() {
  4. var vm = this;
  5. var ctx = this.canvasEl.getContext('2d'),
  6. CHeight = this.videoEl.clientHeight, //获取屏幕大小让canvas自适应
  7. CWidth = this.videoEl.clientWidth;
  8. this.canvasEl.width = CWidth;
  9. this.canvasEl.height = CHeight;
  10. if (vm.localMediaStream) {
  11. console.log(ctx)
  12. ctx.drawImage(this.videoEl, 0, 0, CWidth, CHeight); // 向画布上绘制图片
  13. // HTMLCanvasElement.toDataURL() 返回一个包含图片展示的data url (base64)
  14. var dataURL = this.canvasEl.toDataURL('image/jpeg'); //dataURL = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA'
  15. console.log('dataURL---', dataURL)
  16. vm.imginfo = dataURL;
  17. // 停止摄像机
  18. this.videoEl.pause();
  19. this.stopCapture();
  20. }
  21. },