FC2ブログ


VS2017-はじめの1/10歩(5):音声認識をプログラム制御に使う

   プログラミング [2019/02/25]
しゃべった内容が即文章データになる?

Windows10とかだと、何かの拍子に「何かご質問は?」的な催促をしてくることありますよね。
調子にのって、「Xxxxってどうやるの?」とか聞いてやると、
「何ですか?」とか二三度聞き返した挙句に頓珍漢なことを答えてくれちゃったりする。

#スマフォのサービスだとそんなことないけど。

オンラインのサービスの方が、そりゃ上等な認識をしてくれるのは当然かとは思うけど、
こんなに酷いんじゃねー。
初回の「御使用感」がさんざんなので、その後は間違って押しちゃって出てきても、
「頼んでねーよ」で直ぐに消えてもらいます。

そんな、ネットアクセスが不要な音声認識を何かに使えないか
なーと考えて、短いワードを数種類くらいで、それを検知したら、プログラムの動作制御に使う。
キーボード/マウス・タッチの代わりくらいに使えないかってことです。

やってみることにしました。
ついでに、外人がしゃべったらそれなりに。

●音声認識のセットアップ
 通常、Windowsに標準で組み込まれている。
 デフォルトのロケールに合わせた言語(認識)が入っている。

◆確認の仕方
 [コントロールパネル]-[音声認識]-左側メニュー「高度な音声オプション」をクリック
 「音声認識のプロパティ」ダイアログの「言語」欄に現在の認識エンジンが表示される。
 プルダウンを開くと選択できる認識エンジンが表示される。

◆追加する場合
 Windowsのエディションにより、簡単に追加できる場合と、結構な手順があるエディションがある。
 後者の場合は、問題があっても非標準扱いなので、ケアされない。
 ちなみに、HomeやProfessionalは後者。インストール方法などは、以下を参照願う。
 https://denspe.blog.fc2.com/blog-entry-168.html

※他言語の音声認識を同時に使用することは、ちょっと難しそう。
 [認識言語の切り替え→認識→コマンド解釈→プログラム制御]となる。
 最初の「どの言語に切替えるのか」を、結局何か発話/操作させて行うことにならざるをえない。
 よって、ここは現状の言語(認識)設定で、キーワードを認識させるのが安易な方法だろう。
 [カレントの認識→キーワード解釈→プログラム制御]



●音声(キーワード)認識プログラムを作成
・Microsoft Speech SDK5.1 とLangPackをダウンロードしてインストールし、
 SDKに含まれるサンプルコード「SimplDict」などを参考にする。

・注意事項
1)sapi.libがVC6.0のデバッグ版に対応していないので、デバッグビルドはできない。
2)LangPackをインストールした際、男性/中国語が追加され、32bit環境ではそちらがデフォルトになる。


では、コードの話です。
VS6では、Microsoft Speech SDKを追加しないとダメなはずです。
で、リンクしなくちゃいけないライブラリがVS6のデバッグ環境ではNGなので、
その条件では「無し」にするためにいちいち#ifdef-endifで囲みます。
あっ、「VS6なんか使うかよ」って方の場合は全く不要です。

ヘッダには以下を追加します。
//----------------------------------------------------------
#if _MSC_VER < 1300
#ifndef _DEBUG
#define MYSPEECH_VALID //音声認識関係処理有効
#endif
#else
#define MYSPEECH_VALID
#endif

#ifdef MYSPEECH_VALID
//sapi.libのDebug版はVC6-Debugビルドに未対応-コメントアウト
#include <sphelper.h>
#pragma comment(lib, "sapi.lib")
#define WM_RECOEVENT WM_APP //音声認識→WM_メッセージ
#define GID_DICTATION 0 //grammarID of dictation
#endif
//----------------------------------------------------------


