如意BOOK甲辰版OpenHarmony应用开发(OpenCV调用)

代码说明

本博客中的图片无法展示,有需要着可以前往gitee仓库查看附有图片的文档。
文档说明:https://gitee.com/peeanut/opencv_oh_docs

OpenCV代码仓库:https://gitee.com/peeanut/third_party_opencv

NAPI后端代码仓库:https://gitee.com/peeanut/mysubsys

OpenHarmony应用前端代码仓库:https://gitee.com/peeanut/hellonapi

环境搭建

1 前端环境

1-1 RVBook烧录OpenHarmony

在RVBook上烧录OpenHarmony操作系统的过程参考老师发的文档“如意RISC-V硬件平台用户操作指南 for OpenHarmony”。

recovery按键不太好找,按下去时,会发出“咔嗒”的声音。

1-2 Windows安装DevEco Studio

DevEco Studio的版本需要和OpenHarmony版本相匹配。

下载链接

1-3 Windows连接RVBook

  1. 打开Windows移动热点,RVBook连接该热点。
  2. 在Windows中以管理员身份运行命令提示符,并进入OpenHarmony的Sdk11的工具链目录下(D:\ohenv\tools\OpenHarmony\Sdk\11\toolchains)。
  3. 运行 ./hdc.exe tconn 192.168.137.8:55555,以连接RVBook(ip需自行设定)。
  4. 运行 ./hdc.exe -t 192.168.233.199:55555 shell,以进入RVBook的shell。

2 后端环境

2-1 部署OpenHarmony RISC-V源码和编译环境

过程参考老师发的文档“OpenHarmony RISC-V源码+编译环境快速部署”。

要求磁盘空间至少140G。

2-2 编译结果烧录至RVBook

  1. 更改RVBook文件权限:./hdc.exe shell mount -o rw,remount /
  2. 烧录:./hdc.exe file send 本地文件 目标路径

复现流程

1 编译并部署可在RVBook OpenHarmony平台上运行的OpenCV

进入三仓库路径,并下载已适配好的OpenCV。

cd /home/oh_rv/third_party
git clone https://gitee.com/peeanut/third_party_opencv.git
mv third_party_opencv opencv
cd ..

1-1 将OpenCV加入编译过程

1-1-1 build/compile_standard_whitelist.json

在"deps_added_external_part_module"和"third_deps_bundle_not_add"两个json数组中分别增加以下条目:

"//third_party/opencv/3rdparty/ade:opencv_ade_source",
"//third_party/opencv/3rdparty/carotene:opencv_carotene_source",
"//third_party/opencv/3rdparty/carotene:libtegra_hal",
"//third_party/opencv/3rdparty/ffmpeg/libavcodec:opencv_ffmpeg_avcodec",
"//third_party/opencv/3rdparty/ffmpeg/libavcodec:libopencv_avcodec",
"//third_party/opencv/3rdparty/ffmpeg/libavdevice:opencv_ffmpeg_avdevice",
"//third_party/opencv/3rdparty/ffmpeg/libavdevice:libopencv_avdevice",
"//third_party/opencv/3rdparty/ffmpeg/libavfilter:opencv_ffmpeg_avfilter",
"//third_party/opencv/3rdparty/ffmpeg/libavfilter:libopencv_avfilter",
"//third_party/opencv/3rdparty/ffmpeg/libavformat:opencv_ffmpeg_avformat",
"//third_party/opencv/3rdparty/ffmpeg/libavformat:libopencv_avformat",
"//third_party/opencv/3rdparty/ffmpeg/libswresample:opencv_ffmpeg_swresample",
"//third_party/opencv/3rdparty/ffmpeg/libswresample:libopencv_swresample",
"//third_party/opencv/3rdparty/ffmpeg/libswscale:opencv_ffmpeg_swscale",
"//third_party/opencv/3rdparty/ffmpeg/libswscale:libopencv_swscale",
"//third_party/opencv/3rdparty/ffmpeg/libavutil:opencv_ffmpeg_avutil",
"//third_party/opencv/3rdparty/ffmpeg/libavutil:libopencv_avutil",
"//third_party/opencv/3rdparty/libpng:opencv_libpng_source",
"//third_party/opencv/3rdparty/libpng:libopencv_png",
"//third_party/opencv/3rdparty/libtiff:opencv_libtiff_source",
"//third_party/opencv/3rdparty/libtiff:libopencv_tiff",
"//third_party/opencv/3rdparty/openjpeg:opencv_openjpeg_source",
"//third_party/opencv/3rdparty/openjpeg:libopencv_openjpeg",
"//third_party/opencv/3rdparty/libjpeg-turbo::opencv_libjpeg-turbo_source",
"//third_party/opencv/3rdparty/libjpeg-turbo:libjpeg-turbo",
"//third_party/opencv/3rdparty/zlib:libopencv_zlib",
"//third_party/opencv/3rdparty/zlib:opencv_zlib_source",
"//third_party/opencv/3rdparty/libwebp:libopencv_webp",
"//third_party/opencv/3rdparty/libwebp:opencv_libwebp_source",
"//third_party/opencv/3rdparty/ittnotify:opencv_ittnotify_source",
"//third_party/opencv/3rdparty/ittnotify:libopencv_ittnotify",
"//third_party/opencv/3rdparty/quirc:opencv_quirc_source",
"//third_party/opencv/3rdparty/quirc:libopencv_quirc",
"//third_party/opencv/modules/calib3d:opencv_calib3d_source",
"//third_party/opencv/3rdparty/protobuf:opencv_protobuf_source",
"//third_party/opencv/3rdparty/protobuf:libopencv_protobuf",
"//third_party/opencv/modules/calib3d:libopencv_calib3d",
"//third_party/opencv/modules/core:opencv_core_source",
"//third_party/opencv/modules/core:libopencv_core",
"//third_party/opencv/modules/dnn:opencv_dnn_source",
"//third_party/opencv/modules/dnn:libopencv_dnn",
"//third_party/opencv/modules/features2d:opencv_features2d_source",
"//third_party/opencv/modules/features2d:libopencv_features2d",
"//third_party/opencv/modules/flann:opencv_flann_source",
"//third_party/opencv/modules/flann:libopencv_flann",
"//third_party/opencv/modules/gapi:opencv_gapi_source",
"//third_party/opencv/modules/gapi:libopencv_gapi",
"//third_party/opencv/modules/highgui:opencv_highgui_source",
"//third_party/opencv/modules/highgui:libopencv_highgui",
"//third_party/opencv/modules/imgcodecs:opencv_imgcodecs_source",
"//third_party/opencv/modules/imgcodecs:libopencv_imgcodecs",
"//third_party/opencv/modules/imgproc:opencv_imgproc_source",
"//third_party/opencv/modules/imgproc:libopencv_imgproc",
"//third_party/opencv/modules/ml:opencv_ml_source",
"//third_party/opencv/modules/ml:libopencv_ml",
"//third_party/opencv/modules/objdetect:opencv_objdetect_source",
"//third_party/opencv/modules/objdetect:libopencv_objdetect",
"//third_party/opencv/modules/photo:opencv_photo_source",
"//third_party/opencv/modules/photo:libopencv_photo",
"//third_party/opencv/modules/stitching:opencv_stitching_source",
"//third_party/opencv/modules/stitching:libopencv_stitching",
"//third_party/opencv/modules/ts:opencv_ts_source",
"//third_party/opencv/modules/ts:libopencv_ts",
"//third_party/opencv/modules/video:opencv_video_source",
"//third_party/opencv/modules/video:libopencv_video",
"//third_party/opencv/modules/videoio:opencv_videoio_source",
"//third_party/opencv/modules/videoio:libopencv_videoio",
"//third_party/opencv/napi:opencv_napi"
1-1-2 productdefine/common/inherit/default.json

在thirdparty子系统下新增组件opencv:

{
  "component": "opencv",
  "features": []
}
1-1-3 vendor/iscas/rvbook/config.json

在thirdparty子系统下新增组件opencv:

{
  "component": "opencv",
  "features": []
}

1-2 编译

在/home/oh_rv/下运行命令 ./build.sh --product-name rvbook --no-prebuilt-sdk --ccache进行编译。

1-3 查看结果

其中bin路径下为可执行文件,lib64路径下为库文件。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

1-4 烧录至RVBook

可执行测试文件可烧录至任一路径下,库文件需烧录至/system/lib64/module下。

1-5 test验证

下图是在RVBook上运行opencv_test_core和opencv_test_imgproc的结果。报错的test是因为没有在本地下载测试需要的文件。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2 基于OpenCV能力开发OpenHarmony应用

2-1 NAPI开发

2-1-1 编译

进入oh_rv路径下,并下载已开发好的mysubsys。

cd /home/oh_rv
git clone https://gitee.com/peeanut/mysubsys.git

运行 ./build.sh --product-name rvbook --no-prebuilt-sdk --ccache --build-target=hellonapi命令进行编译。

2-1-2 查看结果

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2-1-3 烧录至RVBook

将libhellonapi.z.so文件烧录至/system/lib64/module下:

./hdc.exe file send libhellonapi.z.so /system/lib64/module

2-2 开发OpenHarmony应用

2-2-1 下载代码
2-2-2 运行并部署

