サミー、ステータス、家族(2)。

サミー、ステータス、家族(2)。

やや明後日の方向に向かっているソフトウェアネタですが、よろしくお付き合いくださいませ。

以前、音声認識の精度を上げるならば、ユーザー側が決まったフォーマットにして次に来る単語をプログラム側で予測すればいいという話を、大きな風呂敷を広げてお話ししました。

イメージ

サミー、ステータス、家族。

この時は、予測単語はあらかじめ用意していました。つまり、シナリオは最初から固定されているというワケです。この方法でも、予想単語のリスト(以下、ルールファイル)を複数用意して切り替えればそれなりに幅を持たせることはできますが、予め考えられたシナリオ以外には対応できないという弱点があります。

そこで、このルールファイルをその場で作ってしまえば柔軟に対応できるじゃんってのが今回のお話しです。

しかも、前回は『かしこまりました。』とは言ったものの、何もしてないというナンチャッテ仕様でしたが、それもちゃんと動作するようにマスコット側にも手を加えました。(build90で対応予定)

流れとしてはこんな感じです。

  1. Fy Mascotから機能一覧を取得する。
  2. 機能一覧を使ってルールファイルを作成し適用。
  3. 音声認識で機能を選択(認識)する。
  4. Fy Mascotからその機能に対するパラメータ一覧を取得
  5. パラメータ一覧を使ってルールファイルを作成し適用(なければフリー入力とする)。
  6. 音声認識でパラメータを選択(認識)する。
  7. 機能とパラメータを使ってFy Mascotに機能を実行させる。

Fy Mascotは、オプションプログラムによって機能を追加できるようになっています。そして、その機能によってパラメータの状況が異なります。

例えば、ランチャーのオプションプログラムは、機能は『LAUNCH』、パラメータは登録された『アプリケーション名』となります。ですから、『LAUNCH』のパラメータは登録されているものだけになるので一覧を取得できます。

逆に、サーチのオプションプログラムは、機能は『SEARCH』や『WHERE』などですが、パラメータは検索する単語そのものなので一覧は取得できません。

このため、5.の動作が場合によって異なるのです。

作成するルールファイルはUTF-8のxmlファイルなので、ファイル関数で簡単に作れます。<P>~</P>の部分に取得した機能やパラメータを列挙していけばOKです。

ルールファイル

<?xml version="1.0" encoding="UTF-8"?>
<GRAMMAR>
<RULE name=”RULE" toplevel="INACTIVE">
  <L>
<P>単語1</P>
<P>単語2</P>
<P>単語3</P>
  </L>
</RULE>
</GRAMMAR>

では、サクッとコードを書きます。長いので主な変更部分だけにしますので、必要ならば前回の記事も参照してください。

ソースコード

/* 初期化部分 */
/* ルールファイル登録の部分が短くなっている */

int recognize_initialize( HWND hWnd )
{
    unsigned __int64 ullInterest;
    if(!SUCCEEDED(CoInitialize(NULL)))
    {
        return FALSE;
    }
    if(!SUCCEEDED(m_cpRecoEngine.CoCreateInstance(CLSID_SpSharedRecognizer)))
    {
        return FALSE;
    }
    if(!SUCCEEDED(m_cpRecoEngine->CreateRecoContext( &m_cpRecoCtxt )))
    {
        return FALSE;       
    }

    if(!SUCCEEDED(m_cpRecoCtxt->SetNotifyWindowMessage( hWnd , WM_RECOEVENT, 0, 0 )))
    {
        return FALSE;
    }   
    ullInterest = SPFEI(SPEI_RECOGNITION) | SPFEI(SPEI_FALSE_RECOGNITION)| SPFEI(SPEI_HYPOTHESIS) | SPFEI(SPEI_SOUND_START) | SPFEI(SPEI_SOUND_END);
    if(!SUCCEEDED(m_cpRecoCtxt->SetInterest(ullInterest, ullInterest)))
    {
        return FALSE;
    }
    /* 名前のルール読み込み */
    if(!SUCCEEDED(m_cpRecoCtxt->CreateGrammar( 0, &m_cpDictationGrammar )))
    {
        return FALSE;
    }
    DictationGrammarFlag = FALSE;
    return TRUE;
}

