[SpeechRecognition] Web/Chrome 음성 인식

2020. 3. 22. 13:53Frontend/JavaScript

반응형

해당 포스트는 https://courses.wesbos.com/account/access/5d760ba285f96c03c1e5725d/view/194128542 를 기반으로 작성되었습니다.
이번 예제는 웹에서 음성 인식 추출 API( Web Speech API ) 사용하기 입니다.
기본으로 제공해주고 있어 별도의 작업 없이 사용할 수 있습니다만, 아쉽게도 Chrome에서만 제공되는 걸로 알고 있습니다.

만들어볼 예제 설명

이러한 웹 사이트를 만드는 예제입니다.

필요 기능을 분석해보자면,

  1. 사이트에 접속하는 순간 부터 음성 인식은 시작되어야 하며, 흐름이 끊기는 순간 문단이 바뀝니다.
  2. 실시간으로 음성 인식 텍스트 결과 값이 그대로 표시됩니다.

예제 시작 코드

Wesbos님께서 시작 코드를 제공해주시고 계십니다.
https://github.com/wesbos/JavaScript30/blob/master/20%20-%20Speech%20Detection
여기서 index-START.html의 코드를 저희가 원하는 방향으로 수정해가면 됩니다.

코드 실행하고 싶으시면 해당 package.json도 같이 정의하시고
npm install -> npm start로 실행 후 index-START.html 눌러주세요.

코드 - 과정

1. SpeechRecognition 사용하기

SpeechRecognition의 자세한 설명은 아래에서 확인하실 수 있습니다.
https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition
MDN 예제 따라 아래와 같이 SpeechRecognition 사용하기 위해 선언해주었습니다.
한국어 지원도 되길래 저는 한국어로 테스트 해보았습니다.

  let recognition = new SpeechRecognition();
  recognition.interimResults = true;
  recognition.lang = 'ko-KR';

설정 값 더 파악해보기

  1. interimResults
    임시 결과를 반환 여부를 제어합니다 false. 임시 결과는 아직 최종 결과가 아닙니다.
    즉, 실시간으로 인식된 결과 값을 모두 확인하고 싶다면 true를 설정하고, 최종 적으로 인식된 문장을 확인하고 싶다면 false를 작성하면 됩니다.

  2. lang
    말 그대로 인식할 언어를 설정합니다.
    영어는 en-US, 한국어는 ko-KR 입력하시면 됩니다.

  3. continuous
    여기에는 없지만 continuous를 설정할 수 있습니다. (기본 값은 false 입니다.)
    true로 설정 할 경우, 각각 인식된 문장을 하나로 합쳐줍니다. 그리고 중간에 쉬어도 stop되지 않습니다. stop을 원할 경우 강제 호출이 필요합니다.

음성 인식 시작 및 종료 제어

음성이 시작 및 종료 이벤트는 onend, onstart를 사용해 파악할 수 있습니다.

저는 웹 페이지에 들어가자마자 음성 인식을 시작하고,
인식이 자동 종료되었을 때 다른 문장을 또 인식하기 위해 onend를 사용해 다시 시작될 수 있게 설정하였습니다.

  recognition.start(); // 음성 인식(녹음) 시작

  recognition.onend = function() {
    console.log('한 뭉탱이 인식이 끝났습니다.');
    recognition.start();
  };

2. 음성 추출 데이터 가공하기

우선 닥치는대로 onresult 이벤트에 따른 결과를 console.log로 출력해보았습니다.

  recognition.onresult = function(e) {
    console.log(e.results);
  }

그 결과 아래 사실을 파악

  1. onresult는 말하는 동안만 유효하다. 한번 말하다가 쉬면 녹음이 끊긴다.
  2. 그 한 타임에 실시간으로 인식한 데이터 최종 값이 계속해서 갱신된다.
  3. 여러 문장이 인식된 경우, 인덱스를 달리하여 각 results 객체 안에 정보를 담아 리턴한다.
  4. 그리고 우리가 원하는 인식된 문장은 results의 첫번째 Alternative값의 transcript에 담겨있다.

