Raspberry pi + PocketSphinx: Оффлайн распознавание речи и управление голосом.

Хотите создать свою маленькую Siri, которая может работать без телефона и без интернета? А может онлайн и кроссплатформенно!

Очень кратко, что нас ждет и как это будет работать:

  • Установим легковесный и быстрый движок PocketSphinx.
  • Будем использовать акустическую языковую модель (hmm) и статическую языковую модель (lm). У нас будет свой словарь употребляемых слов (dict)
  • Создадим строгий каркас (разрешенную последовательность слов) в произносимых фразах (jsgf).

Обо всем этом более подробно можно почитать тут: CMUSphinx.
Принцип работы таков: говорите в микрофон необходимые, заранее описанные в словаре слова, Raspberry распознает предложение, после чего происходит интерпретация в соответствующую команду. Как вариант, включается свет/розетка. Данный процесс не требует подключения к интернету и весьма быстр (на небольшом словаре — менее секунды на распознание). Дополнительно (в данной статье описано не будет, но сразу забегаем вперед) можно осуществлять распознавание из записанного аудио файла, например, передавая его малине из Telegram. Телеграм, в свою очередь, является кроссплатформенным пультом управления от всего, не требующим ВПНов, белой IP-адресации и работающий где угодно.

Для осуществления нашей затеи понадобится любой одноплатник (в нашем случае Raspberry pi3), USB-микрофон и, желательнo, какой-нибудь управляемый ключ / диод или реле. В общем, что-то управляемое, чтобы в конечном итоге мы визуально оценили плоды работы. Микрофон можно использовать от веб камеры, если нет простого.

Подготовим систему для установки PocketSphinx

Здесь и далее будем работать из под root.

Вставляем микрофон, загружаемся и проверяем, что он присутствует в системе:

Вы должны увидеть, что появился еще один девайс помимо ALSA:

Девайс с индефикатором «1» и есть наш микрофон.

Что делать, если микрофон не представлен в системе?

Отлично, идем дальше.
Ставим необходимые пакеты:

Сразу уберем из системы pulseaudio, чтобы не получить ошибки о недоступности микрофона в дальнейшем:

Как проверить микрофон в вашем Raspberry?

Текущие параметры микрофона посмотреть можно следующим образом:
amixer -c X sget ‘Mic’,0 Где X Номер девайса из cat /proc/asound/cards.
В нашем случае команда выглядит так:

Установка чувствительности микрофона.
Подберите нужную по необходимости. Конечно, если девайс поддерживает данную настройку:

Блокировка или toggle

Разблокировка происходит точно так же, как и блокировка:

Запись.
Теперь давайте запишем тестовый фрагмент аудио.
Номер после двоеточия в команде ниже — это номер вашего девайса. У нас 1:

Для окончания записи нажмите Ctrl+C.
Воспроизведение.
Убедитесь что в вашем Raspberry для начала включена поддержка аудио.
В конфиге загрузки /boot/config.txt должен присутствовать параметр dtparam=audio=on.
Плюс потребуются наушники или колонки. Воспроизведем тестовую запись:

С микрофоном определились, теперь давайте ставить PocketSphinx

Установка Sphinxbase

Установка PocketSphinx

Теперь давайте разберемся, что делать дальше. Что мы хотим? Мы хотим управлять розеткой/лампой голосом, говоря в микрофон. При этом требуется произносить структурированную последовательность слов, которые нужно интерпретировать в команды. Правильно сформулированная задача — это наполовину решенная задача.
Для ее решения нам потребуется утилита pocketsphinx_continuous, акустическая языковая модель, статическая языковая модель и словарь. Английская акустическая языковая модель уже присутствует после установки и используется по умолчанию, русскую модель и словарь мы разберем чуть позже.

Пример распознавания речи на английском

Как создать словарь?
Создаем на компьютере текстовый файлик txt со всеми вашими словами, которые вы хотите распознавать.
Вот пример минимального словаря для управления розеткой (каждое слово с новой строки):