在利用hdc进行tconn之后,便可以运行DevEco Studio中的run,该过程会直接将应用部署到RVBook上。

技术路线

1 OpenCV的适配

1-1 GN构建工具

OpenHarmony操作系统选择采用GN(Generate Ninja)构建工具,主要是出于对其高效性、灵活性和易用性的考虑。OpenCV通常采用CMake构建工具,故而需要重构CMake为GN。

1-2 hdc工具

hdc(Harmony Device Connect)工具是华为提供的用于与鸿蒙设备进行连接、调试和文件传输的命令行工具。它为开发者提供了一套便捷的方法来管理和操作鸿蒙设备,特别适用于开发和测试阶段。

hdc工具的主要功能包括:

  • 设备管理 :列出已连接的鸿蒙设备,检查设备状态。
  • 连接管理 :建立和断开与鸿蒙设备的连接。
  • 文件传输 :在本地计算机和鸿蒙设备之间传输文件。
  • 日志查看 :从鸿蒙设备获取日志信息。
  • 命令执行 :在鸿蒙设备上远程执行命令。

2 NAPI开发

OpenHarmony的NAPI(Node API)开发主要用于编写能够与JavaScript交互的原生模块,这些模块可以扩展JavaScript的能力,提供更高效的计算、硬件访问或系统级操作。

2-1 C/C++编程语言

掌握C/C++编程语言是进行NAPI开发的基础。使用C/C++编写原生代码,实现与JavaScript的交互操作。

2-2 NAPI库

NAPI是一个跨版本的ABI(Application Binary Interface),它允许开发者编写可以在不同版本的V8引擎上运行的原生模块。OpenHarmony基于NAPI提供了对原生模块的支持。

核心功能 :

  • 类型转换 :在C/C++和JavaScript之间进行数据类型的转换。
  • 回调机制 :从C/C++调用JavaScript函数,并处理异步操作。
  • 错误处理 :捕获和处理C/C++中的异常,确保不会崩溃整个应用。
  • 资源管理 :管理C/C++中的资源生命周期,避免内存泄漏。

3 OpenHarmony应用开发

3-1 UI框架

  • ArkUI(方舟UI):方舟UI是鸿蒙OS的官方UI框架,支持声明式和组件化编程。方舟UI采用了JavaScript/TypeScript语言,开发者可以使用前端开发经验快速上手。通过声明式编程,可以简化UI的开发和管理。
    • JS开发框架:JS框架用于开发轻量级应用,尤其适用于IoT设备。
    • Declarative UI:鸿蒙支持通过声明式UI进行开发,与React等前端框架类似,开发者通过描述应用界面状态的方式,鸿蒙引擎会自动渲染UI。
  • HMOS API:鸿蒙提供了丰富的API库,开发者可以调用这些API来实现常见的设备操作、UI交互、数据存储等功能。

3-2 开发工具

DevEco Studio :这是华为推出的鸿蒙应用开发工具,基于JetBrains的IntelliJ IDEA开发。它支持多种语言(如Java、C/C++、JS等),并且提供了丰富的模板、调试工具和设备模拟器,帮助开发者快速上手鸿蒙开发。

具体实现

1 OpenCV的适配

OpenHarmony-SIG提供了支持GN构建工具、适配了ARM和X86的OpenCV库:https://gitee.com/openharmony-sig/third_party_opencv

在适配到RVBook上时,主要问题就是需要关闭SSE、AVX、ITT等不适配于RISC-V的编译选项,具体见源码仓库。

2 NAPI开发

OpenCV动态库编译完成之后,需要开发NAPI接口,以基于OpenCV能力开发可供JS使用的接口。

参考由OpenHarmony-SIG/third_party_opencv提供的Native C++工程代码

核心工作是将CMake重构为GN,而后构建出动态库,具体见源码仓库。

2-1 Img2Gray

主要是通过 OpenCV 的 cvtColor将彩色图像转换为灰度图像。

Mat srcGray;
cvtColor(srcImage, srcGray, COLOR_RGB2GRAY);
  • 函数cvtColor 是 OpenCV 中用于图像颜色空间转换的函数。
  • 参数:
    • srcImage:原始的彩色图像(Mat 对象)。
    • srcGray:存储转换后灰度图像的目标 Mat 对象。
    • COLOR_RGB2GRAY:这是 OpenCV 中的一个常量,用于指示将图像从 RGB 色彩空间转换为灰度(单通道)。

在这个步骤中,cvtColor 函数执行了彩色图像到灰度图像的转换,它将原本的 RGB 图像转为灰度图像,灰度图像只有一个颜色通道。

主要使用的函数
  1. cvtColor:OpenCV 中的图像颜色空间转换函数,用于将彩色图像转换为灰度图像。
