Pull to refresh

Pocketsphinx. Распознавание речи и голосовое управление в Linux

Reading time 11 min
Views 124K
— Всё в порядке, Лёня?
Динамики отрегулированы на максимум, я морщусь, отвечаю:
— Да. Тише звук.
— Звук — тише, — соглашается «Виндоус-Хоум», — тише, тише…
— Хватит, Вика
С.Лукьяненко, «Лабиринт отражений»

Введение


В 1997-ом году Лукьяненко пророчил для десктопа сочетание CLI и голосового управления. Однако сейчас голосовое управление — достаточно узкая ниша.
Голосовое управление — взаимодействие с устройством при помощи звуковых команд. Не путайте это понятие с распознаванием речи. Для голосового управления достаточно, чтобы устройство реагировало на единственную нужную команду (ведь ваша собака не может работать машинисткой?). Распознавание речи — гораздо более глобальная проблема: в этом случае устройство должно преобразовывать в текстовый формат все слова, произнесенные вами. Как легко догадаться, распознавание речи на данный момент реализовано поверхностно относительно человеческих возможностей.
Функционал, рассмотренный в статье, может быть применен, к примеру, для организации модного сейчас «умного дома» или просто управления компьютером. Честно говоря, для описания управления компьютером хватило бы пары абзацев, но я попытаюсь показать вам основы работы с CMU Sphinx.
Кстати, процентов 70 описанного здесь подойдет и пользователям Windows.

Для Linux чаще всего упоминаются два развитых проекта распознавания речи: CMU Sphinx и Julius.

В данной статье я не буду касаться Julius, поскольку гайдов по его использованию (в том числе и в Рунете) хватает. Речь будет идти о CMU Sphinx.

Описание и установка


На официальном сайте последняя версия pocketsphinx и sphinxbase — 0.8. В репозитории моего Debian Squeeze есть только устаревшая ветка sphinx2. Значит, будем собирать (проверьте репозитории своих дистрибутивов: в последних версиях Ubuntu и Fedora должны быть актуальные версии). При необходимости вместо pocketsphinx, написанного на C, вы можете использовать Sphinx4 на Java (подробнее).
Скачиваем отсюда исходники pocketsphinx и sphinxbase («support library required by Pocketsphinx and Sphinxtrain») и собираем.
Проблем возникнуть не должно
./configure
make
checkinstall

Не забудьте после установки выполнить:
ldconfig


Готовые пакеты для Debian Squeeze x86

Для работы с /dev/dsp установим согласно FAQ пакет oss-compat.

Базовое использование


Проект предлагает нам проверить работоспособность на базовом примере: распознать фразу на английском «go forward ten meters».
Что ж, пробуем.
Мы воспользуемся утилитой пакетного распознавания pocketsphinx_batch (перед использованием почитайте её man). Также есть утилита распознавания «с микрофона» — pocketsphinx_continuous (синтаксис её схож с pocketsphinx_batch).
Работать будем в отдельной директории, к примеру ~/sphinx.
Синтаксис нашей команды таков:
pocketsphinx_batch -argfile argfile 2>./errors

-argflie: имя файла в текущей директории, содержащего все аргументы.
stderr для удобства перенаправим в файл.
Содержимое argfile:
-hmm /usr/local/share/pocketsphinx/model/hmm/en_US/hub4wsj_sc_8k
-lm /usr/local/share/pocketsphinx/model/lm/en/turtle.DMP
-dict /usr/local/share/pocketsphinx/model/lm/en/turtle.dic
-cepdir /home/saint/sphinx
-ctl ctlfile
-cepext .raw
-adcin true
-hyp outname