Загружаем созданный файл на сайт CMU в утилиту lmtool.
Нажимаем compile knoweledge base. Далее правым щелчком в появившейся странице нажимите на ваш персональный архив, копируйте ссылку на него:

потом вставляете ссылку с командой скачивания:

Распаковываем архив. Введите имя вашего архива (оно уникально):

Проверим, что файлы разархивировались и лежат в домашней директории текущего пользователя (6797 — замените в команде на номер своего архива):

Теперь можем проверить работу распознания из микрофона в связке с загруженным словарем и вашей статической языковой моделью:

В консоли сначала отобразится текущий конфиг, потом вы можете начинать говорить слова из вашего словаря.

Что делать, если вы получили ошибку следующего содержания:

Есть два варианта, почему это происходит:
Первый — вы выбрали не тот девайс, что маловероятно (например, plughw:0,0).
Второй — у вас нестыковочка с пакетами и вам придется кое-что переставить.
Общие рекомендации можно прочитать тут.

Как исправить такую ошибку:
Способ первый
Способ второй
Второй способ более сложный и нужен, если все-таки хотим оставить pulse.

Теперь сделаем процесс распознавания более структурированным, а именно: опишем как должны строиться предложения (каков должен быть порядок слов в предложении). Это необходимо для того, чтобы увеличить процент удачных распознанных фраз, облегчить работу программе и не ждать ненужных слов на входе. Чтобы распознавшие происходило в строгой определенной заранее последовательности, а все, что будет произноситься не по заданному порядку, было бы откинуто.
Для малого количества слов и заранее продуманной структуры предложений можно использовать JavaScript Grammar File вместо статической языковой модели.
Прочитать о JavaScript Grammar File можно тут.
Создаем файл с грамматикой pi.gram:

Круглые скобки говорят о том, что объект и действие обязательны в произносимой фразе, а знак вертикальной черты — возможность выбора между элементами в скобках.

Файл с грамматикой предложений создан. Теперь запустим pocketsphinx_continuous с опцией, указывающей на грамматическую конструкцию в pi.gram

Видим, что распознавание стало происходить только в определенном порядке. Остальные варианты, не описанные в pi.gram, отбрасываются, поэтому говорить теперь стоит несколько четче.

Распознавание речи в Pocketsphinx на русском

Теперь давайте займемся «русификацией» нашего детища. Для этого нам потребуется скачать русскую фонетическую модель, создать новый словарь и прописать грамматику произносимых фраз на русском. Поехали.
Качаем фонетическую модель от sourceforge вот с этого ресурса.
Архив zero_ru_cont_8k_v3.tar.gz переносим к себе в домашнюю директорию пользователя pi через winscp (если у вас Windows) или через scp (если мак/линукс).
Дальше переместим архив в домашнюю директорию root (конечно, это не обязательное условие, вы можете держать все файлы где хотите, просто для наглядности мы помещаем все в одно место, чтобы не надо было далеко ходить).

Разархивируем, скопированный архив:

Скачиваем небольшую программу которая будет генерировать словари для русской фонетической модели. Это аналог того, что вы делали через браузер. Для этого склонируем репозитрий из GitHub. git должен быть установлен в малине. если нет — apt-get install git:

Создаем в Raspberry файл со всеми необходимыми русскими словами (каждое слово с новой строки):

Теперь генерируем словарь с помощью скачанного скрипта, указывая в параметрах путь до файла с нашими словами и путь для записи результирующего файла:

После отработки программы проверим сгенерированный словарь:

Добавим немного последовательности для произносимых фраз:

В квадратных скобках мы указываем необязательное условие, в круглых — обязательное.

Готово. Запускаем pocketsphinx_continuous аналогично примеру на английском. Дополнительно необходимо указать русскую акустическую модель:

