система умный дом
Хотите создать свою маленькую Siri, которая может работать без телефона и без интернета? А может онлайн и кроссплатформенно!
Очень кратко, что нас ждет и как это будет работать:
- Установим легковесный и быстрый движок PocketSphinx.
- Будем использовать акустическую языковую модель (hmm) и статическую языковую модель (lm). У нас будет свой словарь употребляемых слов (dict)
- Создадим строгий каркас (разрешенную последовательность слов) в произносимых фразах (jsgf).
Обо всем этом более подробно можно почитать тут: CMUSphinx.
Принцип работы таков: говорите в микрофон необходимые, заранее описанные в словаре слова, Raspberry распознает предложение, после чего происходит интерпретация в соответствующую команду. Как вариант, включается свет/розетка. Данный процесс не требует подключения к интернету и весьма быстр (на небольшом словаре — менее секунды на распознание). Дополнительно (в данной статье описано не будет, но сразу забегаем вперед) можно осуществлять распознавание из записанного аудио файла, например, передавая его малине из Telegram. Телеграм, в свою очередь, является кроссплатформенным пультом управления от всего, не требующим ВПНов, белой IP-адресации и работающий где угодно.
Для осуществления нашей затеи понадобится любой одноплатник (в нашем случае Raspberry pi3), USB-микрофон и, желательнo, какой-нибудь управляемый ключ / диод или реле. В общем, что-то управляемое, чтобы в конечном итоге мы визуально оценили плоды работы. Микрофон можно использовать от веб камеры, если нет простого.
Подготовим систему для установки PocketSphinx
Здесь и далее будем работать из под root.
1
2
3
|
$ sudo -i
# apt-get update
# apt-get upgrade
|
Вставляем микрофон, загружаемся и проверяем, что он присутствует в системе:
1
|
# cat /proc/asound/cards
|
Вы должны увидеть, что появился еще один девайс помимо ALSA:
1
2
3
4
5
|
root@raspberrypi_lobby:~# cat /proc/asound/cards
0 [ALSA ]: bcm2835 — bcm2835 ALSA
bcm2835 ALSA
1 [Device ]: USB—Audio — USB PnP Sound Device
C—Media Electronics Inc. USB PnP Sound Device at usb—3f980000.usb—1.4, full spe
|
Девайс с индефикатором «1» и есть наш микрофон.
Что делать, если микрофон не представлен в системе?
Отлично, идем дальше.
Ставим необходимые пакеты:
1
2
3
4
5
|
$ sudo -i
# apt-get install bison
# apt-get install alsa-utils libasound2-dev
# apt-get install swig
# apt-get install python-dev
|
Сразу уберем из системы pulseaudio, чтобы не получить ошибки о недоступности микрофона в дальнейшем:
1
2
3
|
# apt-get remove pulseaudio -y
# aptitude purge pulseaudio -y
# sudo mv /usr/include/pulse/pulseaudio.h /usr/include/pulse/pulseaudio.h.old
|
Как проверить микрофон в вашем Raspberry?
Текущие параметры микрофона посмотреть можно следующим образом:
amixer -c X sget ‘Mic’,0 Где X Номер девайса из cat /proc/asound/cards.
В нашем случае команда выглядит так:
1
|
# amixer -c 1 sget ‘Mic’,0
|
Установка чувствительности микрофона.
Подберите нужную по необходимости. Конечно, если девайс поддерживает данную настройку:
1
2
3
4
|
# amixer -c 1 sset ‘Mic’ 100
# amixer -c 1 sset ‘Mic’ 70
…
и тд
|
Блокировка или toggle
1
|
# amixer -c 1 sset ‘Mic’ toggle
|
Разблокировка происходит точно так же, как и блокировка:
1
|
# amixer -c 1 sset ‘Mic’ toggle
|
Запись.
Теперь давайте запишем тестовый фрагмент аудио.
Номер после двоеточия в команде ниже — это номер вашего девайса. У нас 1:
1
|
# arecord -D plughw:1,0 -f cd ./test_record.wav
|
Для окончания записи нажмите Ctrl+C.
Воспроизведение.
Убедитесь что в вашем Raspberry для начала включена поддержка аудио.
В конфиге загрузки /boot/config.txt должен присутствовать параметр dtparam=audio=on.
Плюс потребуются наушники или колонки. Воспроизведем тестовую запись:
1
|
# aplay ./test_record.wav
|
С микрофоном определились, теперь давайте ставить PocketSphinx
Установка Sphinxbase
1
2
3
4
5
6
7
8
|
$ sudo -i
# cd ~/
# wget http://sourceforge.net/projects/cmusphinx/files/sphinxbase/5prealpha/sphinxbase-5prealpha.tar.gz
# tar -zxvf ./sphinxbase-5prealpha.tar.gz
# cd ./sphinxbase-5prealpha
# ./configure —enable-fixed
# make
# sudo make install
|
Установка PocketSphinx
1
2
3
4
5
6
7
8
9
|
# cd ~/
# wget http://sourceforge.net/projects/cmusphinx/files/pocketsphinx/5prealpha/pocketsphinx-5prealpha.tar.gz
# tar -zxvf pocketsphinx-5prealpha.tar.gz
# cd ./pocketsphinx-5prealpha
# ./configure
# make
# sudo make install
# export LD_LIBRARY_PATH=/usr/local/lib
# export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig
|
Теперь давайте разберемся, что делать дальше. Что мы хотим? Мы хотим управлять розеткой/лампой голосом, говоря в микрофон. При этом требуется произносить структурированную последовательность слов, которые нужно интерпретировать в команды. Правильно сформулированная задача — это наполовину решенная задача.
Для ее решения нам потребуется утилита pocketsphinx_continuous, акустическая языковая модель, статическая языковая модель и словарь. Английская акустическая языковая модель уже присутствует после установки и используется по умолчанию, русскую модель и словарь мы разберем чуть позже.
Пример распознавания речи на английском
Как создать словарь?
Создаем на компьютере текстовый файлик txt со всеми вашими словами, которые вы хотите распознавать.
Вот пример минимального словаря для управления розеткой (каждое слово с новой строки):
1
2
3
|
PLUG
ACTIVE
DISABLE
|
Загружаем созданный файл на сайт CMU в утилиту lmtool.
Нажимаем compile knoweledge base. Далее правым щелчком в появившейся странице нажимите на ваш персональный архив, копируйте ссылку на него:
потом вставляете ссылку с командой скачивания:
1
2
|
# cd ~/
# wget http://www.speech.cs.cmu.edu/tools/product/1488625377_12302/TAR6797.tgz
|
Распаковываем архив. Введите имя вашего архива (оно уникально):
1
|
# tar zxvf TAR6797.tgz
|
Проверим, что файлы разархивировались и лежат в домашней директории текущего пользователя (6797 — замените в команде на номер своего архива):
1
|
ls -an | grep 6797
|
1
2
3
4
5
6
|
-rw-r—r— 1 33 33 108 мар 4 2017 6797.dic
-rw-r—r— 1 33 33 1200 мар 4 2017 6797.lm
-rw-r—r— 1 33 33 79 мар 4 2017 6797.log_pronounce
-rw-r—r— 1 33 33 84 мар 4 2017 6797.sent
-rw-r—r— 1 33 33 30 мар 4 2017 6797.vocab
-rw-r—r— 1 0 0 908 мар 4 2017 TAR6797.tgz
|
Теперь можем проверить работу распознания из микрофона в связке с загруженным словарем и вашей статической языковой моделью:
1
|
pocketsphinx_continuous -adcdev plughw:1,0 -dict /root/6797.dic -lm /root/6797.lm -inmic yes
|
В консоли сначала отобразится текущий конфиг, потом вы можете начинать говорить слова из вашего словаря.
Что делать, если вы получили ошибку следующего содержания:
1
2
|
Error opening audio device plughw:1,0 for capture: Connection refused
FATAL: «continuous.c», line 245: Failed to open audio device
|
Есть два варианта, почему это происходит:
Первый — вы выбрали не тот девайс, что маловероятно (например, plughw:0,0).
Второй — у вас нестыковочка с пакетами и вам придется кое-что переставить.
Общие рекомендации можно прочитать тут.
Как исправить такую ошибку:
Способ первый
Способ второй
Второй способ более сложный и нужен, если все-таки хотим оставить pulse.
Теперь сделаем процесс распознавания более структурированным, а именно: опишем как должны строиться предложения (каков должен быть порядок слов в предложении). Это необходимо для того, чтобы увеличить процент удачных распознанных фраз, облегчить работу программе и не ждать ненужных слов на входе. Чтобы распознавшие происходило в строгой определенной заранее последовательности, а все, что будет произноситься не по заданному порядку, было бы откинуто.
Для малого количества слов и заранее продуманной структуры предложений можно использовать JavaScript Grammar File вместо статической языковой модели.
Прочитать о JavaScript Grammar File можно тут.
Создаем файл с грамматикой pi.gram:
1
|
vim pi.gram
|
1
2
3
|
#JSGF V1.0;
grammar PI;
public <cmd> = ( PLUG ) ( ACTIVE | DISABLE );
|
Круглые скобки говорят о том, что объект и действие обязательны в произносимой фразе, а знак вертикальной черты — возможность выбора между элементами в скобках.
Файл с грамматикой предложений создан. Теперь запустим pocketsphinx_continuous с опцией, указывающей на грамматическую конструкцию в pi.gram
1
|
pocketsphinx_continuous -adcdev plughw:1,0 -jsgf /root/pi.gram -dict /root/6797.dic -inmic yes
|
Видим, что распознавание стало происходить только в определенном порядке. Остальные варианты, не описанные в pi.gram, отбрасываются, поэтому говорить теперь стоит несколько четче.
Распознавание речи в Pocketsphinx на русском
Теперь давайте займемся «русификацией» нашего детища. Для этого нам потребуется скачать русскую фонетическую модель, создать новый словарь и прописать грамматику произносимых фраз на русском. Поехали.
Качаем фонетическую модель от sourceforge вот с этого ресурса.
Архив zero_ru_cont_8k_v3.tar.gz переносим к себе в домашнюю директорию пользователя pi через winscp (если у вас Windows) или через scp (если мак/линукс).
Дальше переместим архив в домашнюю директорию root (конечно, это не обязательное условие, вы можете держать все файлы где хотите, просто для наглядности мы помещаем все в одно место, чтобы не надо было далеко ходить).
1
|
# mv /home/pi/zero_ru_cont_8k_v3.tar.gz /root/zero_ru_cont_8k_v3.tar.gz
|
Разархивируем, скопированный архив:
1
2
|
# cd ~/
# tar -zxvf ./zero_ru_cont_8k_v3.tar.gz
|
Скачиваем небольшую программу которая будет генерировать словари для русской фонетической модели. Это аналог того, что вы делали через браузер. Для этого склонируем репозитрий из GitHub. git должен быть установлен в малине. если нет — apt-get install git:
1
2
|
# cd ~/
# git clone https://github.com/zamiron/ru4sphinx/ ru4sphinx
|
Создаем в Raspberry файл со всеми необходимыми русскими словами (каждое слово с новой строки):
1
|
# vim raw_rus_dict
|
1
2
3
4
5
6
7
8
|
Зинаида Александровна
Зиночка
включи
погаси
показывай
розетку
лампу
отчет
|
Теперь генерируем словарь с помощью скачанного скрипта, указывая в параметрах путь до файла с нашими словами и путь для записи результирующего файла:
1
|
# /root/ru4sphinx/text2dict/dict2transcript.pl raw_rus_dict rus_pi_dict
|
После отработки программы проверим сгенерированный словарь:
1
|
# cat rus_pi_dict
|
1
2
3
4
5
6
7
8
9
|
Александровна a ll i k s ay n d r ay v n ay
Зинаида z y n a ii d ay
Зиночка z y n a ch k aa
включи f k ll uj ch ii
лампу l aa m p u
отчет a ch jo t
погаси p ay g a ss ii
показывай p a k aa z y v ay j
розетку r a zz je t k u
|
Добавим немного последовательности для произносимых фраз:
1
|
# vim rus_pi.gram
|
1
2
3
4
5
6
|
#JSGF V1.0;
grammar PI;
public <cmd> = <name> <obj> <action>;
<name> = [ Зинаида Александровна | Зиночка];
<obj> = ( лампу | розетку | отчет);
<action> = ( погаси | включи | показывай);
|
В квадратных скобках мы указываем необязательное условие, в круглых — обязательное.
Готово. Запускаем pocketsphinx_continuous аналогично примеру на английском. Дополнительно необходимо указать русскую акустическую модель:
1
|
# pocketsphinx_continuous -adcdev plughw:1,0 -hmm /root/zero_ru_cont_8k_v3/zero_ru.cd_semi_4000/ -jsgf /root/rus_pi.gram -dict /root/rus_pi_dict -inmic yes -logfn /dev/null
|
Также в этой команде мы убрали подробное логирование, чтобы видеть только итоговый результат распознавания. Результат команды будет примерно такой:
1
2
3
4
|
лампу включи
Зиночка лампу включи
Зиночка лампу погаси
Зинаида Александровна отчет показывай
|
Вы можете увидеть самостоятельно, что «Зиночка» можно произносить, а можно и опустить, так как данная опция стала произвольной. Но для итоговой конфигурации я рекомендую выбрать какое-то одно имя и сделать его принудительным в ожидаемой фразе (указать круглые скобки в грамматике JSGF), дабы уменьшить число возможных ложных срабатываний.
Дополнительно в файле JSGF можно использовать более структурированные конструкции следующего вида:
1
2
3
4
5
|
public <cmd> = <a> <b> <c>..;
<a> = <d> <e>;
<d> = [x1|x2|..];
<e> = (y1|y2|..);
…
|
Такие грамматические обороты могут в конечном итоге помочь отсеять лишние не ожидаемые варианты распознания.
Подключим управляемое реле 220v к Raspberry.
Как было сказано выше, хотелось бы увидеть результат на чем-то осязаемом. Для индикации результата я выбрал обычное коммутируемое реле 220v c 3-5-тивольтовой логикой управления. Как подключить такое реле, вы можете узнать, прочитав подробную статью.
Совсем необязательно подключать на него все 220 вольт, можно использовать цепи с меньшим вольтажом. Для экспериментов вы можете подключить фонарик/диод или все, что дает какую-то индикацию на такое реле, разорвав через него цепь питания. Но статья не об этом. Ниже я напишу два питоновскох скрипта, которые будут включать и выключать такое реле. Сигнальный провод от реле подключен на 17Pin(BCM).
Включение:
1
|
# vim /home/pi/relay_on.py
|
1
2
3
4
5
6
7
8
9
10
11
12
|
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#project: paradox-sec.ru
import RPi.GPIO as GPIO
pin_number = 17
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(pin_number, GPIO.OUT) #устанавливаем пин на выходной сигнал
GPIO.output(pin_number, GPIO.HIGH) #ставим логическую еденицу на выходе
print «включила реле»
|
Делаем сценарий исполняемым:
1
|
# chmod +x /home/pi/relay_on.py
|
Выключение:
1
|
# vim /home/pi/relay_off.py
|
1
2
3
4
5
6
7
8
9
10
11
12
|
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#project: paradox-sec.ru
import RPi.GPIO as GPIO
pin_number = 17
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(pin_number, GPIO.OUT) #устанавливаем пин на выходной сигнал
GPIO.output(pin_number, GPIO.LOW)
print «отключила реле»
|
И не забываем про бит исполнения:
1
|
# chmod +x /home/pi/relay_off.py
|
Преобразование результата распознания PocketSphinx в команды
Теперь самое интересное. Как полученный результат распознавания переводить в системные команды?
На гитхабе есть различные готовые варианты программ различных конструкций. Но не все варианты могут работать так как вы хотите 🙂
Покажу простейший вариант на питоне, который можно будет наращивать самостоятельно с помощью новых блоков elif:
1
|
# vim recognizer.py
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#project: paradox-sec.ru
import subprocess
import time
exe = »’pocketsphinx_continuous -adcdev plughw:1,0 -hmm /root/zero_ru_cont_8k_v3/zero_ru.cd_semi_4000/ -jsgf /root/rus_pi.gram -dict /root/rus_pi_dict -inmic yes -logfn /dev/null»’
p = subprocess.Popen([«%s» % exe], shell=True, stdout=subprocess.PIPE)
while True:
retcode = p.returncode
line = p.stdout.readline()
if line == «отчет показывайn»:
print line
subprocess.call(‘uname -a’, shell=True)
subprocess.call(‘uptime’, shell=True)
elif line == «Зиночка лампу включиn»:
print line
subprocess.call(‘/home/pi/relay_on.py’, shell=True)
elif line == «Зиночка лампу погасиn»:
print line
subprocess.call(‘/home/pi/relay_off.py’, shell=True)
# можете продолжать дальше в том же духе с новыми блоками elif
else:
print line
time.sleep(0.15)
if(retcode is not None):
break
|
Разрешаем исполнение сценария:
1
|
# chmod +x /root/recognizer.py
|
Можем запускать и радоваться проделанной работе. Распознавание наглядно отрабатывает. Родилась интернет-вещь:
1
|
# /root/recognizer.py
|
Для удобства работы, в дальнейшем можете добавить сценарий в автозагрузку. Для этого нужно дописать в файл /etc/rc.local строчку /root/recognizer.py & выше строки exit 0
Оптимизация или работа напильником
Если вас не очень устраивает качество распознавания «из коробки», выход есть. Правда, он может быть для вас не очень простым. Но, так или иначе, процентов на десять точно можно улучшить коробочные результаты. Какие для этого есть варианты?
-
- Самый простой вариант. Запустите pocketsphix_continous без параметров и вы увидите длинный список возможных входных аргументов:
1
|
root@raspberrypi:~# pocketsphinx_continuous
|
Определенно стоит поиграть с параметрами пауз, определением шума, чувствительностью и т.д.
- Можно попытаться произвести адаптацию звуковой модели на основе своих надиктованных данных. Частично можно прочитать об этом тут, а также google вам в помощь.
Заключение
На мой взгляд, описанная связка гораздо удобнее и круче всяких выключений света по хлопку и прочих подобных решений. Это вообще другой уровень инженерной мысли. Вы можете составлять произвольные словари, в которых будет все необходимое. Мой опыт показывает, что для небольших домашних проектов хватает словаря с 20-30 элементами, перечисления всех операций над ними, а также необходимых числительных. Такой объем информации pocketsphinx «на ура» отрабатывает практически на любом одноплатнике. Как быть с USB микрофонами в реальных проектах? Из опыта: можно удлинить стандартный полутораметровый юсб шнур с помощью усилителей или несколько нарастить через преобразования usb-ethernet. Примерно 10 метров кабеля между микрофоном и малиной обычно хватает для удобного размещения и первого и второго девайсов.
Всем упорства, упорства и еще раз терпения. Подписывайтесь на блог в правой верхней части экрана. Полезной информации для дальнейших публикации еще очень много 🙂
умный дом приложение