OpenHarmony集成OCR三方库实现文字提取

1. 简介

Tesseract(Apache 2.0 License)是一个可以进行图像OCR识别的C++库,可以跨平台运行 。本样例基于Tesseract库进行适配,使其可以运行在OpenAtom OpenHarmony(以下简称“OpenHarmony”)上,并新增N-API接口供上层应用调用,这样上层应用就可以使用Tesseract提供的相关功能。

2. 效果展示

动物图片识别文字

身份信息识别

提取文字信息到本地文件

3. 目录结构

4. 调用流程

调用过程主要涉及到三方面,首先应用层实现样例的效果,包括页面的布局和业务逻辑代码;中间层主要起桥梁的作用,提供N-API接口给应用调用,再通过三方库的接口去调用具体的实现;Native层使用了三方库Tesseract提供具体的实现功能。

5. 源码分析

本样例源码的分析主要涉及到两个方面,一方面是N-API接口的实现,另一方面是应用层的页面布局和业务逻辑。

N-API实现

1. 首先在index.d.ts文件中定义好接口

/**
 * 初始化文字识别引擎
 * @param lang 识别的语言, eg:eng、chi_sim、 eng+chi_sim,为Null或不传则为中英文(eng+chi_sim)
 * @param trainDir 训练模型目录,为Null或不传则为默认目录
 *
 * @return 初始化是否成功 0=>成功,-1=>失败
 */
export const initOCR: (lang: string, trainDir: string) => Promise<number>;
 
export const initOCR: (lang: string, trainDir: string, callback: AsyncCallback<number>) => void;
 
/**
 * 开始识别
 * @param imagePath 图片路径(当前支持的图片格式为png, jpg, tiff)
 *
 * @return 识别结果
 */
export const startOCR: (imagePath: string) => Promise<string>;
export const startOCR: (imagePath: string, callback: AsyncCallback<string>) => void;
 
 
/**
 * 销毁资源
 */
export const destroyOCR: () => void;

代码中可以看出N-API接口initOCR和startOCR都采用了两种方式,一种是Promise,一种是Callback的方式。在样例的应用层,使用的是它们的Callback方式。

2.注册N-API模块和接口

EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports) {
napi_property_descriptor desc[] = {
{
"initOCR", nullptr, InitOCR, nullptr, nullptr, nullptr, napi_default, nullptr
},
{
"startOCR", nullptr, StartOCR, nullptr, nullptr, nullptr, napi_default, nullptr
},
{
"destroyOCR", nullptr, DestroyOCR, nullptr, nullptr, nullptr, napi_default, nullptr
},
{
};
napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
return exports;
}
EXTERN_C_END
 
static napi_module demoModule = {
.nm_version = 1,
.nm_flags = 0,
.nm_filename = nullptr,
.nm_register_func = Init,
.nm_modname = "tesseract",
.nm_priv = ((void *)0),
.reserved = {
0
},
};
 
extern "C" __attribute__((constructor)) void RegisterHelloModule(void) {
napi_module_register(& demoModule);
}

通过nm_modname定义模块名,nm_register_func注册接口函数,在Init函数中指定了JS中initOCR,startOCR,destroyOCR对应的本地实现函数,这样就可以在对应的本地实现函数中调用三方库Tesseract的具体实现了。

3.以startOCR的Callback方式为例介绍N-API中的具体实现

static napi_value StartOCR(napi_env env, napi_callback_info info) {
    OH_LOG_ERROR(LogType::LOG_APP, "OCR StartOCR 111");
    size_t argc = 2;
    napi_value args[2] = { nullptr };
  //1. 获取参数
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
 
 
    //2. 共享数据
    auto addonData = new StartOCRAddOnData{
        .asyncWork = nullptr,
    };
    //3. N-API类型转成C/C++类型
    char imagePath[1024] = { 0 };
    size_t length = 0;
    napi_get_value_string_utf8(env, args[0], imagePath, 1024, &length);
 
    addonData->args0 = string(imagePath);
 
    napi_create_reference(env, args[1], 1, &addonData->callback);
 
    //4. 创建async work
    napi_value resourceName = nullptr;
    napi_create_string_utf8(env, "startOCR", NAPI_AUTO_LENGTH, &resourceName);
    napi_create_async_work(env, nullptr, resourceName, executeStartOCR, completeStartOCRForCallback, (void *)addonData, &addonData->asyncWork);
 
    //将创建的async work加到队列中,由底层调度执行
    napi_queue_async_work(env, addonData->asyncWork);
 
    napi_value result = 0;
    napi_get_null(env, &result);
 
    return result;
}

首先通过napi_get_cb_info方法获取JS侧传入的参数信息,将参数转成C++对应的类型,然后创建异步工作,异步工作的方法参数中包含,执行的函数以及函数执行完成的回调函数。

我们看一下执行函数

static void executeStartOCR(napi_env env, void* data) {
    //通过data来获取数据
    StartOCRAddOnData * addonData = (StartOCRAddOnData *)data;
    napi_value resultValue;
    try {
        if (api != nullptr) {
            //调用具体的实现,读取图片像素
            PIX * pix = pixRead((const char*)addonData->args0.c_str());
            //设置api的图片像素
            api->SetImage(pix);
 
            //调用文字提取接口,获取图片中的文字
            char * result = api->GetUTF8Text();
            addonData->result = result;
 
            //释放资源
            pixDestroy (& pix);
            delete[] result;
        }
    } catch (std::exception e) {
        std::string error = "Error: ";
        if (initResult != 0) {
            error += "please first init tesseractocr.";
        } else {
            error += e.what();
        }
        addonData->result = error;
    }
}

这个方法中通过data获取JS传入的参数,然后调用Tesseract库中提供的接口,调用具体的文字提取功能,获取图片中的文字。

执行完成后,会回调到completeStartOCRForCallback,在这个方法中会将执行函数中返回的结果转换为JS的对应类型,然后通过Callback的方式返回。

static void completeStartOCRForCallback(napi_env env, napi_status status, void * data) {
    StartOCRAddOnData * addonData = (StartOCRAddOnData *)data;
    napi_value callback = nullptr;
    napi_get_reference_value(env, addonData->callback, &callback);
    napi_value undefined = nullptr;
    napi_get_undefined(env, &undefined);
    napi_value result = nullptr;
    napi_create_string_utf8(env, addonData->result.c_str(), addonData->result.length(), &result);
 
    //执行回调函数
    napi_value returnVal = nullptr;
    napi_call_function(env, undefined, callback, 1, &result, &returnVal);
 
    //删除napi_ref对象
    if (addonData->callback != nullptr) {
        napi_delete_reference(env, addonData->callback);
    }
 
    //删除异步工作项
    napi_delete_async_work(env, addonData->asyncWork);
    delete addonData;
}

应用层实现

应用层主要分为三个模块:动物图片文字识别,身份信息识别,提取文字到本地文件

1. 动物图片文字识别

build() {
    Column() {
      Row() {
        Text('点击图片进行文字提取  提取结果 :').fontSize('30fp').fontColor(Color.Blue)
        Text(this.ocrResult).fontSize('50fp').fontColor(Color.Red)
      }.margin('10vp').height('10%').alignItems(VerticalAlign.Center)
 
      Grid() {
        ForEach(this.images, (item, index) => {
          GridItem() {
            AnimalItem({
              path1: item[0],
              path2: item[1]
            });
          }
        })
      }
      .padding({left: this.columnSpace, right: this.columnSpace})
      .columnsTemplate("1fr 1fr 1fr")      // Grid宽度均分成3份
      .rowsTemplate("1fr 1fr")     // Grid高度均分成2份
      .rowsGap(this.rowSpace)                  // 设置行间距
      .columnsGap(this.columnSpace)            // 设置列间距
      .width('100%')
      .height('90%')
    }
    .backgroundColor(Color.Pink)
  }

布局主要使用了Grid的网格布局,每个Item都是对应的图片,通过点击图片可以对点击图片进行文字提取,将提取出的文字显示在标题栏。

2. 身份信息识别

build() {
    Row() {
      Column() {
        Image('/common/idImages/aobamao.jpg')
          .onClick(() => {
            //点击图片进行信息识别
            console.log('OCR begin dialog open 111');
            this.ocrDialog.open();
            ToolUtils.ocrResult(ToolUtils.aobamao, (result) => {
              console.log('111 OCR result = ' + result);
              this.result = result;
              this.ocrDialog.close();
            });
          })
          .margin('10vp')
          .objectFit(ImageFit.Auto)
          .height('50%')
 
        Image('/common/idImages/weixiaobao.jpg')
          .onClick(() => {
            //点击图片进行信息识别
            this.ocrDialog.open();
            ToolUtils.ocrResult(ToolUtils.weixiaobao, (result) => {
              console.log('111 OCR result = ' + result);
              this.result = result;
              this.ocrDialog.close();
            });
          })
          .margin('10vp')
          .objectFit(ImageFit.Auto)
          .height('50%')
      }
      .width(this.screenWidth/2)
      .padding('20vp')
 
      Column() {
        Text(this.title).height('10%').fontSize('30fp').fontColor(this.titleColor)
 
        Column() {
          Text(this.result)
            .fontColor('#0000FF')
            .fontSize('50fp')
        }.justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center).height('90%')
      }
      .justifyContent(FlexAlign.Start)
      .width('50%')
 
    }
    .width('100%')
    .height('100%')
  }

