Оглавление.Введение. Общие вопросы компьютерного распознавания и порождения речи.1. Программирование звука в Windows. 2. Основы цифровой обработки звуковых сигналов. 3. Определение параметров речевого сигнала. 4. Алгоритмы распознавания. 5. Использование Microsoft Speech API 5.1 для синтеза и распознавания речи. 6. Использование Microsoft Speech API 4.0 для синтеза речи. Ссылки. Об авторе. |
Компьютерное распознавание и порождение речиГлава 5. Использование Microsoft Speech API 5.1 для синтеза и распознавания речиБольшинство коммерческих приложений для Windows используют для синтеза и распознавания речи Microsoft Speech Application Interface (SAPI) – интерфейс программирования речи от «Майкрософт». Сначала будем рассматривать версию 5.1 как последнюю на момент написания настоящего текста. SAPI существенно снижает количество кода, который нужно написать для использования приложения, использующих распознавание и синтез текста, делая речевые технологии более приемлемыми и устойчивыми для решения широкого круга задач. SAPI обеспечивает высокоуровневый интерфейс между приложением и голосовым движком. Два основных вида SAPI - движков это движки синтеза (TTS – Text-to-Speech) и Распознавания речи. TTS системы синтезируют из текстовых строк или файлов звуки синтетической речи. Распознавание речи преобразует человеческую речь в читабельные текстовые строки или файлы.
Приложения могут управлять TTS, используя COM-интерфейс ISpVoice. Для незнакомых с понятием COM-интерфейса советуем представлять его просто как некий объект, имеющий определенный набор методов-функций. После того как приложение создало объект ISpVoice, ему требуется только вызвать ISpVoice::Speak (то есть функцию Speak объекта типа ISpVoice), чтобы сгенерировать звуковой вывод из текстовых данных. Кроме того интерфейс ISpVoice также предоставляет ряд методов для изменения свойств голоса и синтеза, такие как частота(скорость) речи ISpVoice::SetRate, громкость вывода ISpVoice::SetVolume, и изменения текущего голоса диктора ISpVoice::SetVoice. Специальные элементы управления SAPI также могут быть добавлены в приложения, что позволяет изменять в реальном времени такие свойства синтеза, как голос, высота основного тона, ударение в словах, скорость речи и ее громкость. ISpVoice::Speak может работать как в синхронном режиме (возвращает управление, когда процесс синтеза закончится) или в асинхронном режиме (управление возвращается немедленно, а речь воспроизводится в фоновом режиме). Когда применяется асинхронный режим (SPF_ASYNC), информация о текущем состоянии, такие как состояние воспроизведения и текущее положение в тексте, могут быть получены посредством ISpVoice::GetStatus. Также в этом режиме новый текст может начать проговариваться или немедленно, прерывая текущее воспроизведение (SPF_PURGEBEFORESPEAK), или автоматическим добавлением нового текста в конец текущего воспроизведения. Дополнительно к интерфейсу ISpVoice SAPI содержит множество других интерфейсов и утилит для более продвинутых TTS-приложений. SAPI взаимодействует с приложениями посредством посылки событий, используя стандартный механизм обратного вызова (оконное сообщение, процедура обратного вызова или событие). Для TTS события используются наиболее часто, чтобы синхронизировать воспроизводимую речь. Приложения могут синхронизировать действия в реальном времени по мере того как они совершаются, например по границам слов, границам фонем или по границам пользовательских закладок. Приложения могут инициализировать и управлять этими событиями реального времени посредством интерфейсов ISpNotifySource, ISpNotifySink, ISpNotifyTranslator, ISpEventSink, ISpEventSource и ISpNotifyCallback. Приложения могут реализовывать пользовательское произношение для движков синтеза речи, используя методы предоставляемые интерфейсами ISpContainerLexicon, ISpLexicon и ISPhoneConverter. Поиск и выбор речевых данных, таких как голосовые файлы и словари произношений может быть осуществлен посредством следующих COM-интерфейсов: ISpDataKey, ISpRegDataKey, ISpObjectTokenInit, ISpObjectTokenCategory, ISpObjectToken, IEnumSpObjectTokens, ISpObjectWithToken, ISpResourceManager и ISpTask. И, наконец, существуют интерфейсы для использования различных средств вывода звука, таких как телефон и пользовательское оборудование: ISpAudio, ISpMMSysAudio, ISpStream, ISpStreamFotmat, ISpStreamFotmatConverter. Так же, как ISpVoice является главным интерфесом для синтеза речи, ISpRecoContext является главным интерфейсом для распознавания речи. Так же, как и ISpVoice, это интерфейс ISpEventSource, что означает, что это движок для получения извещений о необходимых событиях распознавания речи. Приложение может выбрать между двумя различными видами движков распознавания речи (ISpRecognizer). Разделяемый распознаватель, который может совместно использоваться различными приложениями, рекомендуется для большинства приложений. Чтобы создать ISpRecoContext для разделяемого ISpRecognizer, приложение должно всего лишь вызвать CoCreateInstance с параметром CLSID_SpSharedRecoContext. В этом случае SAPI установит входящий звуковой поток, привязав к нему установки по умолчанию для входящих звуковых потоков. Для больших серверных приложений более подходящим решением может быть использование движка распознавания типа InProc. Чтобы создать ISpRecoContext для ISpRecognizer типа InProc, нужно сначала вызвать CoCreateInstance с параметром CLSID_SpInprocRecoInstance, чтобы создать свой собственный ISpRecognizer. Затем приложение должно вызвать ISpRecognizer::SetInput, чтобы установить аудиовход. Наконец, приложение может вызвать ISpRecognizer::CreateRecoContext , чтобы получить ISpRecoContext. Следующий шаг – установка извещений для событий, необходимых приложению. Поскольку ISpRecognizer еще и ISpEventSource, который может быть преобразован в ISpNotifySource, приложение может вызвать один из методов интерфейса ISpNotifySource из своего ISpRecoContext, чтобы показать, где должны быть события для этого ISpRecoContext. Затем должен быть вызван ISpEventSource::SetInterest, чтобы показать, о каких событиях необходимо извещение. Самым важным событием является SPEI_RECOGNITION, которое показывает, что ISpRecognizer распознал некий текст для данного ISpRecoContext. Наконец, приложение может создать, загрузить и активировать интерфейс ISpRecoGrammar, который в основном показывает, какой тип высказывания был распознан – диктуемый текст или команда, и провести контроль грамматики. Сначала приложение создает ISpRecoGrammar, используя ISpRecoContext::CreateGrammar. Затем Приложение загружает грамматику, или посредством вызова ISpRecoContext::LoadDictation для диктуемого текста или ISpRecoContext::LoadCmdxxx для команд. Наконец, для того, чтобы активировать эту грамматику одновременно с началом распознавания, приложение вызывает ISpRecoGrammar::SetDictationState для диктуемого текста или ISpRecoGrammar::SetRuleState или ISpRecoGrammar::SetRuleIdState для команд. Когда распознавание передаст управление приложению посредством заданного механизма извещений, свойство lParam структуры SPEVENT будет содержать ISpRecoResult, посредством которого приложение может определить, что было распознано. ISpRecognizer, как InProc, так и разделяемый, может иметь множество ISpRecoContext, связанных с ним, и для каждого может быть создана своя структура событий и извещений. ISpRecoContext может иметь множество ISpRecoGrammar созданных из него, каждое для распознавания различных типов высказываний. 5.1. Синтез речи в Microsoft Speech API 5.1Рассмотрим очень простой пример синтеза речи для консольного приложения на С++. Пример построен на простейшей ( и не слишком функциональной) COM оболочке и занимается тем, что произносит некое предложение на английском языке. Свободно распространяемого русскоязычного движка для SAPI 5.1 в момент написания этих строк не существует, хотя спецификация SAPI 5.1 позволяет его разработать самому и подключить. Но об этом, если получится, дальше. Для начала SAPI надо установить. Его можно свободно скачать по http://www.microsoft.com/downloads в том числе вместе с SDK (набором электронной документации, дополнительных утилит и примеров программ). Теперь создадим консольное приложение и добавим пути к файлам SAPI.lib и SAPI.h. В Visual C++6.0, например, делается так. Выбираем пункт меню "Tools"("Инструменты"), затем - "Options" ("Опции"). В появившемся диалоговом окне переходим на вкладку <Каталоги>, затем в выпадающем списке выбираем <Включить файлы> и добавляем C:\PROGRAM FILES\MICROSOFT SPEECH SDK 5.1\INCLUDE в список каталогов (здесь подразумевается, что SAPI.h после установки находится в этой папке). Затем в том же выпадающем списке выбираем "Библиотечные файлы" и добавляем путь к SAPI.lib (например, C:\PROGRAM FILES\MICROSOFT SPEECH SDK 5.1\LIB\I386). После этого добавляем имя библиотеки SAPI.lib в параметры компоновщика. Для этого выбираем меню "Project"("Проект") , далее "Settings"("Установки"). В появившемся диалоговом окне выбираем вклудку "Links"("Зависимости"). В выпадающем списке выбираем "Главные", после чего в "Object / library modules"("Объект / модули библиотеки") добавляем после пробела строку “SAPI.lib” без кавычек. Теперь текст программы.
Если эту программу откомпилировать и запустить, она обязательно заговорит. Теперь о том, как она работает. Сначала приложение выполняет необходимые установки и подключает необходимые заголовочные файлы. SAPI – это приложение, основанное на модели COM, поэтому COM должен быть инициализирован до начала использования SAPI. Для этого используется оператор ::CoInitialize(NULL). В конце программы оператором ::CoUninitialize() COM освобождается. После того как COM запущена, следующий шаг – создание голоса. Голос – это просто COM-объект. SAPI использует так называемые интеллектуальные умолчания. Во время инициализации объекта, SAPI присваивает большинство значений автоматически, так что объект может быть использован немедленно после инициализации. Это является важным усовершенствованием по сравнению с прежними версиями SAPI. Умолчания извлекаются из установок речи (значок «Речь») в «Панели управления» Windows и содержат, например, такую информацию, как голос (если более чем один голос доступен в Вашей системе) и язык. В то время как одни умолчания очевидны, с другими не все так понятно ( частота речи, акценты и т.д.). Однако все установки могут быть изменены как программным путем, так и в установках речи в “Панели управления”. Непосредственное произнесение фразы осуществляется вызовом функции Speak. Голоса могут быть модифицированы различными методами. Самый прямой путь – посылка команд в формате XML непосредственно в поток. В нашем примере мы снижаем на 10 относительных единиц уровень акцента в голосе. Когда экземпляр голоса больше не нужен, необходимо освободить объект. Установка pVoice в NULL не обязательна, но полезна для обнаружения ошибок и просто явдяется признаком хорошего стиля программирования.
Теперь рассмотрим аналогичный пример, использующий графический интерфейс Windows. Создаем проект приложения Windows 32. Добавим пути к файлам SAPI.lib и SAPI.h в параметры студии и имя библиотеки SAPI.lib в параметры компоновщика так же, как мы делали это в предыдущем примере. Чтобы приложение начало говорить по требованию, необходимо внести одно изменение – оно касается механизма инициализации речи. Чтобы использовать приведенный ниже пример в Visual C++, пользователь должен добавить в меню <Файл> элемент <Говорить> с ID ресурса IDM_SPEAK. Код, управляемый этим пунктом меню будет ниже в приведенном примере. Скомпилируйте и запустите приложение, чтобы убедиться, что все работает.
Шаг 1. Инициализация COM.
Как в любом SAPI-приложении, COM должна быть успешно инициализирована. Это делается простым способом, приведенным ниже, который является куском функции WinMain(). Единственное ограничение состоит в том, что COM должна быть доступна до того, как специфический код SAPI начнет свою работу и должна быть активна все время, пока SAPI используется. Поскольку SAPI выполняется в InitInstance(), COM-операторы идут до InitInstance() и после окончания цикла событий обязательно закрывают инициализацию и цикл сообщений.
Теперь, когда COM работает, самое время создать голос. Просто объявите объект и вызовите CoCreateInstance(). Как было сказано выше, SAPI использует «интеллектуальные умолчания». Это требует минимального количества действий при инициализации и Вы можете использовать голос немедленно. Наш пример имеет ряд особенностей, примененных ради краткости и удобства. Во-первых, он использует InitInstance(), чтобы инициализировать голос. InitInstance() – это наименее нудный путь, каким вызов может быть осуществлен. Приложения, особенно те, которые используют объекты распознавания речи, обычно имеют свои собственные процедуры специально для такого случая, но в нашем случае код более универсален. Во-вторых, голос определен глобально. Возможно, в зависимости от дизайна и требований Вашего приложения, Вы бы хотели избавиться от глобальных определений, что же , Ваше право … В третьих, объект и память немедленно освобождаются. Очевидно, если голос требуется использовать далее, его не следует освобождать преждевременно. Фактически, даже в этом приложение мы не собираемся сохранять эти операторы надолго. И, наконец, если инициализация завершилась ошибкой, приложение останавливается. Самые устойчивые приложения должны были бы проверить ошибки более тщательно и выдать отчет с более детальной информацией.
Шаг 3. Говорим.
К счастью, самая интересная часть также и самая простая. Код, проговаривающий предложение, умещается на одной строке. Текст, который должен будет проговариваться, передается как параметр. Источник текста зависит от приложения. Строка обычно получается из диалогового окна или из файла. Другой путь состоит в том, что строка может быть получена из потока, но тогда требуется другой вид вызова – IspVoice::SpeakStream. Пример использует простое, жестко заданное предложение. В нашем случае ::Speak используется так: Speak(L”I am glad to see you”, SPF_ASYNC, NULL); Строка должна использоваться несколько раз во время работы приложения. Приложение находит каждое слово и разбирает каждое по отдельности. Для этой цели строка копируется в глобальную переменную перед тем, как используется.
Шаг 4. Установка событий.
Как и в большинстве приложений Windows, взаимодействие в SAPI между компонентами организуется посредством посылки сообщений между ними. По мере того как информация обрабатывается посредством TTS-движка или распознающего движка, конкретные действия либо начинаются либо заканчиваются. Многие из подобных действий SAPI или движка могут интересовать приложение. Например, приложению может быть необходимо определить, когда процесс распознавания начался, так чтобы своевременно информировать об этом пользователя. Также приложению может быть интересно знать, когда уже нет информации для обработки, например, чтобы информировать в этом случае пользователя или даже остановить или движок или само приложение, когда это будет безопасно сделать. Приложение создает информацию о таких действиях в два этапа. На первом оно получает главное сообщение от SAPI или SAPI –движка. Это сообщение сходно с другими, такими как события окна, щелчки мыши или куча других сообщений, используемых операционной системой. Поскольку это сообщение не определено операционной системой, приложение должно определить его. Однако все действия SAPI используют то же сообщение. Чтобы определить, какое в точности событие имеет место , SAPI обеспечивает дополнительную информацию, которую называют «интересом». Второй этап начинается после того, как сообщение поймано. Приложение проверяет структуру сообщения, заполненную SAPI и извлекает относящуюся к делу информацию. Итак, первый этап. Во время инициализации SAPI может быть проинформировано об интересах, которые должны возвращаться в программу. Это делается при помощи ISpEventSource::SetInterest. По умолчанию TTS не устанавливает никаких интересов, а распознаватель использует только извещение о распознании (SPEI_RECOGNITION). Поэтому, если SetInterest в программе пропущены, TTS не возвращает в приложение никакой информации, а распознаватель информирует только об успешном распознании. Значения могут комбинироваться при помощи оператора логического ИЛИ. Используя такую комбинацию, можно установить два и более интереса одновременно. Используя первый параметр, приложение может быть извещено о том, что определенный интерес появился. Второй параметр определяет интерес, который савится в очередь для дальнейшего извлечения. На втором этапе, не взирая на выполненную установку интересов, приложение должно присоединить сообщение к SAPI. Это делается посредством ISpNotifySource::SetNotifyWindowMessage. Если не включить вызов этой функции, никакие сообщения не будут возвращаться в приложение. Существует три типа извещений о событиях и как минимум одно из них должно быть включено для получения сообщений. Четвертый тип используется в многопоточных приложениях и не рассматривается здесь. Действительное имя сообщения и его ID определяются сообщением. Пример использует стандартное WM_USER для частных сообщений.
Шаг 5. Определение событий.
Как уже было сказано выше, работа с событиями состоит из двух этапов. Первый прост – он состоит в прикреплении к событиям Windows. Сообщение, сформированное любым образом посылается назад в приложение и цикл сообщений обрабатывает его. В нашем примере WndProc() получает сообщение WM_USER. Поймали сообщение – остальное работа SAPI. Второй этап состоит в том, чтобы определить, появился ли какой-нибудь интерес. В нашем случае метод SetInterest реагирует только на SPEI_WORD_BOUNDARY (граница слова). Какой интерес наступил SAPI определяет по состоянию структуры события типа SPEVENT при помощи метода GetEvents. Таким способом мы можем извлечь специальную информацию, включая тип интереса. Это значение поля eEventId структуры SPEVENT совпадает с параметрами, используемыми методом SetInterest. Структура SPEVENT должна быть инициализирована до первого использования и очищена перед использованием повторным. Это необходимо, так как информация сохраняется в структуре от вызова к вызову. Вспомогательная функция SpClearEvent очищает событие. Возможно, что события будут возникать быстрее, чем приложение будет обрабатывать их. Это обычная ситуация, особенно, если установлен интерес viseme , потому что он генерируется для каждого встречающегося звука. GetEvents может извлечь больше одного события одновременно. Это позволяет пакетную обработку, и более специализированные приложения именно так и поступают. Другой путь обработать эту ситуацию состоит в том, чтобы использовать цикл while, который извлекает события по одному. В нашем же случае достаточно просто сравнить тип интереса в eEventId с действием.
Шаг 6. Реакция на событие. С того момента, как интерес определен, программирование становится более стандартным. В нашем примере приложение определяет отдельные слова используя интерес SPEI_WORD_BOUNDARY. Когда бы этот интерес не возникал, движок SAPI находит определенное слово, обычно по пробелу между символами или по знаку пунктуации в тексте. Также в этом случае относящаяся к делу информация передается посредством Voice::GetStatus в структуру SPVOICESTATUS. Отдельное слово определяется как смещение внутри полной строки, отмечается при этом позиция первой и последней буквы последовательности. Чтобы продемонстрировать это, слова выводятся в панель сообщений на экране. Есть некоторая тонкость в том, чтобы выводить каждое слово так быстро как это возможно. Она состоит в том, что экран обновляется во время проговаривания текста. Эта характеристика управляется посредством флага SPF_ASYNC метода Voice::Speak: pVoice->Speak( theString, SPF_ASYNC, NULL); Альтернатива состоит в том, чтобы ждать пока речь не завершится и затем обрабатывать события и интересы. Например, если второй параметр заменен на NULL, панель сообщений будут все еще отображены на экране, но не будут ждать, пока речь закончится. Различие во времени выполнения может стать для некоторых приложений существенным фактором.
5.2. Распознавание речи в Microsoft Speech API 5.1Первый пример представляет базовые принципы распознавания речи. В последующихпримерах возможности SAPI в распознании будут рассматриваться все глубже и подробнее. Этот пример фокусируется в основном на основах приложений распознавания. Будут обсуждены следующие темы. - Инициализация. Установка движков и грамматик. - События: определение событий, извещений, расширенных событий. - Фразы: определение фраз, грамматических правил, принимающих фраз. После запуска приложения из первого примера должно появиться окно с надписью: «Добро пожаловать в книжный магазин SAPI. Назовите книгу!». Поскольку это простейшее приложение, единственный ваш выбор – сказать «Please go to counter» («Пожалуйста, подойдите к прилавку»). Впрочем, возможны вариации, такие как «eneter counter». В этом приложении даже «go to shop»(«идите в магазин») или «enter the store» («войдите в магазин») будут распознаны по причинам, объясняемым в дальнейшем. Возможно, программа ограничена в функциональности (вы на самом деле не можете даже купить книги в этом книжном магазине), но она представляет основы распознавания речи. На самом деле: команда произнесена, распознана и выполнена. Посмотрим в код программы. Он очень похож на простое приложение Windows, потому что, фактически, он им и является. Приложение выводит одно окно и рисует две строки на нем. Чтобы минимизировать код, оно даже не поддерживает операции с клавиатурой, мышью и меню. Чтобы выйти из программы, нужно просто нажать кнопку «Закрыть» в верхнем правом углу. Команды SAPI сочетаются в программе с командами Windows API. Если Вы не можете опознать команду, структуру или сообщение как принадлежащие к Win API, они, вероятно, принадлежат к SAPI или относятся к тому, в чем речевая обработка нуждается. К примеру сообщение WM_RECOEVENT, которое определено в приложении. Оно не относится к SAPI, но это сообщение необходимо приложению для распознания речи. Также, есть в программе определено несколько относящихся к тому же типу шаблонов (InitSAPI(), CleanupSAPI(),ProcessRecoEvent() и ExecuteCommand()). Внутри этих определенных в программе функций происходит вызов методов SAPI. Также есть несколько #define в верхней части файла, служащих для включения специальных заголовков SAPI 5. Заголовочные файлы. До того, как начать какое-либо кодирование, должны быть включены необходимые заголовочные файлы
Из пяти заголовков два являются специфическими для нашего приложения. Books.h содержит прототипы функций и глобальные переменные, а Common.h – список определений для окна приложения и другие свойства. По мере того, как примеры будут меняться и разрастаться, эти два файла будут обновляться. Однако, это не окажет никакого воздействия на речевые аспекты программы. Третий файл, Stdafx.h относится к программированию COM и он добавляется компилятором. Этот файл может также изменяться и не оказывает влияние на содержимое, относящееся к работе с речью. Еще два файла имеют прямое отношение к работе с речью. Sphelper.h предоставляется SAPI 5 и является заголовочным файлом вспомогательных функций. По программистскому соглашению он содержит список функций, объединяющих по несколько SAPI-методов в один вызов. Использовать вспомогательные функции не обязательно, но если все же использовать, процесс кодирования может быть значительно упрощен. В нашем примере используются вспомогательные функции там, где это только возможно. Наконец, компилятор грамматики использует Booksgram.h. Грамматика – это список слов, доступных приложению. Другими словами, словарь. Темы грамматики и компилятора будут обсуждены позднее. Чтобы SAPI 5 начал работать, необходимо его инициализировать. Это делается в четыре базовых шага, каждый из которых выполняется в случае успеха предыдущего. Вы должны всегда проверять возвращаемые значения, как и всегда при программировании в Windows. Инициализация. Шаг 1. COM. Во-первых, инициализируя COM, мы получаем уверенность, что она загружена и активна. Используем COM-команды CoInitialize() и, позднее, CoUninitialize(). Чтобы гарантировать активность COM в течении всего времени работы приложения, эти команды должны находиться соответственно до и после главного цикла обработки сообщений. // Продолжаем, только если COM успешно инициализирован
Шаг 2. Объект распознавателя. Во-вторых, создаем объект распознавателя. Этот объект обеспечивает доступ к движку распознавателя. //Глобальное определение
Объект распознавателя создан. Существуют две опции, чтобы определить этот объект как разделяемый или InProc-объект. Используются следующие глобальные идентификаторы классов, чтобы установить объект: CLSID_SpSharedRecoContext – создается объект разделяемого ресурса, CLSID_SpInprocRecognizer – создается неразделяемый объект типа InProc. Разделяемый объект позволяет ресурсам, таким как движок распознавателя, аудио-вход (микрофоны), и устройства вывода, использоваться несколькими приложениями одновременно. Это предпочтительный вариант для большинства программ. Обычно настольные системы имеют только один микрофон и, используя разделяемые объекты, различные приложения, такие как браузер, текстовый редактор и игра, могут использовать этот микрофон. Любое приложение, использующее разделяемый объект запускает серверный процесс SAPI. Это программа, запускаемая в фоновом режиме. Она доставляет события в собственное приложение. Объект InProc , наоборот, позволяет одному единственному приложению контролировать ресурс. Это относится к микрофонам и движкам распознавателя. Использование InProc-процедуры очень ограничивает возможности работы и допустимо только в особых обстоятельствах. Например, вы можете поступить так, если Вам требуется микрофон для ввода данных только одним приложением. Программы телефонии хороший пример необходимости использования ограничений в использовании одного микрофона или входа аудио-источника. Шаг 3. Контекст распознавания. В-третьих, создаем контекст распознавания для движка. //Глобальные определения
Контекст – это некое одиночное пространство приложения, необходимое для производства речи. В простейшем случае всему приложению сопоставляется только один контекст распознавания. Не имеет значения, где именно Вы находитесь в приложении, все речевые события и сообщения обрабатываются одной и той же процедурой. Однако каждая часть приложения может иметь свой собственный контекст. Отдельные окна, диалоговые панели, панели меню или даже отдельные пункты меню (такие как «Открыть» или «Печать») могут иметь свой собственный контекст. События и сообщения, сгенерированные из таких пространств обрабатываются своими собственными процедурами. Это похоже на то, как отдельные окна обрабатывают события сообщения в стандартных приложениях Win API. Каждому окну сопоставляется оконная процедура, которая и обрабатывает события и сообщения. Точно так же каждому контексту распознаванию сопоставляется отдельная процедура. В этом случае у Вас больший контроль над программой и обработкой речевых событий. Контексты создаются динамически в тот момент, когда они нужны и уничтожаются, когда необходимость исчезает. Однако Вы можете создать один контекст и сохранять его в течение всей работы приложения. Но наш первый пример будет иметь только один контекст. ISpRecoContext важный интерфейс и он будет первоначально использоваться для распознавании. Из интерфейса программа может загружать и выгружать словари, так же как получать и отсылать события. Шаг 4. Загрузка словарей и правил. Последняя важная часть последовательности установок – загрузка словаря. Словарь определяет, что распознаватель речи будет распознавать. // Загружаем наш словарь
Существует два типа словарей. Один для диктовки, другой для команд и управления. Словарь для диктовки представляет речь в более свободной форме. Вы можете записать большую часть основ слов для языка. Команды и управление значительно более ограничивают список слов. Для первого примера нам нужно всего несколько слов, чтобы зайти в магазин и заказать книги. Приложению нет никакой необходимости догадываться о существовании каких-либо других слов. Список, использованный в примере, предварительно создается и представляет из себя внешний по отношению к программе ресурс, содержащий набор команд. Используется формат XML. Расширенный язык разметки (XML) – это язык, используемый нами, чтобы создать словарь и формат, который по определению используется SAPI. Необходимо компилировать этот файл в бинарный вид, SAPI 5 позволяет это сделать. Можно сделать это заранее или в процессе. В нашем примере, он предварительно откомпилирован для того, чтобы словарь мог попасть внутрь программы. SAPI 5.1 SDK содержит компилятор словаря, названный GramComp, включенный в набор инструментов. ISpRecoContext, как было упомянуто выше, создает словарь. Сделав это однажды, Вы заполняете словарь словами из списка команд. Используем ::LoadCmdFromResource, так как они сохраняются, как ресурс приложения. Другой способ состоит в том, чтобы загрузить их из другого источника, например внешнего файла, памяти или существующего объекта. После того, как Вы извлекли словарь, Вам необходимо установить правила. Существует соглашение, что XML самостоятельно устанавливает начальный набор правил. В частности, это делает тэг TOPLEVEL=”ACTIVE” в нашем примере. Вот пример установки: // Устанавливая правила в активное состояние,
Два первых параметра NULL означают, что никакие правила не будут исключены, активированы будут все. Вы можете также сделать какие-либо правила неактивными, используя этот же метод. Теперь мы имеем дело с приложением, которое способно обрабатывать речевой ввод. Обработка речи происходит в фоновом режиме. Когда у SAPI есть информация, он возвращает ее в приложение. SAPI оповестит Вас, когда событие произойдет. Короче говоря, событие - это условие специального интереса для SAPI. Примерами событий могут быть начало первого звука в микрофоне (SPEI_SOUND_START), когда его окончание (SPEI_SOUND_END) или успешное окончание распознавания слова (SPEI_RECOGNITION). SAPI предоставляет несколько типов событий, перечисленных в типе SPEVENTENUM, содержащем полный список. Извещение показывает, что событие SAPI произошло и приложение может на него реагировать. Чтобы реагировать на извещение, приложение должно связать с ним отдельную процедуру. Есть несколько способов сделать это. Интефейс ISpNotifySource имеет четыре метода: SetNotifyCallbackFunction, SetNotifyCallbackInterface, SetNotifyWin32Event и SetNotifyWindowMessage. Еще есть ISpNotifySink::Notify, представляющий общий метод, позволяющий специальные и необычные условия. Вы можете использовать некоторые или все из этих методов в зависимости от своих нужд. Например, может быть легче в приложении управлять извещениями прямым вызовом функции (::SetNotifyCallbackFunction), например в случае вывода нового диалогового окна или автоматической записи деятельности в файл-журнал. Три первых метода (::SetNotifyCallbackFunction,::SetNotifyCallbackInterface и ::SetNotifyWindowMessage) требуют цикла обработки сообщений и поэтому могут использоваться только в Windows-приложениях. Вы можете использовать другие два (::SetNotifyWin32Event и ::ISpNotifySink::Notify) без цикла обработки сообщений. Первый пример посылает извещение в оконную процедуру. Поскольку SAPI-сообщения не являются сообщениями системного уровня, Вам необходимо сообщить программе о них прямо: hr = g_cpRecoCtxt->SetNotifyWindowMessage( hWnd, WM_RECOEVENT, 0, 0 );
Этот метод сопоставляет сообщение определенному окну. После его выполнения все события SAPI будут получены приложением в виде единственного сообщения WM_RECOEVENT, и затем посланы окну, имеющему дескриптор hWnd. Подробности как всегда содержатся в параметрах wParam и lParam.
Интерес – это флажок, позволяющий или уточняющий вид событий SAPI, которые должны возвращаться в программу. Существует более 30 различных видов событий. По умолчанию SAPI возвращает в программу все виды событий. Совершенно не обязательно знать обо всех видах событий. Для начала нас будет интересовать всего один тип: событие SPEI_RECOGNITION. Мы можем сообщить SAPI, чтобы он возвращал только один вид событий. Чтобы отфильтровать события, используем ::SetInterest. hr =g_cpRecoCtxt->SetInterest( SPFEI(SPEI_RECOGNITION), SPFEI(SPEI_RECOGNITION) );
Так мы установим интерес только к одному сообщению – SPEI_RECOGNITION. Только событие успешного распознания генерирует извещение. SAPI не будет извещать программу о каких-либо других событиях. Вы можете определить множество интересов, используя оператор ИЛИ. Устанавливаются два значения. Первый параметр содержит список всех интересов. Он определяет все возможные виды событий, которые Вас могут заинтересовать. Второй параметр содержит список событий, который должен быть поставлен в очередь, так чтобы приложение могло их обработать в должное время. Здесь нас интересует каждое появление SPEI_RECOGNITION, даже если они приходят так быстро, что программа не может их обработать за раз. Часто эти два параметра идентичны, но, иногда это не так. SPFEI() – это вспомогательная функция, используемая, чтобы преобразовать перечислимые события в число типа ULONGLONG. Что еще нужно приложению? Программа может инициализировать SAPI, получить речь с микрофона, попытаться распознать ее и возвратить событие в случае удачного распознания слова. Вам все еще нужно поместить сообщение в цикл обработки событий, для того, чтобы приложение обработало событие. Следующий фрагмент кода содержится в главной оконной процедуре WndProc: // Наше приложение определяет сообщение окна, чтобы дать нам знать,
Каждый раз, когда SAPI готов вернуть слово, он посылает нам событие. Программа получает это сообщение как WM_RECOEVENT. Главный цикл обработки сообщений выбирает его, и в этом случае посылает его на обработку в процедуру ProcessRecoEvent().
void ProcessRecoEvent( HWND hWnd )
Требуется три вещи, чтобы полностью описать событие. Первая – это CSpEvent. Это вспомогательный класс, который содержит несколько полезных функций и структуру SPEVENT. Один метод ::GetFrom делает две вещи одновременно. Он извлекает следующее событие из очереди и загружает соответствующую информацию в структуру SPEVENT, делая ее готовой для обработки. Вторая необходимая вещь – определить, какие SAPI-события в действительности имеют место. Для этой цели достаточно всего лишь посмотреть атрибут eEventId. Если это событие, которое нас не интересует, пропускаем его и продолжаем проверять другие сообщения. Последняя важная вещь – использование события для своих нужд. В нашем примере нас интеесует только SPEI_RECOGNITION. Если оно произошло, Вы ближе к своей цели поиска значения слова, которое было произнесено. Оператор switch сделает все остальное. Определение актуальных фраз – это последняя часть процесса. Инициализировав SAPI, получив заметку, что SAPI распознал слово и программа обрабатывает сообщение, Вы должны выделить это слово.
SAPI возвращает информацию о слове в списке или группе списков. Эти списки содержат не только слово, но и дополнительную информацию о слове, словах или всей фразе. Посмотрите следующий код: void ExecuteCommand(ISpPhrase *pPhrase, HWND hWnd)
Не зная слишком много о структуре фразы, мы все же можем предположить, что именно в нее и углубляется код. Функция содержит интерфейс ISpPhrase, выделяющий точно элемент фразы (в виде pElements) и определяет, какое правило было вызвано. Следующий пример сделает еще один шаг вперед, и выдаст в точности слово, которе было произнесено.
Чтобы лучше понять правила, нужно посмотреть в xml-файл (Books.xml).
Обратите внимание, что определены два правила. RULE ID описывает оба. Главное правило – VID_Navigator. Второе – VID_Place, но оно менее важное, так как является обслуживающим для VID_Navigator. В основном определены два типа фраз. Первый использует команды «Enter» и «Go To», второй использует «counter», «shop», «store». Распознаватель перемешивает и соединяет слова, выбирая первое из первого типа и вторе из второго типа. Поэтому предложения, такие как «Enter store» и «go to counter» вызывают правило SAPI. Есть также дополнительные слова, которые могут использовать и не использоваться. «Please enter the store» не только более вежливо, но и более грамотно. Однако распознаватель игнорирует «Please» и «the». Таким образом можно говорить более естественно и в то же время не обременять SAPI.
Однако с нашим примером далеко не уйдешь. Код останавливается на уровне правила, в операторе switch в варианте «case VID_Counter». Если Вы сказали «Please enter the store», SAPI вызывает соответствующее правило (VID_Navigator). Вот почему Вы могли сказать «counter», «shop» или «store» и все еще попасть в нужное место. Для контраста: если Вы скажете «Please enter the restaurant», никакое правило не будет запущено, потому что нет определения для слова «restaurant». В других примерах нас будет интересовать точное слово.
В любом случае, правило было вызвано и захвачено приложением. Теперь Вы можете обрабатывать его, как Вам угодно. Наш первый пример заинтересован только в обеспечении Вас необходимой текстовой информацией и поэтому он посылает сообщение посредством PostMessage() назад собственному окну с инструкцией изменить текст. На экране, к примеру, можно вывести сообщение при обработке WM_GETCOUNTER, например «Заказывайте!»
Второй пример расширяет возможности распознавания. Мы продвигаемся по магазину кофе и можем заказывать различные вида кофейных напитков. Предыдущий пример не очень устойчив. Мы ограничены всего пятью словами. Однако используя их мы можем войти в приложение. Использование ограниченного словаря в командах и функциях управления впрочем оправданно. Словарь может быть уподоблен, например, меню. Например, Вы хотели бы, чтобы программа отвечала или хотя бы пыталась ответить на конкретные слова, относящиеся к пунктам меню или подменю, таким как «Файл», «Открыть» или «Печать». Эти слова попадают в отобранный список. Если слово не найдено, оно не распознано. Итак, слова упорядочены в некотором порядке. В первом примере «Go to counter» определялось, а «Counter go to» - нет. Использование приблизительного словаря также называется основанной на правилах или контекстно-свободной грамматикой (словарем). Слово определяется согласно фиксированному множеству правил. Короче говоря, слов или в списке или нет. Не производятся попытки выразить слово в зависимости слов идущих до или после него. Это и означает, что нет контекста для слов.
SAPI использует расширенный язык разметки (XML), чтобы создать этот список. Файл может быть создан заранее или компилироваться во время исполнения программы. Поскольку команды и управление имеют дело, главным образом, со списками, слова могут добавляться автоматически и легко вмещать в себя новые ситуации.
Команды и управление очевидно кратковременны. Как упоминалось, они ограничены в использовании слов. Кто-то видит свой путь в ручном определении множества команд. Часто, Вам бы хотелось говорить несколько слов, так чтобы их распознали. Это то, что делают традиционные программы распознания речи. Вы можете диктовать несколько слов, не важно, на сколько они понятны, в текстовый процессор и увидеть слова переведенными в текст. Чтобы использовать движок распознавателя таким образом, Вы должны заменить словарь команд и управления на словарь диктовки. Вместо основанного на XML-словаря, словарь диктовки использует гораздо большее количество слов и определяет каждое слово, основываясь на контексте. Слова немедленно до и после этого запоминаются и словарь диктовки выбирает наиболее подходящий вариант выходного выражения. По этой причине, этот метод также называется статистической языковой моделью (SLM).
Движок распознавателя имеет большой размер словарей. Движок распознавателя Microsoft SAPI 5 включает 60000 английских слов и обеспечивает адекватное распознавание для большинства людей. Другие движки специализируются, например, на юридической или медицинской деятельности. Это могут быть массивные базы данных, разработанные коммерческими фирмами. В добавок, различные языки, включая японский, китайский, немецкий и русский также доступны.
Не смотря на то, какими различными выглядят эти языки и способы использования, SAPI 5 работает с ними одинаковым способом. Программирование же довольно просто. Мы же пока сфокусируемся на обработке команд и управления.
SAPI возвращает последние распознанные слова в нескольких структурах, совместно называемых фразами. Вы можете увидеть признаки этого процесса с помощью события SPEI_PHRASE_START, показывающего, что процесс распознавания начался. Для использования команд и управления есть два шага: определить активированное правило, и затем исследовать элементы (слова) внутри фразы.
Первый пример делал только первый шаг. Пока генерируется событие распознания, Вы узнаете, какое правило активировано, но останвливаетесь на этом. Второй пример содержит второй логический шаг – распознание конкретных слов, используемое для того, чтобы клиент получил-таки свои напитки.
Исследование находится в процедуре ExecuteCommand(). Как и в первом примере, один из параметров – это фраза. Помните, эта фраза – конечный результат, но скорее гипотетический. Предположите иеперь, что SAPI достаточно сообразителен, чтобы перевестив точности, что же Вы сказали. Вы зависите от этой фразы в вопросах перемещения. С этой точки зрения, Вас интересует только правило, которое было активировано. Это означает, что клиент все еще не может добраться до разных мест в магазине, даже если ему очень нужно. Все операторы передвижения всегда ведут к прилавку.
Наш новый пример предлагает еще одно грамматическое правило: VID_Books. Определенное в Books.xml, это правило содержит список всех напитков, которые можно заказывать. На самом деле есть несколько правил, собранных вместе. Но об этом позже.
Итак, мы добрались только до верхнего уровня правила VID_Books. Если вы закажете нечто, что соответствует этому правилу, правило активируется и SAPI возвратит результат. Типичным требованием может быть «Get me a new russian detective» («Дайте мне новый русский детектив»). Если заказ успешно распознан, приложение будет, используя метод GetPhrase() интерфейса ISpPhrase, преобразовывать оригинальную фразу в список элементов-слов. SPPHRASE *pElements;
Если метод отработал успешно, pElements содержит информацию, требуемую, чтобы сконструировать предложение. Чтобы определить, какое правило активировано и затем узнать больше о нем, обратимся к структуре SPHRASERULE, которая полно описывает правило. Идентификатор правила находится в атрибуте ulId. Приложение численно определяет правило VID_Books в XML-файле. Используем простой оператор switch, чтобы определить процедуры для более детальной обработки. Следует упомянуть еще о двух вещах. Во-первых, слова представляются числои раньше, чем строкой. Чтобы получить строку по номеру, используется таблица соответствий. Приложение сохраняет слова как ресурсы. Во-вторых, текущие слова формируются списком связи посредством каждого слова, представленного как элемент последовательности. Первый элемент- структура типа SPPHRASEPROPERTY, получаемая как pElements->pProperties и каждая структура подпоследовательности использует атрибут этой структуры pNextSibling. Прохождение по этой цепи – стандартная процедура списка связи. Чтобы начать работу с элементами, программа проходит по связям одной вершины до тех пока следующая вершина не станет равна NULL (что означает, что больше нет вершин to transverse) или если не она не посещалась ранее, для чего требуется как минимум число вершин равное MAX_ID_ARRAY. Кроме прохода по списку связей, приведенный выше код также сохраняет слова во внешний массив для последующей обработки. Это также полезно для сортировки. Запомните, не стоит беспокоиться о порядке слов. Однако, если Вы меняете порядок слов, Вам нужна возможность сортировать список извне. Чтобы показать пустые элементы массива, отметьте их нулем, используя вызов процедуры Win32 ZeroMemory(). Вы можете использовать другие методы, просто этот удобен для данного примера.
После прохода по списку, программа готова вывести вновь полученную информацию. Сообщение, посылаемое собственному окну (WM_BOOKSOORDER) показывает, что приложение произвело дополнитеную обработку. С этой точки зрения SAPI нам больше не нужен. Можно даже освободить объекты SAPI, хотя приложение и должно вручную освободить pElements, как оно вручную создало его. Даже при этом COM достаточно умна, чтобы удалить любые вершины в списке связи, связанные со списком. Оставшаяся часть обработки сводится к выводу информации на экран и его обновлению. Когда Вы заговорите снова, весь процесс повторится заново.
Как упоминалось выше, этот пример использует словарь команд и управления. Это конечный список слов, связанный с некоторым набором правил. Прграмма сохраняет этот список в двух формах. Файлы, основанные на XML позволяют Вам работать с этим списком. К сожалению, SAPI способен читать только двоичную или компилированную версию этого файла. Это конфигурация словаря, сохраняемая в файле с расширением .cfg . Идея о том, чтобы использовать CFG не только в смысле «конфигурация», но и в смысле «context-free grammar» («контекстно-свободная грамматика») была достаточно неглупой. Файлы словарей могли бы динамически генерироваться во время работы приложения. Если они обеспечиваются только xml-файлом, SAPI скомпилирует файл автоматически и будет использовать получившийся словарь. С другой стороны, словарь может быть также откомпилирован заранее. Это ограничит доступ к словарю, так что пользователи не смогут изменить его неожиданно. Это метод также быстрее для программы, поскольку не требуется времени на его компиляцию. SAPI предоставляет компилятор GramComp.
Вкратце коснемся структуры xml-файла. В нем определено несколько правил, но только два относятся к верхнему уровню: VID_Books и VID_Navigation. Это два наиболее важных для SAPI правила. Когда установлено соответствие правила, это - те идентификаторs, которые возвращаются в программу. Также обратите внимание на ExecuteCommand(). Имена двух операторов выбора совпадают с именами правил верхнего уровня.
Тэг TOPLEVEL внутри оператора RULE дает оператору его специальный статус: не только идентифицирует правило как правило верхнего уровня, но и устанавливает в активное состояние. Только правила верхнего уровня могут быть активированы ли деактивированы. SAPI распознает активные правила и не распознает неактивные. Приложение может изменить статус правила в процессе выполнения. Если правило больше не нужно, оно может быть деактивировано. Это позволяет включать или выключать их в зависимости от контекста распознавания. Например, если у Вас неактивно меню или пункт меню, SAPI не нужно пытаться распознать слова, ассоциированные с ним. Когда меню снова активировано, правило также снова может быть активировано.
Слова или фразы перечислены внутри правила. Они могут быть обязательными или необязательными. Необязательные слова не являются необходимыми для успешного сопоставления правила. SAPI добавляет их как возможность для диктора. «Please enter the shop» более естественно и приятно звучит по сравнению с командной формой этого же пожелания. Обязательные слова, конечно же, являются необходимыми. Однако Вы можете представить список альтернативных слов, одно из которых может быть использовано для того чтобы соответствие считалось полным. В случае VID_Navigation Вы можере сказать «enter» или «go to» - но не оба варианта одновременно.
Также Вы можете сослаться на другое правило, только не правило верхнего уровня. Продолжая пример с VID_Navigation, последняя часть правила состоит в том, чтобы правило VID_Place должно быть успешно сопоставлено. В VID_Place определены три альтернативы: «counter», «shop» и «store». Если Вы произнесете одно из этих трех слов, правило будет успешно сопоставлено. В случае успешного завершения всех требований, правило верхнего уровня VID_Navigation считается сопоставленным и событие SPEI_RECOGNITION возвращается назад в программу.
Первый кусок файла грамматики сопоставляет числовые значения отдельным элементам. SAPI не требует этого, хотя с помощью этого Вы можете, например, отсортировать слова. Сортировка основана на тэге «VAL=». Не забудьте для этого сохранить текущие найденные слова в массиве pulIds. |