代码说明
本博客中的图片无法展示,有需要着可以前往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
- 打开Windows移动热点,RVBook连接该热点。
- 在Windows中以管理员身份运行命令提示符,并进入OpenHarmony的Sdk11的工具链目录下(D:\ohenv\tools\OpenHarmony\Sdk\11\toolchains)。
- 运行
./hdc.exe tconn 192.168.137.8:55555
,以连接RVBook(ip需自行设定)。 - 运行
./hdc.exe -t 192.168.233.199:55555 shell
,以进入RVBook的shell。
2 后端环境
2-1 部署OpenHarmony RISC-V源码和编译环境
过程参考老师发的文档“OpenHarmony RISC-V源码+编译环境快速部署”。
要求磁盘空间至少140G。
2-2 编译结果烧录至RVBook
- 更改RVBook文件权限:
./hdc.exe shell mount -o rw,remount /
- 烧录:
./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 图像转为灰度图像,灰度图像只有一个颜色通道。
主要使用的函数
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)控制着图像中哪些区域被视为边缘。
主要使用的函数
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
:连接类型。
这里遍历所有检测到的人脸位置,使用绿色矩形框标记出来。
主要使用的函数
cvtColor
:OpenCV 函数,用于将彩色图像转换为灰度图像。faceCascade.load
:加载 Haar 特征分类器模型文件。detectMultiScale
:Haar 分类器的核心函数,用于检测图像中的人脸。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 算法的主要作用是自动选择一个最佳的阈值,将图像分割成前景和背景。此方法通常用于图像分割和二值化。
主要使用的函数
cvtColor
:OpenCV 函数,用于将彩色图像转换为灰度图像。threshold
:OpenCV 函数,用于执行图像的阈值处理,进行二值化处理。此处使用 Otsu 算法进行自动阈值分割。
总结
- 图像分割 采用了 Otsu 算法,该算法通过自动计算最优阈值,将灰度图像分割为前景和背景。
2-5 QRCodeIdentification
基于 OpenCV 的 QR 码检测和解码功能,结合 Node.js 的 N-API (Node Addon API) 来实现异步操作。它会异步处理图像中的 QR 码识别,并通过回调函数或 Promise 返回结果。以下是代码各个组件及整体流程的详细解释:
存储参数和回调信息的结构体
ParametersInfo
:该结构体包含了输入的图像(mat
)、一些标志(detectOnly
和multiQR
),以及回调引用(callback
)用于处理结果。AsyncCallbackInfoQrcode
:该结构体存储了进行异步 QR 码检测所需的信息,包括环境 (env
)、异步工作句柄 (asyncWork
)、输入参数 (params
)、检测结果 (detected
和decodeInfos
)、以及回调信息 (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 码识别的主入口函数:
- 使用
ParseParametersQrcode
函数解析输入参数,加载图像。 - 创建一个
AsyncCallbackInfoQrcode
对象来处理异步操作。 - 设置回调函数或 Promise,以便在操作完成时返回结果。
- 使用
napi_create_async_work
创建并排队一个异步工作项,执行 QR 码检测和解码的操作。 - 在后台执行 QR 码检测,通过调用
napi_queue_async_work
来异步执行工作。 - 如果使用回调,则返回
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
:将异步工作项排入队列,执行异步操作。
执行流程
- JavaScript 调用
QRCodeIdentification
:JavaScript 函数接收图像文件目录、文件名和标志作为参数。 - 参数解析:通过
ParseParametersQrcode
函数加载图像并准备好参数。 - 创建异步工作项:
QRCodeIdentification
函数创建并排队一个异步工作项。 - QR 码检测与解码:后台执行
runQRCode
函数,进行 QR 码检测。 - 完成回调:当 QR 码检测完成时,调用
AsyncCompleteCallbackQrcode
函数格式化结果并返回。 - 返回结果: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 组件的树。- 在该方法中,使用了
Tabs
和TabContent
等组件来构建标签栏以及每个标签的内容。 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
是一个包含一级分类的数组。item
是FirstLevelCategory
类型的对象,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
。这个方法将根据index
、selectedImage
、unselectedImage
和tabBarName
来构建每个标签的显示内容。
3-2-2-2 OPENCV_SAMPLE_CATEGORIES
1 childNodes: IMAGE_CATEGORIES
:该属性指向了 IMAGE_CATEGORIES
,表示这个一级分类下面的子分类项是关于图像(Images)的分类。
2 selectedImage
和 unselectedImage
:用于获取资源文件,其参数是资源的路径字符串。具体的资源会通过路径 '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. 图像显示区域
图像显示区域包含两个部分:一个用于显示原始图像(如果尚未灰度化),另一个用于显示处理后的灰度图像(如果 isGray
为 true
)。
- 当
isGray
为false
时,显示原始图像 (5.jpg
)。 - 当
isGray
为true
时,显示灰度化后的图像,通过this.imagePixelMap
获取。
c. 按钮 (Buttons)
有两个按钮:一个用于进行图像灰度化,另一个用于恢复图像。
- “灰度化”按钮:
- 当
isGray
为false
时,按钮是蓝色的,用户可以点击此按钮将图像转换为灰度图像。 - 按钮点击后调用 OpenCV 或类似的图像处理库进行灰度化:
hellonapi.img2Gray
方法用于将图像转换为灰度图,返回一个包含像素信息的对象。- 使用
image.createPixelMap
方法将灰度图像的字节数据转换为像素图pixelMap
。 - 如果转换成功,则
this.imagePixelMap
被赋值为新的像素图。
- 当
- “恢复”按钮:
- 当
isGray
为true
时,按钮点击后恢复显示原始图像。
- 当
4. 图像处理逻辑
-
图像灰度化:
通过调用
hellonapi.img2Gray
方法将图像'5.jpg'
转换为灰度图像,返回的pixelInfo
对象包含了转换后的图像信息,如buffSize
、byteBuffer
等。 -
创建像素图 (PixelMap):
image.createPixelMap
方法根据pixelInfo
中的字节数据创建一个PixelMap
对象,表示处理后的图像。- 如果创建成功,图像数据将保存在
this.imagePixelMap
中,并更新页面显示。
5. 布局与样式
- 使用
Column
和Row
控件来实现垂直和水平布局。 - 设置了图像和按钮的宽度、背景色、字体颜色等样式。
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
,并设置了width
和height
来控制图标大小。点击图标会触发onClick()
事件,通过router.back()
返回上一页。 - 标题: 使用
Text()
显示页面标题 “视频播放”,并设置了字体大小、字体加粗等样式,确保标题清晰可见。
视频播放器
-
-
视频播放器组件
- 使用
Video()
组件来播放视频。传入了三个属性:
src
: 设置视频源路径,this.innerResource
指向了本地文件lazy.mp4
。previewUri
: 设置视频预览图,this.previewUris
指向了预览图资源,通常在视频加载前展示。controller
: 传入的视频控制器,用于控制视频的播放行为。