Тема распознавания голоса микроконтроллером довольно интересна и нова, поэтому я решил представить вам схему устройства распознавания голоса на микроконтроллере, а точнее на 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] = '\0'; // 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] != '\0' && s[i] != ' ')
        ++i;
 
    return i;
}

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

Демонстрация работы устройства:


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

По материалам tiriboy.blogspot.ru

 

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


Защитный код
Обновить