总结
  • cvtColor 是实现图像变灰的核心函数,它负责将彩色图像转换为灰度图像。

2-2 EdgeDetection

主要是通过 OpenCV 的 Canny 算子(Canny Edge Detection)来进行边缘检测处理。

边缘检测处理:使用 Canny 算法
Mat cannyImage;
Canny(srcImage, cannyImage, 50, 20);
  • 函数Canny 是 OpenCV 提供的一个边缘检测函数,用于通过 Canny 算法检测图像的边缘。
  • 参数:
    • srcImage:输入的图像(Mat 对象)。
    • cannyImage:输出的边缘检测结果,存储边缘检测后的图像。
    • 50:低阈值,用于边缘检测中的边缘连接。
    • 20:高阈值,用于边缘检测中的边缘连接。

Canny 算法的基本流程包括图像的高斯滤波、梯度计算、非最大值抑制、边缘连接等步骤。这里使用的低高阈值参数(50 和 20)控制着图像中哪些区域被视为边缘。

主要使用的函数
  1. Canny:OpenCV 中的边缘检测函数,通过 Canny 算法执行边缘检测。
总结
  • Canny 算法 是实现边缘检测的核心方法,它通过高低阈值确定图像中哪些像素属于边缘。

2-3 FaceDetect

使用了 OpenCV 的 Haar 特征分类器(Haar Cascade)来进行人脸检测。

加载 Haar 特征分类器模型
if (!GetModelFromRawFile(env, info, "haarcascade_frontalface_alt.xml")) {
    return result;
}

这里通过 GetModelFromRawFile 加载 Haar 特征分类器模型文件 haarcascade_frontalface_alt.xml,它是用于检测人脸的预训练模型。

图像转换为灰度图像
Mat grayImage;
cvtColor(srcImage, grayImage, COLOR_RGB2GRAY);
  • 函数cvtColor 用于将彩色图像转换为灰度图像。
  • 参数:
    • srcImage:输入的彩色图像。
    • grayImage:输出的灰度图像。
    • COLOR_RGB2GRAY:表示从 RGB 转换到灰度图像。

灰度图像通常用于人脸检测,因为颜色信息对检测过程影响较小,且灰度图像计算量更小。

使用 Haar 分类器进行人脸检测
bool bLoad = faceCascade.load("/data/storage/el2/base/haps/entry/files/haarcascade_frontalface_alt.xml");
std::vector<Rect> faces;
faceCascade.detectMultiScale(grayImage, faces, 1.2, 6, 0, Size(0, 0));
  • 函数faceCascade.load 用于加载 Haar 分类器模型。
  • detectMultiScale:这是 Haar 分类器用于检测图像中人脸的函数。
    • 参数:
      • grayImage:输入的灰度图像。
      • faces:存储检测到的人脸位置(用 Rect 存储矩形框的位置和大小)。
      • 1.2:图像缩放比例,表示每次图像缩小的比例。
      • 6:最小邻域数,表示每个目标周围需要多少相邻目标以决定是否接受检测。
      • 0:标志参数,通常为 0 表示基本检测。
      • Size(0, 0):表示检测的最小尺寸,0 表示不限制。
在图像上绘制人脸框
if (faces.size() > 0) {
    for (int i = 0; i < faces.size(); i++) {
        rectangle(srcImage, Point(faces[i].x, faces[i].y),
                  Point(faces[i].x + faces[i].width, faces[i].y + faces[i].height), Scalar(0, 255, 0), 4,8);
    }
}
  • 函数rectangle 用于在图像上绘制矩形框。
  • 参数:
    • srcImage:要绘制矩形框的图像。
    • Point(faces[i].x, faces[i].y):矩形框的左上角。
    • Point(faces[i].x + faces[i].width, faces[i].y + faces[i].height):矩形框的右下角。
    • Scalar(0, 255, 0):矩形框的颜色(绿色)。
    • 4:矩形框的线宽。
    • 8:连接类型。

这里遍历所有检测到的人脸位置,使用绿色矩形框标记出来。

主要使用的函数
  1. cvtColor:OpenCV 函数,用于将彩色图像转换为灰度图像。
  2. faceCascade.load:加载 Haar 特征分类器模型文件。
  3. detectMultiScale:Haar 分类器的核心函数,用于检测图像中的人脸。
  4. rectangle:OpenCV 函数,用于在图像上绘制矩形框。
总结
  • 人脸检测 使用了 OpenCV 的 Haar 特征分类器来进行。首先,图像被转换为灰度图像,然后通过加载预训练的 Haar 分类器模型来检测人脸。检测到的人脸位置会用矩形框标记出来。

2-4 ImgSegmentation

