在這篇文章中,我們將討論游戲 Emoji Scavenger Hunt 的內部運作方式。我們將向您展示如何使用 TensorFlow 訓練用于對象識別的自定義模型以及如何在 Web 前端使用 TensorFlow.js 使用該模型。在使用瀏覽器 API 進行攝像頭訪問和文本到語音的轉換時,我們還將介紹一些挑戰和解決方法。這個游戲的所有代碼都是開源的,可以在 Github 上找到(https://github.com/google/emoji-scavenger-hunt)。
介紹游戲 Emoji Scavenger Hunt
Emoji Scavenger Hunt 是一個有趣的游戲,你會看到一個表情符合,并在秒數之內找到真實世界的等效物體。當你在現實世界中發現表情符號時,隨后的表情符號顯示難度增加。從你可能擁有的物品開始,比如鞋子,書本或者你自己的手以及像香蕉,蠟燭甚至踏板車這樣的東西。
我們的目標是以有趣,互動的方式展示機器學習技術。
訓練對象識別模型
Emoji Scavenger Hunt 游戲的核心功能是識別您的相機所看到的物體,并將其與游戲要求您找到的物體(表情符號)相匹配。但相機如何知道它看到了什么?我們需要一個可以幫助識別物體的模型。最初我們開始使用名為 MobileNet 的預訓練模型。這個模型是輕量級的,并針對移動設備進行了優化,但其中的對象太具體,不適合我們的游戲。例如,確定了像 “金毛獵犬” 這樣的犬種,但沒有 “狗” 的通用對象類。我們逐漸意識到需要訓練自定義的圖像識別模型。
這是轉移學習可以派上用場的地方。轉移學習是一種技術,它通過將其用于另一個目標任務來重用針對特定任務而訓練的機器學習模型。我們通過使用此教程中描述的過程重新訓練基于 MobileNet 的模型來構建我們自己的自定義模型。我們添加了一個全連接層,它將默認輸出 logits 映射到我們想要的表情符號對象,如 “手” 和 “鍵盤” 等。我們列出了大約400個對象用于物體識別并收集 100-1000 個圖像作為每個對象的訓練數據。添加的全連接層通過組合來自 MobileNet 輸出層的 1000 個信號來推斷這 400 個對象。
注:教程 鏈接
https://www.tensorflow.org/hub/tutorials/image_retraining
訓練腳本可在 TensorFlow Github 存儲庫中找到(https://github.com/tensorflow/hub/blob/master/examples/image_retraining/retrain.py)。我們將訓練過程編譯為 Dockerfile,以便您可以通過指向自己的圖像數據集來訓練自己的模型
我們運行腳本將訓練圖像數據提供給模型。為了簡化我們的訓練流程,我們在 Google Cloud Platform 上構建了整個管道。所有的訓練數據都將存儲在 Google Cloud 存儲桶中。通過在 Google Functions 設置云存儲觸發器,一旦在存儲桶中檢測到任何變化,就會啟動計算引擎上的 GPU 實例。GPU 實例以 TensorFlow SavedModel 格式輸出再訓練模型,并將其保存在云存儲上的另一個存儲桶中。
模型培訓的數據管道
我們如何與 TensorFlow.js 集成
完成上述模型訓練中的步驟后,我們最終得到了一個用于對象識別的 TensorFlow SavedModel。為了通過 TensorFlow.js 在瀏覽器中訪問和使用此模型,我們使用 TensorFlow.js 轉換器將此 SavedModel 轉換為 TensorFlow.js 可以加載的格式。
識別對象的行為可以分為兩個子任務。首先,從相機中抓取像素,然后將圖像數據發送到 TensorFlow.js,以根據我們之前訓練過的模型預測它的想法。
相機和模型設置
在我們開始預測對象之前,我們需要確保相機(通過 MediaDevices.getUserMedia)準備好顯示內容,并且我們的機器學習模型已加載并準備好開始預測。在我們開始預測之前,我們使用以下代碼段來啟動這兩個代碼并執行一些任務設置。
1Promise.all([
2this.emojiScavengerMobileNet.load().then(() => this.warmUpModel()),
3camera.setupCamera().then((value: CameraDimentions) => {
4camera.setupVideoDimensions(value[0], value[1]);
5}),
6]).then(values => {
7// Both the camera and model are loaded, we can start predicting
8this.predict();
9}).catch(error => {
10// Some errors occurred and we need to handle them
11});
一旦成功完成,相機設置和模型加載都將以 Promise 解析。您會注意到,一旦加載了模型,我們就會調用 this.warmUpModel()。這個函數只是做一個預測調用來編譯程序并將權重上傳到 GPU,這樣當我們想要傳遞真實數據進行預測時,模型就會準備就緒。
將圖像數據發送到 TensorFlow.js
以下代碼片段(已刪除注釋)是我們的預測函數調用,它從相機中獲取數據,將其解析為正確的圖像大小,將其發送到我們的 TensorFlow.js 并使用生成的識別對象來查看我們是否找到了表情符號。
1async predict() {
2if (this.isRunning) {
3const result = tfc.tidy(() => {
4
5const pixels = tfc.fromPixels(camera.videoElement);
6const centerHeight = pixels.shape[0] / 2;
7const beginHeight = centerHeight - (VIDEO_PIXELS / 2);
8const centerWidth = pixels.shape[1] / 2;
9const beginWidth = centerWidth - (VIDEO_PIXELS / 2);
10const pixelsCropped =
11pixels.slice([beginHeight, beginWidth, 0],
12[VIDEO_PIXELS, VIDEO_PIXELS, 3]);
13
14return this.emojiScavengerMobileNet.predict(pixelsCropped);
15});
16
17const topK =
18await this.emojiScavengerMobileNet.getTopKClasses(result, 10);
19
20this.checkEmojiMatch(topK[0].label, topK[1].label);
21}
22requestAnimationFrame(() => this.predict());
21}
讓我們更詳細地看一下這個片段。我們將整個預測代碼邏輯包裝在 requestAnimationFrame 調用中,以確保瀏覽器在進行屏幕繪制更新時以最有效的方式執行此邏輯。如果游戲處于運行狀態,我們只執行預測邏輯。通過這種方式,我們可以確保在執行屏幕動畫(如結束和贏取屏幕)時,我們不會運行任何 GPU 密集型預測代碼。
另一個小而重要的性能改進是將 TensorFlow.js 邏輯包裝在對 tf.tidy() 的調用中。這將確保在執行該邏輯期間創建的所有 TensorFlow.js 張量都將在之后得到清理,從而確保更好的長期運行性能。請參閱https://js.tensorflow.org/api/latest/#tidy
我們預測邏輯的核心與從相機中提取圖像以發送到 TensorFlow.js 有關。我們不是簡單地拍攝整個相機圖像并將其發送出去,而是從相機中心切出一部分屏幕并將其發送到 TensorFlow.js。在我們的游戲中,我們使用 224 像素x 224 像素的參考圖像訓練我們的模型。將與我們的參考訓練數據具有相同尺寸的圖像發送到 TensorFlow.js,從而確保更好的預測性能。我們的相機元素(它只是一個 HTML 視頻元素)不是 224 像素的原因是因為我們想要確保用戶的全屏體驗,這意味著使用 CSS 將相機元素擴展到 100% 的屏幕。
以下參考圖像顯示左上角的切片,該切片將發送到 TensorFlow.js。
然后,模型使用該圖像數據生成前 10 個最可能項目的列表。您會注意到我們獲取前 2 個值并將其傳遞給 checkEmojiMatch 以確定我們是否找到了匹配項。我們選擇使用前 2 個匹配而不是最頂級的項目,因為它使游戲更有趣,并允許我們根據模型在匹配中留有一些余地。擁有一個過于準確和嚴格的模型會導致用戶在無法識別對象時感到沮喪。
在上面的圖像示例中,您可以看到我們目前的任務是找到 “鍵盤” 表情符號。在此示例中,我們還顯示了一些調試信息,因此您可以根據輸入圖像查看模型預測的所有 10 個可能項目。這里的前兩個匹配是 “鍵盤” 和 “手”,它們都在圖像中,而 “手” 具有稍大的可能性。雖然 “鍵盤” 在第二個檢測到的位置,但游戲在這里檢測到匹配,因為我們使用前兩個匹配進行檢查。
為我們的模型提供文本到語音的轉換
作為游戲的一個有趣的補充,我們實施了 SpeechSynthesis API。從而當你在尋找表情符號的時候,大聲朗讀出模型預測。在 Android 上的 Chrome 中,通過以下代碼實現這一點非常簡單:
1speak(msg: string) {
2if (this.topItemGuess) {
3if ('speechSynthesis' in window) {
4let msgSpeak = new SpeechSynthesisUtterance();
5msgSpeak.voice = this.sleuthVoice['activeVoice'];
6
7msgSpeak.text = msg;
8speechSynthesis.speak(msgSpeak);
9}
10}
11}
此 API 在 Android 上即時運行,但 iOS 將任何 SpeechSynthesis 調用限制為直接與用戶操作相關的調用(例如點擊事件),因此我們需要為該平臺找到替代解決方案。我們已經熟悉 iOS 將音頻播放事件綁定到用戶操作的要求,我們通過啟動用戶最初單擊 “播放” 按鈕時播放的所有音頻文件來處理我們游戲中的其他聲音,然后立即暫停所有這些音頻文件。最后,我們最終制作了一個音頻精靈,其中包含了所有 “成功” 的語音線(例如,“嘿,你找到了啤酒”)。這種方法的缺點是這個音頻精靈文件變得非常大,對話需要更多。
我們嘗試過的一種方法是將音頻精靈分解為前綴(“嘿,你找到了”,“是那個”)和后綴(“啤酒”,“香蕉” 等),但我們發現 iOS 在播放一個音頻文件的片段,暫停,移動播放頭,然后播放同一文件的另一個片段之間增加了不可避免的一秒延遲。前綴和后綴之間的差距很長,以至于感覺很刺耳,我們經常發現語音會遠遠落后于實際的游戲玩法。我們仍在調查 iOS 上語音改進的其他選項。
下面是我們播放音頻文件的函數,其中包含通過開始和停止時間戳處理播放音頻精靈片段的附加代碼:
1playAudio(audio: string, loop = false, startTime = 0,
2endTime:number = undefined) {
3let audioElement = this.audioSources[audio];
4if (loop) {
5audioElement.loop = true;
6}
7if (!this.audioIsPlaying(audio)) {
8audioElement.currentTime = startTime;
9let playPromise = audioElement.play();
10if (endTime !== undefined) {
11const timeUpdate = (e: Event) => {
12if (audioElement.currentTime >= endTime) {
13audioElement.pause();
14audioElement.removeEventListener('timeupdate', timeUpdate);
15}
16};
17audioElement.addEventListener('timeupdate', timeUpdate);
18}
19if (playPromise !== undefined) {
20playPromise.catch(error => {
21console.log('Error in playAudio: ' + error);
22});
23}
24}
25}
通過 getUserMedia 進行相機訪問時可能存在的風險
Emoji Scavenger Hunt 在很大程度上依賴于能夠通過瀏覽器中的 Javascript 訪問相機。我們在瀏覽器中使用 MediaDevices.getUserMedia API 來訪問攝像頭。并非所有瀏覽器都支持此 API,但大多數主流瀏覽器的最新版本都有很好的支持。
要通過此 API 訪問相機,我們使用以下代碼段:
1if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
2const stream = await navigator.mediaDevices.getUserMedia({
3'audio': false,
4'video': {facingMode: 'environment'}
5});
6(
7this.videoElement.srcObject = stream;
8}
此 API 提供了一種通過傳入配置對象并指定 facingMode 來訪問前置和后置攝像頭的方法。
無法通過 UIWebViews 訪問
在測試期間,我們意識到 Apple 不支持任何基于 webkit 瀏覽器的 UIWebView 使用 getUserMedia API,這意味著 iOS 上任何實現自己瀏覽器的應用程序,如第三方 Twitter 客戶端或 iOS 上的 Chrome,都無法訪問攝像頭。
為解決此問題,我們會檢測到相機初始化失敗,并提示用戶在本機 Safari 瀏覽器中打開體驗。
致謝
通過這個實驗,我們想要創建一個有趣和愉快的游戲,利用當今瀏覽器中提供的驚人的機器學習技術。這只是一個開始,我們希望您能使用 TensorFlow.js 和 TensorFlow.js 轉換器實現您所有的想法。如上所述,我們的代碼可以在 Github 上找到(https://github.com/google/emoji-scavenger-hunt),所以請用它來開始你自己的想法。
在構建這個實驗的過程中,我們要感謝 Takashi Kawashima,Daniel Smilkov,Nikhil Thorat 和 Ping Yu 的幫助。
-
圖像
+關注
關注
2文章
1085瀏覽量
40477 -
機器學習
+關注
關注
66文章
8420瀏覽量
132680 -
tensorflow
+關注
關注
13文章
329瀏覽量
60536
原文標題:讓我們看看,如何使用 TensorFlow.js 構建 Emoji Scavenger Hunt
文章出處:【微信號:tensorflowers,微信公眾號:Tensorflowers】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論