-hmm: путь к каталогу, содержащему файлы акустической модели (шаблоны отдельных звуков).
-lm: путь к файлу триграммной языковой модели (можете почитать здесь).
-dict: путь к файлу словаря произношения.
-cepdir: путь к каталогу со звуковыми файлами. Будьте внимательны: если вы вносите -cepdir в файл аргументов, то сокращенный путь ~/sphinx обрабатывается неправильно: приходится писать полный путь. Если вы будете прописывать аргумент после команды, то можете использовать сокращенный путь.
-ctl: файл с именами обрабатываемых файлов. Файл goforward.raw мы возьмем из комплекта исходников pocketsphinx (там есть еще пару файлов *.raw — можете распознать и их).
-cepext: расширение обрабатываемых файлов
-adcin: указатель принадлежности обрабатываемого файла к raw.
-hyp: имя файла, в который будет выведен распознанный текст.
Аргументы с путями к файлам моделей указывать обязательно. Помните, что многие параметры заданы по умолчанию (смотрите stderr). Поэтому для работы с файлом *.raw необходимо принудительно указать расширение, иначе будет использован параметр по умолчанию — расширение .mfc (а таких файлов у нас в базовом примере, естественно, нету — будут сыпаться ошибки).
В результате исполнения у нас в файле outname будет следующее содержимое:
go forward ten meters (goforward -26532)

Параллельно можете посмотреть, откомпилировать и запустить в каталоге с файлом goforward.raw программку аналогичного назначения на C (пример от разработчиков).
Для проверки на своих примерах я решил не мудрствовать и воспользовался sox (проверьте, установлен ли этот пакет у вас).
Писать звук будем следующим образом (можете почитать man sox):
— для raw
rec -r 16k -e signed-integer -b 16 -c 1 filename.raw

— для wav
rec -r 16k -e signed-integer -b 16 -c 1 filename.wav

Окончание записи по Ctrl+C.
У меня sox при этом ругался на невозможность использования частоты дискретизации: can't set sample rate 16000; using 48000. Учтите: нагло лжет — на самом деле все в порядке.
Я писал и распознавал raw и wav на различных примерах из подключенных словарей — все распознавалось вполне приемлимо.

Адаптация звуковой модели


Адаптация звуковой модели должна улучшить распознавание для конкретного голоса, произношения, акцента или окружающей среды. Рассмотрим этот процесс.

Скачиваем по первой ссылке предлагаемые файлы в отдельную директорию, в которой и будем работать.
Теперь надиктуем предложения из файла arctic20.txt по образцу: у вас должно получиться двадцать файлов, названных по порядку согласно схеме arctic_0001.wav...arctic_0020.wav.
Чтобы упростить запись, воспользуемся предложенным скриптом:
for i in `seq 1 20`; do 
       fn=`printf arctic_%04d $i`; 
       read sent; echo $sent; 
       rec -r 16000 -e signed-integer -b 16 -c 1 $fn.wav 2>/dev/null; 
done < arctic20.txt

Соответственно, чтобы прослушать полученное, выполним:
for i in *.wav; do play $i; done

Скопируем акустическую модель (с которой мы и работали) из /usr/local/share/pocketsphinx/model/hmm/en_US/hub4wsj_sc_8k в нашу рабочую директорию.
Теперь создадим файлы акустических особенностей (напоминаю: работаем в директории с файлами *.wav).
sphinx_fe -argfile hub4wsj_sc_8k/feat.params -samprate 16000 -c arctic20.listoffiles -di . -do . -ei wav -eo mfc -mswav yes

В результате получаем файлы *.mfc.
Скачиваем экстра-пак (89,0 МБ); файл под названием mixture_weights из него, расположенный в pocketsphinx-extra/model/hmm/en_US/hub4_wsj_sc_3s_8k.cd_semi_5000 помещаем в каталог с акустической моделью.
Также необходимо конвертировать mdef-файл акустической модели в текстовый формат:
pocketsphinx_mdef_convert -text hub4wsj_sc_8k/mdef hub4wsj_sc_8k/mdef.txt

Теперь, согласно терминологии гайда по адаптации, соберем накопленные данные. Скопируем утилиту bw из /usr/local/libexec/sphinxtrain/bw в рабочий каталог (перед этим не забудьте установить sphinxtrain!).
./bw -hmmdir hub4wsj_sc_8k -moddeffn hub4wsj_sc_8k/mdef.txt -ts2cbfn .semi. -feat 1s_c_d_dd -svspec 0-12/13-25/26-38 -cmn current -agc none -dictfn arctic20.dic -ctlfn arctic20.fileids -lsnfn arctic20.transcription -accumdir .