这段代码实现了图像分割功能,采用了 Otsu 算法(Otsu’s Thresholding)进行阈值分割。

转换为灰度图像
Mat srcGray;
cvtColor(srcImage, srcGray, COLOR_RGB2GRAY);
  • 函数cvtColor 用于将彩色图像转换为灰度图像。
  • 参数:
    • srcImage:输入的彩色图像。
    • srcGray:输出的灰度图像。
    • COLOR_RGB2GRAY:表示将 RGB 图像转换为灰度图像。
使用 Otsu 算法进行阈值分割
Mat thresh;
threshold(srcGray, thresh, 0, 255, 8);
  • 函数threshold 用于对图像进行阈值处理。
  • 参数:
    • srcGray:输入的灰度图像。
    • thresh:输出的二值化图像,像素值为 0 或 255。
    • 0:Otsu 算法的标志,表示自动计算最佳阈值。
    • 255:最大输出值,对于二值化图像而言,通常为 255。
    • 8:表示使用 Otsu 自适应阈值的类型 THRESH_OTSU(该标志表示采用 Otsu 算法来自动确定最佳阈值)。

Otsu 算法的主要作用是自动选择一个最佳的阈值,将图像分割成前景和背景。此方法通常用于图像分割和二值化。

主要使用的函数
  1. cvtColor:OpenCV 函数,用于将彩色图像转换为灰度图像。
  2. threshold:OpenCV 函数,用于执行图像的阈值处理,进行二值化处理。此处使用 Otsu 算法进行自动阈值分割。
总结
  • 图像分割 采用了 Otsu 算法,该算法通过自动计算最优阈值,将灰度图像分割为前景和背景。

2-5 QRCodeIdentification

基于 OpenCV 的 QR 码检测和解码功能,结合 Node.js 的 N-API (Node Addon API) 来实现异步操作。它会异步处理图像中的 QR 码识别,并通过回调函数或 Promise 返回结果。以下是代码各个组件及整体流程的详细解释:

存储参数和回调信息的结构体
  • ParametersInfo:该结构体包含了输入的图像(mat)、一些标志(detectOnlymultiQR),以及回调引用(callback)用于处理结果。
  • AsyncCallbackInfoQrcode:该结构体存储了进行异步 QR 码检测所需的信息,包括环境 (env)、异步工作句柄 (asyncWork)、输入参数 (params)、检测结果 (detecteddecodeInfos)、以及回调信息 (info)。
WrapJsQRCodeInfo函数

这个函数将解码后的 QR 码信息包装成 JavaScript 可用的对象,并格式化返回:

  • 参数:
    • detected:一个布尔值,表示是否检测到 QR 码。
    • dectedInfos:包含已解码的 QR 码字符串的向量。
    • result:最终返回的结果对象。
  • 该函数创建一个 JavaScript 对象,包含 detected(布尔值)和 decodes(解码字符串的数组),并返回该对象。
runQRCode函数

这是实际进行 QR 码检测和解码的核心函数,使用 OpenCV 的 QRCodeDetector 实现。它根据输入参数决定是否进行单一 QR 码检测或多 QR 码检测:

  • 参数:
    • qrcode:OpenCV 的 QRCodeDetector 实例。
    • input:输入图像,将进行 QR 码检测。
    • callbackInfo:包含回调和检测结果的回调信息。
    • corners:检测到的 QR 码的角点(可用于定位或验证)。

它支持两种模式:

  • 单一 QR 码模式:使用 detectAndDecode 进行单个 QR 码的检测和解码。
  • 多 QR 码模式:使用 detectAndDecodeMulti 进行多个 QR 码的检测和解码。
SetQrcode 函数

该函数将 QR 码检测的结果格式化为 JavaScript 对象。它设置 detected 属性,并将解码的 QR 码字符串放入 decodes 数组中。

AsyncCompleteCallbackQrcode 函数

这是异步工作完成后调用的回调函数。它会检查操作是否成功,然后:

  • 创建结果对象并使用 SetQrcode 格式化 QR 码信息。
  • 调用 ReturnCallbackPromise 来解析 Promise 或调用回调函数,并返回结果。
  • 清理资源,删除异步工作项和回调引用。
ParseParametersQrcode 函数

该函数解析传入的参数:

  • 从参数中获取文件目录和文件名。
  • 加载图像并将其存储在 ParametersInfo 结构体的 mat 属性中。
  • 如果图像加载失败,返回 false
QRCodeIdentification 函数

