iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 21
0
Modern Web

JS30 錄系列 第 21

Day 21 - Speech Detection

  • 分享至 

  • xImage
  •  

緣起

記得在果王大賽中落選,因此憤憤不平而決定撼動整個農業界的老樹猴嗎?(詳情請見 Day4Day6 )。為了實現大陰謀,他整整計畫了十五篇之久,決定踏出關鍵的第一步:竊聽農產大會的內部趨勢分析,藉以得知明年的潛力水果組合。

真是好算計!可惜老樹猴的聽力不太好,要是能夠有語音辨識的網頁將他竊聽的資訊即時轉成文字顯示在螢幕上就好了。於是,他找上了我們。

任務目標

將麥克風輸入的音訊轉換成文字顯示在螢幕上。實際結果

作法

這次我們要使用瀏覽器端的 Web Speech API 來達成語音辨識的任務。 Web Speech API 讓我們能將「聲音」這個要素融合進網頁應用程式中,不過該 API 尚未標準化,各瀏覽器支援程度不一。

Web Speech API 主要分成兩塊,一塊是 Speech Synthesis ,即文字轉聲音。另一塊是 Speech Recognition,即聲音轉文字,這次用到的是後者。

Speech Recognition 需要使用者同意麥克風權限,另外,應用程式必須在安全網路來源才能執行,因此要透過開啟 Local Server 並透過瀏覽器連入的方式才有辦法執行!和 Webcam 那篇一樣,作者提供了 browser-sync 使用,自己用 python 架也行。

接下來開始正題!

首先要檢查瀏覽器是否有支援不需要 prefixspeechRecognition 介面,若沒有則將 webkit 標頭的 webkitSpeechRecognition 指定給該全域變數,方便我們使用。

window.SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;

要使用 speechRecognition ,必須透過 SpeechRecognition 建構式建立一個物件實例。

const recognition = new SpeechRecognition();

要怎麼取得語音辨識的結果呢?必須對語音辨識的物件實例架設監聽器,當語音辨識回傳結果時,觸發監聽事件,我們再用自訂函式來操控辨識結果。

result 事件便是我們要監聽的事件。該事件會在語音辨識回傳結果時被觸發。並且會將整個事件當作參數傳進自訂函數內。

設定完後要使用 start() 才會開始辨識,先來看看結果長什麼樣子。

recognition.addEventListener('result', e => {
  // 將傳入參數顯示在 console 上
  console.log(e)
});

// 開始語音辨識
recognition.start();

打開瀏覽器開發者指令列觀看結果,發現該事件包成一大包。點開 results 屬性,results 屬性是儲存所有單一 SpeechRecognitionResult 的清單,裡面的第 0 項就是第一個回傳的結果。打開還會看到一個由 SpeechRecognitionAlternative 組成的清單,裡面的第 0 項儲存著其中一種可能的分析結果。裡面的 transcription 就是我們要的分析結果的值。如果你在台灣,你講中文,此時在 transcription 內應該會看到中文。

各個 result 代表片段的字, results 就是分析完的字們。我們的目的是要取出所有 results 裡面的字值,然後將其合併成句子。

首先將清單轉成陣列,用陣列方法 map 複製出由 transcript 所組成的陣列,再用字串方法 join 合併成句子。

recognition.addEventListener('result', e => {
  const transcript = Array.from(e.results)
    .map(result => result[0])
    .map(result => result.transcript)
    .join('')
});

接下來只要放進事先準備好的 <p> 元素,再加到網頁上就好。

  // 事先準備好的容器 <p>
  let p = document.createElement('p');
  const words = document.querySelector('.words');
  words.appendChild(p);

  // 設定語音辨識及後續流程
  recognition.addEventListener('result', e => {
    // 將辨識結果組成句子
    const transcript = Array.from(e.results)
      .map(result => result[0])
      .map(result => result.transcript)
      .join('')
    
    // 將鋸子塞進容器內
    p.textContent = transcript;
  });

這裡會有兩個問題,第一個問題是,我們會發現句子講完一段時間後才會出現結果。這是因為語音辨識會先從各種可能的組合結果中判斷哪一種是最有可能的,然後才會輸出最後的句子。這需要時間,雖然也不長。

如果想要讓語音辨識結果直接將還不是最終結果的組合輸出,讓文字可以即時顯示,可以將 recognition.interimResults 這個屬性設為 true 。我們將該設定插入到一開始的物件實例化下方。

const recognition = new SpeechRecognition();
// 輸出非最終結果
recognition.interimResults = true;

無須擔心,即使中間結果優先被輸出,當最終判斷結果出來時,文字還是會被替換為最終結果。

第二個問題是,每次重講一個句子時,原來的句子就會被洗掉,那是因為我們始終都在同一個 <p> 中重複輸出文字而已。解決辦法是讓語音辨識判斷出我們講完一串句子了,就會執行跳行的動作。

回傳事件的 results[0] 內有項 isFinal 參數,當句子的被判斷為最終結果時,該 isFinal 值就會顯示為 true ,只要在那之後建立新段落即可。程式碼如下:

recognition.addEventListener('result', e => {
  // 前略

  // 將鋸子塞進容器內
  p.textContent = transcript;
  // 若已為最終結果,建立新段落 <p>
  if(e.results[0].isFinal) {
    p = document.createElement('p');
    words.appendChild(p);
  }    
});

最後,一次 start() 只會開啟一次語音辨識,辨識完就關閉了。我們希望這句講完後,結果輸出了,會再開啟下一次的語音辨識。這時候可以讓語音辨識監聽 end 事件, end 事件當語音辨識關閉時會被觸發,在自訂函式中開啟下一輪的語音辨識循環即可。程式碼如下:

recognition.addEventListener('end', recognition.start);

大功告成!

以上就是 JS30 第二十一篇!

Reference

Web Speech API
Speech Recognition
Speech Recognition - result Event
Speech Recognition - interimResults
Speech Recognition - end Event
完整程式碼

後記

老樹猴野心勃勃的潛入農產大會內部趨勢分析小組會議辦公室,並將竊聽網頁神不知鬼不覺地開啟了。這個小小的動作,沒想到就是未來長達十五年的國際規模大戰「農產會戰」的開端...


上一篇
Day 20 - Webcam Fun - Part II - Pixel Manipulation
下一篇
Day 22 - Follow Along Link Highlighter
系列文
JS30 錄30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言