開(kāi)始之前,請先在項目的根目錄位置創(chuàng )建一個(gè)static目錄。我們將從這里提供HTML和JavaScript。
現在,我們首先使用WebRTC的getUserMedia抓取一個(gè)本地攝像頭Feed。從這里,我們需要將該Feed的快照發(fā)送到剛剛創(chuàng )建的對象檢測Web API,獲取結果,然后使用canvas實(shí)時(shí)地在視頻上顯示這些結果。
HTML
我們先創(chuàng )建local.html文件:

此網(wǎng)頁(yè)的作用如下:
- 使用WebRTC adapter.js代碼填充(polyfill)
- 設置一些樣式,以便
- 將各個(gè)元素一個(gè)個(gè)疊加起來(lái)
- 將視頻放在底部,這樣我們就能使用canvas在它上面繪圖
- 為我們的getUserMedia流創(chuàng )建一個(gè)視頻元素
- 鏈接到一個(gè)調用getUserMedia的JavaScript文件
- 鏈接到一個(gè)將與我們的對象檢測API交互并在我們的視頻上繪制方框的JavaScript文件
獲取攝像頭流
現在,在靜態(tài)目錄中創(chuàng )建一個(gè)local.js文件,并將下面的代碼添加到該文件中:

在這里你會(huì )看到我們首先設置了一些約束條件。對于我自己的情況,我需要一段1280×720視頻,但要求范圍在640×480與1920×1080之間。然后,我們使用這些約束條件執行g(shù)etUserMedia,并將所生成的流分配給我們在HTML中創(chuàng )建的視頻對象。
對象檢測API的客戶(hù)端版本
TensorFlow對象檢測API教程包含了可執行以下操作的代碼:獲取現有圖像,將其發(fā)送給實(shí)際API進(jìn)行“推斷”(對象檢測),然后為它所看到的對象顯示方框和類(lèi)名。要想在瀏覽器中模擬這一功能,我們需要:
- 抓取圖像——我們會(huì )創(chuàng )建一個(gè)canvas來(lái)完成這一步
- 將這些圖像發(fā)送給API——為此,我們會(huì )將文件作為XMLHttpRequest中form-body的一部分進(jìn)行傳遞
- 再使用一個(gè)canvas將結果繪制在我們的實(shí)時(shí)流上
- 要完成所有這些步驟,需要在靜態(tài)文件夾中創(chuàng )建一個(gè)objDetect.js文件。
初始化和設置
我們需要先定義一些參數:

你會(huì )注意到,我將其中一些參數作為data-元素添加到了自己的HTML代碼中。我最終是要在多個(gè)不同的項目中使用這段代碼,并且希望重用相同的代碼庫,而如此添加參數就可以輕松做到這一點(diǎn)。待具體使用這些參數時(shí),我會(huì )一一解釋。
設置視頻和canvas元素
我們需要一個(gè)變量來(lái)表示我們的視頻元素,需要一些起始事件,還需要創(chuàng )建上面提到的2個(gè)canvas。

drawCanvas用于顯示我們的方框和標簽。imageCanvas用于向我們的對象檢測API上傳數據。我們需要向可見(jiàn)HTML添加drawCanvas,這樣我們就能在繪制對象框時(shí)看到它。接下來(lái),需要跳轉到ObjDetect.js底部,逐函數向上編寫(xiě)。
啟動(dòng)該程序
1.觸發(fā)視頻事件
我們來(lái)啟動(dòng)該程序。首先要觸發(fā)一些視頻事件:

先查找視頻的onplay事件和loadedmetadata事件——如果沒(méi)有視頻,圖像處理也就無(wú)從談起。我們需要用到元數據來(lái)設置我們的繪圖canvas尺寸,使其與下一部分中的視頻尺寸相符。
2.啟動(dòng)主對象檢測子例程

雖然drawCanvas必須與視頻元素大小相同,但imageCanvas絕不會(huì )顯示出來(lái),只會(huì )發(fā)送到我們的API。可以使用文件開(kāi)頭的uploadWidth參數減小此大小,以幫助降低所需的帶寬量和服務(wù)器上的處理需求。需要注意的是,減小圖片可能會(huì )影響識別準確度,特別是圖片縮減得過(guò)小的時(shí)候。
至此我們還需要為drawCanvas設置一些樣式。我選擇的是cyan,但你可以任選顏色。只是要確保所選的顏色與視頻Feed對比明顯,從而提供很好的可見(jiàn)度。
3.toBlob conversion

設置好canvas大小后,我們需要確定如何發(fā)送圖像。一開(kāi)始我采取的是較為復雜的方法,結果看到Fippo的grab()函數在最后一個(gè)KrankyGeek WebRTC事件處,所以我又改用了簡(jiǎn)單的toBlob方法。待圖片轉換為blob(二進(jìn)制大對象)后,我們就會(huì )將它發(fā)送到我們要創(chuàng )建的下一個(gè)函數,即postFile。

有一點(diǎn)需要注意——Edge似乎不支持HTMLCanvasElement.toBlob方法。好像可以改用此處推薦的polyfill或改用msToBlob,但這兩個(gè)我都還沒(méi)有機會(huì )試過(guò)。
將圖像發(fā)送至對象檢測API