这是 QR 码识别的主入口函数:

  1. 使用 ParseParametersQrcode 函数解析输入参数,加载图像。
  2. 创建一个 AsyncCallbackInfoQrcode 对象来处理异步操作。
  3. 设置回调函数或 Promise,以便在操作完成时返回结果。
  4. 使用 napi_create_async_work 创建并排队一个异步工作项,执行 QR 码检测和解码的操作。
  5. 在后台执行 QR 码检测,通过调用 napi_queue_async_work 来异步执行工作。
  6. 如果使用回调,则返回 null;否则,返回 Promise。
关键的 N-API 函数
  • napi_get_cb_info:获取 JavaScript 回调传入的参数。
  • napi_create_object:创建一个新的 JavaScript 对象。
  • napi_create_array:创建一个新的 JavaScript 数组。
  • napi_create_string_utf8:将 C++ std::string 转换为 UTF-8 字符串。
  • napi_set_named_property:设置 JavaScript 对象中的属性。
  • napi_set_element:在 JavaScript 数组中设置元素。
  • napi_create_async_work:创建一个异步工作项,用于后台执行函数,然后调用完成回调。
  • napi_queue_async_work:将异步工作项排入队列,执行异步操作。
执行流程
  1. JavaScript 调用 QRCodeIdentification:JavaScript 函数接收图像文件目录、文件名和标志作为参数。
  2. 参数解析:通过 ParseParametersQrcode 函数加载图像并准备好参数。
  3. 创建异步工作项QRCodeIdentification 函数创建并排队一个异步工作项。
  4. QR 码检测与解码:后台执行 runQRCode 函数,进行 QR 码检测。
  5. 完成回调:当 QR 码检测完成时,调用 AsyncCompleteCallbackQrcode 函数格式化结果并返回。
  6. 返回结果:JavaScript 获取到结果,返回 detected 状态和解码的 QR 码字符串。
错误处理
  • 如果图像无法加载或 QR 码未检测到,函数会返回 null 或设置错误码。
  • 如果在异步操作中出现问题,代码会处理并返回错误信息。
总结

这段代码通过 OpenCV 实现了 QR 码的异步检测与解码功能,配合 Node.js 的 N-API 提供了一个支持回调和 Promise 的 API。它支持单个或多个 QR 码的检测,能够有效地进行异步操作,从而避免阻塞主线程,适合高性能需求的应用。

3 OpenHarmony应用开发

NAPI接口开发完成之后,将库烧录至RVBook,而后在OpenHarmony的sdk中声明接口格式。

参考由OpenHarmony-SIG/third_party_opencv提供的Native C++工程代码

核心工作是声明接口格式以及开发UI界面,具体见源码仓库。

3-1 功能介绍

主要实现应用的UI设计,并调用各种接口实现不同功能。

3-2 界面UI结构

3-2-1 调用接口(interfae),描述多级分类结构
export interface FirstLevelCategory {
  childNodes: SecondLevelCategory[] | ThirdLevelCategory[],
  selectedImage: Resource, 
  unselectedImage: Resource, 
  tabBarName: Resource 
}
export interface SecondLevelCategory {...}
export interface ThirdLevelCategory {...}
export interface FourthLevelCategory {...}

接口通过嵌套关系表示了一个层次化的数据结构,其中每一层都包含了不同的属性和子项:

(1).childNodes: 该属性可以是 SecondLevelCategory[] 或 ThirdLevelCategory[] 数组,表示一级分类下面的子分类节点。这里使用了联合类型(SecondLevelCategory[] | ThirdLevelCategory[]),表示它可以包含二级分类或三级分类。

(2).selectedImage: 选择该一级分类时显示的图标。类型是 Resource,代表资源对象。

(3).unselectedImage: 未选择该一级分类时显示的图标。与 selectedImage 相似,类型也是 Resource。

(4).tabBarName: 一级分类的标题,类型是 Resource,也是一个资源对象,可能是一个文本或者图片。

3-2-2 一级分类接口:构建页签栏(Tabs)及其内容
3-2-2-1 Tabs({ barPosition: BarPosition.End }) {…}
1. @State tabsIndex: number = 0
  • tabsIndex 是一个状态变量,表示当前选中的标签的索引。使用 @State 装饰器表明它是可变的,UI 会根据该值的变化自动更新。
  • 初始值为 0,意味着默认选中的标签是第一个标签。
2. build() 方法
  • build() 方法负责构建 UI 组件的树。
  • 在该方法中,使用了 TabsTabContent 等组件来构建标签栏以及每个标签的内容。
  • Tabs({ barPosition: BarPosition.End }):这个代码片段构建了一个标签栏,barPosition: BarPosition.End 表示标签栏的位置在底部(可能是末尾)。
  • .barHeight(56).barWidth('100%'):设置标签栏的高度为 56 像素,宽度为 100%。
  • .vertical(false):标签栏不是垂直排列,而是水平排列。
  • .backgroundColor($r('app.color.background_shallow_grey')):标签栏的背景色设置为浅灰色。
  • .onChange((index: number) => { this.tabsIndex = index }):当选中的标签发生变化时,更新 tabsIndex,即更新当前选中的标签索引。