身份信息识别的布局最外层是一个水平布局,分为左右两部分,左边的子布局是垂直布局,里面是两张不同的身份证图片,右边子布局也是垂直布局,主要是标题区和识别结果的内容显示区。

3. 提取文字到本地文件

Row() {
      Column() {
        Image('/common/save2FileImages/testImage1.png')
          .onClick(() => {
            //点击图片进行信息识别
            ToolUtils.ocrResult(ToolUtils.testImage1, (result) => {
              let path = this.dir + 'ocrresult1.txt';
              try {
                let fd = fileio.openSync(path, 0o100 | 0o2, 0o666);
                fileio.writeSync(fd, result);
                fileio.closeSync(fd);
                this.displayText = '文件写入' + path;
              } catch (e) {
                console.log('OCR fileio error = ' + e);
              }
            });
          })
        Image('/common/save2FileImages/testImage2.png')
          .onClick(() => {
            //点击图片进行信息识别
            ToolUtils.ocrResult(ToolUtils.testImage2, (result) => {
              let path = this.dir + 'ocrresult2.txt';
              let fd = fileio.openSync(path, 0o100 | 0o2, 0o666);
              fileio.writeSync(fd, result);
              fileio.closeSync(fd);
              this.displayText = '文件写入' + path;
            });
          })
      }
      Column() {
        Text(this.title)
        Column() {
          Text(this.displayText)
        }
      }
    }

这个功能首先通过接口识别出图片中的文字,然后再通过fileio的能力将文字写入文件中。

6. 总结

样例通过Native的方式将C++的三方库集成到应用中,通过N-API方式提供接口给上层应用调用。对于依赖三方库能力的应用,都可以使用这种方式来进行,移植三方库到Native,通过N-API提供接口给应用调用。

为了帮助到大家能够更有效的学习OpenHarmony 开发的内容,下面特别准备了一些相关的参考学习资料:

OpenHarmony 开发环境搭建:https://qr18.cn/CgxrRy

《OpenHarmony源码解析》:https://qr18.cn/CgxrRy

  • 搭建开发环境
  • Windows 开发环境的搭建
  • Ubuntu 开发环境搭建
  • Linux 与 Windows 之间的文件共享
  • ……

系统架构分析:https://qr18.cn/CgxrRy

  • 构建子系统
  • 启动流程
  • 子系统
  • 分布式任务调度子系统
  • 分布式通信子系统
  • 驱动子系统
  • ……

OpenHarmony 设备开发学习手册:https://qr18.cn/CgxrRy

在这里插入图片描述

OpenHarmony面试题(内含参考答案):https://qr18.cn/CgxrRy

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/636991.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

第198题|很精彩的一道题|函数强化训练(五)|武忠祥老师每日一题

