当前位置: 首页 > news >正文

基于PaddleOCR开发懒人精灵文字识别插件

目的

       懒人精灵是 Android 平台上的一款自动化工具,它通过编写 lua 脚本,结合系统的「 无障碍服务 」对 App 进行自动化操作。在文字识别方面它提供的有一款OCR识别插件,但是其中有识别速度慢,插件大的缺点,所以这里将讲解一下如何集成基于PaddleOCR文字识别开发的插件,阅读本篇文字需要对PaddleOCR有个基本的了解,还需要有一点Android开发基础,文章最后有相关插件下载地址。

准备工作

1、android studio最新版本即可

下载地址:Download Android Studio & App Tools - Android Developers​​​​​​​

2、下载PaddleOCR提供的安卓版文字识别demo

下载地址:​​​​​​​PaddleOCR/deploy/android_demo at release/2.5 · PaddlePaddle/PaddleOCR · GitHub

3、导入Android studio并成功运行

以上三步工作完成后,将开始我们的懒人精灵文字识别插件开发。

插件开发

1、项目结构对比

修改前 VS 修改后,调整了一些文件,去除了Activity入口。

 2、插件SDK集成

在项目的build.gradle文件中添加:

allprojects {repositories {// ...maven { url 'https://jitpack.io' }}
}

在app的build.gradle文件中添加

dependencies {// ... implementation 'com.alibaba:fastjson:1.1.46.android'
}

3、删除无用的Activity文件

 4、修改AndroidManifest.xml

两处包名替换成自己的包名,其他地方如下代码不动。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"package="com.tomato.ocr"><applicationandroid:allowBackup="true"android:icon="@mipmap/ic_launcher"android:label="@string/app_name"android:roundIcon="@mipmap/ic_launcher_round"android:supportsRtl="true"android:debuggable="true"android:theme="@style/AppTheme"tools:ignore="HardcodedDebugMode"></application>
</manifest>

5、修改Predictor文件

添加这两行文件:

 

 调整loadLabel代码如下:

6、修改cpp包名 

修改native.cpp文件,将官方的_com_baidu_paddle_lite_demo_ocr_替换成我们自己的包名,如_com_tomato_ocr_,如下截图:

7、新建OCRApi接口类

package com.tomato.ocr.ec;import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Point;
import android.media.ExifInterface;
import android.util.Log;import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.tomato.ocr.OCRResultModel;
import com.tomato.ocr.Predictor;
import com.tomato.ocr.Utils;import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;public class OCRApi {private final int useOpencl = 0;private final int cpuThreadNum = 1;private final String cpuPowerMode = "LITE_POWER_HIGH";private final int detLongSize = 960;private final float scoreThreshold = 0.1f;// 检测protected int run_det = 1;// 分类protected int run_cls = 1;// 识别protected int run_rec = 1;private final String assetModelDirPath = "models/ch_PP-OCRv2";private String assetlabelFilePath = "labels/ppocr_keys_v1.txt";private Context mContext;private Predictor mPredictor;private static OCRApi ocrApi;public static OCRApi init(Context mContext) {if (ocrApi == null) {ocrApi = new OCRApi(mContext);}return ocrApi;}public OCRApi(Context mContext) {this.mContext = mContext;try {String path = Utils.setPathForDefaultDataForLr(mContext, this.getClass());Log.d("OCR加载路径", path);} catch (IOException e) {e.printStackTrace();}this.mPredictor = new Predictor();boolean flag = this.mPredictor.init(this.mContext, assetModelDirPath, assetlabelFilePath, useOpencl, cpuThreadNum,cpuPowerMode,detLongSize, scoreThreshold);if (!flag) {Log.d("*************", "初始化失败");} else {Log.d("*************", "初始化成功");}}public void release() {if (mPredictor != null) {mPredictor.releaseModel();}if (ocrApi != null) {ocrApi = null;}}public String ocrFile(final String imagePath) {return this.ocrFile(imagePath, -1);}public String ocrFile(final String imagePath, int type) {if (type == 0) {// 只检测return this.ocrFile(imagePath, 1, 0, 0).toJSONString();} else if (type == 1) {// 方向分类 + 识别return this.ocrFile(imagePath, 0, 1, 1).toJSONString();} else if (type == 2) {// 只识别return this.ocrFile(imagePath, 0, 0, 1).toJSONString();} else if (type == 3) {// 检测 + 识别return this.ocrFile(imagePath, 1, 0, 1).toJSONString();}// 默认 检测 + 方向分类 + 识别return this.ocrFile(imagePath, 1, 1, 1).toJSONString();}private JSONArray ocrFile(final String imagePath, int run_det, int run_cls, int run_rec) {try {Bitmap image;if (imagePath.contains(".jpg") || imagePath.contains(".JPG") || imagePath.contains(".jpeg") || imagePath.contains(".JPEG")) {ExifInterface exif = null;exif = new ExifInterface(imagePath);int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION,ExifInterface.ORIENTATION_UNDEFINED);image = BitmapFactory.decodeFile(imagePath);image = Utils.rotateBitmap(image, orientation);} else {image = BitmapFactory.decodeFile(imagePath);}this.mPredictor.setInputImage(image);boolean flag = runModel(run_det, run_cls, run_rec);if (!flag) {Log.d("****************", "无法运行!");return new JSONArray();}return transformOCRResult(this.mPredictor.outputResultList);} catch (IOException e) {e.printStackTrace();}return new JSONArray();}public String ocrBitmap(final Bitmap bitmap) {return this.ocrBitmap(bitmap, -1);}public String ocrBitmap(final Bitmap bitmap, int type) {if (type == 0) {// 只检测return this.ocrBitmap(bitmap, 1, 0, 0).toJSONString();} else if (type == 1) {// 方向分类 + 识别return this.ocrBitmap(bitmap, 0, 1, 1).toJSONString();} else if (type == 2) {// 只识别return this.ocrBitmap(bitmap, 0, 0, 1).toJSONString();} else if (type == 3) {// 检测 + 识别return this.ocrBitmap(bitmap, 1, 0, 1).toJSONString();}// 默认 检测 + 方向分类 + 识别return this.ocrBitmap(bitmap, 1, 1, 1).toJSONString();}private JSONArray ocrBitmap(Bitmap bitmap, int run_det, int run_cls, int run_rec) {this.mPredictor.setInputImage(bitmap);boolean flag = runModel(run_det, run_cls, run_rec);if (!flag) {Log.d("****************", "无法运行!");return new JSONArray();}return transformOCRResult(this.mPredictor.outputResultList);}private boolean runModel(int run_det, int run_cls, int run_rec) {return this.mPredictor.runModel(run_det, run_cls, run_rec);}private JSONArray transformOCRResult(List<OCRResultModel> ocrResultModelList) {JSONArray jsonArray = new JSONArray();for (OCRResultModel ocrResultModel : ocrResultModelList) {JSONObject jsonObject = new JSONObject();jsonObject.put("words", ocrResultModel.getLabel());JSONArray objects = new JSONArray();for (Point point : ocrResultModel.getPoints()) {JSONArray points = new JSONArray();points.add(point.x);points.add(point.y);objects.add(points);}jsonObject.put("location", objects);jsonObject.put("score", ocrResultModel.getConfidence());jsonArray.add(jsonObject);}Log.d("OCR", jsonArray.toJSONString());return jsonArray;}}

8、打包插件

执行:Build->Build Bundle(s)/APKS->Build APK(S)

 一个10M以下的插件就完成了。

9、在懒人精灵应用中编写lua代码

首先将apk文件放到资源目录下,然后用loadApk()加载该插件

import('java.io.File')
import('java.lang.*')
import('java.util.Arrays')
import('android.content.Context')
import('android.hardware.Sensor')
import('android.hardware.SensorEvent')
import('android.hardware.SensorEventListener')
import('android.hardware.SensorManager')
import('com.nx.assist.lua.LuaEngine')local loader = LuaEngine.loadApk("TomatoOCR.apk")local OCR = loader.loadClass("com.tomato.ocr.lr.OCRApi")local ocr = OCR.init(LuaEngine.getContext())local type = -1;
-- type 可传可不传
-- type=0 : 只检测
-- type=1 : 方向分类 + 识别
-- type=2 : 只识别
-- type=3 : 检测 + 识别-- 只检测文字位置:type=0
-- 全屏识别: type=3或者不传type
-- 截取单行文字识别:type=1或者type=2-- 例子一
local result1 = ocr.ocrFile("/storage/emulated/0/0.jpg", type)
-- local result1 = ocr.ocrFiles(["/storage/emulated/0/0.jpg","/storage/emulated/0/0.jpg",...],type)
printEx(result1);-- 例子二
local result2 = ocr.ocrBitmap("bitmap对象", type)
-- local result2 = ocr.ocrBitmaps(["bitmap对象","bitmap对象",...],type)
printEx(result2);-- 例子三
local result3 = ocr.ocrBase64("图片base64字符串", type)
-- local result3 = ocr.ocrBase64s(["图片base64字符串","图片base64字符串",...],type)
printEx(result3);-- 释放
ocr.release()

完毕!!!

总结

        相对来说,在熟悉PaddleOCR和Android开发的情况下,进行懒人精灵插件开发还是比较容易的,而且通过自己开发插件的形式可以集成更多的功能,比如只进行文本检测、其他语言识别模型、身份识别模型等等,相对来说比较自由,这是官方提供不了的。今天就分享到这里,感谢支持!

插件下载地址:​​​​​​​地址​​​​​​​

相关文章:

基于PaddleOCR开发懒人精灵文字识别插件

目的 懒人精灵是 Android 平台上的一款自动化工具&#xff0c;它通过编写 lua 脚本&#xff0c;结合系统的「 无障碍服务 」对 App 进行自动化操作。在文字识别方面它提供的有一款OCR识别插件&#xff0c;但是其中有识别速度慢&#xff0c;插件大的缺点&#xff0c;所以这里将讲…...

PyTorch 深度学习实战 | DIEN 模拟兴趣演化的序列网络

01、实例&#xff1a;DIEN 模拟兴趣演化的序列网络深度兴趣演化网络(Deep Interest Evolution Network,DIEN)是阿里巴巴团队在2018年推出的另一力作,比DIN 多了一个Evolution,即演化的概念。在DIEN 模型结构上比DIN 复杂许多,但大家丝毫不用担心,我们将DIEN 拆解开来详细地说明…...

pyspark null类型 在 json.dumps(null) 之后,会变为字符串‘null‘

在将 hive 数仓数据写入 MySQL 时候&#xff0c;有时我们需将数据转为 json 字符串&#xff0c;然后再存入 MySQL。但 hive 数仓中的 null 类型遇到 json 函数之后会变为 ‘null’ 字符串&#xff0c;这时我们只需在使用 json 函数之前对值进行判断即可&#xff0c;当值为 null…...

LeetCode - 两数相加

题目信息 源地址&#xff1a;两数相加 给你两个 非空 的链表&#xff0c;表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的&#xff0c;并且每个节点只能存储 一位 数字。 请你将两个数相加&#xff0c;并以相同形式返回一个表示和的链表。 你可以假设除了数字…...

Office 2021专业版安装包及激活教程

[软件名称]: Office 2021 [软件大小]: 4.33GB [安装环境]: Win11/Win 10 [软件安装包下载]:https://pan.quark.cn/s/169ed49988b2 “Microsoft Office 2021是Microsoft推出的办公软件。2021年10月5日&#xff0c;Office 2021 for Mac发布&#xff0c;其中包含许多新功能 Micro…...

git版本规范-前端

前言 本文档适用于前端的小伙伴。针对目前前端只有测试环境和生产环境&#xff0c;为更好管理前端代码和适用于自动化部署&#xff0c;编写次文档&#xff0c;有不同意见的小伙伴可以进行讨论。 分支 由于没有目前没有预发环境&#xff0c;简化开发、测试、部署和发布流程&a…...

UEFI Device Path (1): 重新认识Device Path

从事UEFI开发的人员&#xff0c;对UEFI Device Path的概念都有一定了解&#xff0c;但未必都建立了比较系统而深刻的认识。UEFI Device Path的认知仅限于: 1)它是用来表示系统中设备的路径&#xff1b;2) 在UEFI SPEC中定义了它的数据结构和若干操作它的UEFI Protocol。除此以外…...

合成孔径成像的应用及发展

一、引言 合成孔径成像自20世纪50年代提出&#xff0c;应用于雷达成像&#xff0c;历经70年的研发&#xff0c;已经日趋成熟&#xff0c;成功地用于环境资源监测、灾害监测、海事管理及军事等领域。受物理环境制约&#xff0c;合成孔径在声呐成像中的研发与应用起步稍迟&#…...

MyBatis-Plus的基本操作

目录 1、配置文件 1、添加依赖 2、启动类 3、实体类 4、添加Mapper类 5、测试Mapper接口 2、CRUD测试 1、insert添加 2、修改操作 3、删除操作 3、MyBatis-Plus条件构造器 4、knife4j 1、Swagger介绍 2、集成knife4j 3.添加依赖 4 添加knife4j配置类 5、 Cont…...

HTTPAPI使用

1、使用浏览器 1.1、获取当前IP(限制 1200次 /小时) 用浏览器访问 http://ip.hahado.cn/current-ip 输入用户名和密码 [{"ip": "180.102.181.64","ttl": 262.87515091896057} ] "ip"&#xff1a; 字段是当前的外网IP ("ip&qu…...

Windos下设置java项目开机自启动

这里是将java项目注册为Windows服务实现开机自启动。 查看.NET framework版本 因为使用winsw工具运行时需要使用.NET framework,基本上现在的win10系统带自带有.NET framework4.0&#xff0c;为了选择合适的版本&#xff0c;我们可以查看本机.NET Framework版本&#xff0c;根…...

(链表)移除链表元素(双指针法)

文章目录前言&#xff1a;问题描述&#xff1a;解题思路&#xff08;双指针法&#xff09;&#xff1a;代码实现&#xff1a;总结&#xff1a;前言&#xff1a; 此篇是针对链表的经典练习题。 问题描述&#xff1a; 给你一个链表的头节点 head 和一个整数 val &#xff0c;请…...

Raft协议

文章目录一、目的&#xff08;与Paxos相同&#xff09;二、名字来源三、服务器状态四、基本实现1、任期2、RPC调用3、领导者选举4、日志复制5.领导者更替三、Raft与Paxos的区别1.表现形式2.简单性3.领导选举算法一、目的&#xff08;与Paxos相同&#xff09; 保证日志完全相同…...

动态规划概述

动态规划概述动态规划的两个要求&#xff1a; 1.最优子结构 例&#xff1a;现有一座10级台阶的楼梯&#xff0c;我们要从下往上走&#xff0c;每次只能跨一步&#xff0c;一步可以往上走1级或者2级台阶&#xff0c;请问一共有多少种解法呢&#xff1f; 台阶数12345678910走法数…...

CPU缓存架构+Disruptor内存队列

文章目录CPU缓存架构Disruptor内存队列CPU缓存架构介绍缓存一致性问题缓存一致性协议MESI协议伪共享问题高性能内存队列DisruptorCPU缓存架构Disruptor内存队列 CPU缓存架构 介绍 cpu与内存的交互数据之间&#xff0c;有一个高速缓存层。有些处理器有3层缓冲&#xff0c;有些…...

Spark SQL join操作详解

一、 数据准备 本文主要介绍 Spark SQL 的多表连接&#xff0c;需要预先准备测试数据。分别创建员工和部门的 Datafame&#xff0c;并注册为临时视图&#xff0c;代码如下&#xff1a; val spark SparkSession.builder().appName("aggregations").master("lo…...

设计模式-day04

5&#xff0c;结构型模式 5.6 组合模式 5.6.1 概述 对于这个图片肯定会非常熟悉&#xff0c;上图我们可以看做是一个文件系统&#xff0c;对于这样的结构我们称之为树形结构。在树形结构中可以通过调用某个方法来遍历整个树&#xff0c;当我们找到某个叶子节点后&#xff0c;…...

线段树的学习(2023.4.5)

今天我来学习线段树 首先它是树有着树的结构,线段树由于本身是专门用来处理区间问题的 它的作用可以处理区间的问题拥有更快的速度. 对于每一个子节点而言&#xff0c;都表示整个序列中的一段子区间&#xff1b;对于每个叶子节点而言&#xff0c;都表示序列中的单个元素信息…...

Java 实现excel、word、txt、ppt等办公文件在线预览功能

相信大家在开发的过程中都会遇到在线预览功能&#xff0c;有没有想过如何通过java来实现excel、word、txt、ppt等办公文件在线预览功能&#xff1f;今天我们就来解决这一疑问&#xff01; 其实&#xff0c;网上还是有些公司对这一功能提供了收费服务。那么&#xff0c;如何实现…...

《Vue3实战》 第九章 路由

1、安装路由 cnpm install vue-router42、router-link应用 2.1、创建views/OrderList.vue组件 <template> <h1>订单列表页面......</h1> </template> <script> export default{name: OrderList,data(){return{arr:[4,2,5]} } …...

Python|GIF 解析与构建(5):手搓截屏和帧率控制

目录 Python&#xff5c;GIF 解析与构建&#xff08;5&#xff09;&#xff1a;手搓截屏和帧率控制 一、引言 二、技术实现&#xff1a;手搓截屏模块 2.1 核心原理 2.2 代码解析&#xff1a;ScreenshotData类 2.2.1 截图函数&#xff1a;capture_screen 三、技术实现&…...

CTF show Web 红包题第六弹

提示 1.不是SQL注入 2.需要找关键源码 思路 进入页面发现是一个登录框&#xff0c;很难让人不联想到SQL注入&#xff0c;但提示都说了不是SQL注入&#xff0c;所以就不往这方面想了 ​ 先查看一下网页源码&#xff0c;发现一段JavaScript代码&#xff0c;有一个关键类ctfs…...

C++:std::is_convertible

C++标志库中提供is_convertible,可以测试一种类型是否可以转换为另一只类型: template <class From, class To> struct is_convertible; 使用举例: #include <iostream> #include <string>using namespace std;struct A { }; struct B : A { };int main…...

CRMEB 框架中 PHP 上传扩展开发:涵盖本地上传及阿里云 OSS、腾讯云 COS、七牛云

目前已有本地上传、阿里云OSS上传、腾讯云COS上传、七牛云上传扩展 扩展入口文件 文件目录 crmeb\services\upload\Upload.php namespace crmeb\services\upload;use crmeb\basic\BaseManager; use think\facade\Config;/*** Class Upload* package crmeb\services\upload* …...

多种风格导航菜单 HTML 实现(附源码)

下面我将为您展示 6 种不同风格的导航菜单实现&#xff0c;每种都包含完整 HTML、CSS 和 JavaScript 代码。 1. 简约水平导航栏 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport&qu…...

自然语言处理——循环神经网络

自然语言处理——循环神经网络 循环神经网络应用到基于机器学习的自然语言处理任务序列到类别同步的序列到序列模式异步的序列到序列模式 参数学习和长程依赖问题基于门控的循环神经网络门控循环单元&#xff08;GRU&#xff09;长短期记忆神经网络&#xff08;LSTM&#xff09…...

ios苹果系统,js 滑动屏幕、锚定无效

现象&#xff1a;window.addEventListener监听touch无效&#xff0c;划不动屏幕&#xff0c;但是代码逻辑都有执行到。 scrollIntoView也无效。 原因&#xff1a;这是因为 iOS 的触摸事件处理机制和 touch-action: none 的设置有关。ios有太多得交互动作&#xff0c;从而会影响…...

python报错No module named ‘tensorflow.keras‘

是由于不同版本的tensorflow下的keras所在的路径不同&#xff0c;结合所安装的tensorflow的目录结构修改from语句即可。 原语句&#xff1a; from tensorflow.keras.layers import Conv1D, MaxPooling1D, LSTM, Dense 修改后&#xff1a; from tensorflow.python.keras.lay…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

【JVM】Java虚拟机(二)——垃圾回收

目录 一、如何判断对象可以回收 &#xff08;一&#xff09;引用计数法 &#xff08;二&#xff09;可达性分析算法 二、垃圾回收算法 &#xff08;一&#xff09;标记清除 &#xff08;二&#xff09;标记整理 &#xff08;三&#xff09;复制 &#xff08;四&#xff…...