HTML5+tracking.js實現刷臉支付

最近刷臉支付很火,老闆們當然要追趕時代潮流,於是就有了刷臉支付這個項目。前端實現關鍵的技術是攝影機錄像拍照人臉比對,本文來探討一下如何在html5環境中如何實現刷臉支付以及開發過程中遇到的問題。

1.攝影機

1.1 input獲取攝影機

html5中獲取用戶攝影機,有兩種方式,使用input,如下:

<input type="file" capture="camera" accept="image/*"/>

另外如果想打開相冊,可以這樣:

<input type="file" accept="img/*">

但是這兩種方式都會有兼容性問題,用過的同學可能都知道。

1.2 getUserMedia獲取攝像圖

getUserMedia是html5一個新的api,官方一點的定義是:

MediaDevices.getUserMedia() 會提示用戶給予使用媒體輸入的許可,媒體輸入會產生一個MediaStream,裡面包含了請求的媒體類型的軌道。此流可以包含一個影片軌道(來自硬體或者虛擬影片源,比如相機、影片採集設備和螢幕共享服務等等)、一個音頻軌道(同樣來自硬體或虛擬音頻源,比如麥克風、A/D轉換器等等),也可能是其它軌道類型。

簡單一點說就是可以獲取到用戶攝影機。

同上面input一樣,這種方式也有兼容性問題,不過可以使用其他方式解決,這裡可以參考MediaDevices.getUserMedia(),文檔中有介紹”在舊的瀏覽器中使用新的API”。我這裡在網上也找了一些參考,總結出一個相對全面的getUserMedia版本,程式碼如下:

// 訪問用戶媒體設備
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 {
        this.scanTip = "你的瀏覽器不支援訪問用戶媒體設備"
    }
}

1.3 播放視屏

獲取設備方法有兩個回調函數,一個是成功,一個是失敗。成功了就開始播放影片,播放視屏其實就是給video設置一個url,並調用play方法,這裡設置url要考慮不同瀏覽器兼容性,程式碼如下:

success(stream) {
    this.streamIns = stream
    // 設置播放地址,webkit內核瀏覽器
    this.URL = window.URL || window.webkitURL
    if ("srcObject" in this.$refs.refVideo) {
        this.$refs.refVideo.srcObject = stream
    } else {
        this.$refs.refVideo.src = this.URL.createObjectURL(stream)
    }
    this.$refs.refVideo.onloadedmetadata = e => {
        // 播放影片
        this.$refs.refVideo.play()
        this.initTracker()
    }
},
error(e) {
    this.scanTip = "訪問用戶媒體失敗" + e.name + "," + e.message
}

注意:播放視屏方法最好寫在onloadmetadata回調函數中,否則可能會報錯。播放影片的時候必須在本地環境,也就是//localhost/xxxx中測試,或者帶有//xxxxx環境中測試,不然的話或有跨域問題。

2. 捕捉人臉

2.1 使用tracking.js捕捉人臉

視屏在video中播放成功之後就開始識別人臉了,這裡使用到一個第三方的功能tracking.js,是國外的大神寫的JavaScript影像識別插件。關鍵程式碼如下:

// 人臉捕捉
initTracker() {
    this.context = this.$refs.refCanvas.getContext("2d")    // 畫布
    this.tracker = new tracking.ObjectTracker(['face'])     // tracker實例
    this.tracker.setStepSize(1.7)                           // 設置步長
    this.tracker.on('track', this.handleTracked)            // 綁定監聽方法
    try {
        tracking.track('#video', this.tracker)      // 開始追蹤
    } catch (e) {
        this.scanTip = "訪問用戶媒體失敗,請重試"
    }
}

捕獲到人臉之後,可以在頁面上用一個小方框標註出來,這樣有點交互效果。

// 追蹤事件
handleTracked(e) {
    if (e.data.length === 0) {
        this.scanTip = '未檢測到人臉'
    } else {
        if (!this.tipFlag) {
            this.scanTip = '檢測成功,正在拍照,請保持不動2秒'
        }
        // 1秒後拍照,僅拍一次
        if (!this.flag) {
            this.scanTip = '拍照中...'
            this.flag = true
            this.removePhotoID = setTimeout(() => {
                this.tackPhoto()
                this.tipFlag = true
            }, 2000)
        }
        e.data.forEach(this.plot)
    }
}

在頁面中畫一些方框,標識出人臉:

<div class="rect" v-for="item in profile"
             :style="{ width: item.width + 'px', height: item.height + 'px', left: item.left + 'px', top: item.top + 'px'}"></div>
// 繪製跟蹤框
plot({x, y, width: w, height: h}) {
    // 創建框對象
    this.profile.push({ width: w, height: h, left: x, top: y })
}

2.2 拍照

拍照,就是使用video作為圖片源,在canvas中保存一張圖片下來,注意這裡使用toDataURL方法的時候可以設置第二個參數quality,從0到1,0表示圖片比較粗糙,但是文件比較小,1表示品質最好。

// 拍照
tackPhoto() {
    this.context.drawImage(this.$refs.refVideo, 0, 0, this.screenSize.width, this.screenSize.height)
    // 保存為base64格式
    this.imgUrl = this.saveAsPNG(this.$refs.refCanvas)
    // this.compare(imgUrl)
    this.close()
},
// Base64轉文件
getBlobBydataURI(dataURI, type) {
    var binary = window.atob(dataURI.split(',')[1]);
    var array = [];
    for(var i = 0; i < binary.length; i++) {
        array.push(binary.charCodeAt(i));
    }
    return new Blob([new Uint8Array(array)], {
        type: type
    });
},
// 保存為png,base64格式圖片
saveAsPNG(c) {
    return c.toDataURL('image/png', 0.3)
}

拍照完成之後就可以把文件發送給後端,讓後端進行對比驗證,這裡後端使用的是阿里雲的介面。

3. 最後效果

3.1 demo

最後,demo我已經放在github上了,感興趣可以打開看一下。

效果如下:

3.2 在項目中落地

最後放在項目中,無非就是最後一個步驟,去調用介面比對,根據比對結果成功是成功還是失敗,決定是人臉支付還是繼續使用原來的密碼支付,效果如下:

ps:這裡人臉比對失敗了,是因為我帶著口罩,就不呲牙露臉了。