/* ルールのセット・解除 */
/* 予め機能、パラメータ一覧をlist.txtというファイルに書き出しておきます。 */

/* ルールファイル作成 */

int recognize_createrule( char *rule , char *list )
{
    char buffer[512];
    wchar_t unicode[MAX_PATH];
    char *p;
    FILE *fp_r , *fp_l;
    fp_r = fopen( rule , "w" );
    fp_l = fopen( list , "r" );
    if((!fp_r)||(!fp_l))
    {
        if(fp_r)fclose(fp_r);
        if(fp_l)fclose(fp_l);
        return FALSE;
    }
    /* ルール作成 */
    fprintf( fp_r , "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n<GRAMMAR>\r\n" );
    fprintf( fp_r , "<RULE name=\"RULE\" toplevel=\"INACTIVE\">\r\n" );
    fprintf( fp_r , "<L>\r\n" );
    while(fgets( buffer , 512 , fp_l))
    {
        p = strchr( buffer , '\r' );
        if(p){*p = '\0';}
        p = strchr( buffer , '\n' );
        if(p){*p = '\0';}
        fprintf( fp_r , "<P>%s</P>\r\n" , buffer );
    }
 
    fprintf( fp_r , "</L>\r\n" );
    fprintf( fp_r , "</RULE>\r\n" );
    fprintf( fp_r , "</GRAMMAR>\r\n" );
    if(fp_r)fclose(fp_r);
    if(fp_l)fclose(fp_l);
    /* ルール作成ここまで */
    /* 新しいルールを読み込む */
    MultiByteToWideChar( CP_ACP , 0 , rule , -1 , unicode , MAX_PATH );
    if(!SUCCEEDED(m_cpDictationGrammar->LoadCmdFromFile( unicode , SPLO_STATIC )))
    {
        return FALSE;
    }
    /* 新しいルールをアクティブにする */
    m_cpDictationGrammar->SetRuleState( L"RULE", NULL, SPRS_ACTIVE );
    DictationGrammarFlag = 1;
    return TRUE;
}

/* フリー入力に切り替え */

int recognize_createnorule( )
{
    if(!SUCCEEDED(m_cpDictationGrammar->LoadDictation( NULL , SPLO_STATIC )))
    {
        return FALSE;
    }
    m_cpDictationGrammar->SetDictationState( SPRS_ACTIVE );
    DictationGrammarFlag = 2;
    return TRUE;
}

/*いずれの場合も解放*/

int recognize_deleterule()
{
    /* ルールを解放する */
    switch(DictationGrammarFlag)
    {
    case 1:
        m_cpDictationGrammar->SetRuleState( L"RULE", NULL, SPRS_INACTIVE );
        DictationGrammarFlag = 0;
        break;
    case 2:
        m_cpDictationGrammar->SetDictationState( SPRS_ACTIVE );
        DictationGrammarFlag = 0;
        break;
    default:
        break;
    }
    return TRUE;
}

あとは、前回のrecognize_setactive関数の部分です。
前回は、複数のルールをアクティブにしたり解除したりしていましたが、今回はルールを作り直しているので関数名もrecognize_setruleに変更しました。場所はrecognize_setactiveの時と同じくWM_RECOEVENTのところなどで使います。

/* ルールの設定 */

