Тема распознавания голоса микроконтроллером довольно интересна и нова, поэтому я решил представить вам схему устройства распознавания голоса на микроконтроллере, а точнее на Arduino. Распознавание голоса довольно непростая задача, а реализовать это на микроконтроллере еще сложнее, в силу ограниченности его ресурсов. В нашем случае реализация распознавания голоса будет на микроконтроллере ATmega328P, работающего на частоте 16МГц.

Распознавание голоса на Arduino

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

В моей схеме распознавания голоса на микроконтроллере была использована uSpeech в силу своей автономности и малых размеров. Хотя у неё есть недостаток, такой как ограниченность распознавания. Эта библиотека позволяет распознавать только фонемы, т.е. отдельные звуки, но для многих схем и устройств этого более чем достаточно. Ниже приведен список используемых фонем (звуков):

Фонема (звук) Соответствующая ей буква (может быть несколько)
“е” е
“х” х, ш, щ, дж, ж, з
“в” в, может срабатывать на з
“ф” ф
“с” с
“о” о, а, ш, л, м, н, у, ю
” “ слишком тихий звук

В качестве микрофона используется электретный микрофон (ссылка на статью на Wikipedia), обычно он выглядит так:

Электретный микрофон

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

Микрофонный усилитель на операционном усилителе (ОУ) LM358

Источник: electronics-lab.com

Либо можно купить готовый микрофон с усилителем на eBay или AliExpress, найти можно по запросу «Mic amplifier arduino» или «Микрофонный усилитель Arduino». Выглядит он так:

Микрофонный усилитель (готовый модуль)

Микрофон с микрофонным усилителем желательно подключить к микроконтроллеру через резистор 470…2К и разделительный конденсатор (он уже есть в самих схемах усилителей, а также на готовых платах), который убирает постоянную составляющую.

Схема подключения микрофона и усилителя к Arduino следующая: микрофон через усилитель подключается к аналоговому порту Ардуино A0, три светодиода через резисторы подключаются к цифровым выходам 5,6,7 (схему можно изменить, внеся соответствующие, небольшие правки в исходный код программы).

Схема подключения микрофона к Arduino для распознавания голоса

В качестве индикаторов распознанных команд были использованы три светодиода разных цветов.

В исходном примере библиотеки uSpeech сравнивались одиночные фонемы (звуки). Пример позволял распознать 6 фонем (звуков): «ф», «е», «о», «в», «с», «х» (f, e, o, v, s, h). Мной был использован массив байт, который содержал паттерны, распознаваемых слов, что позволило в конечном итоге распознавать не отдельные фонемы (звуки), а целые слова, состоящие из распознаваемых фонем. Массив полученных звуков сравнивается с заранее прописанным массивом байт (паттерном слова), и в случае совпадения, с учетом заданного порога чувствительности, делается вывод о том, какое слово было произнесено.
Например, заранее прописанные паттерны для английских слов green,orange и white были следующие  “vvvoeeeeeeeofff”, “hhhhhvoovvvvf”, “hooooooffffffff”. Для нахождения наиболее ближайшего эквивалента произносимом  слову необходимо находить минимальное редакционное расстояние  (ссылка Расстояние Левенштейна). Для повышения точности и игнорирования нерелевантных паттернов при распозновании использовалась константа LOWEST_COST_MAX_THREASHOLD, определяющая уровень достоверности. Подбирая её значение можно добиться высокой точности распознавания.

Исходный код программы, скетч для Arduino:

#include <uspeech.h>
#define ledGreen 7
#define ledOrange 6
#define ledWhite 5
#define MIN3(a, b, c) ((a) < (b) ? ((a) < (c) ? (a) : (c)) : ((b) < (c) ? (b) : (c)))
signal voice(A0);
const int BUFFER_MAX_PHONEMES=32;
char inputString[BUFFER_MAX_PHONEMES]; // Allocate some space for the string
byte index = 0; // Index into array; where to store the character
//
const int DICT_MAX_ELEMNTS=3;
char dict[DICT_MAX_ELEMNTS][BUFFER_MAX_PHONEMES]={“vvvoeeeeeeeofff”,”hhhhhvoovvvvf”,”hooooooffffffff”};
int LOWEST_COST_MAX_THREASHOLD=20;
void setup(){
/*
Phoneme      |   Literal
——————————————————————–
e            | The e sound.
——————————————————————–
h            | The `/sh/` sound. It can also be raised by
| `ch`, `j` and `z`
——————————————————————–
v            | The `v` sound, occasionally triggered by  , ‘z’
| (may need to be fixed)
——————————————————————–
f            | The `f` sound.
——————————————————————–
s            | The ‘s’ sound.
——————————————————————–
o            | ‘a’,’o’,’i’,’r’,’l’,’m’,’n’ and ‘u’ sounds.
——————————————————————–
‘ ‘          | Too Quiet for anything
——————————————————————–
*/
voice.f_enabled = true;
voice.minVolume = 1500;
voice.fconstant = 500;
voice.econstant = 2;
voice.aconstant = 4;
voice.vconstant = 6;
voice.shconstant = 10;
voice.calibrate();
Serial.begin(9600);
pinMode(ledGreen, OUTPUT);
pinMode(ledOrange, OUTPUT);
pinMode(ledWhite, OUTPUT);
}

