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.2 使用Android版SDK
2.2.1 库目录文件介绍
jar包文件
- hcicloud-8.1.jar
必选模块
- libhci_curl.so
- libhci_sys.so
- libhci_sys_jni.so
- libhci_hwr.so
- libhci_hwr_jni.so
本地识别
- libhci_hwr_local_recog.so (本地识别库,包含了中文多字与英文连写)
- libhci_hwr_associate.so (联想库)
- libhci_hwr_penscript.so (笔迹库)
- libopencv_core.so (opencv库,英文连写依赖)
- libopencv_highgui.so (opencv库,英文连写依赖)
- libopencv_imgproc.so (opencv库,英文连写依赖)
云端识别
- libhci_hwr_cloud_recog.so
特别注意
Module
的build.gradle
中,必须配置packagingOptions.doNotStrip
参数,以保留灵云SDK中的签名信息,该信息在SDK内部使用,用来保护灵云SDK的知识版权。 若此参数未配置,或配置不正确,在进行本地能力调用时,将会返回本地引擎初始化失败的信息。
build.gradle示例
apply plugin: 'com.android.application' android { compileSdkVersion 17 buildToolsVersion "27.0.3" defaultConfig { applicationId "com.sinovoice.example" minSdkVersion 7 targetSdkVersion 17 packagingOptions { doNotStrip "*/armeabi/*.so" doNotStrip "*/armeabi-v7a/*.so" doNotStrip "*/arm64-v8a/*.so" // add other if needed } } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' } } } dependencies { compile files('libs/hcicloud-8.1.jar') compile files('libs/hcicloud_recorder-8.1.jar') }
2.2.2 添加用户权限
在工程 AndroidManifest.xml 文件中添加如下权限。
<!--通常需要设置一些sd卡路径(例如日志路径)为可写,因此需要能够写外部存储 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!--以下访问网络的权限均需要打开-->
<!--连接网络权限,用于执行云端能力 -->
<uses-permission android:name="android.permission.INTERNET" />
<!--读取网络信息状态 -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!--获取当前wifi状态 -->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<!--允许程序改变网络连接状态 -->
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<!--读取手机信息权限 -->
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<!--以下访问权限可选-->
<!--手机定位信息-->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
3. HWR能力集成说明
本章节主要讲述HWR能力的集成过程,开发者可根据本章内容完成HWR能力的简单调用。本章节以本地识别为例,讲述HWR能力的集成调用流程,调用顺序参考识别流程。
3.1 通用模块初始化
在调用HWR能力之前,需要初始化灵云SDK的通用模块。详见灵云SDK开发手册
Android示例代码
// 创建初始化参数辅助类 InitParam initparam = new InitParam(); // 授权文件所在路径,此项必填 String authDirPath = context.getFilesDir() .getAbsolutePath();; initparam.addParam (InitParam.AuthParam.PARAM_KEY_AUTH_PATH, authDirPath) ; // 灵云云服务的接口地址,此项必填 initparam.addParam (InitParam.AuthParam.PARAM_KEY_CLOUD_URL, "http://api.hcicloud.com:8888"); // 开发者密钥,此项必填,由捷通华声提供 initparam.addParam (InitParam.AuthParam.PARAM_KEY_DEVELOPER_KEY, "01234567890"); // 应用程序序号,此项必填,由捷通华声提供 initparam.addParam (InitParam.AuthParam.PARAM_KEY_APP_KEY, "1234abcd"); // 日志的路径,可选,如果不传或者为空则不生成日志 String logDirPath = "/storage/emulated/0/sinovoice/com.sinovoice. example/log"; initparam.addParam (InitParam.LogParam.PARAM_KEY_LOG_FILE_PATH, logDirPath); //日志等级,0=无,1=错误,2=警告,3=信息,4=细节,5=调试 //SDK将输出小于等于logLevel的日志信息 initparam.addParam (InitParam.LogParam.PARAM_KEY_LOG_LEVEL, "5"); // 灵云系统初始化 // 第二个参数在Android平台下,必须为当前的Context int errCode = HciCloudSys.hciInit (initparam.getStringConfig(), this); if(errCode != HciErrorCode.HCI_ERR_NONE) { // "系统初始化失败" return; }
3.2 授权检测
在初始化灵云SDK的通用模块后,还需要调用授权检测函数获取云端授权。
Android示例代码
// 获取授权 private int checkAuthAndUpdateAuth() { // 获取系统授权到期时间 int initResult; AuthExpireTime objExpireTime = new AuthExpireTime (); initResult = HciCloudSys.hciGetAuthExpireTime (objExpireTime); if (initResult == HciErrorCode.HCI_ERR_NONE) { // 显示授权日期,如用户不需要关注该值,此处代码可忽 略 Date date = new Date (objExpireTime.getExpireTime() * 1000); SimpleDateFormat sdf = new SimpleDateFormat ("yyyy-MM-dd",Locale.CHINA); Log.i(TAG, "expire time: " + sdf.format(date)) ; if (objExpireTime.getExpireTime() * 1000 > System.currentTimeMillis()) { Log.i(TAG, "checkAuth success"); return initResult; } } // 获取过期时间失败或者已经过期 initResult = HciCloudSys.hciCheckAuth(); if (initResult == HciErrorCode.HCI_ERR_NONE) { Log.i(TAG, "checkAuth success"); return initResult; } else { Log.e(TAG, "checkAuth failed: " + initResult); return initResult; } }
3.3 HWR初始化
在初始化灵通用模块和获取授权成功后,我们还需要调用初始化函数初始化HWR能力。
- Android代码
HwrInitParam hwrInitParam = new HwrInitParam();
//设置本地资源库的路径
hwrInitParam.addParam(HwrInitParam.PARAM_KEY_DATA_PATH,
"/storage/emulated/0/sinovoice/com.sinovoice.example/data");
hwrInitParam.addParam(HwrInitParam.PARAM_KEY_INIT_CAP_KEYS, "hwr.local.freestylus");
int errCode = HciCloudHwr.hciHwrInit(hwrInitParam.getStringConfig());
对于使用本地多能力时,可以使用initCapKeys传入需要使用的所有能力,以';'隔开。 initCapKeys的作用是告知SDK,需要预先准备哪些能力并提前加载对应的库资源,提高执行效率。
- Android代码
initParam.addParam(HwrInitParam.PARAM_KEY_INIT_CAP_KEYS,
"hwr.local.letter;hwr.local.freestylus;hwr.local.associateword");
3.4 开启会话(session)
在HWR能力初始化之后,需要通过开启识别会话来启用一种具体的能力。
- Android代码
HwrConfig sessionConfig = new HwrConfig();
//调用具体能力需要相应的capkey,以开启多字识别为例
sessionConfig.addParam(HwrConfig.SessionConfig.PARAM_KEY_CAP_KEY, "hwr.local.freestylus");
Session session = new Session();
// 开始会话
errCode = HciCloudHwr.hciHwrSessionStart(sessionConfig.getStringConfig(), session);
3.5 HWR识别
在启动会话(session)成功后,即可进行 HWR 识别了。HWR识别具体可分为单字、多字识别,获取联想词,笔势识别以及拼音识别等,各识别功能以本地能力为例,详见如下各小章节。
配置参数详见灵云SDK开发手册。
3.5.1 单、多字识别
- Android代码
HwrRecogResult recogResult = new HwrRecogResult();
String recogConfig = "";
//strokes是笔迹数据,以“-1 ,0 , -1, -1” 结尾
errCode = HciCloudHwr.hciHwrRecog(session, strokes, recogConfig.getStringConfig(),
recogResult);
if (HciErrorCode.HCI_ERR_NONE == errCode)
{
ShowMessage("hciHwrRecog Success");
//输出识别结果
showRecogResultResult(recogResult);
}
else
{
ShowMessage("hciHwrRecog error:" + HciCloudSys.hciGetErrorInfo(errCode));
}
3.5.2 获取联想词
- Android代码
//开始联想
String associateWords = "中国";
HwrAssociateWordsResult associateResult = new HwrAssociateWordsResult();
errCode = HciCloudHwr.hciHwrAssociateWords(session,
recogConfig.getStringConfig(),
associateWords,
associateResult);
if (HciErrorCode.HCI_ERR_NONE == errCode)
{
ShowMessage("hciHwrAssociateWords Success");
showAssociateResultResult(associateResult);
}
else
{
ShowMessage("hciHwrRecog error:" + HciCloudSys.hciGetErrorInfo(errCode));
}
3.5.3 笔势识别
- Android代码
HwrConfig sessionConfig = new HwrConfig();
sessionConfig.addParam(HwrConfig.SessionConfig.PARAM_KEY_CAP_KEY, "hwr.local.gesture");
Session session = new Session();
// 开始会话
errCode = HciCloudHwr.hciHwrSessionStart(sessionConfig.getStringConfig(), session);
HwrRecogResult recogResult = new HwrRecogResult();
String recogConfig = "";
//strGestureStrokeData:笔势识别使用的点集数据,以“-1 ,0 , -1, -1” 结尾
errCode = HciCloudHwr.hciHwrRecog(session, strGestureStrokeData, recogConfig.getStringConfig(),
recogResult);
if (HciErrorCode.HCI_ERR_NONE == errCode)
{
ShowMessage("hciHwrRecog Success");
//输出识别结果
showRecogResultResult(recogResult);
}
else
{
ShowMessage("hciHwrRecog error:" + HciCloudSys.hciGetErrorInfo(errCode));
}
3.5.4 模拟笔形
- Android代码
Bitmap mBitmap = Bitmap.createBitmap(1080, 1785, Config.ARGB_8888);
int size = strokes.length;
HwrPenScriptResult hwrPenScriptResult = new HwrPenScriptResult();
String strPenscriptConfig = "penmode=brush";
for(int positionIndex = 0 ;positionIndex+1<size;positionIndex+=2)
{
if(strokes[positionIndex]== -1 && strokes[positionIndex+1] == -1)
{
break;
}
errCode = HciCloudHwr.hciHwrPenScript(strPenscriptConfig,
strokes[positionIndex],
strokes[positionIndex+1],
hwrPenScriptResult);
if (errCode != HciErrorCode.HCI_ERR_NONE)
{
ShowMessage("hciHwrPenScript failed,return "+errCode);
break;
}
else {
for (int nIndex = 0; nIndex < hwrPenScriptResult
.getPenScriptResultList().size(); nIndex++)
{
HwrPenScriptResultItem item = hwrPenScriptResult
.getPenScriptResultList().get(nIndex);
short[] mPageImg = item.getPageImg();
for(int i = 0; i < item.getHeight(); i++ )
{
for ( int j = 0; j < item.getWidth(); j++ )
{
if(mPageImg[item.getWidth() * i + j] == 0)
{
mBitmap.setPixel(j + item.getX(), i + item.getY(),
item.getPenColor() | 0xFF000000);
}
}
}
}
}
}
if (errCode == HciErrorCode.HCI_ERR_NONE) {
ShowMessage("hciHwrPenScript success");
saveBitmap("penscript",mBitmap);
}
3.5.5 获取拼音
- Android代码
暂不支持
3.5.6 实时识别
启用实时识别时,对于每次连续的识别内容,可以多次调用hci_hwr_recog() 。每次调用追加输入新的数据, 每次输入的数据以(-1,0,-1,-1)结束,也即每次输入的笔画是完整的,可以一次输入多个笔画。
每次实时识别时,不论是单个笔画或是一次输入多个笔画,笔迹最后需以(-1,0)(-1,-1)结束。
启动实时识别时,需要在创建识别会话时,加入realtime=yes选项。实时识别时,每次调用本函数都会返回从头开始的完整结果,新输入的数据会导致切分发生变化, 因此后一次结果不一定是前次结果再追加字符,可能会更改掉部分前次结果。实时识别中每次返回的识别结果都需要释放。
- Android代码
HwrConfig sessionConfig = new HwrConfig();
sessionConfig.addParam(HwrConfig.SessionConfig.PARAM_KEY_CAP_KEY, "hwr.local.freestylus");
sessionConfig.addParam(HwrConfig.SessionConfig.PARAM_KEY_REALTIME, "yes");
Session nSessionId = new Session();
//开始会话
errCode = HciCloudHwr.hciHwrSessionStart(sessionConfig.getStringConfig(), nSessionId);
while(true) {
HwrRecogResult recogResult = new HwrRecogResult();
//开始识别
//sTempShortData:每次传入的笔迹,以(-1,0)(-1,-1)结尾。
//例如‘人’字是两划,那么左边一撇和右边一撇单独传入识别,以-1,0)(-1,-1)结尾。
errCode = HciCloudHwr.hciHwrRecog(nSessionId, sTempShortData,
recogConfig.getStringConfig(), recogResult)
if (HciErrorCode.HCI_ERR_NONE != errCode) {
System.out.println("HciCloudHwr hciHwrRecog return " + errCode);
return;
}
// 如果是最后一笔,跳出循环
if (is_last_stroke())
break;
}
3.5.7 英文连写识别
- Android 代码
HwrConfig sessionConfig = new HwrConfig();
sessionConfig.addParam(HwrConfig.SessionConfig.PARAM_KEY_CAP_KEY, "hwr.local.freestylus.v7");
Session session = new Session();
// 开始会话
errCode = HciCloudHwr.hciHwrSessionStart(sessionConfig.getStringConfig(), session);
HwrRecogResult recogResult = new HwrRecogResult();
String recogConfig = "";
//strokes是笔迹数据,以“-1 ,0 , -1, -1” 结尾
errCode = HciCloudHwr.hciHwrRecog(session, strokes, recogConfig.getStringConfig(),
recogResult);
if (HciErrorCode.HCI_ERR_NONE == errCode)
{
ShowMessage("hciHwrRecog Success");
//输出识别结果
showRecogResultResult(recogResult);
}
else
{
ShowMessage("hciHwrRecog error:" + HciCloudSys.hciGetErrorInfo(errCode));
}
3.6 结束识别
最后我们需要反初始化,依次关闭会话,终止HWR能力,关闭灵云系统。
- Android代码
//关闭HWR识别会话
errCode = HciCloudHwr.hciHwrSessionStop(session);
//终止HWR能力
errCode = HciCloudHwr.hciHwrRelease();
//终止灵云系统
errCode = HciCloudSys.hciRelease();
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,代表执行成功,请您继续后续的集成。