1. 概述

HWR (HandWritten Recognition) 联机手写识别技术,可以将在手写设备上书写时产生的有序轨迹信息化转化为汉字内码。

手写识别从识别过程来说分成脱机识别(off-line)和联机识别(on-line)两大类。通俗来讲,联机手写输出的是预先设置好的标准字体,手写的时候即使是用连笔字、草书,输出之后也会变成标准的宋体、楷体或者其他字体,而脱机手写则是输入的是什么样子,就会以什么样子显示出来。

本文档旨在讲解如何快速地集成灵云HWR功能到开发者应用中。关于各服务接口更详细的说明,请参考灵云SDK开发手册。在集成过程中如有疑问,可登录灵云开发者论坛,查找答案或与其他开发者交流。

1.1 概念解释

名称 说明
Session Session用来标记一个能力运行过程的上下文。一个应用最多可同时创建的Session数受到授权的限制。
单字识别 只能将一段输入的笔迹识别为一个单字。这种模式目前可支持87种语言的识别 (包括英文、阿拉伯数字、符号)。单字识别支持本地识别和云端识别。
多字识别 支持叠写和行写模式,即会自动将输入的笔迹进行切割,按照多字进行识别。 目前这种模式支持云端和本地,支持中文简繁体、英文、韩语、日语的识别(都包括英文、阿拉伯数字、符号等)。多字识别可以满足单字识别的场景,一般都应该选用这种模式。
获取联想词 即输入一个字符,输入法自动匹配候选词汇。
笔势识别 输入的笔迹被识别为列表里50种笔势(笔势列表)中的一种,输出结果为对应的索引值,存于结果条目HWR_RECOG_RESULT_ITEM 中字段pszResult。详见灵云SDK开发手册
模拟笔形 输入点迹数据,通过这些点迹数据回显出笔形,以图片的形式表现出来。
拼音识别 你输入一个汉字,自动获取该汉字的拼音,比如你输入"重",得到拼音“chóng”,“zhòng”。目前仅在windows平台支持。
英文连写识别 即你输入英文连写单词,输入法自动获取候选词汇。

1.2 能力定义(capkey)

HWR支持多种能力,通过 capkey 区分不同能力的调用和配置。其中常用 capkey 如下所示:

  • 本地能力
capkey 说明 所需资源文件
hwr.local.letter 本地单字识别 letter.dic
letter.conf
tibetanmap.txt(只藏文需要)
hwr.local.freestylus 本地多字识别 fwlib.dic
letter.dic
letter.conf
wwlib.dic
hwr.local.freestylus.v7 本地英文连写识别 wfse_cmn.bin.en
wfse_mdl.bin.en.big
wfse_mdl.bin.en.sml
hwr.local.associateword 获取联想词 wa.user.dct(用户词典)
wa.system.dct(系统词典)
hwr.local.penscript 本地笔迹库能力
hwr.local.gesture 本地笔势识别 gesture.dic
hwr.local.pinyin 本地拼音识别 gp.system.dct
  • 云端能力
capkey 说明 所需资源文件
hwr.cloud.letter 云端单字识别
hwr.cloud.freewrite 云端多字识别
hwr.cloud.gesture 云端笔势识别

1.3 识别流程

备注:获取联想词和拼音、模拟笔形等,目前只有本地端能力

2. HWR能力使用说明

2.1 准备工作

下载手写识别SDK并解压缩。

如果需要使用本地能力,请下载相应资源包并解压缩。

2.3 使用C++版SDK

  • 必选模块

    • libhci_curl.dll
    • hci_sys.dll
    • hci_hwr.dll
  • 云端识别

    • hci_hwr_cloud_recog.dll
  • 本地识别

    • hci_hwr_local_recog.dll
    • hci_hwr_associate.dll (联想库)
    • hci_hwr_penscript.dll (笔形库)
    • hci_hwr_pinyin.dll (拼音库)
    • mkl_avx.dll,mkl_avx2.dll,mkl_core.dll, mkl_def.dll,mkl_rt.dll,mkl_sequential.dll, mkl_vml_avx.dll,mkl_vml_avx2.dll,mkl_vml_p4n.dll (mkl库,英文连写依赖)

