Вы думаете, чтобы распознать человеческую речь нужен как минимум мощный процессор(>100МГц), большой объем ОЗУ и памяти? Arjo Chakravarty считает иначе. Не обязательно иметь мощный процессор и большой объем памяти, достаточно обойтись Arduino. Многие хотели реализовать такое на микроконтроллере. Arjo Chakravarty написал библиотеку uSpeech как раз для этого.
Распознавание происходит в реал-тайм и побуквенно. В основном, распознавание производят путем разложения звука на фонемы с использованием FFT(Быстрым преобразованием Фурье) и еще сложнейших математических вычислений, библиотека uSpeech избегает этого, используя иные алгоритмы.
Все что нам нужно — это подключить микрофон к аналоговому порту Arduino и произвести калибровку. Как пишет автор, к порту нужно подключить электретный микрофон с пред-усилителем. Из-за алгоритма, используемого в библиотеке, микрофон — очень важен, каждый его параметр может отразиться на распознавании. У меня не оказалось электретного микрофона, поэтому я использовал обычный с пред-усилителем на ОУ. Однако распознавание оказалось вообще никаким. Пред-усилитель работал, но на фоне был шум(хотя раньше повторял эту схему, шума не было) и «трещащий» звук. И поэтому решил собрать Hi-Fi пред-усилитель одного американца.
Правда вместо OPA2134, я использовал свой любимый К140УД6 🙂 Также вместо R7 — поставил переменный резистор, чтобы регулировать Коф. Усиления. Качество действительно хорошее, я бы сказал отличное.
Теперь перейдем к калибровке библиотеки. Скетч — Calibration.
Загружаем его, затем подключаем пред-усилитель к Arduino (в A0).
Запитываем всю нашу конструкцию и открываем Com порт.
Молчим… 1сек. После, смотрим на значения в порту — это уровень тишины. Если значений много, то нужно определить средний уровень тишины. К примеру — 12, это число нужно поставить к #define SILENCE, в файле uSpeech.h библиотеки.
Теперь можно посмотреть на распознавание фонем. Загрузим такой скетч:
1 2 3 4 5 6 7 8 9 10 11 | #include <uspeech.h> signal voice(A0); void setup(){ voice.calibrate(); Serial.begin(9600); } void loop(){ char v = voice.getPhoneme(); if (v != 'h') Serial.println(v); //h - по умолчанию - Шум, выводим все кроме h } |
Фонемы(англ.), звуки речи(не путать с буквами), — хранятся в файле phoneme.cpp Вот они: ‘s‘, ‘k‘, ‘z‘, ‘g‘, ‘v‘, ‘p‘, ‘e‘, ‘o‘, ‘i‘, ‘a‘. И у каждого есть свой параметр vowel, вычисляемый, грубо говоря, по «силе» определенной частоты и некоторым другим параметрам. А из них уже можно составить буквы.
Буква | Фонема |
a | a |
e | e |
i | i |
o | o |
r | r,l,m,n |
v | v, w |
p | p, b |
g | g |
z | z, f, ch, j, tr, th |
k | k,c,qu |
s | s, sh |
Это также сказывается на произношении, некоторые звуки мы не произносим | ||
«Stop» | ———> | ‘Sop’ |
Поняв принцип, можно русифицировать библиотеку. О фонемах можно почитать там и там. Теперь скетч ➡ . Открываем Com-порт и начинаем говорить/произносить различные слова(англ) с небольшим акцентом. «Курица! Oxygen! Вкл! Stop! » С первого раза у вас в ком порту будет всякая чепухня, нужно будет поработать со значением тишины. У меня при движении микрофона уже сыпались фонемы.
После изнурительных мучений, я подобрал значение SILENCE — 20 и нашел оптимальный коф.усиления. Хотя распознавание так и осталось кривоватым. Теперь сам скетч!
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | #include <uspeech.h> signal voice(A0); String collvoice; void left(){ voice.calibrate(); Serial.println("left"); } void stopp(){ voice.calibrate(); Serial.println("stop"); } void right(){ voice.calibrate(); Serial.println("right"); } void setup() { voice.calibrate(); delay(100); Serial.begin(9600); } void loop() { char phoneme = voice.getPhoneme(); if( phoneme != 'h'){ //Убираем "Шум" collvoice = denoise(phoneme,collvoice); //Еще раз убираем шум и передаем фонемы, объединяя их. Serial.println(phoneme); //(Можно убрать) Выводим фонемы. } else { //Очень хитрый аглоритм int i[3],j,min,x; i[0] = umatch(collvoice,"sop"); //stop i[1] = umatch(collvoice,"ez"); //left i[2] = umatch(collvoice,"i"); //right //find the lowest number while(j<0){ if(i[j]<min){ x = j; min = i[j]; } j++; } if(x == 0){ //Stop! stopp(); } if(x == 1){ //Left! left(); } if(x == 2){ //Right! right(); } } } |
Нужно загрузить скетч, запустить программу, затем открыть Com-порт, подождать 1 сек., сказать «ЛЭФТ«/»РАЙД«/»СТОП«. Если все настройки верны, и вы хорошо говорите по-английски, то шанс 80%, что распознается. Здесь можно заметить распознавание по фонемам: Stop -> sop, left -> ez, right -> i.
Из-за особенностей моего пред-усилителя, либо моей криворукости — у меня не получилось добиться полного распознавания. Я написал свою, программу распознавания по 3-м фонемам, которая управляет светодиодом.
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | #include <uspeech.h> signal voice(A0); char collvoice[3]; int ins = 0; char charOn[13], charOff[13]; void setup() { voice.calibrate(); delay(100); Serial.begin(9600); pinMode(13, OUTPUT); while(!Serial.available()) { char phoneme = voice.getPhoneme(); if (phoneme != 'h') Serial.println(phoneme); } siln = Serial.read(); Serial.println("Enter thee 'on' char..."); while(1) { if (Serial.available()>0) {charOn[ins] = Serial.read(); ins++;} if (ins>=3) {ins=0; break;} } Serial.println("Enter thee 'off' char..."); while(1) { if (Serial.available()>0) {charOff[ins] = Serial.read(); ins++;} if (ins>=3) {ins=0; break;} } Serial.println(charOn); Serial.println(charOff); Serial.println("Well...Done"); } void loop() { char phoneme = voice.getPhoneme(); if( phoneme != 'h') { collvoice[0] = phoneme; while(1) { phoneme = voice.getPhoneme(); if (phoneme != 'h') collvoice[1] = phoneme; break; } while(1) { phoneme = voice.getPhoneme(); if (phoneme != 'h') collvoice[2] = phoneme; break; } } if ( collvoice[0]==charOn[0] && collvoice[1]==charOn[1] && collvoice[2]==charOn[2] ) digitalWrite(13, HIGH); if ( collvoice[0]==charOff[0] && collvoice[1]==charOff[1] && collvoice[2]==charOff[2] ) digitalWrite(13, LOW); collvoice[0]=0; collvoice[1]=0; collvoice[2]=0; } |
Итак, после загрузки программы открываем Com-порт и молчим. Затем мы можем что-то говорить, а на экран будут выводится фонемы. Если отправить что-нибудь в Com-порт, программа спросит фонемы для ВКЛ светодиода на 13-ом пине. (вводить нужно 3 анг. фонемы) После, попросит ввести фонемы для ВЫКЛ светодиода. Теперь включается само распознавание.
Этот способ работает вполне удовлетворительно. Конечно, у автора, по идее, должно работать гораздо лучше, чем у меня. У него применяются различные фильтры, псевдо-шумоподавители в программе. Документацию к библиотеке можно почитать там ->/. Итоги…
Достоинства:
- Низкие требования к ЦПУ и ОЗУ
- Потоковое распознавание
- Малый размер библиотеки (~4.5кб)
Недостатки:
МикрофонозависимостьСпецифические требования к входному сигналу- Точность распознавания <90
Распознавание голоса на Arduino — великолепная идея, можно, к примеру, зажигать лампочки одним словом, включать моторы свистом и т.п.
Как только найду электретный микрофон и пред-усилитель для него — результат будет лучше… наверное…
Удачи! 🙂
PS: Отличную идею для улучшения распознавания предложил mihtm. (см. ниже).