媒體流數據獲取和人臉檢測

背景

因爲最近這些年互聯網面向的受衆面愈來愈光,有了更多的人和設備訪問互聯網,這也就意味着咱們要作的就愈來愈多, 不在是像之前那樣只須要調整頁面樣式和切圖就能夠了。因此就有了這篇文章的誕生,本次講的如何在本地喚醒相機而後進行拍照,同時對拍照的照片進行修改,還能夠對攝像頭所捕捉的數據進行一個轉發,同時還能夠檢測攝像頭中是否存在人。vue

關鍵字API以及兼容性

喚醒攝像頭主要用的API是 navigator.mediaDevices.getUserMedia。入參是一個對象{ audio: true, video: true } 是判斷是否啓動攝像頭和錄音,返回一個promise對象。之前的navigator.getUserMedia已經被廢棄。若是video沒有設置自動播放 須要在成功回調函數那裏手動調用執行,只有這樣攝像頭纔會被執行,否則就不會正確顯示。git

目前 IOS 設備的微信和 Safari 均不支持,較新的安卓和桌面端瀏覽器均支持。另外,出於安全問題考慮,Chrome 只支持 HTTPS 頁面啓用攝像頭。github

開啓本地相機

test是用vue來的。由於考慮到到時候直接複製就好,因此在用的時候須要考慮本身的環境來進行部分修改和調整web

HTML部分
  
  <div class="contentarea">
    <div class="camera">
      <video ref="video" autoplay muted @canplay="canplay"></video>
      <button ref="startbutton" id="startbutton" @click="takepicture">Take photo</button>
    </div>
    <canvas ref="canvas"></canvas>
    <div class="output">
      <img ref="photo">
    </div>
  </div>
  
  CSS部分
  
    #photo {
      border: 1px solid black;
      width: 320px;
      height: 240px;
    }
    
    #canvas {
      display: none;
    }
    
    .camera {
      width: 340px;
      display: inline-block;
    }
    
    .output {
      width: 340px;
      display: inline-block;
    }
    
    #startbutton {
      display: block;
      position: relative;
      margin-left: auto;
      margin-right: auto;
      bottom: 32px;
      background-color: rgba(0, 150, 0, 0.5);
      border: 1px solid rgba(255, 255, 255, 0.7);
      box-shadow: 0px 0px 1px 2px rgba(0, 0, 0, 0.2);
      font-size: 14px;
      font-family: "Lucida Grande", "Arial", sans-serif;
      color: rgba(255, 255, 255, 1);
    }
    
    .contentarea {
      font-size: 16px;
      font-family: "Lucida Grande", "Arial", sans-serif;
      width: 760px;
    }
    
    JS部分
    
    // 監聽攝像頭是否啓動成功而且按照4:3的比例初始化攝像頭和畫布
    canplay () {
      let { video, canvas } = this.$refs
      if (!this.streaming) {
        this.height = video.videoHeight / (video.videoWidth / this.width);
        console.log(this.height)
        if (isNaN(this.height)) {
          this.height = this.width / (4 / 3);
        }
        video.setAttribute("width", this.width);
        video.setAttribute("height", this.height);
        canvas.setAttribute("width", this.width);
        canvas.setAttribute("height", this.height);
        this.streaming = true;
      }
    },
    // 獲取攝像頭被點擊的那一刻的數據而且存到canvas裏面以便進行修改 而後在存儲在img標籤顯示在頁面
    takepicture () {
      let { video, canvas, photo } = this.$refs
      let context = canvas.getContext("2d")
      if (this.width && this.height) {
        canvas.width = this.width;
        canvas.height = this.height;
        context.drawImage(video, 0, 0, this.width, this.height);
        let data = canvas.toDataURL("image/png");
        photo.setAttribute("src", data);
      } else {
        this.clearphoto();
      }
    },
    // 清除canvas信息
    clearphoto () {
      let { canvas, photo } = this.$refs
      var context = canvas.getContext("2d");
      context.fillStyle = "#AAA";
      context.fillRect(0, 0, canvas.width, canvas.height);

      var data = canvas.toDataURL("image/png");
      photo.setAttribute("src", data);
    },
    // 初始化攝像頭調用
    startup() {
      let { video } = this.$refs
      this.width = 320
      this.height = 0
      this.streaming = false
      navigator.mediaDevices
        .getUserMedia({ video: true, audio: false })
        .then((stream) => {
          video.srcObject = stream;
          video.play();
        })
        .catch((err) => {
          console.log("An error occurred: " + err);
        })
    }

autoplay 須要開啓自動播放否則須要在成功的回調裏面手動執行,否則不會顯示內容. 若是隻是開啓攝像頭部分咱們不須要聲音,因此關閉掉了canvas

<video ref="video" autoplay muted @canplay="canplay"></video>

咱們須要等頁面加載以後手動開啓攝像頭方法。api

mounted() {
   this.startup()
}

startup() {
  let { video } = this.$refs // 獲取頁面上面的視頻元素
  this.width = 320 // 初始化寬度
  this.height = 0 // 初始化高度
  this.streaming = false // 初始化是否進行中
  navigator.mediaDevices
    .getUserMedia({ video: true, audio: false }) // 這裏咱們只開啓攝像頭
    .then((stream) => { // 成功的執行
      video.srcObject = stream;
    })
    .catch((err) => { // 失敗的執行
      console.error("An error occurred: " + err);
    })
},

