使用灵云的手写识别功能需要先在官网上进行注册应用。官网地址
注册比较简单,就不做过多介绍了,注册完应用以后,在后台创建自己的应用,创建完应用以后需要给应用开通对应的手写功能。
capKey说明:
下载灵云的Android版本手写识别功能,下载地址
如果使用在线功能,下载对应的SDK,里面有jar包和so,就可以满足需求了。如果要使用离线的手写识别功能,还需要下载灵云资源文件
资源文件列表:
如果是在android平台上使用,需要修改一下名字,修改为如下,并加入到libs/armeabi目录下
Github
需要加入的so和jar包有:
需要使用的权限
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
加载配置类
package com.miao.util;
/**
* 灵云配置信息
* Created by 10048 on 2016/12/3.
*/
public class ConfigUtil {
/**
* 灵云APP_KEY
*/
public static final String APP_KEY = "c85d54f1";
/**
* 开发者密钥
*/
public static final String DEVELOPER_KEY = "712ddd892cf9163e6383aa169e0454e3";
/**
* 灵云云服务的接口地址
*/
public static final String CLOUD_URL = "test.api.hcicloud.com:8888";
/**
* 需要运行的灵云能力
*/
//云端多字识别功能
public static final String CAP_KEY_HWR_CLOUD_FREETALK = "hwr.cloud.freetalk";
//云端单字识别功能
public static final String CAP_KEY_HWR_CLOUD_LETTER = "hwr.cloud.letter";
//离线单字识别功能
public static final String CAP_KEY_HWR_LOCAL_LETTER = "hwr.local.letter";
//离线多字识别功能
public static final String CAP_KEY_HWR_LOCAL_FREESTYLUS = "hwr.local.freestylus";
//离线联想功能
public static final String CAP_KEY_HWR_LOCAL_ASSOCIATE_WORD = "hwr.local.associateword";
//离线笔形功能
public static final String CAP_KEY_HWR_LOCAL_PENSCRIPT = "hwr.local.penscript";
}
封装灵云系统的初始化功能
package com.example.sinovoice.util;
import android.content.Context;
import android.os.Environment;
import android.util.Log;
import com.sinovoice.hcicloudsdk.api.HciCloudSys;
import com.sinovoice.hcicloudsdk.common.AuthExpireTime;
import com.sinovoice.hcicloudsdk.common.HciErrorCode;
import com.sinovoice.hcicloudsdk.common.InitParam;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
/**
* Created by miaochangchun on 2016/11/28.
*/
public class HciCloudSysHelper {
private static final String TAG = HciCloudSysHelper.class.getSimpleName();
private static HciCloudSysHelper mHciCloudSysHelper = null;
private HciCloudSysHelper(){
}
public static HciCloudSysHelper getInstance() {
if (mHciCloudSysHelper == null) {
return new HciCloudSysHelper();
}
return mHciCloudSysHelper;
}
/**
* 初始化函数
* @param context
* @return
*/
public int init(Context context){
//配置串参数
String strConfig = getInitParam(context);
int errCode = HciCloudSys.hciInit(strConfig, context);
if (errCode != HciErrorCode.HCI_ERR_NONE){
Log.e(TAG, "hciInit Failed and return errcode = " + errCode);
return errCode;
}
errCode = checkAuthAndUpdateAuth();
if (errCode != HciErrorCode.HCI_ERR_NONE) {
Log.e(TAG, "checkAuthAndUpdateAuth Failed and return errcode = " + errCode);
return errCode;
}
return HciErrorCode.HCI_ERR_NONE;
}
/**
* 获取授权
* @return
*/
private int checkAuthAndUpdateAuth() {
// 获取系统授权到期时间
AuthExpireTime objExpireTime = new AuthExpireTime();
int 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()) {
// 已经成功获取了授权,并且距离授权到期有充足的时间(>7天)
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;
}
}
/**
* 获取配置传参数
* @param context
* @return
*/
private String getInitParam(Context context) {
InitParam initParam = new InitParam();
//灵云云服务的接口地址,此项必填
initParam.addParam(InitParam.AuthParam.PARAM_KEY_APP_KEY, ConfigUtil.APP_KEY);
//灵云云服务的接口地址,此项必填
initParam.addParam(InitParam.AuthParam.PARAM_KEY_DEVELOPER_KEY, ConfigUtil.DEVELOPER_KEY);
//灵云云服务的接口地址,此项必填
initParam.addParam(InitParam.AuthParam.PARAM_KEY_CLOUD_URL, ConfigUtil.CLOUD_URL);
String authPath = context.getFilesDir().getAbsolutePath();
//授权文件所在路径,此项必填
initParam.addParam(InitParam.AuthParam.PARAM_KEY_AUTH_PATH, authPath);
//日志数目,默认保留多少个日志文件,超过则覆盖最旧的日志
initParam.addParam(InitParam.LogParam.PARAM_KEY_LOG_FILE_COUNT, "5");
String logPath = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator
+ "sinovoice" + File.separator
+ context.getPackageName() + File.separator
+ "log" + File.separator;
File file = new File(logPath);
if (!file.exists()) {
file.mkdirs();
}
//日志的路径,可选,如果不传或者为空则不生成日志
initParam.addParam(InitParam.LogParam.PARAM_KEY_LOG_FILE_PATH, logPath);
Log.d(TAG, "logPath = " + logPath);
//日志大小,默认一个日志文件写多大,单位为K
initParam.addParam(InitParam.LogParam.PARAM_KEY_LOG_FILE_SIZE, "1024");
//日志等级,0=无,1=错误,2=警告,3=信息,4=细节,5=调试,SDK将输出小于等于logLevel的日志信息
initParam.addParam(InitParam.LogParam.PARAM_KEY_LOG_LEVEL, "5");
return initParam.getStringConfig();
}
/**
* 反初始化
* @return
*/
public int release(){
return HciCloudSys.hciRelease();
}
}
封装灵云手写识别功能
package com.miao.util;
import android.content.Context;
import android.util.Log;
import com.sinovoice.hcicloudsdk.api.hwr.HciCloudHwr;
import com.sinovoice.hcicloudsdk.common.HciErrorCode;
import com.sinovoice.hcicloudsdk.common.Session;
import com.sinovoice.hcicloudsdk.common.hwr.HwrAssociateWordsResult;
import com.sinovoice.hcicloudsdk.common.hwr.HwrConfig;
import com.sinovoice.hcicloudsdk.common.hwr.HwrInitParam;
import com.sinovoice.hcicloudsdk.common.hwr.HwrRecogResult;
import com.sinovoice.hcicloudsdk.common.hwr.HwrRecogResultItem;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* Created by miaochangchun on 2016/11/1.
*/
public class HciCloudHwrHelper {
public static final String TAG = HciCloudHwrHelper.class.getSimpleName();
private static HciCloudHwrHelper mHciCloudHwrHelper = null;
private HciCloudHwrHelper(){
}
public static HciCloudHwrHelper getInstance(){
if (mHciCloudHwrHelper == null) {
return new HciCloudHwrHelper();
}
return mHciCloudHwrHelper;
}
/**
* 手写功能初始化
* @param context 上下文
* @param capkey 使用的capkey,手写单字识别为hwr.local.letter,多字识别为hwr.local.freestylus,联想功能为hwr.local.associateword
* @return 返回0为成功,其他为失败
*/
public int initHwr(Context context, String capkey){
String strConfig = getHwrInitParam(context, capkey);
Log.d(TAG, "strConfig = " + strConfig);
int errorCode = HciCloudHwr.hciHwrInit(strConfig);
return errorCode;
}
/**
* 手写识别函数
* @param data 笔迹坐标
* @param capkey 使用的capkey,手写单字识别为hwr.local.letter,多字识别为hwr.local.freestylus,联想功能为hwr.local.associateword
* @return 返回识别的结果
*/
public String recog(short[] data, String capkey){
Session session = new Session();
String sessionConfig = getHwrSessionParam(capkey);
Log.d(TAG, "sessionConfig = " + sessionConfig);
//sessionStart开启一个session
int errorCode = HciCloudHwr.hciHwrSessionStart(sessionConfig, session);
if (errorCode != HciErrorCode.HCI_ERR_NONE) {
Log.e(TAG, "hciHwrSessionStart failed and return " + errorCode);
HciCloudHwr.hciHwrSessionStop(session);
return null;
}
//识别结果类进行初始化
HwrRecogResult recogResult = new HwrRecogResult();
//识别的配置串参数可以设置为空,默认就使用sessionConfig配置串参数
errorCode = HciCloudHwr.hciHwrRecog(session, data, "", recogResult);
if (errorCode != HciErrorCode.HCI_ERR_NONE) {
Log.e(TAG, "hciHwrRecog failed and return " + errorCode);
return null;
}
//关闭Session
errorCode = HciCloudHwr.hciHwrSessionStop(session);
if (errorCode != HciErrorCode.HCI_ERR_NONE) {
Log.e(TAG, "hciHwrSessionStop failed and return " + errorCode);
return null;
}
//返回识别结果
StringBuilder sb = new StringBuilder();
List lists = recogResult.getResultItemList();
Iterator iterator = lists.iterator();
while (iterator.hasNext()) {
HwrRecogResultItem item = iterator.next();
sb.append(item.getResult()).append(" , ");
}
return sb.toString();
// return recogResult.getResultItemList().get(0).getResult();
}
/**
* 联想词功能,对str进行联想,返回联想结果
* @param str 需要联想的字符串
* @param assCapkey 联想功能对应的capkey。
* @return
*/
public String associateWord(String str, String assCapkey){
Session session = new Session();
String sessionConfig = getAssociateWordSessionParam(assCapkey);
int errorCode = HciCloudHwr.hciHwrSessionStart(sessionConfig, session);
if (errorCode != HciErrorCode.HCI_ERR_NONE) {
Log.e(TAG, "HciCloudHwr.hciHwrSessionStart failed and return " + errorCode);
}
HwrAssociateWordsResult hwrAssociateWordsResult = new HwrAssociateWordsResult();
errorCode = HciCloudHwr.hciHwrAssociateWords(session, str, "", hwrAssociateWordsResult);
if (errorCode != HciErrorCode.HCI_ERR_NONE) {
Log.e(TAG, "HciCloudHwr.hciHwrAssociateWords failed and return " + errorCode);
}
errorCode = HciCloudHwr.hciHwrSessionStop(session);
if (errorCode != HciErrorCode.HCI_ERR_NONE) {
Log.e(TAG, "HciCloudHwr.hciHwrSessionStop failed and return " + errorCode);
}
ArrayList lists = hwrAssociateWordsResult.getResultList();
StringBuilder sb = new StringBuilder();
Iterator iterator = lists.iterator();
while (iterator.hasNext()) {
String string = iterator.next();
sb.append(string).append(",");
}
return sb.toString();
}
/**
* 设置联想词的配置参数
* @param assCapkey 联想词功能所需的capkey,需要设置为 assCapkey=hwr.local.associateword
* @return 联想词配置的字符串
*/
private String getAssociateWordSessionParam(String assCapkey) {
HwrConfig hwrConfig = new HwrConfig();
hwrConfig.addParam(HwrConfig.SessionConfig.PARAM_KEY_CAP_KEY, assCapkey);
return hwrConfig.getStringConfig();
}
/**
* 获取手写识别的配置参数
* @param hwrCapkey 使用的capkey,手写单字识别为hwr.local.letter,多字识别为hwr.local.freestylus
* @return 返回配置串
*/
private String getHwrSessionParam(String hwrCapkey) {
HwrConfig hwrConfig = new HwrConfig();
hwrConfig.addParam(HwrConfig.SessionConfig.PARAM_KEY_CAP_KEY, hwrCapkey);
//设置识别结果的候选个数
hwrConfig.addParam(HwrConfig.ResultConfig.PARAM_KEY_CAND_NUM, "10");
//设置识别结果的范围
hwrConfig.addParam(HwrConfig.ResultConfig.PARAM_KEY_RECOG_RANGE, "gbk");
return hwrConfig.getStringConfig();
}
/**
* 获取手写的初始化配置参数
* @param context 上下文
* @param initCapkeys 使用的capkey,手写单字识别为hwr.local.letter,
* 多字识别为hwr.local.freestylus,
* 联想功能为hwr.local.associateword
* 可以设置多个,中间以分号隔开
* @return 返回配置串
*/
private String getHwrInitParam(Context context, String initCapkeys) {
HwrInitParam hwrInitParam = new HwrInitParam();
hwrInitParam.addParam(HwrInitParam.PARAM_KEY_INIT_CAP_KEYS, initCapkeys);
hwrInitParam.addParam(HwrInitParam.PARAM_KEY_FILE_FLAG, "android_so");
String dataPath = context.getFilesDir().getAbsolutePath().replace("files", "lib");
hwrInitParam.addParam(HwrInitParam.PARAM_KEY_DATA_PATH, dataPath);
return hwrInitParam.getStringConfig();
}
/**
* 手写反初始化功能
* @return 返回0为成功,其他为失败
*/
public int releaseHwr(){
return HciCloudHwr.hciHwrRelease();
}
}
在MainActivity中使用手写识别的功能
package com.miao.hwrtest;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PaintFlagsDrawFilter;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.Window;
import android.widget.EditText;
import android.widget.Toast;
import com.miao.util.ConfigUtil;
import com.miao.util.HciCloudHwrHelper;
import com.miao.util.HciCloudSysHelper;
import com.sinovoice.hcicloudsdk.common.HciErrorCode;
public class MainActivity extends AppCompatActivity implements SurfaceHolder.Callback{
private static final int MAX_POINT = 2048;
private SurfaceHolder surfaceHolder;
private SurfaceView surface;
private Paint paint;
private Path path;
private int top;
private int bottom;
private boolean isRunning = true;
private Canvas canvas;
private String TAG = MainActivity.class.getSimpleName();
private boolean start = true;
private long init;
private long now;
private int mCurX, mCurY;
private int mCurIndex;
private short[] mPoints = null;
private boolean mEnd;
private HciCloudSysHelper mHciCloudSysHelper;
private HciCloudHwrHelper mHciCloudHwrHelper;
private EditText myEditText;
private Handler myHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.arg1) {
case 0:
Bundle bundle = msg.getData();
String result = bundle.getString("result");
myEditText.setText(result);
break;
default:
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
initView();
initSinovoice();
}
private void initView() {
surface = (SurfaceView) findViewById(R.id.surface);
myEditText = (EditText) findViewById(R.id.text);
surfaceHolder = surface.getHolder(); // 获得SurfaceHolder对象
surface.setZOrderOnTop(true); //使surface可见
surfaceHolder.setFormat(PixelFormat.TRANSPARENT); //设置背景透明
surfaceHolder.addCallback(this); // 为SurfaceView添加状态监听
paint = new Paint(); // 创建一个画笔对象
path = new Path();
mPoints = new short[MAX_POINT * 2];
mCurIndex = 0;
}
/**
* 灵云系统初始化
*/
private void initSinovoice() {
mHciCloudSysHelper = HciCloudSysHelper.getInstance();
mHciCloudHwrHelper = HciCloudHwrHelper.getInstance();
int errorCode = mHciCloudSysHelper.init(this);
if (errorCode != HciErrorCode.HCI_ERR_NONE) {
Toast.makeText(this, "系统初始化失败,错误码=" + errorCode, Toast.LENGTH_SHORT).show();
return;
}
errorCode = mHciCloudHwrHelper.initHwr(this, ConfigUtil.CAP_KEY_HWR_LOCAL_LETTER);
if (errorCode != HciErrorCode.HCI_ERR_NONE) {
Toast.makeText(this, "手写初始化失败,错误码=" + errorCode, Toast.LENGTH_SHORT).show();
return ;
}
}
/**
* 把坐标添加到short数组
* @param x
* @param y
* @return
*/
private boolean addStroke(short x, short y) {
if (mCurX >= 0 && mCurY >= 0) {
if ((mCurIndex / 2) < (MAX_POINT - 2)) {
mPoints[mCurIndex] = x;
mCurIndex++;
mPoints[mCurIndex] = y;
mCurIndex++;
return true;
} else if ((mCurIndex / 2) == (MAX_POINT - 2)) {
mPoints[mCurIndex] = -1;
mCurIndex++;
mPoints[mCurIndex] = 0;
mCurIndex++;
return true;
}
}
return false;
}
/**
* 最后一笔添加坐标(-1,-1)
*/
public void addLastStrokePoint() {
if(mCurIndex < 2 ||(mPoints[mCurIndex-1] == -1 && mPoints[mCurIndex-2] == -1))
{
return;
}
mPoints[mCurIndex] = -1;
mCurIndex++;
mPoints[mCurIndex] = -1;
mCurIndex++;
}
/**
* 每次抬笔添加坐标(-1,0)
*/
private void addStrokeEnd() {
mPoints[mCurIndex] = -1;
mCurIndex++;
mPoints[mCurIndex] = 0;
mCurIndex++;
}
/**
* 重置笔迹点数据
*/
private void resetStroke() {
mPoints = new short[MAX_POINT * 2];
mCurIndex = 0;
}
/**
* 获取笔迹点数组
* @return 笔迹点数组
*/
public short[] getStroke() {
mEnd = true;
addStrokeEnd();
addLastStrokePoint();
short[] stroke = new short[mCurIndex];
System.arraycopy(mPoints, 0, stroke, 0, mCurIndex);
return stroke;
}
/**
* 当SurfaceView创建的时候,调用此函数
* @param holder
*/
@Override
public void surfaceCreated(SurfaceHolder holder) {
int[] location = new int[2];
surface.getLocationOnScreen(location);
top = location[1];
Log.d(TAG, "top = " + top);
bottom = top + surface.getHeight();
Log.d(TAG, "bottom = " + bottom);
//开启一个绘画线程
new Thread(wlineThread).start();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getY() >= top && event.getY() <= bottom) {
start = true;
init = now;
now = System.currentTimeMillis();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (now - init >= 100 && now - init <= 1000) { //抬笔操作,加上(-1,0)
// Log.d(TAG, "X坐标=" + "-1" + "\tY坐标=" + "0");
addStrokeEnd();
}
path.moveTo(event.getX(), event.getY() - top);
// Log.d(TAG, "X坐标=" + event.getX() + "\tY坐标=" + event.getY());
// addStroke((short) event.getX(), (short) event.getY());
break;
case MotionEvent.ACTION_MOVE:
path.lineTo(event.getX(), event.getY() - top);
// Log.d(TAG, "X坐标=" + event.getX() + "\tY坐标=" + event.getY());
addStroke((short) event.getX(), (short) event.getY());
break;
default:
break;
}
}
return true;
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
isRunning = false;
}
return super.onKeyDown(keyCode, event);
}
/**
* 当SurfaceView的视图发生改变的时候,调用此函数
* @param holder
* @param format
* @param width
* @param height
*/
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
/**
* 当SurfaceView销毁的时候,调用此函数
* @param holder
*/
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
Log.d(TAG, "surface Destroyed");
isRunning = false;
}
Runnable wlineThread =new Runnable(){
@Override
public void run() {
while (isRunning) {
drawView();
if (start) {
long temp = System.currentTimeMillis() - now;
// Log.d(TAG, "temp=" + temp);
if (temp > 1000 && temp < 100000) { //抬笔时间超过1秒,加上坐标(-1,-1),过滤第一次时间的计算
// Log.d(TAG, "X坐标=" + "-1" + "\tY坐标=" + "-1");
short[] data = getStroke();
String result = mHciCloudHwrHelper.recog(data, ConfigUtil.CAP_KEY_HWR_LOCAL_LETTER);
Message message = new Message();
message.arg1 = 0;
Bundle bundle = new Bundle();
bundle.putString("result", result);
message.setData(bundle);
myHandler.sendMessage(message);
start = false;
clearCanvas();
}
}
}
}
};
/**
* 清空Canvas上的笔迹
*/
private void clearCanvas() {
for (int i = 0; i < 4; i++) {
try {
if (surfaceHolder != null) {
canvas = surfaceHolder.lockCanvas();
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
path.reset();
resetStroke();
mEnd = false;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (canvas != null){
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
}
/**
* 再Canvas上画
*/
private void drawView() {
try {
if (surfaceHolder != null) {
canvas = surfaceHolder.lockCanvas();
canvas.setDrawFilter(new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG));
paint.setColor(Color.RED);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(5);
canvas.drawPath(path, paint);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (canvas != null) {
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
@Override
protected void onDestroy() {
releaseSinovoice();
super.onDestroy();
}
/**
* 灵云系统反初始化
*/
private void releaseSinovoice() {
if (mHciCloudHwrHelper != null) {
mHciCloudHwrHelper.releaseHwr();
}
if (mHciCloudSysHelper != null) {
mHciCloudSysHelper.release();
}
}
}
注:灵云的离线手写识别能力,第一次使用的时候也是需要有一个联网授权的过程,授权成功以后,即可在授权期内使用离线手写能力。