Запускаем и видим:
SYSTEM_ERROR: "corpus.c", line 339: Unable to open arctic20.fileids for reading: No such file or directory
Очевидно, правая рука у разработчиков не ведает, что творит левая (про неактуальность части документации я уже не говорю).
Переименовываем в рабочем каталоге файл arctic20.listoffiles в arctic20.fileids
Теперь все работает.
Произведем MLLR-адаптацию (эффективна для ограниченного объема данных в модели):
cp /usr/local/libexec/sphinxtrain/mllr_solve /your/work/dir/mllr_solve

./mllr_solve -meanfn hub4wsj_sc_8k/means -varfn hub4wsj_sc_8k/variances -outmllrfn mllr_matrix -accumdir .

Эта команда создаст файл адаптационных данных mllr_matrix.
Теперь при распознавании с адаптированной моделью можно добавлять параметр -mllr /path/to/mllr_matrix.
Параллельно произведем другой метод адаптации: MAP.
cp /usr/local/libexec/sphinxtrain/map_adapt /your/work/dir/map_adapt

Сделаем копию модели:
cp -a hub4wsj_sc_8k hub4wsj_sc_8kadapt

И произведем MAP-адаптацию:
./map_adapt -meanfn hub4wsj_sc_8k/means -varfn hub4wsj_sc_8k/variances -mixwfn hub4wsj_sc_8k/mixture_weights -tmatfn hub4wsj_sc_8k/transition_matrices -accumdir . -mapmeanfn hub4wsj_sc_8kadapt/means -mapvarfn hub4wsj_sc_8kadapt/variances -mapmixwfn hub4wsj_sc_8kadapt/mixture_weights -maptmatfn hub4wsj_sc_8kadapt/transition_matrices

Теперь создадим sendump файл, отличающийся меньшим размером:
cp /usr/local/libexec/sphinxtrain/mk_s2sendump /your/work/dir/mk_s2sendump

./mk_s2sendump -pocketsphinx yes -moddeffn hub4wsj_sc_8kadapt/mdef.txt -mixwfn hub4wsj_sc_8kadapt/mixture_weights -sendumpfn hub4wsj_sc_8kadapt/sendump

Адаптация закончена.

Тестирование адаптации


Суть эксперимента: мы записываем несколько образцов, на которых оригинальная акустическая модель спотыкается, и обрабатываем их с помощью адаптированных акустических моделей.
Создадим в рабочем каталоге поддиректорию test. В ней создаем поддиректорию wav, в которой будут наши тестовые записи.
Здесь для подтверждения результата эксперимента я выкладываю свои образцы и две адаптированные акустические модели.

Проверяем (помните, что адаптация не приведет к стопроцентно верному результату: адаптированные модели будут точно так же ошибаться; плюс в том, что они будут делать это реже. Мои вполне наглядные записи были сделаны далеко не с первой попытки: было достаточно записей, где ошибались все модели):
1. Распознавание с помощью базовой модели:
pocketsphinx_batch -hmm /usr/local/share/pocketsphinx/model/hmm/en_US/hub4wsj_sc_8k -lm /usr/local/share/pocketsphinx/model/lm/en/turtle.DMP -dict /usr/local/share/pocketsphinx/model/lm/en/turtle.dic -cepdir wav -ctl adaptation-test.fileids -cepext .wav -adcin yes -hyp adaptation-test.hyp

Результат:
hello halt say forty (test1 -27391)
go forward ten meter (test2 -35213)
hall doing home (test3 -30735)