3. HWR能力集成说明

本章节主要讲述HWR能力的集成过程,开发者可根据本章内容完成HWR能力的简单调用。本章节以本地识别为例,讲述HWR能力的集成调用流程,调用顺序参考识别流程

3.1 通用模块初始化

在调用能力之前,需要初始化灵云SDK的通用模块。详见灵云SDK开发手册

  • C++示例代码
HCI_ERR_CODE err_code = HCI_ERR_NONE;
//配置串是由"字段=值"的形式给出的一个字符串,多个字段之间以','隔开。字段名不分大小写。
string init_config = "";
//灵云应用序号
init_config += "appKey=1234abcd";
//灵云开发者密钥
init_config += ",developerKey=1234567890";
//灵云云服务的接口地址
init_config += ",cloudUrl=http://api.hcicloud.com:8888";
//授权文件所在路径,保证可写
init_config += ",authpath=../../bin";
//日志的路径
init_config += ",logfilepath=../../bin";
//日志的等级
init_config += ",loglevel=5";
//日志文件的大小
init_config += ",logfilesize=512";
//其他配置使用默认值,不再添加,如果想设置可以参考开发手册
err_code = hci_init( init_config.c_str() );

提示 因灵云SDK8.1使用Visual Studio 2015开发的原因,在windows下的开发运行环境需要确认安装运行组件vc_redist.x64.exe,vc_redist.x86.exe, 具体请参考https://www.microsoft.com/zh-cn/download/details.aspx?id=48145

3.2 授权检测

在初始化灵云SDK的通用模块后,还需要调用授权检测函数获取云端授权。

C++示例代码

//获取过期时间
int64 expire_time;
int64 current_time = (int64)time( NULL );
HCI_ERR_CODE err_code = hci_get_auth_expire_time( &expire_time );
if( err_code == HCI_ERR_NONE )
{
    //获取成功则判断是否过期
    if( expire_time > current_time )
    {
        //没有过期
        printf( "auth can use continue\n" );
        return true;
    }
}
//获取过期时间失败或已经过期
//手动调用更新授权
err_code = hci_check_auth();
if( err_code == HCI_ERR_NONE )
{
    //更新成功
    printf( "check auth success \n" );
    return true;
}
else
{
    //更新失败
    printf( "check auth return (%d)\n", err_code);
    return false;
}

3.3 HWR初始化

在初始化灵通用模块和获取授权成功后,我们还需要调用初始化函数初始化HWR能力。

  • C++代码
char * pszHwrInitConfig = "dataPath=document/hwr,initCapKeys=hwr.local.freestylus";
errCode = hci_hwr_init(pszHwrInitConfig);
if (err_code != HCI_ERR_NONE)
{
    printf("hci_hwr_init return (%d:%s) \n",err_code,hci_get_error_info(err_code));
    return;
}
printf( "hci_hwr_init success\n" );

常见errcode,详见灵云SDK开发手册

对于使用本地多能力时,可以使用initCapKeys传入需要使用的所有能力,以';'隔开。 initCapKeys的作用是告知SDK,需要预先准备哪些能力并提前加载对应的库资源,提高执行效率。

C++示例代码

//HWR 初始化
string hwr_init_config = "dataPath=";
hwr_init_config += data_path;
hwr_init_config += ",initCapkeys=";
hwr_init_config += "hwr.local.letter;hwr.local.freestylus;hwr.local.associateword";

3.4 开启会话(session)

在HWR能力初始化之后,需要通过开启识别会话来启用一种具体的能力。

  • C++代码
int nSessionId = 0;
//调用具体能力需要相应的capkey,以开启多字识别为例
char * pszSessionConfig = "capKey=hwr.local.freestylus";
errCode = hci_hwr_session_start(pszSessionConfig, &nSessionId);
if( err_code != HCI_ERR_NONE )
{
    printf( "hci_hwr_session_start return (%d:%s)\n", err_code ,hci_get_error_info(err_code));
    return false;
}

