Надшвидке розпізнавання мовибез серверів на реальному прикладі

У цій статті я докладно розповім і покажу, як правильно і швидко прикрутити розпізнавання української мови на движку Pocketsphinx (для iOS порт OpenEars) на реальному Hello World прикладі управління домашньою технікою. Чому саме домашньою технікою? Так тому що завдяки такому прикладу можна оцінити тушвидкість і точність, якої можна досягти при використанніповністю локальногорозпізнавання мови без серверів типуGoogle ASRабоЯндекс SpeechKit.До статті я також додаю всі вихідні програми та саму збірку під Android.

З чого раптом?

Навіщо нам щось ще окрім Яндекса та Google?

Як те саме «практичне застосування» я обрав темуголосового управління розумним домом. Чому саме такий приклад? Тому що на ньому можна побачити кілька переваг повністю локального розпізнавання мови перед розпізнаванням з використанням хмарних рішень. А саме:

  • Швидкість— ми не залежимо від серверів і тому не залежимо від їх доступності, пропускної спроможності тощо. факторів
  • Точність- наш двигун працює тільки з тим словником, який цікавить наш додаток, підвищуючи тим самим якість розпізнавання
  • Вартість- нам не доведеться платити за кожен запит до сервера
  • Голосова активація— як додатковий бонус до перших пунктів — ми можемо постійно слухати ефір, не витрачаючи при цьому свій трафік і не навантажуючи сервера

Так Android же вміє розпізнавати мову без інтернету!

Так-так… Тільки на JellyBean. І лише з півметра, не більше. І це розпізнавання — це те ж диктування, тільки з використанням набагато меншої моделі. Тож керувати нею та налаштовувати її ми теж не можемо. І що вона поверне нам унаступного разу — невідомо. Хоча для СМС-ок якраз!

Що будемо робити?

Мікрофон активуватимемо або голосом, або натисканням на іконку мікрофона, або навіть просто поклавши руку на екран. Екран у свою чергу може бути повністю вимкненим.

Що таке Pocketsphinx

Ми зможемо «годувати» движку розпізнавання українську мовну модель (ви можете знайти її у вихідниках) та граматику запитів користувача. Це саме те, що розпізнаватиме наш додаток. Нічого іншого воно не зможе розпізнати. Отже, практично ніколи не видасть щось, чого ми не очікуємо.

Pocketsphinx також може працювати за статистичною моделлю мови, що дозволяє розпізнавати спонтанну мову, яка не описується контекстно-вільною граматикою. Але для нашого завдання це не потрібно. Наша граматика складатиметься лише з назв пристроїв. Після процесу розпізнавання Pocketsphinx поверне нам звичайний рядок тексту, де пристрої йдуть один за одним.

Знак плюсаозначає, що користувач може назвати не один, а кілька пристроїв поспіль. Додаток отримує список пристроїв від контролера розумного будинку (див. далі) і формує таку граматику в класі Grammar.

Транскрипції

серверів
Граматика описує те, що може говорити користувач . Для того, щоб Pocketsphinx знав, як він це буде вимовляти, необхідно для кожного слова з граматики написати, як воно звучить у відповідній мовній моделі. Тобтотранскрипціюкожного слова. Це називаєтьсясловник.

Транскрипції описуються за допомогою спеціального синтаксису. Наприклад:

У принципі нічого складного. Подвійна голосна транскрипції позначає наголос. Подвійна приголосна - м'яку приголосну, заякою йде голосна. Усі можливі комбінації для всіх звуків української мови можна знайти у самій мовній моделі.

Зрозуміло, що заздалегідь описати всі транскрипції в нашому додатку ми не можемо, тому що ми не знаємо заздалегідь назв, які користувач дасть своїм пристроям. Тому ми генелюватимемо «на льоту» такі транскрипції за деякими правилами української фонетики. Для цього можна реалізувати такий клас PhonMapper, який зможе отримувати на вхід рядок і генерувати для неї правильну транскрипцію.

Голосова активація

Це можливість движка розпізнавання мови весь час «слухати ефір» з метою реакцію заздалегідь задану фразу (чи фрази). При цьому всі інші звуки та мова будуть відкидатися. Це не те саме, що описати граматику і просто включити мікрофон. Наводити тут теорію цього завдання та механіку того, як це працює, я не буду. Скажу лише, що нещодавно програмісти, які працюють над Pocketsphinx, реалізували таку функцію, і тепер вона доступна "з коробки" в API.

Одне варто згадати обов'язково. Для активаційної фрази потрібно як вказати транскрипцію, а й підібрати відповіднезначення порога чутливості. Занадто мале значення призведе до безлічі помилкових спрацьовувань (це коли ви не говорили активаційну фразу, а система її розпізнає). А надто висока — до несприйнятливості. Тому це налаштування має особливу важливість. Приблизний діапазон значень - від 1e-1 до 1e-40залежно від активаційної фрази.

Запускаємо розпізнавання

Pocketsphinx надає зручний API для конфігурування та запуску процесу розпізнавання. Це класиSppechRecognizerтаSpeechRecognizerSetup. Ось як виглядає конфігурація та запускрозпізнавання:

Тут ми спершу копіюємо всі необхідні файли на диск (Pocketpshinx вимагає наявності на диску акустичної моделі, граматики та словника з транскрипціями). Потім конфігурується сам двигун розпізнавання. Вказуються шляхи до файлів моделі та словника, а також деякі параметри (поріг чутливості до активаційної фрази). Далі конфігурується шлях до файлу із граматикою, а також активаційна фраза.

Як видно з цього коду, один двигун конфігурується одночасно і для граматики, і для розпізнавання активаційної фрази. Для чого так робиться? Для того, щоб ми могли швидко перемикатися між тим, що потрібно розпізнавати. Ось як виглядає запуск процесу розпізнавання активаційної фрази:

А ось так - розпізнавання мови за заданою граматикою:

Другий аргумент (необов'язковий) — кількість мілісекунд, після якого розпізнавання автоматично завершуватиметься, якщо ніхто нічого не говорить. Як бачите, можна використовувати тільки один двигун для вирішення обох завдань.

Як отримати результат розпізнавання

Щоб отримати результат розпізнавання, потрібно також вказати слухача подій, що імплементує інтерфейсRecognitionListener. У нього є кілька методів, які викликаються pocketsphinx-ом при настанні однієї з подій:

  • onBeginningOfSpeech- движок почув якийсь звук, можливо це мова (а можливо і немає)
  • onEndOfSpeech- звук закінчився
  • onPartialResultє проміжні результати розпізнавання. Для активаційної фрази це означає, що вона спрацювала. АргументHypothesisмістить дані про розпізнавання (рядок та score)
  • onResult- кінцевий результат розпізнавання. Цей метод будевикликаний після виклику методуstopуSpeechRecognizer. АргументHypothesisмістить дані про розпізнавання (рядок та score)

Реалізуючи тим чи іншим способом методи onPartialResult і onResult, можна змінювати логіку розпізнавання та отримувати остаточний результат. Ось як це зроблено у випадку з нашим додатком:

Коли ми отримуємо подію onEndOfSpeech, і якщо при цьому ми розпізнаємо команду для виконання, необхідно зупинити розпізнавання, після чого відразу буде викликаний onResult. В onResult потрібно перевірити, що тільки що було розпізнано. Якщо це команда, то потрібно запустити її на виконання і переключити двигун на розпізнавання активаційної фрази. Якщо ми його виявляємо, то одразу запускаємо процес розпізнавання команди. Ось як він виглядає:

Тут ми спочатку граємо невеликий сигнал для оповіщення користувача, що ми його почули та готові до його команди. На цей час мікрофон має бути вимкнений. Тому ми запускаємо розпізнавання після невеликого таймууту (трохи більше, ніж тривалість сигналу, щоб не почути його відлуння). Також запускається потік, який припинить розпізнавання примусово, якщо користувач говорить занадто довго. У цьому випадку це 3 секунди.

Як перетворити розпізнаний рядок на команди

Ну тут все вже специфічно для конкретної програми. У випадку з голим прикладом, ми просто витягуємо зі рядка назви пристроїв, шукаємо по них потрібний пристрій і або змінюємо його стан за допомогою HTTP запиту на контролер розумного будинку, або повідомляємо його про поточний стан (як у випадку з термостатом). Цю логіку можна побачити у класі Controller.

Як синтезувати мовлення

Синтез мовице операція, обернена до розпізнавання. Тут навпаки - потрібно перетворити рядок тексту на мову, щоб його почув користувач. У випадку з термостатом ми повинні змусити наш Android пристрій вимовити поточну температуру. За допомогою APITextToSpeechце зробити досить просто (спасибі гуглу за прекрасний жіночий TTS для української мови):

Скажу напевно банальність, але перед процесом синтезу потрібно обов'язково відключити розпізнавання. На деяких пристроях (наприклад, усі самсунги) взагалі неможливе одночасно й слухати мікрофон, і щось синтезувати.

У ньому ми просто перевіряємо, чи ще немає чогось у черзі на синтез, і включаємо розпізнавання активаційної фрази, якщо нічого більше немає.

Так! Як бачите, швидко і якісно розпізнати мову прямо на пристрої зовсім нескладно завдяки наявності таких чудових проектів, як Pocketsphinx. Він надає дуже зручний API, який можна використовувати у вирішенні завдань, пов'язаних із розпізнаванням голосових команд.

В даному прикладі ми прикрутили розпізнавання до цілком кокрентного завдання -голосового управління пристроями розумного будинку. За рахунок локального розпізнавання ми досягли дуже високої швидкості роботи і мінімізували помилки. Зрозуміло, що той же код можна використовувати і для інших завдань, пов'язаних з голосом. Це не обов'язково має бути саме розумний будинок.

Всі вихідники, а також саму збірку програми ви можете знайти в репозиторії на GitHub. Також на моєму каналі в YouTube ви можете побачити деякі інші реалізації голосового управління, і не тільки системами розумних будинків.