我們的postFile接受圖像blob作為實(shí)參。要發(fā)送此數據,我們需要使用XHR將其作為表單數據通過(guò)POST方法發(fā)布。不要忘了,我們的對象檢測API還接受一個(gè)可選的閾值,所以在這里我們也可以加入此閾值。為便于調整,同時(shí)避免操作此庫,你可以在我們在開(kāi)頭設置的data-標記中加入此參數及其他一些參數。
我們設置好表單后,需要使用XHR來(lái)發(fā)送它并等待響應。獲取到返回的對象后,我們就可以繪制它們(見(jiàn)下一個(gè)函數)。這樣就大功告成了。由于我們想要持續不斷地執行上述操作,因此我們需要在獲取到上一API調用返回的響應后,立即繼續抓取新圖像并再次發(fā)送。
繪制方框和類(lèi)標簽
接下來(lái)我們需要使用一個(gè)函數來(lái)繪制對象API輸出,以便我們可以實(shí)際查看一下檢測到的是什么:

由于我們希望每次都使用一個(gè)干凈的繪圖板來(lái)繪制矩形,我們首先要使用clearRect來(lái)清空canvas。然后,直接使用class_name對項目進(jìn)行過(guò)濾,然后對剩余的每個(gè)項目執行繪圖操作。
在objects對象中傳遞的坐標是以百分比為單位表示的圖像大小。要在canvas上使用它們,我們需要將它們轉換成以像素數表示的尺寸。我們還要檢查是否啟用了鏡像參數。如果已啟用,我們需要翻轉x軸,以便與視頻流翻轉后的鏡像視圖相匹配。最后,我們需要編寫(xiě)對象class_name并繪制矩形。
讓我們試一下吧!
現在,打開(kāi)你最喜歡的WebRTC瀏覽器,在地址欄中輸入網(wǎng)址。如果你是在同一臺計算機上運行,網(wǎng)址將為http://localhost:5000/local(如果設置了證書(shū),則為https://localhost:5000/local)。
關(guān)于優(yōu)化
上述設置將通過(guò)服務(wù)器運行盡可能多的幀。除非為T(mén)ensorflow設置了GPU優(yōu)化,否則這會(huì )消耗大量的CPU資源(例如,我自己的情況是消耗了一整個(gè)核心),即便不作任何改動(dòng)也是如此。更高效的做法是,限制調用該API的頻率,僅在視頻流中有新活動(dòng)時(shí)才調用該API。為此,我在一個(gè)新的objDetectOnMotion.js文件中對objDetect.js做了一些修改。
修改前后內容大致相同,我只不過(guò)添加了2個(gè)新函數。首先,不再是每次都抓取圖像,而是使用一個(gè)新函數sendImageFromCanvas(),僅當圖片在指定的幀率內發(fā)生了變化時(shí),該函數才發(fā)送圖片。幀率用一個(gè)新的updateInterval參數表示,限定了可以調用該API的最大間隔。為此,我們需要使用新的canvas和內容。
這段代碼很簡(jiǎn)單:

imageChangeThreshold是一個(gè)百分比,表示有改動(dòng)的像素所占的百分比。我們獲得此百分比后將其傳遞給imageChange函數,此函數返回True或False,表示是否超出了閾值。下面顯示的就是這個(gè)函數:

上面的這個(gè)函數其實(shí)是經(jīng)過(guò)大幅改進(jìn)后的版本,之前的版本是我很久以前編寫(xiě)的,用于在動(dòng)作檢測嬰兒監視器程序中檢測嬰兒動(dòng)作。它首先測量每個(gè)像素的RGB顏色值。如果這些值與該像素的總體顏色值相比絕對差值超過(guò)10,則將該像素視為已改動(dòng)。10只是隨意定的一個(gè)值,但在我的測試中似乎是很合適的值。如果有改動(dòng)的像素數超出threshold,該函數就會(huì )返回True。
對此稍微深入研究后,我發(fā)現其他一些算法通常轉換成灰度值,因為顏色并不能很好地反映動(dòng)作。應用高斯模糊也可以消除編碼差異。Fippo提出了一個(gè)很好的建議,即借鑒test.webrtc.org在檢測視頻活動(dòng)時(shí)使用的結構相似性算法(見(jiàn)此處此處)。后續還會(huì )分享更多技巧。
適合任何視頻元素
這段代碼實(shí)際上對任何

不妨使用你自己的視頻試一下。只是提醒一下,如果你使用的是托管在另一臺服務(wù)器上的視頻,要注意CORS問(wèn)題。
一不小心寫(xiě)成了長(cháng)篇
完成上面這一切耗費了很多時(shí)間,不過(guò)現在我希望開(kāi)始著(zhù)手有趣的部分了:嘗試不同的型號和訓練我自己的分類(lèi)器。已發(fā)布的對象檢測API是針對靜態(tài)圖像設計的。那么針對視頻和對象跟蹤進(jìn)行了調整的模型又是什么樣的呢?很值得一試。
此外,這里還有太多的優(yōu)化工作需要做。我在運行此服務(wù)時(shí)并沒(méi)有配備GPU,如果配備的話(huà),性能會(huì )大為不同。如果幀的數量不多,支持1個(gè)客戶(hù)端需要大約1個(gè)核心,而我使用的是最快但準確性最低的模型。在這方面有很大的性能提升空間。如果觀(guān)察此服務(wù)在GPU云端網(wǎng)絡(luò )中性能如何,想必也很有趣。