3.5 HWR识别

在启动会话(session)成功后,即可进行 HWR 识别了。HWR识别具体可分为单字、多字识别,获取联想词,笔势识别以及拼音识别等,各识别功能以本地能力为例,详见如下各小章节。

配置参数详见灵云SDK开发手册

3.5.1 单、多字识别

  • C++ 代码
short g_StrokeData[]=
{        
    103 ,283 ,105 ,283 ,107 ,283 ,113 ,283 ,120 ,283,        
    129 ,283 ,138 ,283 ,146 ,283 ,156 ,283 ,162 ,283,        
    ...        
    -1 ,0 , -1, -1
};
char * strRecogConfig = "";
HWR_RECOG_RESULT hwrRecogResult;
//nSessionId 为上一步 hci_hwr_session_start() 所返回的会话标识
//g_StrokeData:笔迹数据
//hwrRecogResult:识别返回结果  
errCode = hci_hwr_recog( nSessionId, g_StrokeData, sizeof(g_StrokeData), pszRecogConfig, 
            &hwrRecogResult );

3.5.2 获取联想词

  • C++ 代码
int nSessionId = -1;
string session_config = "capkey=hwr.local.associateword";
hci_hwr_session_start(session_config.c_str(),&nSessionId);

// 输入必须是UTF8的,在某些平台上需要转码
char * szWords = "中华人民";
// 获取联想词
ASSOCIATE_WORDS_RESULT sWords;
errCode = hci_hwr_associate_words(nSessionId,"",szWords, &sWords );
// 显示联想词
for (int i = 0; i < (int)sWords.uiItemCount; ++i)
{        
    // 输出是UTF8的,在某些平台下输出需要转码        
    printf("Associate result[%d]: %s\n",i,sWords.pItemList[i].pszWord);}
    // 释放联想词结果
    errCode = hci_hwr_free_associate_words_result(&sWords );
}

3.5.3 笔势识别

  • C++ 代码
int nSessionId = -1;
string session_config = "capkey=hwr.local.gesture";
err_code = hci_hwr_session_start( session_config.c_str(), &nSessionId );

HWR_RECOG_RESULT hwrRecogResult;
string recog_config = "";
//g_sGestureStrokeData:笔势识别使用的点集数据,以“-1 ,0 , -1, -1” 结尾
err_code = hci_hwr_recog(nSessionId, 
                         g_sGestureStrokeData, 
                         sizeof(g_sGestureStrokeData),
                         recog_config.c_str(),
                         &hwrRecogResult);

3.5.4 模拟笔形

  • C++ 代码
int nSessionId = -1;
string session_config = "capkey=hwr.local.penscript";
err_code = hci_hwr_session_start( session_config.c_str(), &nSessionId );

short g_sShortData[]=
{
    230,212,230,219,217,276,169,343,148,360,148,360,-1,0,
    209,288,216,295,250,319,277,336,288,342,291,343,-1,0,
    -1,-1
} 