그래서 제가 원하는 실시간 인식된 최종 문장을 이렇게 가공하였습니다.

  recognition.onresult = function(e) {
    let texts = Array.from(e.results)
            .map(results => results[0].transcript).join("");
    console.log(texts);
  }

문장 별로 텍스트 실시간 변화 모습 출력해주기

예제 결과처럼 우리는

  1. 연속으로 말할 경우 계속 인식하여 출력하고, 말을 멈추면 문단을 바꾸어 동일한 작업을 실시한다.
  2. 인식 중인 실시간 변화 값을 그대로 출력해준다.

이를 위해서 새로운 p 엘리먼트에 textNode를 추가하여 DOM에 넣어주는 작업이 필요합니다.
우선, 해당 작업을 간단히 테스트 해보았습니다.

  let p = document.createElement('p');
  p.textContent = "테스트";
  document.querySelector('.words').appendChild(p); // .words가 정의된 div내에 새로운 p 문단들을 넣어줄 예정입니다.
  1. 연속으로 말할 경우 계속 인식하여 출력하고, 말을 멈추면 문단을 바꾸어 동일한 작업을 실시한다.
    해당 작업을 수행하기 위해 새로운 p 생성 및 div 내에 추가하는 메소드를 정의하였습니다.
    그리고 음성 인식 작업이 시작될 때 마다 해당 메소드를 호출하였습니다.

    let makeNewTextContent = function() {
     p = document.createElement('p');
     document.querySelector('.words').appendChild(p);
    };
    
    let p = null;
    
    recognition.start();
    recognition.onstart = function() {
     makeNewTextContent(); // 음성 인식 시작시마다 새로운 문단을 추가한다.
    };
  2. 인식 중인 실시간 변화 값을 그대로 출력해줍니다.
    onresult에서 실시간으로 인식된 값을 p.textNode에 설정해줍니다.

    recognition.onresult = function(e) {
     let texts = Array.from(e.results)
             .map(results => results[0].transcript).join("");
    
     p.textContent = texts;
    };

기타 - 문자 대체하기

추가로, '느낌표, 강조, 뿅'이라는 음성이 인식될 경우 ❗를 대신 출력해주는 간단한 코드를 추가해봅시다.

  recognition.onresult = function(e) {
    let texts = Array.from(e.results)
            .map(results => results[0].transcript).join("");

    texts.replace(/느낌표|강조|뿅/gi, '❗️');

    p.textContent = texts;
  };

최종 결과

코드

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Speech Detection</title>
</head>
<body>

  <div class="words" contenteditable>
  </div>

<script>
  window.SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;

  let recognition = new SpeechRecognition();
  recognition.interimResults = true;
  recognition.lang = 'ko-KR';

  let makeNewTextContent = function() {
    p = document.createElement('p');
    document.querySelector('.words').appendChild(p);
  };

  let p = null;

  recognition.start();
  recognition.onstart = function() {
    makeNewTextContent(); // 음성 인식 시작시마다 새로운 문단을 추가한다.
  };
  recognition.onend = function() {
    recognition.start();
  };

  recognition.onresult = function(e) {
    let texts = Array.from(e.results)
            .map(results => results[0].transcript).join("");

    texts.replace(/느낌표|강조|뿅/gi, '❗️');

    p.textContent = texts;
  };
</script>


  <style>
    html {
      font-size: 10px;
    }

    body {
      background: #ffc600;
      font-family: 'helvetica neue';
      font-weight: 200;
      font-size: 20px;
    }

    .words {
      max-width: 500px;
      margin: 50px auto;
      background: white;
      border-radius: 5px;
      box-shadow: 10px 10px 0 rgba(0,0,0,0.1);
      padding: 1rem 2rem 1rem 5rem;
      background: -webkit-gradient(linear, 0 0, 0 100%, from(#d9eaf3), color-stop(4%, #fff)) 0 4px;
      background-size: 100% 3rem;
      position: relative;
      line-height: 3rem;
    }

    p {
      margin: 0 0 3rem;
    }

    .words:before {
      content: '';
      position: absolute;
      width: 4px;
      top: 0;
      left: 30px;
      bottom: 0;
      border: 1px solid;
      border-color: transparent #efe4e4;
    }
  </style>

</body>
</html>
반응형