void loop(){
voice.sample();
char p = voice.getPhoneme();
if(p==’ ‘ || index >= BUFFER_MAX_PHONEMES){
if(strLength(inputString)>0){
Serial.println(“received:”+String(inputString));
parseCommand(inputString);
inputString[0]=0;//clear string char array
index=0;
}
}else{
//printArray(voice.arr);
inputString[index] = p; // Store it
index++;
inputString[index] = ”; // Null terminate the string
}
}

char* guessWord(char* target){
int len = strlen(target);//held target length

for(int i=0;i<DICT_MAX_ELEMNTS;i++){
//simple validation
if(dict[i]==target){
return dict[i];
}
}

unsigned int cost[DICT_MAX_ELEMNTS];//held minimum distance cost

//calculating each words cost and hits
for(int j=0;j<DICT_MAX_ELEMNTS;j++){//loop through the dictionary
cost[j]=levenshtein(dict[j],target);
Serial.println(“dict[j]=”+String(dict[j])+” target=”+String(target)+” cost=”+String(cost[j]));
}

//Determining lowest cost but still all letters in the pattern hitting word
int lowestCostIndex=-1;
int lowestCost=LOWEST_COST_MAX_THREASHOLD;
for(int j=0;j<DICT_MAX_ELEMNTS;j++){//loop through the dictionary
//Serial.println(“dict[j]=”+dict[j]+” dict[j].length()=”+String(strlen(dict[j]))+” cost=”+String(cost[j])+” hits=”+String(hits[j])+” j=”+String(j));
if(cost[j]<lowestCost){
lowestCost = cost[j];
lowestCostIndex=j;
}
}

//Serial.println(“lowestCostIndex=”+String(lowestCostIndex)+” lowestCost=”+String(lowestCost));

if(lowestCostIndex>-1){
//Serial.println(“lowestCost=”+String(lowestCost)+” lowestCostIndex=”+String(lowestCostIndex));
return dict[lowestCostIndex];
}else{
return “”;
}
}

void parseCommand(char* str){
char *gWord = guessWord(str);
Serial.println(“guessed :”+String(gWord));
if(gWord==””){
return;
}else if(gWord==dict[0]){
digitalWrite(ledGreen, HIGH);
digitalWrite(ledOrange, LOW);
digitalWrite(ledWhite, LOW);
}else if(gWord==dict[1]){
digitalWrite(ledGreen, LOW);
digitalWrite(ledOrange, HIGH);
digitalWrite(ledWhite, LOW);
}else if(gWord==dict[2]){
digitalWrite(ledGreen, LOW);
digitalWrite(ledOrange, LOW);
digitalWrite(ledWhite, HIGH);
}
}

unsigned int levenshtein(char *s1, char *s2) {
unsigned int s1len, s2len, x, y, lastdiag, olddiag;
s1len = strlen(s1);
s2len = strlen(s2);
unsigned int column[s1len+1];
for (y = 1; y <= s1len; y++)
column[y] = y;
for (x = 1; x <= s2len; x++) {
column[0] = x;
for (y = 1, lastdiag = x-1; y <= s1len; y++) {
olddiag = column[y];
column[y] = MIN3(column[y] + 1, column[y-1] + 1, lastdiag + (s1[y-1] == s2[x-1] ? 0 : 1));
lastdiag = olddiag;
}
}
return(column[s1len]);
}

int strLength(char const* s) {
int i = 0;
while (s[i] != ” && s[i] != ‘ ‘)
++i;

return i;
}

Скомпилированный скетч занимает около 20% FLASH-памяти микроконтроллера и около 500 байт, т.е. 25% ОЗУ.
Библиотеку для распознавания голосовых команд на Ардуино – uSpeech можно скачать здесь (необходимо нажать зеленую кнопку “Clone or download”). Установка библиотеки стандартная – необходимо распаковать архив и поместить папку в “C:Users<Имя пользователя>DocumentsArduinolibraries”.

Демонстрация работы устройства:
https://www.youtube.com/watch?v=_zlD2lvWB7k

Скачать архив с исходником и библиотекой.

Напишите комментарий