CImage newimg;
if(!newimg.CreateEx(1200,1200,24,BI_RGB)){
    printf("create bitmap failed");
}
DC hDC = newimg.GetDC();
PEN_SCRIPT_RESULT hwrPenscriptResult;
int size = sizeof(g_sShortData)/sizeof(g_sShortData[0]);
string strPenscriptConfig = "";
for(int positionIndex = 0 ;positionIndex<size;positionIndex+=2)
{
    if(g_sShortData[positionIndex]== -1 && g_sShortData[positionIndex+1] == -1)
    {
        break;
    }
    err_code = hci_hwr_pen_script(nSessionId,
                                    strPenscriptConfig.c_str(),
                                    g_sShortData[positionIndex],
                                    g_sShortData[positionIndex+1]
                                    , &hwrPenscriptResult);
    if (err_code == HCI_ERR_NONE)
    {
        for (unsigned int nIndex = 0; nIndex < hwrPenscriptResult.uiItemCount; nIndex++)
        {
            PEN_SCRIPT_RESULT_ITEM item = hwrPenscriptResult.pItemList[nIndex];
            for(int i = 0; i < item.nHeight; i++ )
            {
                for ( int j = 0; j < item.nWidth; j++ )
                {
                    if ( !item.psPageImg[i*item.nWidth+j] )
                    {
                        //设置参数COLORREF值,让字迹可见
                        //SetPixel(hDC,j+item.x,i+item.y,item.unPenColor);
                        SetPixel(hDC,j+item.x,i+item.y,12632256);
                    }
                }
            }
        }
        err_code = hci_hwr_free_pen_script_result(&hwrPenscriptResult);
    }
    else{
        break;
    }
}

3.5.5 获取拼音

  • C++ 代码
int nSessionId = -1;
string session_config = "capkey=hwr.local.pinyin";
err_code = hci_hwr_session_start( session_config.c_str(), &nSessionId );

const char* psWord = "捷";
unsigned char *psUtf8 = NULL;
HciExampleComon::GBKToUTF8((unsigned char*)psWord,&psUtf8);
PINYIN_RESULT pResult;
//识别配置支持 拼音显示模式字段(有音调显示/无音调显示),默认有音调;
string recog_config = ""; 
err_code = hci_hwr_pinyin(nSessionId,recog_config.c_str(),(const char*)psUtf8,&pResult); 
HciExampleComon::FreeConvertResult(psUtf8);
if( err_code == HCI_ERR_NONE )
{
    printf( "hci_hwr_pinyin success\n" );
    //输出识别结果
    PrintPinyin(pResult);
    hci_hwr_free_pinyin_result(&pResult);
}

3.5.6 实时识别

启用实时识别时,对于每次连续的识别内容,可以多次调用hci_hwr_recog() 。每次调用追加输入新的数据, 每次输入的数据以(-1,0,-1,-1)结束,也即每次输入的笔画是完整的,可以一次输入多个笔画。

每次实时识别时,不论是单个笔画或是一次输入多个笔画,笔迹最后需以(-1,0)(-1,-1)结束。

启动实时识别时,需要在创建识别会话时,加入realtime=yes选项。实时识别时,每次调用本函数都会返回从头开始的完整结果,新输入的数据会导致切分发生变化, 因此后一次结果不一定是前次结果再追加字符,可能会更改掉部分前次结果。实时识别中每次返回的识别结果都需要释放。

C++示例代码

char * pszSessionConfig = "capKey=hwr.local.freestylus,realtime=yes";
int nSessionId;errCode = hci_hwr_session_start(pszSessionConfig, &nSessionId);
while (true) {        
    char * strRecogConfig = "";        
    HWR_RECOG_RESULT hwrRecogResult;  

    //g_StrokeData:每次传入的笔迹,以(-1,0)(-1,-1)结尾。
    //例如‘人’字是两划,那么左边一撇和右边一撇单独传入识别,以-1,0)(-1,-1)结尾。  
    errCode = hci_hwr_recog( nSessionId, g_StrokeData, g_StrokeLen, pszRecogConfig, &hwrRecogResult );        
    // 可以打印当前的识别结果,每次返回都是从头开始的完整结果        
    ...        
    // 每次的返回结果都需要释放        
    errCode = hci_hwr_free_recog_result(&hwrRecogResult);        
    // 如果是最后一笔,跳出循环        
    if (is_last_stroke())                
    break;
}

3.5.7 英文连写识别

  • C++ 代码