3. ForEach(OPENCV_SAMPLE_CATEGORIES, (item: FirstLevelCategory, index: number) => {...})
  • ForEach 是一个遍历数组的构造,OPENCV_SAMPLE_CATEGORIES 是一个包含一级分类的数组。
  • itemFirstLevelCategory 类型的对象,index 是其在数组中的索引。
4. TabContent() { TabContentNavigation({ categories: item.childNodes }) }
  • TabContent 表示每个标签的内容区域。
  • TabContentNavigation({ categories: item.childNodes }) 表示为每个标签生成一个 TabContentNavigation,并将该标签下的子分类(item.childNodes)传递进去。childNodes 是二级分类或更深层次分类的集合。
5..tabBar(this.TabBarBuilder(index, item.selectedImage, item.unselectedImage, item.tabBarName))
  • tabBar 为标签栏添加了自定义的 TabBarBuilder。这个方法将根据 indexselectedImageunselectedImagetabBarName 来构建每个标签的显示内容。
3-2-2-2 OPENCV_SAMPLE_CATEGORIES

1 childNodes: IMAGE_CATEGORIES:该属性指向了 IMAGE_CATEGORIES,表示这个一级分类下面的子分类项是关于图像(Images)的分类。

2 selectedImageunselectedImage:用于获取资源文件,其参数是资源的路径字符串。具体的资源会通过路径 'app.media.ic_select_component''app.media.ic_unselect_component' 获取。

3 tabBarName:这是该一级分类在 UI 上显示的名称,$r('app.string.opencv_image') 表示获取资源文件中的字符串资源。

3-2 图片功能UI介绍

3-2-1 整体介绍

使用@Component 定义了多个组件,TabContentNavigation、ThirdLevelNavigation、FourthLevelNavigation,每个组件负责显示不同层级的分类内容和交互。

3-2-2 export struct TabContentNavigation {…}
关键点
  • categories 属性:表示分类数据,可能是 ThirdLevelCategory[]SecondLevelCategory[]

  • hasSecondLevelCategory 方法:用于判断当前类别是否属于第二层分类。如果分类没有 image 属性,则认为它是第二层分类。

  • build
    

    方法:构建 UI 布局。使用了

    Column、List、ListItem
    

    等组件来展示分类内容,且通过条件判断来决定如何展示子分类。

    • ForEach 用于遍历并渲染每个分类及其子分类。
    • 如果是第二层分类,则渲染该层分类的标题,并递归渲染其子分类(第三层分类)。
    • 如果是第三层分类,则直接渲染它们,并跳转到对应页面。
3-2-3 struct ThirdLevelNavigation {…}
关键点
  • isUnfold:状态变量,用于控制第三层分类是否展开显示子分类(第四层分类)。

  • build
    

    方法:

    • Row 用于水平布局,显示每个第三层分类的图标、标题。
    • 点击该分类项时,如果没有子分类,则跳转到相应的页面;如果有子分类,则展开显示子分类。
    • 展开时,递归渲染第四层分类。
3-2-4 struct FourthLevelNavigation {...}
关键点
  • FourthLevelNavigation 组件用于显示第四层分类的标题,并处理点击事件跳转到对应的页面。
3-2-5 export const IMAGE_CATEGORIES: ThirdLevelCategory[] =[...]
1. ThirdLevelCategory 类型

每个图像处理类别都是 ThirdLevelCategory 类型的实例,包含了以下几个属性:

  • image: 一个资源标识符,指向该类别的图标。
  • title: 类别的名称(通常是一个字符串资源,用于显示给用户)。
  • url: 该类别的详情页 URL,当用户点击该类别时,应用会跳转到此页面。
2.export const IMAGE_CATEGORIES

这些定义的 ThirdLevelCategory 对象( IMAGE_COLOR, IMAGE_EDGE, IMAGE_QRCODE, IMAGE_OBJDETECT, IMAGE_IMGSEGMENTATION)被导出并存储在 IMAGE_CATEGORIES 数组中.

3-2-6 struct Image2Gray {...}(以灰度转换为例,介绍四级接口的UI)
1. Image2Gray 组件

该组件实现了图像处理功能,用户可以点击按钮将图片灰度化,并显示处理后的灰度图像,按钮的状态也会随着处理的结果发生变化。