當攝像頭被成功啓動以後, 就會執行 canplay 方法promise

canplay () {
  let { video, canvas } = this.$refs // 獲取節點 
  if (!this.streaming) { // 是不是在運行中 若是是
    this.height = video.videoHeight / (video.videoWidth / this.width); // 初始化高度
    console.log(this.height)
    if (isNaN(this.height)) { // 這裏初始化高度是爲了和寬度進行一個響應 初始成一個4:3的比例
      this.height = this.width / (4 / 3);
    }
    video.setAttribute("width", this.width); // 這個時候開始設置節點的寬度和高度
    video.setAttribute("height", this.height);
    canvas.setAttribute("width", this.width);
    canvas.setAttribute("height", this.height);
    this.streaming = true;
  }
}

這個時候攝像頭的基本也就已經操做完畢了 若是沒有一場的時候就能夠在頁面上看到你攝像頭所捕捉到的內容 這個時候咱們須要把你想要捕捉的內容轉成圖片顯示到頁面上瀏覽器

takepicture () { // 點擊方法 當咱們點擊 Take photo 按鈕的時候 這個時候把這部分的信息轉成圖片
  let { video, canvas, photo } = this.$refs // 獲取節點
  let context = canvas.getContext("2d")
  if (this.width && this.height) { // 把video的數據存入canvas以便咱們進行其餘操做 轉成圖片資源而後顯示在頁面上
    canvas.width = this.width;
    canvas.height = this.height;
    context.drawImage(video, 0, 0, this.width, this.height);
    let data = canvas.toDataURL("image/png");
    photo.setAttribute("src", data);
  } else { // 若是初始化的寬高不存在 這個時候說明有異常須要排除,同時清除掉畫布上面的圖片數據
    this.clearphoto();
  }
},
clearphoto () { // 清除畫布圖片數據 同時更新img
  let { canvas, photo } = this.$refs
  var context = canvas.getContext("2d");
  context.fillStyle = "#AAA";
  context.fillRect(0, 0, canvas.width, canvas.height);

  var data = canvas.toDataURL("image/png");
  photo.setAttribute("src", data);
},

對攝像頭捕捉的畫面進行獲取和解析

應用場景

完成以上步驟以後,沒有問題。咱們就能夠喚醒鏈接的攝像頭設備,捕捉你想要的畫面存到你想要存的地方。當咱們把捕捉到的畫面存到canvas裏面去以後,咱們能夠進行不少操做,能夠對你捕捉的畫面增長你想要的特效,這個時候canvas可以實現的功能均可以在你捕捉到畫面上面實現,而且轉成圖片。
此時,攝像頭所捕捉到的內容仍是單向的,沒法與別人通信。若是你想實現一種相似於視頻聊天的功能。這個時候就要改一下咱們的代碼,參考一下webRTC即時通信協議。須要搭建一個第三方的信令服務器,來進行雙向通信。安全

人臉檢測工具的使用

我如今藉助的是的face-api這款工具 有捕捉人臉,檢測面部特徵。這個包能夠把你想要檢測的圖片視頻以及攝像頭上面的內容都是支持捕捉人臉和檢測面部特徵。我這裏主要實現的是攝像頭捕捉人臉和檢測面部特徵。服務器

官網: https://github.com/justadudew...

HTML部分

<video id="video" autoplay muted></video>

JS部分

const video = document.getElementById("video"); // 獲取節點
Promise.all([ // 加載我想要使用的模型 同時對應的josn文件和shard文件要處在同一目錄 否則讀取的時候可能讀取不到。當你讀取不到的時候你可能會報 SyntaxError: Unexpected token < in JSON at position 0。這點略坑
  faceapi.nets.tinyFaceDetector.loadFromUri("/models"),
  faceapi.nets.faceLandmark68Net.loadFromUri("/models"),
  faceapi.nets.faceRecognitionNet.loadFromUri("/models"),
  faceapi.nets.faceExpressionNet.loadFromUri("/models")
]).then(startVideo); // 載入成功以後喚醒攝像頭

function startVideo() {
  navigator.getUserMedia(
    { video: {} },
    stream => (video.srcObject = stream),
    err => console.error(err)
  );
}

video.addEventListener("play", () => { // 當攝像頭成功啓動以後 開始執行這部分的方法
  const canvas = faceapi.createCanvasFromMedia(video); // 建立一個畫布
  document.body.append(canvas);
  const displaySize = { width: 640, height: 480 }; // 這部分的大小是等同video的大小的
  faceapi.matchDimensions(canvas, displaySize); // 聲明大小
  setInterval(async () => { // 咱們須要持續傳遞攝像頭的數據
    const detections = await faceapi
      .detectAllFaces(video, new faceapi.TinyFaceDetectorOptions())
      .withFaceLandmarks()
      .withFaceExpressions();
    const resizedDetections = faceapi.resizeResults(
      detections,
      displaySize
    );
    canvas.getContext("2d").clearRect(0, 0, canvas.width, canvas.height);
    faceapi.draw.drawDetections(canvas, resizedDetections);
    faceapi.draw.drawFaceLandmarks(canvas, resizedDetections);
    faceapi.draw.drawFaceExpressions(canvas, resizedDetections);
  }, 100);
});

場景

相關文章
相關標籤/搜索