解题思路&#xff1a;解决这道题有两种方法&#xff1a;第一种直接法排除法&#xff0c;第二种秒杀法 直接法排除法 (A) 要证明f(x)是以2为周期的函数&#xff1a;则要证明f(x2)f(x); 证明过程如下&#xff1a; A得证。 (B) 变上限积分关于周期的结论&#xff1a; f(x)连…

dubbo复习: (6)和springboot集成时的条件路由

根据指定的条件&#xff0c;对不满足条件的请求进行拦截。 比如拦截ip地址为192.168.31.227的请求。只需要在dubbo admin中的条件路由菜单创建相应的规则 enabled: true force: true runtime: true conditions:- host ! 192.168.31.227

安装petalinux工具

petalinux 并不是一个特殊 Linux 内核&#xff0c;而是一套开发环境配置的工具&#xff0c;降低 uboot、内核、 根文件系统的配置的工作量&#xff0c;可以从 Vivado 的导出硬件信息自动完成相关软件的配置。 petalinux 是赛灵思基于 buildroot 工具链为自家处理器方便适配 Li…

51单片机汇编语言设计流水灯

1、仿真原理图 2、汇编代码及详细注释 &#xff08;1&#xff09;、代码1 ORG 0000H ; 设置代码起始地址为0000H 熄灭发光二极管 MOV A,#0FEH ; 将数值0FEH载入A寄存器&#xff0c;熄灭所有发光二极管 MOV P1, A ; 将A寄存器的值移动到P1寄存器&#xff0c;将0FEH写入P1…

重组蛋白表达系统优缺点对比|卡梅德生物

重组蛋白是现代生物技术中不可或缺的一部分&#xff0c;它们广泛应用于药物开发、研究工具和工业酶的生产。根据目标蛋白的特性和所需的修饰&#xff0c;可以选择不同的表达系统。下文罗列一下四个主要蛋白表达系统的优缺点&#xff1a; 1. 原核表达系统&#xff08;如大肠杆菌…

MySQL学习之DQL语句(数据查询语言)