int nSessionId = -1;
string session_config = "capkey=hwr.local.freestylus.v7";
err_code = hci_hwr_session_start( session_config.c_str(), &nSessionId );
short g_StrokeData[]=
{        
    103 ,283 ,105 ,283 ,107 ,283 ,113 ,283 ,120 ,283,        
    129 ,283 ,138 ,283 ,146 ,283 ,156 ,283 ,162 ,283,        
    ...        
    -1 ,0 , -1, -1
};
char * strRecogConfig = "";
HWR_RECOG_RESULT hwrRecogResult;
//nSessionId 为上一步 hci_hwr_session_start() 所返回的会话标识
//g_StrokeData:笔迹数据
//hwrRecogResult:识别返回结果  
errCode = hci_hwr_recog( nSessionId, g_StrokeData, sizeof(g_StrokeData), pszRecogConfig, 
            &hwrRecogResult );

3.6 结束识别

最后我们需要反初始化,依次关闭会话,终止HWR能力,关闭灵云系统。

C++示例代码

//关闭HWR识别会话
errCode = hci_hwr_session_stop(nSessionId);
//终止HWR能力
errCode = hci_hwr_release();
//终止灵云系统
errCode = hci_release();

5. FAQ

Q: 我运行你们的 SDK 报 19 号错误:START LOG FAILED, 应该如何解决?

A: 出现此问题,一般由两种原因造成。

  • 工程文件里引入了中文路径,导致SDK启动时,读写对应的日志路径报错。
  • 日志文件定义的路径由于权限问题不可读写,比如在 Android 平台上运行时,没有 WRITE_EXTERNAL_STORAGE的权限。

Q: 我运行你们的 SDK 报 23 号错误:LOAD_FUNCTION_FROM_DLL FAILED,应该如何解决?

A: 这种情况,通常是由于缺库。最常见的场景是使用本地能力的 capkey,如 hwr.loacl.letter,没有将库文件对应组入(我们 SDK 示例程序,与 example 同层有全套库文件,示例程序内默认只组入了云端库),将相关库补齐即可使用。此外,由于 AFR能力引用了第三方的 MKL 库,因此如果第三方库没有拷入,也会导致此问题。

Q: 我运行你们的 SDK 报 14 号错误:LOCAL_RES_MISSING,应该如何解决?

A: 本地能力常会遇到此问题,此问题是由于没有组入对应的本地资源文件。请在我们开发者社区下载需求的本地资源(VPR本地模型)等,并将资源文件组入代码 datapath 指定路径,然后再次尝试运行程序。

Q: 我运行你们的 SDK 报 11 号错误:SERVICE_RESPONSE_FAILED ,应该如何解决?

A: 此问题原因较为复杂,可能是传入的参数问题与服务器不匹配,如调用私有云时,没有传入 property 等配置项。也可能是服务端内部的返回原因,请联系技术支持排查。

Q: 我运行你们的SDK报 7 号错误:CONFIG_UNSUPPORT ,应该如何解决?

A: 与 11号错误类似,还是配置串问题,7 号错误原因为传入了不支持的配置串。请仔细检查各接口传入的所有配置串,并联系技术支持排查。

Q: 我运行你们的SDK报 9 号错误: SERVICE_TIMEOUT ,应该如何解决?

A: 这个是因为您请求我们服务器到返回结果的时间,超过了上限。请检查网络环境相关的原因,并协助提供日志。

Q: 我运行你们的 SDK 报 16 号错误: SESSION_INVALID ,应该如何解决?

A: 这个需要检查详细的代码调用过程,调用每个能力核心接口时,都是需要传入 sessionid 的,如果在 sessionstart 的时候,已经发生了错误,或者使用的id,并不是 sessionstart 时候创建的,就会返回此错误,请联系技术支持排查。

Q: 我运行你们的 SDK 报 0 号错误: ERROR NONE,应该如何解决?

A: SDK 报 0 号错误, ERROR NONE,代表执行成功,请您继续后续的集成。

Q: Win8 Win10操作系统下使用VS2015集成灵云SDK,发生类似"缺少mfc140d.dll"错误,如何解决?

请按微软官网要求,安装运行使用 Visual Studio 2015 生成的 C++ 应用程序所需的运行时组件。`

results matching ""

    No results matching ""