Также в этой команде мы убрали подробное логирование, чтобы видеть только итоговый результат распознавания. Результат команды будет примерно такой:

Вы можете увидеть самостоятельно, что «Зиночка» можно произносить, а можно и опустить, так как данная опция стала произвольной. Но для итоговой конфигурации я рекомендую выбрать какое-то одно имя и сделать его принудительным в ожидаемой фразе (указать круглые скобки в грамматике JSGF), дабы уменьшить число возможных ложных срабатываний.

Дополнительно в файле JSGF можно использовать более структурированные конструкции следующего вида:

Такие грамматические обороты могут в конечном итоге помочь отсеять лишние не ожидаемые варианты распознания.

Подключим управляемое реле 220v к Raspberry.

Как было сказано выше, хотелось бы увидеть результат на чем-то осязаемом. Для индикации результата я выбрал обычное коммутируемое реле 220v c 3-5-тивольтовой логикой управления. Как подключить такое реле, вы можете узнать, прочитав подробную статью.
Совсем необязательно подключать на него все 220 вольт, можно использовать цепи с меньшим вольтажом. Для экспериментов вы можете подключить фонарик/диод или все, что дает какую-то индикацию на такое реле, разорвав через него цепь питания. Но статья не об этом. Ниже я напишу два питоновскох скрипта, которые будут включать и выключать такое реле. Сигнальный провод от реле подключен на 17Pin(BCM).

Включение:

Делаем сценарий исполняемым:

Выключение:

И не забываем про бит исполнения:

Преобразование результата распознания PocketSphinx в команды

Теперь самое интересное. Как полученный результат распознавания переводить в системные команды?
На гитхабе есть различные готовые варианты программ различных конструкций. Но не все варианты могут работать так как вы хотите 🙂

Покажу простейший вариант на питоне, который можно будет наращивать самостоятельно с помощью новых блоков elif:

Разрешаем исполнение сценария:

Можем запускать и радоваться проделанной работе. Распознавание наглядно отрабатывает. Родилась интернет-вещь:

Для удобства работы, в дальнейшем можете добавить сценарий в автозагрузку. Для этого нужно дописать в файл /etc/rc.local строчку /root/recognizer.py & выше строки exit 0

Оптимизация или работа напильником

Если вас не очень устраивает качество распознавания «из коробки», выход есть. Правда, он может быть для вас не очень простым. Но, так или иначе, процентов на десять точно можно улучшить коробочные результаты. Какие для этого есть варианты?

    1. Самый простой вариант. Запустите pocketsphix_continous без параметров и вы увидите длинный список возможных входных аргументов:
Определенно стоит поиграть с параметрами пауз, определением шума, чувствительностью и т.д.

  1. Можно попытаться произвести адаптацию звуковой модели на основе своих надиктованных данных. Частично можно прочитать об этом тут, а также google вам в помощь.

Заключение

На мой взгляд, описанная связка гораздо удобнее и круче всяких выключений света по хлопку и прочих подобных решений. Это вообще другой уровень инженерной мысли. Вы можете составлять произвольные словари, в которых будет все необходимое. Мой опыт показывает, что для небольших домашних проектов хватает словаря с 20-30 элементами, перечисления всех операций над ними, а также необходимых числительных. Такой объем информации pocketsphinx «на ура» отрабатывает практически на любом одноплатнике. Как быть с USB микрофонами в реальных проектах? Из опыта: можно удлинить стандартный полутораметровый юсб шнур с помощью усилителей или несколько нарастить через преобразования usb-ethernet. Примерно 10 метров кабеля между микрофоном и малиной обычно хватает для удобного размещения и первого и второго девайсов.

Всем упорства, упорства и еще раз терпения. Подписывайтесь на блог в правой верхней части экрана. Полезной информации для дальнейших публикации еще очень много 🙂