2. 状态与变量定义
  • btnFontColor: 按钮字体颜色,使用 $r() 获取资源文件中的颜色。
  • pixelMapFormat: 图像像素映射格式,默认是 3(可能代表某种特定的像素格式)。
  • isGray: 用于判断图像是否已经被转换为灰度图像的状态变量。初始值为 false,表示图像未被灰度化。
  • imagePixelMap: 存储灰度化后的图像数据,如果图像被成功转换成灰度图像,将会在此变量中保存像素图。
3. build 方法

build 方法定义了该组件的界面布局和交互逻辑。该方法中使用了多个 UI 控件,并设置了它们的属性和行为。

a. 标题栏 (TitleBar)
  • 使用 $r() 获取应用中定义的字符串资源来设置标题栏的文字为“灰度化”。
b. 图像显示区域

图像显示区域包含两个部分:一个用于显示原始图像(如果尚未灰度化),另一个用于显示处理后的灰度图像(如果 isGraytrue)。

  • isGrayfalse 时,显示原始图像 (5.jpg)。
  • isGraytrue 时,显示灰度化后的图像,通过 this.imagePixelMap 获取。
c. 按钮 (Buttons)

有两个按钮:一个用于进行图像灰度化,另一个用于恢复图像。

  • “灰度化”按钮
    • isGrayfalse 时,按钮是蓝色的,用户可以点击此按钮将图像转换为灰度图像。
    • 按钮点击后调用 OpenCV 或类似的图像处理库进行灰度化:
      • hellonapi.img2Gray 方法用于将图像转换为灰度图,返回一个包含像素信息的对象。
      • 使用 image.createPixelMap 方法将灰度图像的字节数据转换为像素图 pixelMap
      • 如果转换成功,则 this.imagePixelMap 被赋值为新的像素图。
  • “恢复”按钮
    • isGraytrue 时,按钮点击后恢复显示原始图像。
4. 图像处理逻辑
  • 图像灰度化:

    通过调用 hellonapi.img2Gray 方法将图像 '5.jpg' 转换为灰度图像,返回的 pixelInfo 对象包含了转换后的图像信息,如 buffSizebyteBuffer 等。

  • 创建像素图 (PixelMap):

    • image.createPixelMap 方法根据 pixelInfo 中的字节数据创建一个 PixelMap 对象,表示处理后的图像。
    • 如果创建成功,图像数据将保存在 this.imagePixelMap 中,并更新页面显示。
5. 布局与样式
  • 使用 ColumnRow 控件来实现垂直和水平布局。
  • 设置了图像和按钮的宽度、背景色、字体颜色等样式。

3-4 视频功能UI介绍

3-4-1 export const VIDEO_CATEGORIES: ThirdLevelCategory[] = [...]

VIDEO_BASIC 是一个 ThirdLevelCategory 类型的常量,表示视频类别中的一个基础类别。它包含了以下信息:

  • image: 图标的资源路径,使用 $r() 函数从资源文件中加载 ic_list_and_grid 图标,表示视频类别的图标。
  • title: 类别的标题,同样使用 $r() 从资源文件中加载字符串 video_title。这个字符串在界面中作为视频类别的名称显示。
  • url: 类别的跳转链接,即用户点击该视频类别时,应用将跳转到 pages/video/video_play 页面进行视频播放。
3-4-2 struct Video_play {...}
1. 私有变量定义
private controller: VideoController | undefined;
private previewUris: Resource = $r('app.media.preview');
private innerResource: Resource = $rawfile('lazy.mp4');
  • controller: 用于控制视频播放器的控制器,类型为 VideoController
  • previewUris: 视频的预览图资源路径,使用 $r() 函数从资源文件中加载。预览图通常是视频的封面图或缩略图,用于在视频加载时展示给用户。
  • innerResource: 这是一个视频文件的路径,视频源的文件路径通过 $rawfile() 获取,加载的是本地文件 lazy.mp4
2. build 方法

该方法定义了界面的布局及交互逻辑。布局使用了 Column()Row() 来排版,展示视频播放器界面。

界面布局
  • 返回按钮: 使用 Image() 显示返回按钮的图标 ic_back,并设置了 widthheight 来控制图标大小。点击图标会触发 onClick() 事件,通过 router.back() 返回上一页。
  • 标题: 使用 Text() 显示页面标题 “视频播放”,并设置了字体大小、字体加粗等样式,确保标题清晰可见。
视频播放器
  • 视频播放器组件
    使用
    Video()
    

    组件来播放视频。传入了三个属性:

    • src: 设置视频源路径,this.innerResource 指向了本地文件 lazy.mp4
    • previewUri: 设置视频预览图,this.previewUris 指向了预览图资源,通常在视频加载前展示。
    • controller: 传入的视频控制器,用于控制视频的播放行为。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

罗马尼亚硬拉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值