int recognize_setrule( int status , HWND hWnd )
{
    char current[MAX_PATH];
    char rule[MAX_PATH], list[MAX_PATH];
    char multibyte[256];
    wchar_t unicode[256];
    char utf8[256];
    char *p;
    GetModuleFileName( (HINSTANCE)(LONG_PTR)GetWindowLongPtr( hWnd , GWLP_HINSTANCE ) , current , MAX_PATH );
    p = strrchr( current , '\\' );    //基本的にパスなので絶対あるハズ
    *p = '\0';
    sprintf( rule , "%s\\rule.xml" , current );
    sprintf( list , "%s\\list.txt" , current );
    switch(status)
    {
    case 1:
        {
            /* 最初 */
            /* キャラクター名をセットして呼ばれるまで待機 */
            FILE *fp;
           
            fp = fopen( list , "w" );
            sprintf( multibyte , "サミー" );
            MultiByteToWideChar( CP_ACP , 0 , multibyte , -1 , unicode , 256 );
            WideCharToMultiByte( CP_UTF8 , 0, unicode, -1, utf8, 256, NULL, NULL );
            fprintf( fp , "%s\r\n" , utf8 );
            fclose( fp );
            recognize_createrule( rule , list );
            return TRUE;
        }
        break;
    case 2:
        {
            /* 名前を呼ばれたので命令を取得 */
            /* 命令を取得するまで待機 */
            recognize_deleterule();
            if(gicp_functionlist( "ORDER" , "" , list ))
            {
                recognize_createrule( rule , list );
            }
            else
            {
                recognize_createnorule();               
            }
            return TRUE;
        }
        break;
    case 3:
        {
            /* 命令されたので対応する内容を取得 */
            /* 内容を取得するまで待機 */
            /* あれば*/
            int count;
            char buffer[256];
            /* エラーはないものとして処理している */
            count = SendMessage( GetDlgItem(hWnd,2), LB_GETCOUNT, 0L, 0L );
            SendMessage( GetDlgItem(hWnd,2), LB_GETTEXT, count-1, (LPARAM)buffer );
            recognize_deleterule();
            /* 命令は英語のみなので文字コード変換は不要 */
            if(gicp_functionlist( "PARAM" , buffer , list ))
            {
                recognize_createrule( rule , list );
            }
            else
            {
                recognize_createnorule();               
            }
            return TRUE;
        }
        break;
    case 4:
        {
            /* 内容を取得したので元に戻る */
            int count;
            char order[256],param[256];
            /* エラーはないものとして処理している */
            count = SendMessage( GetDlgItem(hWnd,2), LB_GETCOUNT, 0L, 0L );
            SendMessage( GetDlgItem(hWnd,2), LB_GETTEXT, count-1, (LPARAM)param );
            SendMessage( GetDlgItem(hWnd,2), LB_GETTEXT, count-2, (LPARAM)order );
            gicp_functionrequest( order , param );
            SendMessage( GetDlgItem(hWnd,2), LB_RESETCONTENT, 0L , 0L );
            recognize_deleterule();
           
            return TRUE;
        }
        break;
    default:
        {
            return FALSE;
        }
        break;
    }
}

なお、gicp_functionlist関数は『Fy Mascot』から機能やパラメータの一覧を取得して『list.txt』に書き出す関数、gicp_functionrequest関数は『Fy Mascot』に機能とパラメータを指定して実行する関数です。

それでは、いつもながらスクリーンショットで見てもピンと来ませんが実行してみます。

1.ちゃんと、音声認識パレットがでてきました。オフになっているのでオンにします。

イメージ

2.名前を呼びます。名前もそのままサミーです。。。
あっさり認識され、ステータスが2に移行します。
人工無能も正しく反応しています。

イメージ

3.機能を呼びます。機能の一覧があるので正しく認識させることができます。
ただ、誤認識はないのですが、英語は発音が悪いらしく何回か認識してくれませんでした。

イメージ

4.パラメータであるペイントを呼びます。
今回はランチャーなのでパラメータ一覧があるので正しく認識して、
実際にランチャーが実行されてペイントが起動します。

イメージ

なお、逆にサーチの場合はフリー入力なので誤認識が結構発生していました。

今回は、オプションプログラムとして音声認識機能を加え、同じくオプションプログラムとして加えられたランチャー機能を実際に扱っているのがポイントです。機能やパラメータのリストはその都度取得されるので、時と場合によって変更するなど柔軟な対応が可能です。これでちっとは音声認識も使えそうな雰囲気になったでしょうか。

ちなみに、この考えのきっかけになった『イブの時間』というアニメなんですが、今年のクリスマスに何かあるそうです。ということで、ちょっとタイムリーかもしれない話題でした。

(イヴの時間 blog) http://timeofeve.com/diarypro/diary.cgi