Комментарии к “Raspberry pi + PocketSphinx: Оффлайн распознавание речи и управление голосом.”: 17

  1. error while loading shared libraries: libpocketsphinx.so.3: cannot open shared object file: No such file or directory
    вылезает ошибка, не могу разобрать в чем дело

      1. Да, спасибо с этим разобрался. Появилась новая проблема, если ничего не говорить, он выводит случайные команды из словаря. как будто бы я их говорю. и вообще распознавание происходит медленно.
        (Orange Pi+2e, ubuntu server 16.04.2; карта transcend 32gb)

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

          данный вывод произвольных команд связан с тем , что из микрофона у вас идет не тишина а постоянный поток данных.

          как вариант можете, когда не нужно ставить микрофон на тоггл програмно. насчет медленно не уверен 1.5-2 секунды — у меня средний результат отработки команды с распознанием в комнатных помещениях.

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

  2. использование нормальной (не ходовой в разговоре) ключевой фразы перед реальной командой уберет еще 90-95% ложных срабатываний.

    1. Александр! Как это сделать? Вот, например, хочу чтобы ключевой фразой была «Лиза». Но как с ней, так и без нее на выходе получаем строчку «Лиза включи свет в гостевой»… у вас как-нибудь получилось использовать кодовое слово?

  3. Алекс, спасибо огромно за статью! Наконец допилил включение по голосу, хотя пару мелочей пришлось делать не так как тут. но все равно, 5+ тебе!

  4. Я задал несколько грамматических правил и все они хорошо распознаются, но проблема в том, что если я скажу левую фразу PocketSphinx выдает как-нибудь вариант из всех правил. Т.е. в обычной речи он улавливает правила. Как от этого избавиться? Можно ли активировать его по ключевой фразе -kws, а потом послушать фразу из грамматических правил (как известно -jsgf конфликтует с -kws)

  5. Здравствуйте Александр.
    Вопрос по аппаратной части. Будет ли (потянет, соберется) эта связка работать на Raspberry Pi Zero?

  6. Огромное спасибо за статью!!! Собрал пакеты на Orange Pi Zero, микрофон аналоговый подключен к соответствующим входам — распознавание хорошее. По ключевым фразам управляю внешними контроллерами с помощью get-запросов. На питоне «пишу» первый раз, подключил urllib2 и отправляю команды так:
    if line == «Лиза включи лампу гостевой\n»:
    print line
    response = urllib2.urlopen(‘http://192.168.12.21/sec/?pt=27&cmd=27:1’)
    print «Ответ контроллера: «, response.code
    elif line == «Лиза выключи лампу гостевой\n»:
    print line
    response = urllib2.urlopen(‘http://192.168.12.21/sec/?pt=27&cmd=27:0’)
    print «Ответ контроллера: «, response.code
    В принципе все работает! Однако при прерывании процесса вылазит следующее:
    Traceback (most recent call last):
    File «/root/recognizer.py», line 13, in
    line = p.stdout.readline()
    KeyboardInterrupt

    С чем это может быть связано???

    1. KeyboardInterrupt означает, что вы просто напросто нажали CTRL+C. Это что-то типа обработки сигнала в Python. Попробуйте ввести ключевое слово для завершения и по нему завершать процесс.

  7. А можно не пользоваться консольной утилитой, а дёргать прямо из питона обёртки для неё.
    Правда у меня она не совсем стабильно работает. и не получилось Sphinxbase для 3 питона завести

  8. добрый день.
    благодаря мануалу собрал робота, прицепил к нему один из лучших девайс-микрофонов — respeaker v.2 (вышла в 2018). там есть и шумоподавление, и определение с какой стороны звук пришел и т.п.
    все работает, но полно ложных срабатываний, несмотря на то, что тестировал в полной тишине в комнате.
    если робот едет, то из-за шума моторов (траки танка) ложных срабатываний еще больше. грусть (

        1. не знаю даже. есть невероятная версия о наводках) попробуйте экранировать микрофон, типа мелкой металлической сектой

Добавить комментарий

А не бот ли вы? *