2. Распознавание с помощью модели с MLLR-адаптацией: при указании параметром -mllr пути к моей матрице происходила ошибка сегментирования (копаться я не стал). При распознавании без этой опции результат полностью идентичен результату оригинальной модели.
Впрочем, в мануале заявлено, что MLLR-адаптация лучше всего подходит для непрерывной модели (т.е. для Sphinx4).
3. Распознавание с помощью модели с MAP-адаптацией:
pocketsphinx_batch -hmm ../hub4wsj_sc_8kadapt -lm /usr/local/share/pocketsphinx/model/lm/en/turtle.DMP -dict /usr/local/share/pocketsphinx/model/lm/en/turtle.dic -cepdir wav -ctl adaptation-test.fileids -cepext .wav -adcin yes -hyp adaptation-test.hyp

Результат:
hello rotate display (test1 -28994)
go forward ten meters (test2 -33877)
lost window (test3 -29293)


Как можно убедиться, результат полностью идентичен записи. Адаптация реально работает!

Русский язык в Pocketsphinx


Скачаем отсюда русские модели, созданные на voxforge. На разнообразие моделей можно посмотреть здесь и просто в интернетах.

Реализовывать пример голосового управления компьютером мы будем на русском, а значит нам нужны собственная языковая модель и собственный словарь (вероятнее всего, части наших слов в распространенных примерах не будет).

Создание собственной статической языковой модели

Вообще, для малого количества слов вместо статической языковой модели можно использовать jsgf-словарь. Однако, это частный случай и мы его рассмотрим чуть ниже.
Руководство по созданию языковой модели находится здесь.
Создавать будем с помощью CMUCLMTK.
Скачиваем, собираем.
Для начала создадим текстовый файл с предложениями для нашей языковой модели.
lmbase.txt
<s> предлагаю свернуть окно браузера </s>
<s> нужно развернуть окно и радоваться </s>
<s> тише едешь громче падаешь </s>
<s> открыть терминал и закрыть окно </s>
<s> вперёд вперёд плешивые стада </s>
<s> играй и не смотри назад </s>
<s> пора проверить почту </s>
<s> почта находится за углом </s>
<s> сделай паузу выключи компьютер </s>
<s> стоп проход запрещен </s>
<s> пауза это небольшой перерыв </s>
<s> замолчи а потом говори </s>
<s> компьютер разверни окно </s>
<s> открой браузер и покажи мне интернет </s>
<s> сделай шаг вперёд и два шага назад </s>
<s> запустить браузер можно голосом </s>
<s> открой окно давай вперед </s>
<s> запустить окно несложно </s>
<s> давай запусти терминал </s>
<s> можно запустить компьютер </s>
<s> сделай тише </s>
<s> можно играть тише </s>
<s> нужно проверить компьютер </s>
<s> компьютер прошу громче </s>
<s> делай громче </s>
<s> пора все закрыть </s>
<s> закрыть все окна </s>
<s> пора свернуть все к черту </s>
<s> нужно свернуть окна </s>
<s> сверни окно </s>
<s> браузер необходимо срочно свернуть </s>
<s> пора все развернуть </s>
<s> нужно развернуть окно </s>
<s> разверни окно </s>


Далее создаем словарный файл:
text2wfreq <lmbase.txt | wfreq2vocab> lmbase.tmp.vocab
cp lmbase.tmp.vocab  lmbase.vocab

Создаем языковую модель в arpa-формате:
text2idngram -vocab lmbase.vocab -idngram lmbase.idngram < lmbase.txt
idngram2lm -vocab_type 0 -idngram lmbase.idngram -vocab lmbase.vocab -arpa lmbase.arpa

И создаем DMP-модель.
sphinx_lm_convert -i lmbase.arpa -o lmbase.lm.DMP


Создание собственного словаря

Тащим с гитхаба утилиты:
git clone https://github.com/zamiron/ru4sphinx/ yourdir

Переходим в каталог ./yourdir/text2dict и создаем там текстовый файл my_dictionary с вашим списком слов (каждое новое слово — с нового абзаца).
Пример my_dictionary
браузер
громче
закрыть
запустить
окно
почта
развернуть
свернуть
свернуть
терминал
тише

Затем выполняем:
perl dict2transcript.pl my_dictionary my_dictionary_out

И вот ваш словарь создан.