で、以下のオブジェクトは、外部に宣言しておくのがいいでしょう。
//----------------------------------------------------------
#ifdef MYSPEECH_VALID
//音声認識系
CComPtr g_RecogContext; //音声認識コンテキスト
CComPtr g_DictGrammar; //書取り文法オブジェクト
//----------------------------------------------------------


以下の初期設定用と終了処理用のルーチン
//----------------------------------------------------------
//name :InitVoiceDict
//function :音声認識の初期化
//parameter :hWnd :[i]ウィンドウハンドル
//return :TRUE-成功、FALSE-失敗

BOOL InitVoiceDict(HWND hWnd)
{
HRESULT result = S_OK;
CComPtr cpRecogEngine;

//音声認識インスタンスの作成
result = cpRecogEngine.CoCreateInstance(CLSID_SpInprocRecognizer);
if(result == S_OK) {
//音声認識コンテキストの作成
result = cpRecogEngine->CreateRecoContext(&g_RecogContext);
}
if(result == S_OK) {
//音声認識からの通知メッセージの定義
result = g_RecogContext->SetNotifyWindowMessage(hWnd, WM_RECOEVENT, 0, 0);
}
if(result == S_OK) {
//通知イベントの設定
//SPEI_RECOGNITION:認識エンジンが音声をテキスト(最優先候補)に変換して
//リターンするときにイベントで通知するという意味らしい。
//SPEVENT.lParamは、ISpRecoResultへのポインタを含む・・・?
const ULONGLONG ullInterest = SPFEI(SPEI_RECOGNITION);
result = g_RecogContext->SetInterest(ullInterest, ullInterest);
}

//デフォルトの音声オブジェクト生成
CComPtr cpAudio;
result = SpCreateDefaultObjectFromCategoryId(SPCAT_AUDIOIN, &cpAudio);

//認識エンジンへの入力を設定
result = cpRecogEngine->SetInput(cpAudio, TRUE);
result = cpRecogEngine->SetRecoState(SPRST_ACTIVE);
if(result == S_OK) {
//Grammar(書取り文法?)コンテキストを生成
result = g_RecogContext->CreateGrammar(GID_DICTATION, &g_DictGrammar);
}
if(result == S_OK) {
//文法の読み込み
result = g_DictGrammar->LoadDictation(NULL, SPLO_STATIC);
}
if(result == S_OK) {
//文法のアクティベート
result = g_DictGrammar->SetDictationState(SPRS_ACTIVE);
}
if(result != S_OK) {
//失敗した場合は、解放
g_DictGrammar.Release();
g_RecogContext.Release();
}

return (result == S_OK);
}

//----------------------------------------------------------
//name :speech_proc_start
//function :音声制御開始
//parameter :hWnd :[i]ウィンドウハンドル
//return :TRUE-成功、FALSE-失敗

BOOL speech_proc_start(
HWND hWnd)
{
//音声認識の初期化
if (!InitVoiceDict(hWnd)) {
wsprintf(g_MsgBuf, "音声認識の初期化に失敗しました。rc=%d", GetLastError());
MessageBox(NULL, g_MsgBuf, "音声認識", MB_ICONERROR|MB_OK);
return FALSE;
}

return TRUE;
}

//----------------------------------------------------------
//name :speech_proc_stop
//function :音声制御終了
//parameter :hWnd :[i]ウィンドウハンドル
//return :なし

VOID speech_proc_stop(
HWND hWnd)
{
g_RecogContext.Release();
g_DictGrammar.Release();
return;
}
//----------------------------------------------------------


上記初期化の方を実行した後は、
マイクに向かって何か言うと、その区切りごとにWM_RECOEVENTが発生します。
そのイベント理由をさらに確認して処理するルーチンが以下になります。
//----------------------------------------------------------
//name :RecogEvent
//function :音声認識のイベント処理
//parameter :hWnd :[i]ウィンドウハンドル
//return :TRUE-メッセージ処理完了、FALSE-未完了

