• 基于SAPI的中文语音识别的xml书写与编程


    先来看看识别中文的xml是怎么写的。其实和英文的差不多,先把LANGID改成中文编号为804,且存储的时候必须使用Unicode编码。其他的和英文的区别不大。

    代码
    <!-- 中文编号为804,且存储的时候必须使用Unicode编码 -->

    <GRAMMAR LANGID="804">
        
    <DEFINE>
            
    <ID NAME="VID_Counter" VAL="1"/>

            
    <ID NAME="VID_Single" VAL="10"/>
            
    <ID NAME="VID_Double" VAL="11"/>

            
    <ID NAME="VID_Short" VAL="14"/>
            
    <ID NAME="VID_Tall" VAL="15"/>
     
            
    <ID NAME="VID_Mocha" VAL="25"/>
            
    <ID NAME="VID_Cappuch" VAL="28"/>
         
            
    <ID NAME="VID_Place" VAL="253"/>
            
    <ID NAME="VID_Navigation" VAL="254"/>
            
    <ID NAME="VID_EspressoDrinks" VAL="255"/>

            
    <ID NAME="VID_Shots" VAL="258"/>
            
    <ID NAME="VID_Size" VAL="261"/>

            
    <ID NAME="VID_DrinkType" VAL="262"/>
            
    <ID NAME="VID_OrderList" VAL="263"/>


            
    <ID NAME="VID_Shots_PRO" VAL="500"/>
            
    <ID NAME="VID_Size_PRO" VAL="501"/>
            
    <ID NAME="VID_DrinkType_PRO" VAL="502"/>
           
    <ID NAME="VID_Place_PRO" VAL="503"/>
            
    <ID NAME="VID_Shots_PRO2" VAL="504"/>
            
    <ID NAME="VID_Size_PRO2" VAL="505"/>
            
    <ID NAME="VID_DrinkType_PRO2" VAL="506"/>
        
    </DEFINE>

        
    <RULE ID="VID_EspressoDrinks" TOPLEVEL="ACTIVE">
            
    <O>
                
    <L>
                    
    <P>我想吃</P>
                    
    <P>请来一份</P>
                
    </L>
            
    </O>
            
    <O>一份</O>
            
    <MIN="1" MAX="7">
                
    <RULEREF REFID="VID_OrderList" />
            
    </P>
            
    <O>please</O>
        
    </RULE>

        
    <RULE ID="VID_Navigation" TOPLEVEL="ACTIVE">
            
    <O></O>
            
    <P>
                
    <L>
                    
    <P>进入</P>
                    
    <P>来吧</P>
                
    </L>
            
    </P>
            
    <RULEREF REFID="VID_Place" /> 
        
    </RULE>

        
    <RULE ID="VID_Place" >
            
    <PROPID="VID_Place_PRO">
                
    <VAL="VID_Counter">商店</P>
                
    <VAL="VID_Counter">咖吧</P>
            
    </L>
        
    </RULE>


        
    <RULE ID="VID_Shots" >
            
    <PROPID="VID_Shots_PRO2">
                
    <VAL="VID_Single">一杯</P>
                
    <VAL="VID_Double">两杯</P>
            
    </L>
        
    </RULE>

        
    <RULE ID="VID_Size" >
            
    <PROPID="VID_Size_PRO2">
                
    <VAL="VID_Short">小杯</P>
                
    <VAL="VID_Tall">大杯</P>
            
    </L>
        
    </RULE>


        
    <RULE ID="VID_DrinkType" >
            
    <PROPID="VID_DrinkType_PRO2">
                
    <VAL="VID_Mocha">摩卡</P>
                
    <VAL="VID_Cappuch">意大利面</P>
            
    </L>
        
    </RULE>

        
    <RULE ID="VID_OrderList" >
            
    <L>
                
    <RULEREF REFID="VID_Shots" PROPID="VID_Shots_PRO"/> 
                
    <RULEREF REFID="VID_Size" PROPID="VID_Size_PRO"/> 
                
    <RULEREF REFID="VID_DrinkType" PROPID="VID_DrinkType_PRO"/> 
            
    </L>
        
    </RULE>
    </GRAMMAR>

    这个xml其实给出来的另外一种解读是这样的。

    树状结构如下所示

    因为有两个rule的状态被设置为ACTIVE,所以应该有两棵树

    第一棵树根据RULE IDVID_Navigation的规则而定

     

    第二棵树根据RULE IDVID_EspressoDrinks的规则而定

    注意这些图示,然后就可以理解下面代码中的某些语句的含义了

    pElements->Rule.ulId 这一句其实找到的是top-level的rule的id,就是在xml里被标记为ACTIVE的RULE的ID

    pRule = pElements->Rule.pFirstChild;(在第二张图中)相当于是找到了VID_OrderList

    pProp = pElements->pProperties;相当于是找到VID_OrderList下的属性VID_Shots_PRO,VID_Size_PRO, VID_DrinkTye_PRO中的一个(按照实际识别出来的单词属于那个属性而定)

    pProp = pProp->pNextSibling;在上图中等于是找旁支,在逻辑上其实是过渡到下一个别识别词上

    pRule = pRule->pNextSibling;在上图中等于是找旁支等于是找到下一个词所属于的rule上

     

    简言之,第一个属性是按照树形结构查下来的第一个PROP_ID,firstchild是第一个孩子,而NextSibling是旁支。

     

    另外,在识别之后,每一个被识别的单词都会有对应的rule和property

     

     

    其实我觉得比较难以理解的是rule和property的关系。原文是这么讲的Grammar rules are elements that SAPI 5-compliant speech recognition (SR) engines use to restrict the possible word or sentence choices during the SR process. 而property也被称作semantic tags。It provides a powerful means for semantic information to be easily embedded inside a grammar.研究了老半天搞不懂。

    在大致的思考之后得出了如下的猜测:

    rule规定了一个句子,此句子是由一组词和/或rule组成的。最难懂的是property,也就是语义标签。我后来觉得它是不是用来提醒系统,看到了打了property的地方就是可能出现需要识别的词的地方(可能是rule也可能是某个词)。如果按照这个逻辑,那么如下代码也很好理解了。那就是要找到那个词需要用property(SPPHRASEPROPERTY),而要找到词所在的句子,或者根据句子找到所识别出来的词的位置,就需要rule(SPPHRASERULE)。

    另外,有一点必须要注意的,比如说对于第二章图这个例子,即使我们在说话的时候说的是“意大利面,摩卡”,那么在识别的时候也是按照在xml里的定义顺序出来的,也就是说,得到的结果是“摩卡,意大利面”

     再看看根据这些规则而写的一系列的函数实现

    代码
    void ExecuteCommand(ISpPhrase *pPhrase, HWND hWnd)
    {
        SPPHRASE 
    *pElements;

        
    // Get the phrase elements, one of which is the rule id we specified in
        
    // the grammar.  Switch on it to figure out which command was recognized.
        if (SUCCEEDED(pPhrase->GetPhrase(&pElements)))
        {        
            
    switch ( pElements->Rule.ulId ) 
    //被识别出来的词所属的top-level的rule。SPPHRASE::RULE指的是top-level rule (and rule-reference hierarchy)。我的理解就是这里获取最主要的(在xml里被定义为ACTIVE)那些RULE的id
            {
                
    case VID_EspressoDrinks:
                {
                    ID_TEXT 
    *pulIds = new ID_TEXT[MAX_ID_ARRAY];  
                    
    // This memory will be freed when the WM_ESPRESSOORDER
                    
    // message is processed
                    const SPPHRASEPROPERTY *pProp = NULL;
                    
    const SPPHRASERULE *pRule = NULL;
                    ULONG ulFirstElement, ulCountOfElements;
                    
    int iCnt = 0;

                    
    if ( pulIds )
                    {
                        ZeroMemory( pulIds, 
    sizeof( ID_TEXT[MAX_ID_ARRAY] ) );
                        
    //获取子一级rule的属性
                        pProp = pElements->pProperties; 
                        
    //获取子一级的RULE,如果没有子集RULE那么就会返回NULL
                        pRule = pElements->Rule.pFirstChild; 
                      
                        
    // Fill in an array with the drink properties received
                        while ( pProp && iCnt < MAX_ID_ARRAY )
                        {
                            
    // Fill out a structure with all the property ids received as well
                            
    // as their corresponding text
                            
    //SPPHRASEPROPERTY::ulFirstElement 指的是The first spoken element spanned by this property. 
                            pulIds[iCnt].ulId = static_cast< ULONG >(pProp->pFirstChild->vValue.ulVal);
                            
    // Get the count of elements from the rule ref, not the actual leaf
                            
    // property
                            if ( pRule )
                            {
                    
    //取得所识别出来的单词在整个语句中的位置,0为第一个单词
        
    // SPPHRASERULE::ulFirstElement指的是The index of the first spoken element (word) of this rule. 
                                    ulFirstElement = pRule->ulFirstElement;
        
    // SPPHRASERULE::ulCountOfElements指的是Number of spoken elements (words) spanned by this rule. 
                                ulCountOfElements = pRule->ulCountOfElements;
                            }
                            
    else
                            {
                                ulFirstElement 
    = 0;
                                ulCountOfElements 
    = 0;
                            }
                            
    // This is the text corresponding to property iCnt - it must be
                            
    // released when we are done with it
                    
    // 获取识别出来那个词的文本
                            pPhrase->GetText( ulFirstElement, ulCountOfElements,
                                              FALSE, 
    &(pulIds[iCnt].pwstrCoMemText), NULL);
                            
    //我的理解是接下去要做的事情就是检查同根的旁支
                            
    // Loop through all properties
                            pProp = pProp->pNextSibling;
                            
    // Loop through rulerefs corresponding to properties
                            if ( pRule )
                            {
                                pRule 
    = pRule->pNextSibling;
                            }
                            iCnt
    ++;
                        }
                        PostMessage( hWnd, WM_ESPRESSOORDER, NULL, (LPARAM) pulIds );                   
                    }
                }
                
    break;
                
    case VID_Navigation:
                {
                    
    switch( pElements->pProperties->vValue.ulVal )
                    {
                        
    case VID_Counter:
                            PostMessage( hWnd, WM_GOTOCOUNTER, NULL, NULL );                        
                        
    break;
                    }
                }
                
    break;
            }
            
    // Free the pElements memory which was allocated for us
            ::CoTaskMemFree(pElements);
        }

    }
  • 相关阅读:
    iOS启动速度优化
    iOS Instruments工具使用
    iOS开发 AFN配置https请求
    git使用教程
    iOS之 接入新浪微博 SDK(微信支付) 的坑(registerApp 的问题)
    iOS之应用间跳转
    iOS设置iTunes文件共享
    IOS平台下抓包工具使用以及抓取API接口
    用CornerStone配置SVN,HTTP及svn简单使用说明
    iOS之取消键盘遮挡
  • 原文地址:https://www.cnblogs.com/aicro/p/1945499.html
Copyright © 2020-2023  润新知