Теперь пробуем распознавать слова, присутствующие в словаре (благо, в нашем примере их немного). Не забудьте указать в аргументах собственную языковую модель и словарь — все должно работать. При желании можно произвести адаптацию акустической модели (сразу предупреждаю: при использовании утилиты bw в процессе адаптации для большинства акустических моделей опция -svspec не нужна ).

Использование JavaScript Grammar File вместо статической языковой модели

Читать тут.
Синтаксис:
#JSGF V1.0;
grammar test;
public <test> = ( <a> | <a> <b> );
<a> = ( тише | громче | закрыть | свернуть );
<b> = [ окно ];

"|" обозначает условие выбора. Т.е. мы можем сказать «тише» или «закрыть окно». Правда, по сравнению с использованием языковой модели есть один минус: говорить нужно гораздо членораздельнее.
Созданный jsgf-файл указываем с параметром -jsgf (параметр -lm в таком случае не нужен).

Реализация голосового управления


Моей целью не было реализовать крутой интерфейс управления: здесь все будет очень примитивно (если есть желание и возможность, можете посмотреть на заброшенный проект Gnome Voice Control).
Действовать будем следующим образом:
1. Пишем команду, распознаем её.
2. Передаем распознанный текст в файл, в соответствии с ним выполняем команду.
В качестве тестовых команд будем использовать уменьшение и увеличение громкости звука.

Внимательно почитав мануал к sox, я решил оканчивать запись по истечении секунды тишины с порогом тишины в 3.8% (порог явно является сугубо индивидуальным значением и зависит от вашего микрофона и окружающей обстановки).
К сожалению, я не нашел в pocketsphinx_batch параметр вывода только для распознанных слов, поэтому я воспользуюсь инструментом sed:
cat ~/sphinx/rus/outname  | sed 's/\( (.*\)//'

Эта конструкция удалит из строки вида «наша команда (audio -4023)» пробел перед открывающей скобкой, её саму и все последующее содержимое. В результате мы получим строку вида «наша команда», что нам и нужно.
Вот сам скрипт:
#!/bin/bash
rec -r 16k -e signed-integer -b 16 -c 1 test.wav silence 0 1 00:01 3.8%
pocketsphinx_batch -argfile argfile 2>./errors
a=$(cat ~/sphinx/rus/outname  | sed 's/\( (.*\)//')
case $a in
тише)
amixer -q sset Master 10-
a=$(amixer sget Master | grep "Mono: Playback")
notify-send "$a" 
;;
громче)
amixer -q sset Master 10+
a=$(amixer sget Master | grep "Mono: Playback")
notify-send "$a"
;;
*)
;;
esac

Скрипт в ответ на команды «тише» или «громче» выполняет соответствующие действия с сигнализацией через notify-send.
К сожалению, работать он будет только при запуске из терминала (иначе звук не пишется). Впрочем, представление о голосовом управлении он дает (возможно, вы предложите лучший метод).

Пару слов в качестве заключения


Когда я начинал писать эту статью, я просто хотел рассказать вам, что со свободным распознаванием сейчас далеко не всё так глухо, как кажется. Есть движки, ведется работа над акустическими моделями на www.voxforge.org (вы можете посодействовать им начитыванием лексики). Вместе с тем, работа с распознаванием не является чем-то сложным для простого пользователя.
Несколько дней назад было объявлено, что в Ubuntu для планшетов будет использоваться Pocketsphinx или Julius. Надеюсь, в этом свете данная тема выглядит немного актуальнее и интереснее.
В статье я пытался рассмотреть основные моменты работы с Pocketsphinx, ведя речь скорее о теории, чем о практике. Однако вы могли убедиться, что распознавание не фикция, оно работает.
Помните, что я затронул только поверхностные аспекты: в документации на официальных сайтах и различных форумах описано намного больше. Пробуйте, рассказывайте в комментариях о своем опыте, делитесь наработками, удачными мыслями и готовыми решениями для голосового управления.
Tags:
Hubs:
+54
Comments 37
Comments Comments 37

Articles