BOOL RecogEvent()
{
USES_CONVERSION;
CSpEvent event;
HRESULT result;

//音声認識イベント取得ループ
while (event.GetFrom(g_RecogContext) == S_OK) {
switch (event.eEventId) {
//※このイベントは来ない
case SPEI_SOUND_START:
//音声入力開始
//g_RecogStatus = IDS_INSOUND;
break;

case SPEI_RECOGNITION:
{ //dstrTextのスコープを限定的にするため
//g_RecogStatus = IDS_RECOG;
CSpDynamicString dstrText; //WCHAR型。セットされたテキスト領域も参照が解けると解放される?
result = event.RecoResult()->GetText(SP_GETWHOLEPHRASE, SP_GETWHOLEPHRASE, TRUE, &dstrText, NULL);
//認識テキスト取得成功
if (result == S_OK) {
int wl = wcslen(dstrText);
if (wl * 2 > sizeof(g_szRecogText)) {
dstrText[(sizeof(g_szRecogText)-2)/2] = 0x0000;
}
strcpy_s(g_szRecogText, sizeof(g_szRecogText), W2T(dstrText));
return TRUE;
}
//g_RecogStatus = IDS_SOUNDERR;
break;
}

//※このイベントは来ない
case SPEI_SOUND_END:
//音声入力完了
//g_RecogStatus = IDS_RECOGEND;
break;
} //end switch
} //end get event

return FALSE;
}
#endif
//----------------------------------------------------------


で、上記の関数をどう使うかですが。
まずは、外部変数にしておくといいのは、有効無効フラグと
取り出した文字列を格納するバッファ。
//----------------------------------------------------------
#ifdef MYSPEECH_VALID
BOOL g_fAcctiveSpeech = FALSE; //音声認識動作中
TCHAR g_szRecogText[512] = {0}; //認識結果文字列
#endif
//----------------------------------------------------------


あとはウィンドウプロシジャ内に以下をだいたいぶっこんどけば。
//----------------------------------------------------------
case WM_INITDIALOG:

#ifdef MYSPEECH_VALID
//音声認識開始
if (speech_proc_start(hDlg)) {
g_fAcctiveSpeech = TRUE;
}
#endif
break;

case WM_DESTROY:

#ifdef MYSPEECH_VALID
if (g_fAcctiveSpeech) {
speech_proc_stop(hDlg);
g_fAcctiveSpeech = FALSE;
}
#endif
break;


#ifdef MYSPEECH_VALID
//****** 音声認識関係
case WM_RECOEVENT:
if (RecogEvent()) {
//g_fAcctiveSpeechでなければ、ここにはこないので判定は省略
//キーワードが含まれるかチェックして、
if (ChecKeyWord(hDlg)) {
//含まれるときは、それ相応の処理
//PostMessage(hDlg, WM_COMMAND, IDC_BTxxxx, NULL);
}
}
return TRUE;
#endif
//----------------------------------------------------------


以上のような感じです。

プログラムを動作させる環境、
音声辞書設定を飛ばしてきたので、次回に説明します。

では、また。m(__)m
スポンサーサイト





コメントの投稿

非公開コメント

カレンダー
10 | 2019/11 | 12
- - - - - 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
プロフィール

さるもすなる

Author:さるもすなる
さるです。別HPサイト「さるもすなる」から侵食してきました。 山菜/きのこ、それとタイトルにしたPPバンド籠のことをメインに徒然に・・・・暇を持て余したさるの手仕事:男手芸のブログってことで。

最新記事
最新コメント
月別アーカイブ
カテゴリ
天気予報

-天気予報コム- -FC2-








本家のHPのトップ
山菜や茸の話です
PPバンドの籠作品と作り方です
投稿をお待ちしております



ブログランキング・にほんブログ村へ にほんブログ村 ハンドメイドブログへ



マニュアルのお申し込み



検索フォーム
リンク
RSSリンクの表示
ブロとも申請フォーム

この人とブロともになる

QRコード
QR