!!!以下内容为作者原创,首发于掘金平台。未经原作者同意与许可,任何人、任何组织不得以任何形式转载。原创不易,如果对您的问题提供了些许帮助,希望得到您的点赞支持。
0.写在最前面
上一篇文章与大家分享了本地用paddleOcr搭建本地图片文字识别,感兴趣同学可以去看看手把手0基础Centos下安装与部署paddleOcr 教程
但因为各种原因,公司还是想再找一个安卓设备端直接Ocr SDK方案。经过网上一通搜索与寻找,找到百度AI —— 文字识别提供的离线SDK。
为什么要做apicloud的模块呢? 因为我们公司安卓app开发技术栈主要用的是apicloud,所以就引出本篇教程:apicloud项目中如何开发百度安卓ocr sdk模块
本篇教程技术要求:
1.了解apicloud。最好是熟悉apicloud中的模块开发(没开发过模块也没问题,只需要把下面官方视频教程看完就可以) 官方模块开发视频教程链接
2.了解安卓原生开发基本知识。
1. 环境准备
- 开发工具:
- 项目下载
apicloud 官方模块开发demo 下载链接
百度Ocr SDK:
百度离线SDK是收费的,单个设备授权费用299。不过官方也提供了2个免费试用的授权,激活开始有效期30天。请到百度AI后台自行申请并下载SDK
2.Android Studio导入百度SDK DEMO运行
- 百度sdk demo导入进来基本没有任务问题,配置一下本地SDK,NDK,JDK路径就可以正常运行到安卓设备中。
注意:百度SDK对安卓版本应该是有要求的,手上几台设备测试下来:
安卓5.1.1 X不支持
安卓10 支持
其它版本的设备未做测试
-
百度后台激活授权码 通过申请试用后,会自动分配2个试用版的授权,进入后台“添加序列号”,然后稍等片刻,将下方生成的序列号复制出来:
-
修改授权码并运行 修改PredictorWrapper中的initLicense方法,将序列号替换为上一步刚刚生成的序列号
public static boolean initLicense(Activity activity, TextView textView) {
// 获取鉴权相关本地设备及应用相关信息
BDLicenseLocalInfo bdLicenseLocalInfo =
AndroidLicenser.getInstance().authGetLocalInfo(activity, Predictor.getAlgorithmId());
Log.d(TAG, "BDLicenseLocalInfo :" + bdLicenseLocalInfo.toString());
// 在线自动激活
int ret = BDLicenseActivator.initLicenseOnLine(activity, "BADH-XXXX-XXXX-VXVY",
"", Predictor.getAlgorithmId());
if (ret != 0) {
setTextViewOnUiThread(activity, textView, "鉴权失败");
} else {
setTextViewOnUiThread(activity, textView, "鉴权成功");
}
return true;
}
- 选择连接设备调试运行
USB连上设备,在android studio中选择上设备直接运行。
如果选不到设备请检查: 1.设备打开开发者模式 2.设备usb连接时选择传输文件
先点击初始化,会提示鉴权成功,初始化模型成功
然后可以使用官方DEMO里面提供的3种方式获取图片,并进行ocr识别
3. AndroidStudio 导入Apicloud模块开发demo
apicloud模块开发demo下载下来解压开是分android studio和eclipse,我们直接在androidstudio导入android studio目录下的APICloudModuleSDK 文件夹
3.1 demo项目做如下修改:
- 将app和所有模块的gradle.properties中最低版本修改为14。demo中很多模块都指定为9,后续运行会报错。
- 将app的build.gradle中添加一行minSdkVersion。demo中少了这行,会默认最低版本为1,一直报错。
defaultConfig {
applicationId project.ANDROID_BUILD_APP_PKG
targetSdkVersion Integer.parseInt(project.ANDROID_BUILD_TARGET_SDK_VERSION)
minSdkVersion Integer.parseInt(project.ANDROID_BUILD_MIN_SDK_VERSION)
versionCode Integer.parseInt(project.ANDROID_BUILD_VERSION_CODE)
versionName project.ANDROID_BUILD_VERSION_NAME
multiDexEnabled true
}
3.2 新建百度Ocr模块(moduleOcr)
- 碰到的第1个问题:
android studio新建模块必须要是androidx,否则会报如下错: Projects needs to be converted to androidx.* dependencies
但是模块开发demo却不支持androidx:
- 解决方法: 修改gradle.properties: 添加如下两行:
android.useAndroidX=true
android.enableJetifier=true
-
新建一个模块名称为moduleOcr apicloud模块开发对命名有规则约束,详情可以去apicloud官方的模块开发文档中查看。
-
将百度sdk下的所有的代码,资源,jniLibs下的so文件,assets下的所有文件都复制到当前模块下
为了区分,我将sdk中的MainActivity名称都修改为TestActivity,所以下面代码部分的TestActivity都是原sdk中的MainActivity
- 模块源代码下新增一个 OcrDemo.java ,内容如下(注意包名更改为自己模块中新建的包名):
package com.factoryeasy.ocr;
import android.content.Intent;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.widget.Toast;
import com.uzmap.pkg.uzcore.UZWebView;
import com.uzmap.pkg.uzcore.uzmodule.UZModule;
import com.uzmap.pkg.uzcore.uzmodule.UZModuleContext;
import static android.Manifest.permission.CAMERA;
import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
public class OcrDemo extends UZModule {
public OcrDemo(UZWebView webView) {
super(webView);
getPermission();
}
public void jsmethod_ocrDemo(UZModuleContext moduleContext){
Intent intent = new Intent(mContext, TestActivity.class);
startActivity(intent);
}
void getPermission() {
if (ContextCompat.checkSelfPermission(mContext, CAMERA) != PERMISSION_GRANTED ||
ContextCompat.checkSelfPermission(mContext, WRITE_EXTERNAL_STORAGE) != PERMISSION_GRANTED ||
ContextCompat.checkSelfPermission(mContext, READ_EXTERNAL_STORAGE) != PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(mContext,
new String[]{CAMERA, READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE}, 1);
}
}
}
3.3 androidx还原与模块代码调整
前面为了新建模块,我们暂时将gradle.properties中设备为androidx,但是官方的模块在集成时却不支持androidx,所以这一步我们就将环境改回来,并对moduleOcr中的代码做部分调整(主要是踢掉使用了androidx的部分)
- gradle.properties中将刚刚新增的两个参数设为false
android.useAndroidX=false
android.enableJetifier=false
- 用下面内容替换掉moduleOcr下的build.gradle内容,目的就是去除模块中的androidx依赖,并引入百度SDK lib依赖
apply plugin: 'com.android.library'
android {
compileSdkVersion 28
buildToolsVersion "28.0.03"
defaultConfig {
minSdkVersion 14
targetSdkVersion 28
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
}
}
sourceSets{
main{
jniLibs.srcDirs = ['libs']
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
provided files('../app/libs/apiEngine v1.1.0.jar')
implementation files('libs/bd_unifylicense.jar')
implementation files('libs/liantian.jar')
implementation files('libs/ocrgeneralocr.jar')
implementation 'com.android.support:support-v4:28.0.0'
}
- java源码修改: 将复制过来的百度sdk java源码部分做如下修改
- 找到所有的activity,将继承于 AppCompatActivity 改为 Activity
public class TestAssetsImageActivity extends Activity
-
将代码中其它部分报错的与androidx相关的都导入为工具提示的相应其它包
-
修改TestCameraImageActivity.getImageStreamFromExternal,将下面的FileProvider包名改成自动获取,不要写死为百度的包
if (Build.VERSION.SDK_INT >= 24) {
uri = FileProvider.getUriForFile(this, getPackageName() + ".fileprovider", picPath);
} else {
uri = Uri.fromFile(picPath);
}
- 修改layout下面所有的布局文件,将 androidx.coordinatorlayout.widget.CoordinatorLayout直接替换为LinearLayout
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".TestAssetsImageActivity">
- 修改styles,直接用下面的内容替换掉原来内容
<resources>
<!-- Base application theme. -->
<style name="AppTheme">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
<style name="AppTheme.NoActionBar">
</style>
</resources>
- 修改模块的AndroidManifest.xml
用下面的内容替换掉原始内容,下面的包名以自己实际新建模块时的包名为准。
这里面主要是定义文件provider,定义activity,申明app使用权限
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.factoryeasy.ocr">
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" tools:node="replace"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application>
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
<activity android:name=".TestActivity"/>
<activity android:name=".TestCameraImageActivity" />
<activity android:name=".TestAssetsImageActivity" />
<activity android:name=".TestCameraStreamActivity" />
<activity android:name=".TestPhotoImageActivity" />
</application>
</manifest>
经过以上改动后,代码编译就可以通过。
3.4 app项目引入moduleOcr模块
- app的build.gradle 添加moduleOcr依赖
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:support-v4:26.0.0'
compile 'com.android.support:multidex:1.0.1'
//5个模块
compile project(':moduleDemo')
compile project(':moduleSyncInterface')
compile project(':moduleRefresh')
compile project(':moduleAppdelegate')
compile project(':moduleOcr')
}
- app目录下assets.uzmap中的module.json添加如下模块引用
{
"name":"moduleOcr",
"class":"com.factoryeasy.ocr.OcrDemo"
}
- app目录下的assets.widget中的index_frm.html编写测试方法
声明模块变量:
var ocr = null;
apiready中引入模块:
ocr = api.require('moduleOcr');
定义测试方法:
function ocrDemo(){
var params = {};
ocr.ocrDemo(params);
}
html页面添加:
<div class='itemtitle'>0、离线Ocr Demo</div>
<div class="clickbtn" tapmode="active" onclick="ocrDemo()" >点击进入Ocr Demo</div>
- app目录下的jniLibs下面将百度ocr需要用的so文件都复制过来,请注意so文件是区分64位和32位的,需要将百度sdk相应目录下的so分别复制到app的jniLibs下
这里一定要注意:
如果下面截图中的目录不存在,请自行手动创建,因为本地运行时一般会用到arm64-v8a下面的so文件; 而apicloud上传模块之后的云编译会用到armeabi下面的32位so。
这里一定不能错,否则会报: couldn't find "xxx.so" is 64-bit instead of 32-bit 之类的错误
- 将百度sdk目录下的识别模型整个目录都复制到app下的assets.widget下
- 修改PredictorWrapper代码,将initModel下面的代码作如下调整: 主要是initModelFromAssets下面的目录改为 widget/ocrgeneralocr_models
public static boolean initModel(Activity activity, TextView textView) {
try {
// 进行模型初始化
int ret = Predictor.getInstance().initModelFromAssets(activity, "widget/ocrgeneralocr_models", 1);
if (ret != 0) {
Log.d(TAG, "initModel error : " + ret);
setTextViewOnUiThread(activity, textView, "模型初始化失败");
return false;
} else {
setTextViewOnUiThread(activity, textView, "模型初始化成功");
return true;
}
} catch (Exception e) {
setTextViewOnUiThread(activity, textView, "模型初始化失败");
e.printStackTrace();
return false;
}
}
为什么有上两步修改?
- 因为百度sdk中的initModelFromAssets默认的起始目录为app项目下的assets目录,这个方法目前无法修改。
- 本地测试我们可以把ocrgeneralocr_models这个目前直接放到app下的assets中,但是后面我们打包模块再上传到apicloud服务器做云打包时是不会将app里面的assets资源一起打上的,会造成模块引入到我们真正项目时找不到资源。
- 经过解压apicloud云打包后的app,我发现一点apicloud打包策略:它将我们正常项目下的所有资源都一起打到assets/widgets下面,所以我们可以变通一下,将百度sdk用到的资源包ocrgeneralocr_models 直接放到我们项目根目录下。并且调整一下读取路径就可以实现本地运行与打包后模块引入都可以读取到了。
做到此一步结束,我们可以本地运行起apicloud的模块demo,并且可以正常测试成功demo中的百度离线ocr sdk。
3.5打包模块
- 选中模块,Build-》Make Module 完成后在build.outputs.aar目录下会生成模块的aar包,可以将aar包解压出来,确保lib下的jar包和so文件都已经打入到其中,参考下图
- 创建一个目录moduleOcr,将模块aar包放入其中,并且同目录下新建一个module.json文件,内容如下:
{
"name":"moduleOcr",
"class":"com.factoryeasy.ocr.OcrDemo"
}
- 将该目录压缩为moduleOcr.zip压缩包,并上传到apicloud项目下的自定义模块中
- 项目中添加该模块,并且云编译测试是否通过
4.项目引入moduleOcr模块
- 根据前面说明,我们要将ocrgeneralocr_models 这个识别模型放置到项目的根目录下
- 业务代码中引入模块,并且测试
最外层声明变量
var ocr ;
引用模块
ocr = api.require("moduleOcr");
模块测试
testOcr = function () {
var params = {};
ocr.ocrDemo({});
}
至此,我们就做好了apicloud百度离线的Ocr sdk的模块:功能上比较简单就是直接内置了百度sdk,后面可以根据自己项目需要在模块中做一些定制的修改了。
5.重点步骤总结
1.如果将资源放置到整个项目的assets下面?
通过将资源目录直接放置在apicloud项目的根目录下面,云打包就会将资源打包进assets.widget下面
2.如何将模块下的资源在打aar包的时候包含在其中?
模块下的build.gradle 要包含下面这段配置:sourceSets{ main{ jniLibs.srcDirs = ['libs'] } }
- so文件的32,64位区分清楚,分别放到不同的lib目录下 arm64-v8a: 64位so
armeabi: 32位so
armeabi-v7a: 32位so