最近在做一个安卓手机端的号码识别项目,号码识别代码是用C开发的,要将其应用于安卓平台,可通过jni来实现,最初的想法是,由于训练文件是一旦确定,便不会再更改,所以希望将训练好的参数文件放只在安卓项目的assets文件夹中,由安卓端传递文件路径给C代码端,由C代码端直接访问解析参数文件,获取所需数据,经多次尝试失败后,发现jni中只能通过AAssetManager读取assets目录中文件内容,并将内容存储到一个char*的buffer中传给C代码。那么问题来了,这个buffer中存储的是文件中的所有内容,如果是knn训练好的xml文件,不仅有C代码端需要的两个Mat数据,还有一些其他杂项,我很难从中将Mat数据提取出来并加以区分,于是想到在训练的时候将训练数据和训练标签两个Mat数据保存在一个txt文件中,人为记住其大小,然后将其放在安卓项目的assets文件夹中,进而在预测的时候将其读入两个Mat中,先用于训练,创建Knn,以便进行预测,最后测试通过。
其中用到的一些函数如下:
1. 同时写两个Mat到txt文件中
intWriteMatToFile(char *fileName, Mat src,Mat src2)
{
FILE*fp;
fp= fopen(fileName, "wb+");//一定要是”wb+”方式,如果为”w+”方式,写出的txt文件中会都一些空格,导致数据不对
if(fp!= NULL)
{
fwrite(src.data,src.elemSize(), src.rows * src.cols, fp);
fwrite(src2.data,src2.elemSize(), src2.rows * src2.cols, fp);
fclose(fp);
}
else
{
return-1;
}
return0;
}
2. 读取txt文件中内容到char*的buffer中
stringstPath;//txt文件路径
FILE *fp;
fp= fopen(stPath.c_str(), "rb");
fseek(fp,0,SEEK_END);//定位到文件末
intnFileLen = ftell(fp); //文件大小
char*dst = (char*)malloc(nFileLen+1); /* 根据文件大小动态分配内存空间 */
if(fp != NULL)
{
fseek(fp,0, SEEK_SET);
fread(dst,nFileLen, 1, fp);
fclose(fp);
}
3. 将文件路径中的’/’改为’\\’
charstParaName[256] = “E:/android/tt.txt”;
int length = strlen(stParaName);
intcnt = 0;
for(inti= 0; i < length ; i++)
{
if(stParaName[i]=='/')
{
cnt++;
}
}
intcount = 0;
stringstPath;
stPath.resize(length+cnt);
for(int i=0; i<length; ++i)
{
if(stParaName[i] == '/')
{
count++;
stPath[i+count-1]= '\\';
stPath[i+count]= '\\';
}
else
{
stPath[i+count]= stParaName[i];
}
}
4. Jni使用AAssetManager读取assets中文件内容
#include <android/asset_manager.h> #include <android/asset_manager_jni.h>
JNIEXPORTjstring JNICALL Java_pack_idnumocr_IDNumOCR_IDNumOCR (JNIEnv *env, jclass, jintArray buf, jint w, jint h,jobject assetManager, jstring fileName, jint normSize, jint K) { LOGI("ReadAssets"); AAssetManager* mgr = AAssetManager_fromJava(env, assetManager); if(mgr==NULL) { LOGI(" %s","AAssetManager==NULL"); return 0; } jboolean iscopy; const char *mfile = env->GetStringUTFChars(fileName, &iscopy); //char *mfile = (char*)env->GetStringUTFChars(fileName, &iscopy); AAsset* asset = AAssetManager_open(mgr, mfile,AASSET_MODE_UNKNOWN); if(asset==NULL) { LOGI(" %s","asset==NULL"); return 0; } off_t bufferSize = AAsset_getLength(asset); LOGI("file size : %d\n",bufferSize); char *buffer=(char *)malloc(bufferSize+1); buffer[bufferSize]=0; int numBytesRead = AAsset_read(asset, buffer, bufferSize); LOGI(": %s",buffer); //将string传给JString的方法 string output = "abcdefg"; LOGE("打印日志信息: string output = %d",output.length()); int len = output.length(); if (len == 0) { return NULL;//当数据为空,返回NULL }
const char* data = output.c_str();//使用NewStringUTF时,形参必须为const char*,若为char*时,会报错误JNI DETECTED ERROR IN APPLICATION: input is not valid Modified UTF-8: illegal start byte 0xff //释放资源 free(buffer); //一定要记得释放,否则导致程序闪退 AAsset_close(asset); //一定要记得释放,否则导致程序闪退 jstring result = env->NewStringUTF(data); env->ReleaseIntArrayElements(buf, cbuf, 0); //一定要记得释放,否则导致程序闪退 env->ReleaseStringUTFChars(fileName, mfile); //一定要记得释放,否则导致程序闪退 return result; }
需要
#include <android/asset_manager.h> #include <android/asset_manager_jni.h>
并且在Application.mk文件中设置APP_PLATFORM := android-15 #这句是设置最低安卓平台
在Android.mk文件中设置//LOCAL_LDLIBS += -lm -llog LOCAL_LDLIBS += -L$(SYSROOT)/usr/lib -llog LOCAL_LDLIBS += -landroid
特别地,native接口函数为public native static String numOCR(int[] buff, int w, int h,AssetManager ass,String trainedFile,int normSize, int K);
Java端对String是否为空的判断是:String a;if(a == null)