准备SQL CREATE TABLE student ( id int, -- 编号 name varchar(20), -- 姓名 age int, -- 年龄 sex varchar(5), -- 性别 address varchar(100), -- 地址 math int, -- 数学 english int -- 英语 );INSERT INTO student(id,NAME,age,sex,address,math,english) VALUES (1,…

百变大侦探秘之馆的魔术师是谁 秘之馆的魔术师怎么打真相解析

百变大侦探秘之馆的魔术师是一个6人的困难剧本&#xff0c;这次我们将来到动物世界&#xff0c;虽然参与进来的都是各种代号的动物&#xff0c;但他们每个都聪明绝顶&#xff0c;所以今天的真相解析也会比较困难&#xff0c;故事就这样开始了&#xff0c;我们来看看谁才是魔术师…

什么是住宅IP代理?为什么需要家庭 IP 代理

家庭代理 IP 允许您选择特定位置&#xff08;国家、城市或移动运营商&#xff09;并作为代理上网该区域的真实用户。住宅代理 IP 可以定义为保护用户免受一般网络流量影响的中介。它们在隐藏您的 IP 地址的同时充当缓冲区。住宅代理 IP 是服务提供商分配给用户的替代 IP 地址。…

docker部署kafka实战

目录 一、部署kafaka、zookeeper 二、测试信息发送与接收 三、kafka进阶 一、部署kafaka、zookeeper 请提前安装docker、docker-compose 安装docker&#xff1a;docker--安装docker-ce-CSDN博客 安装docker-compose&#xff1a; 安装docker-compose_安装 docker-compose-CSD…

Vue前端项目打包,并部署Vue项目到Linux云服务器上

一. vue前端项目打包 1.使用vscode开发项目 2.在config目录下的prod.env.js文件当中配置我们后端服务器的IP地址和端口号&#xff0c;因为这是在实际的部署当中所以必须要在生成环境下进行项目的部署。 如图所示&#xff1a; 3.在config目录下的index.js文件当中要改assetsPu…

C++容器之多重映射(std::multimap)

目录 1 概述2 使用实例3 接口使用3.1 construct3.2 assigns3.3 iterators3.4 capacity3.5 insert3.6 erase3.7 swap3.8 clear3.9 emplace3.10 emplace_hint3.11 key_comp3.12 value_comp3.13 find/count3.14 lower_bound/upper_bound/equal_range3.15 get_allocator1 概述 多重…

小白跟做江科大32单片机之新建工程

项目基础配置 1.新建item文件夹&#xff0c;包含全部stm32项目文件 2.在item文件夹中新建第一个项目&#xff0c;2-1 stm32工程模板&#xff0c;起名字是project 3.工程芯片选择STM32F10318&#xff0c;因为课程中的芯片是STM32F10318C8T6 4.在固件库\STM32F10x_StdPeriph_…

【Docker实操】启动redis服务

一、步骤 1、获取redis镜像 执行获取redis镜像命令&#xff1a;docker pull redis。打印镜像清单&#xff0c;可以看到新拉到的redis镜像。 2、创建redis.conf配置文件 linux主机 mkdir -p /root/www/redis/conf touch /root/www/redis/conf/redis.conf cat << EOF &…

ICML2024高分论文!大模型计算效率暴涨至200%,来自中国AI公司

前段时间&#xff0c;KAN突然爆火&#xff0c;成为可以替代MLP的一种全新神经网络架构&#xff0c;200个参数顶30万参数&#xff1b;而且&#xff0c;GPT-4o的生成速度也是惊艳了一众大模型爱好者。 大家开始意识到—— 大模型的计算效率很重要&#xff0c;提升大模型的token…

深入解析文华量化交易策略---交易指令如何选择

随着金融投资的迅猛发展&#xff0c;自动化策略模型已逐渐成为现代投资领域的一股重要力量。量化交易模型均以数据为驱动&#xff0c;通过运用数学模型和算法&#xff0c;对期货、黄金等投资市场走势进行精准预测和高效交易。 艾云策略整理了量化策略相关资料&#xff0c;希望通…

孢子捕捉分析仪的工作原理

TH-BZ1孢子捕捉分析仪是一种专门用于捕捉和分析空气中飘浮的病原菌孢子的设备。它利用现代传感技术、图像识别技术和网络通信技术&#xff0c;通过设置在田间的设备&#xff0c;连续不断地抽吸周围空气&#xff0c;吸附空气中漂浮的病原菌孢子到特制的载玻带上。然后&#xff0…

【AJAX前端框架】Asynchronous Javascript And Xml

1 传统请求及缺点 传统的请求都有哪些&#xff1f; 直接在浏览器地址栏上输入URL。点击超链接提交form表单使用JS代码发送请求 window.open(url)document.location.href urlwindow.location.href url… 传统请求存在的问题 页面全部刷新导致了用户的体验较差。传统的请求导…

【Linux安全】Firewalld防火墙

目录 一.Firewalld概述 二.Firewalld和iptables的关系 1.firewalld和iptables的联系 2.firewalld和iptables的区别 三.Firewalld区域 1.概念 2.九个区域 3.区域介绍 4.Firewalld数据处理流程 四.Firewalld-cmd命令行操作 1.查看 2.增加 3.删除 4.修改 五.Firewa…

hbase版本从1.2升级到2.1 spark读取hive数据写入hbase 批量写入类不存在问题

在hbase1.2版本中&#xff0c;pom.xml中引入hbase-server1.2…0和hbase-client1.2.0就已经可以有如下图的类。但是在hbase2.1.0版本中增加这两个不行。hbase-server2.1.0中没有mapred包&#xff0c;同时mapreduce下就2个类。版本已经不支持。 <dependency><groupId>…

GBase 8s 如何查看回滚的事务 和对应的SQL

描述&#xff1a; 如何查看当前数据库中是否有事务在回滚&#xff0c; 如果有&#xff0c; 具体是哪条 SQL 在回滚&#xff1f; 解决办法&#xff1a; 方法1&#xff1a; 通过 onstat -u|grep RP&#xff1b; 可以获取相关的 sessionid。 通过 onstat -g ses sid 获取 SQL&a…