# 凌智视觉模块文档---基于 RV1106 **Repository Path**: LockzhinerAI/lingzhi-visual-module-document ## Basic Information - **Project Name**: 凌智视觉模块文档---基于 RV1106 - **Description**: 凌智视觉模块整体文档(基于 RV1106 芯片进行开发),支持的网络 picodet、yolov5、crnn、ocr、mobilenet、arcface等神经网络模型 - **Primary Language**: Unknown - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: https://gitee.com/LockzhinerAI/LockzhinerVisionModule - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2025-05-15 - **Last Updated**: 2025-08-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 凌智视觉模块开发指南

描述图片

凌智视觉模块(Lockzhiner Vision Module) 是福州市凌睿智捷电子有限公司联合百度飞桨倾力打造的一款高集成度人工智能视觉模块,专为边缘端人工智能和机器视觉应用而设计,其特色包括: - 计算能力:凌智视觉模块搭载单核 ARM Cortex-A7 处理器,并集成了 NEON 和 FPU, 以更出色地处理 SIMD 和浮点计算。同时,该模块支持 Int8 量化,内置 1 TOPs 的 NPU, 足以应对绝大多数的视觉应用场景。 - 开源适配:凌智视觉模块与飞桨低代码开发工具 PaddleX 完美适配,目前支持PaddleX 中的通用图像分类模型产线, 其余任务场景正在适配中, 未来会陆续上线。 - 简单易用:配合飞桨星河社区 Al Studio, 用户可以实现一键训练;配合凌智视觉算法部署库,用户可以实现一键部署,极大地减轻了用户的使用成本。

描述图片

文件密级:□绝密 □秘密 □内部资料 ■公开 --- **免责声明** 本文档按**现状**提供,福州凌睿智捷电子有限公司(以下简称**本公司**)不对本文档中的任何陈述、信息和内容的准确性、可靠性、完整性、适销性、适用性及非侵权性提供任何明示或暗示的声明或保证。本文档仅作为使用指导的参考。 由于产品版本升级或其他原因,本文档可能在未经任何通知的情况下不定期更新或修改。 **读者对象** 本教程适用于以下工程师: - 技术支持工程师 - 软件开发工程师 - 硬件爱好者 **修订记录** | **日期** | **版本** | **作者** | **修改说明** | | :--------- | -------- | ------------------------ | ------------ | | 2025/05/14 | V 1.0 | 福州凌智锐捷科技有限公司 | 初始版本 |
## 目录 [TOC]
## 1 凌智视觉模块开发准备 ### 1.1 凌智视觉模块镜像烧录指南 #### 1.1.1 简介 在本章节中,我们将讲解如何使用烧录工具将镜像文件烧录到凌智视觉模块中。 #### 1.1.2 前期准备 在开始烧录工作开始之前,我们需要做一些前期的准备: * 请准备一张 **64G 容量以下** 的 SD 卡,这里我们选用的是 16G 的 SD 卡。请将 SD 卡插入读卡器并正确连接电脑。 * 下载凌智视觉模块镜像 提取码为 772f 注意,前往镜像下载地址下载镜像及其烧录工具时,至少保证下载以下三个文件夹: ![](./images/zip.png) 使用解压工具(这里选用的是 Bandzip)解压 DriverAssitant_v5.12.zip 和 SocToolKit.zip 到你喜欢的位置 ![](./images/unzip.png) #### 1.1.3 安装驱动(已经安装可以跳过) 打开 DriverAssitant 安装 USB 驱动程序,此过程无需连接读卡器,安装完成后请重启电脑。 ![](./images/RKDriver.png) #### 1.1.4 开始烧录 打开 SocToolKit.exe 软件,这是一个镜像烧写工具 ![](images/tools_dir.jpg) 弹出的界面中选择 RV1106 ![](images/select_toolkit.jpg) 按照下图顺序,依次点击 **SD卡启动** -> **选择USB磁盘** -> **选择启动文件** ![](images/select_image_0.png) 弹出的窗口中 **找到 nowifi 目录** -> **选择所有文件** -> **点击打开按钮** ![](images/select_image_1.png) ![](images/select_image_2.png) 点击创建 SD 卡 ![](images/create_sd.png) #### 1.1.5 插入 SD 卡 将 SD 卡插入 Lockzhiner Vision Module ![](images/insert_sd.png) #### 1.1.6 验证是否烧录成功 烧录完成后,参照凌智视觉模块连接指南测试是否成功。 ### 1.2 凌智视觉模块连接指南 #### 1.2.1 简介 SSH 是一种用于在不安全网络上安全地访问和传输数据的协议,Lockzhiner Vision Module 使用 SSH 来让用户和设备进行通信。在本章节中,你将学会如何使用 SSH 连接设备并在屏幕上输出 Hello World。 #### 1.2.2 下载并安装 electerm (可选) [electerm] 是一款跨平台的 (linux, mac, win) 开源终端客户端,支持 ssh/sftp 等多种通信方式。我们默认使用 electerm 进行 SSH 和 FTP 通信,当然你也可以自由的选择其他的客户端来完成通信。 在开始安装前,请前往 来下载 electerm。 解压你下载的压缩包,打开你 electerm 安装包,一直点击下一步即可完成安装。 ![](images/electerm_install.png) #### 1.2.3 设置本机 IP 地址 要想使用 SSH 就必须保证本机和虚拟网口处在同一个 IP 下。请将设备连接上电脑并按照你的操作系统,按顺序执行以下步骤: ##### 1.2.3.1 Win11 设置本机 IP 地址 键盘按下 **Win + Q** 呼出搜索框 -> 输入并点击设置 ![](./images/open_setting_win11.png) 点击 **网络和 Internet** -> 点击 **以太网** ![](images/setting_internet.png) 点击 **编辑** 并配置 IP 地址。这里将 IPV4 地址设置为 **10.1.1.155**,子网掩码设置为 **255.0.0.0**。 ![](images/click_internet_edit.png) ![](images/set_internet.png) 填完完成后点击 **保存**,你的界面应该如下图所示: ![](images/true_internet_config.png) ##### 1.2.3.2 Win10 设置本机 IP 地址 键盘按下 **Win + Q** 呼出搜索框 -> 输入并点击设置 ![](./images/open-setting_win10.png) 点击 **网络和 Internet** -> 点击 **以太网** -> 点击 **更改适配器** ![](./images/select_adapter_win10.png) 连接设备点击新增的**虚拟网卡** ![](./images/config_net_win10.png) 点击 **编辑** 并配置 IP 地址。这里将 IPV4 地址设置为 **10.1.1.155**,子网掩码设置为 **255.0.0.0**。 ![](./images/config_ipv4_win10.png) #### 1.2.4 使用 SSH 连接设备 为了方便大家使用,Lockzhiner Vision Module 在开机时默认启动 SSH 服务器并虚拟化一个网口。Lockzhiner Vision Module 的 SSH 详细信息如下: ```bash 登录账号:root 登录密码:lzdz 静态IP地址:10.1.1.144 ``` 打开 electerm 或者你本地的 SSH 编辑器,打开网络配置,将 IP、用户名、密码分别进行以下配置 ![](images/ssh_config.png) 点击保存并连接来连接到设备 ![](images/connect_to_device.png) 屏幕上将出现以下界面 ![](images/ssh_success.png) ### 1.3 凌智视觉模块开发环境搭建指南 #### 1.3.1 简介 Lockzhiner Vision Module 的 C++ 开发依赖 Ubuntu 系统,但是大多数用户仍然希望能在 Windows 系统上进行开发,因此我们特地推出了在 Windows 上使用 Docker 来进行开发的方法。在进行开发之前,我们需要创建 Docker 容器并安装开发必备的软件,主要分为以下几步进行: * 安装 Docker Destop * 读取镜像并创建和配置容器 * 安装交叉编译工具链 #### 1.3.2 安装 Docker Destop 前往 [Docker 官网](https://www.docker.com/)下载 Docker Destop 安装包 ![](images/download_docker_by_web.png) 如在下载中有困难,可使用我们提供的百度网盘下载 Docker Destop 安装包 [Docker安装包](https://pan.baidu.com/s/1Z2GYRx3RG6u4a1pDxZAoQg) 提取码: g5mc 打开安装包,按照以下顺序进行安装并重启电脑 ![](images/config_docker.png) ![](images/Snipaste_2024-08-29_09-55-48.png) 重启电脑后,打开 Docker Destop,按顺序点击以下按钮 ![](images/Snipaste_2024-08-29_09-57-28.png) ![](images/Snipaste_2024-08-29_09-57-47.png) ![](images/Snipaste_2024-08-29_09-58-08.png) ![](images/Snipaste_2024-08-29_09-58-31.png) 安装完成后将会自动打开 Docker Destop,如下图 ![](images/docker_destop.png) 如在这一步出现下图所示 ![](images/docker_stop.png) 请参照如下博客进行WSL的安装和更新 [docker 安装问题解决方案](https://blog.csdn.net/cplvfx/article/details/138033592) #### 1.3.3 创建 Lockzhiner Vision Module 工作目录 打开磁盘,选择合适的位置准备创建 Lockzhiner Vision Module 工作目录。注意,工作目录存放的绝对路径请不要包含任何中文字符。 接下来**右键鼠标** -> **新建** -> **文件夹**,文件夹名字取名为 **LockzhinerVisionModuleWorkSpace** ![](images/new_dir.png) 进入新建的文件夹,先点击地址栏检查地址是否包含中文路径。 ![](images/dir_url.png) #### 1.3.4 使用 Docker 加载镜像 接下来使用 **Shift + 鼠标右键** -> **在此处打开 PowerShell 窗口** ![](images/click_powershell.png) 在 PowerShell 中输入以下命令来下载并安装 ubuntu-22.04 镜像: ```bash wget https://gitee.com/LockzhinerAI/LockzhinerVisionModule/releases/download/v0.0.0/ubuntu-22.04.tar -Outfile ubuntu-22.04.tar docker load -i ubuntu-22.04.tar ``` ![](images/docker_load.png) > 注意: > > 如果执行 **docker load** 时报错 **The system cannot find the file specified.** 请检查 Docker Destop 是否已经正常开启。 #### 1.3.5 使用 Docker 创建并配置容器 在 PowerShell 中继续输入以下命令来创建并进入 Docker 容器,挂载容器时会将当前的工作目录映射到容器的 **/LockzhinerVisionModuleWorkSpace** 目录下 ```bash docker run -it --name LockzhinerVisionModule -v ${PWD}:/LockzhinerVisionModuleWorkSpace ubuntu:jammy /bin/bash ``` ![](images/docker_container.png) 执行以下命令来更新 Apt 源 ```bash # 配置中科大软件源 sed -i 's@//.*archive.ubuntu.com@//mirrors.ustc.edu.cn@g' /etc/apt/sources.list # 更新 Apt 源 apt update ``` ![](images/apt_update.png) 执行以下命令来下载并安装命令行补全工具 ```bash # 安装 bash-completion apt-get install -y bash-completion # 运行这行命令后输入 no,并按回车 dpkg-reconfigure dash # 运行 bash_completion cd /usr/share/bash-completion chmod +x bash_completion ./bash_completion ``` ![](images/install_bash-completion.png) ![](images/dpkg-reconfigure.png) ![](images/bash_completion.png) 执行以下命令来退出容器 ```bash exit ``` ![](images/exit.png) #### 1.3.6 配置开发项目编译环境 安装完 Docker 环境后,我们需要在容器中配置项目的编译环境。每一次在 Pwoershell 中使用命令来打开容器比较麻烦,这里我们使用 Docker Destop 来打开容器: * 打开 **Docker Destop** * 点击 **Containers** * 点击运行按钮 * 点击 **LockzhinerVisionModule** 容器 * 点击 Exec 按钮进入命令行界面 ![](images/docker_run_container.png) ![](images/docker_exec.png) ##### 1.3.6.1 下载必备软件包 在 Docker 容器中输入以下命令来安装交叉编译工具所需的依赖工具包 ```bash apt install -y cmake git wget unzip apt install build-essential ``` ![](images/install_cmake_git.png) ##### 1.3.6.2 下载交叉编译工具链 为了将 C++ 代码编译为在 Lockzhiner Vision Module 上运行的程序,我们需要在 Docker 容器中下载并安装交叉编译工具。 执行以下代码,进入 Lockzhiner Vision Module 工作目录并下载交叉编译工具 ```bash cd /LockzhinerVisionModuleWorkSpace/ git clone https://gitee.com/LockzhinerAI/arm-rockchip830-linux-uclibcgnueabihf.git ``` ![](images/cd_work_dir.png) ![](images/install_arm-rockchip830-linux-uclibcgnueabihf.png) ##### 1.3.6.3 下载/更新 LockzhinerVisionModule 仓库 如果是首次配置环境,执行以下命令 ```bash cd /LockzhinerVisionModuleWorkSpace git clone https://gitee.com/LockzhinerAI/LockzhinerVisionModule.git cd LockzhinerVisionModule mkdir -p third_party ``` 如果是为了更新 LockzhinerVisionModule 仓库,执行以下命令 ```bash cd /LockzhinerVisionModuleWorkSpace/LockzhinerVisionModule git pull ``` ##### 1.3.6.4 下载/更新 OpenCV Mobile 库 执行以下命令来安装最新的 OpenCV Mobile 库(会删除旧版本) ```bash cd /LockzhinerVisionModuleWorkSpace/LockzhinerVisionModule rm -rf opencv-mobile-4.10.0-lockzhiner-vision-module.zip wget https://gitee.com/LockzhinerAI/opencv-mobile/releases/download/v0.0.0/opencv-mobile-4.10.0-lockzhiner-vision-module.zip unzip -qo opencv-mobile-4.10.0-lockzhiner-vision-module.zip -d third_party ``` ##### 1.3.6.5 下载/更新 LockzhinerVisionModule SDK 执行以下命令来安装最新的 LockzhinerVisionModule SDK(会删除旧版本) ```bash cd /LockzhinerVisionModuleWorkSpace/LockzhinerVisionModule rm -rf third_party/lockzhiner_vision_module_sdk rm -rf lockzhiner_vision_module_sdk.zip # 其中v0.0.6为SDK的版本号,最新版本号请以发行版更新为准。 wget https://gitee.com/LockzhinerAI/LockzhinerVisionModule/releases/download/v0.0.6/lockzhiner_vision_module_sdk.zip unzip -qo lockzhiner_vision_module_sdk.zip -d third_party ``` 参考[连接设备指南](./connect_device_using_ssh.md)打开 Electerm 并连接设备,点击 **Sftp** 按钮。 ![](images/update_lockzhiner_vision_module_0.png) 使用任务管理器打开 **LockzhinerVisionModuleWorkSpace** 文件夹,按以下顺序操作来找到 SDK 存放目录: * 打开 LockzhinerVisionModule * 打开 third_party ![](images/update_lockzhiner_vision_module_1.png) 点击地址栏并复制地址,如下图 ![](images/update_lockzhiner_vision_module_2.png) 回到 Electerm 将复制的地址粘贴到地址栏中并跳转到该目录,如下图: ![](images/update_lockzhiner_vision_module_3.png) 选中 SDK ,单击鼠标右键并点击 **上传** 将 SDK 传输到 Lockzhiner Vision Module ![](images/update_lockzhiner_vision_module_4.png) 点击 **Ssh** 回到 Lockzhiner Vision Module 命令行执行界面,运行以下命令拷贝动态库到系统中: ```bash cd lockzhiner_vision_module_sdk bash install-sh.sh ``` ![](images/update_lockzhiner_vision_module_5.png) #### 1.3.7 验证开发环境 请参考 [编写第一个 Hello World 程序](../../Cpp_example/hello_world/README.md) 来验证开发环境是否能够正常使用 ## 2 基本外设开发示例 ### 2.1 摄像头模块使用指南 本章节主要演示如何使用LockAI进行视频流的读取,同时使用Edit模块进行图像传输。 #### 2.1.1 基础知识讲解 ##### 2.1.1.1 OpenCV简介 OpenCV(Open Source Computer Vision Library)是一个开源的计算机视觉库,提供丰富的图像处理和视频捕获功能。通过其 VideoCapture 类,开发者可以轻松调用摄像头设备并获取视频流。 ##### 2.1.1.2 VideoCapture模块 cv::VideoCapture 是OpenCV中用于管理视频输入的核心类,支持从摄像头、视频文件或网络流读取帧。常用功能包括: * 设备初始化与参数设置(分辨率、帧率) * 逐帧捕获图像 * 资源释放管理 #### 2.1.2 API文档 ##### 2.1.2.1 cv::VideoCapture类 ###### 2.1.2.1.1 cv::VideoCapture类依赖头文件 ```cpp #include ``` ###### 2.1.2.1.2 初始化摄像头 ```cpp cv::VideoCapture cap; ``` * **功能**:创建摄像头管理对象 * **说明**:该对象用于后续所有摄像头操作,未调用 open() 前不占用硬件资源 ###### 2.1.2.1.3 设置摄像头分辨率 ```cpp cap.set(cv::CAP_PROP_FRAME_WIDTH, width); cap.set(cv::CAP_PROP_FRAME_HEIGHT, height); ``` * **参数**: * cv::CAP_PROP_FRAME_WIDTH : 帧宽度(像素) * cv::CAP_PROP_FRAME_HEIGHT : 帧高度(像素) **分辨率对照表**:根据摄像头的分辨率和帧率,选择合适的分辨率和帧率。以下为常见分辨率与帧率对照表 | 摄像头分辨率(4:3) | FPS | |---------------------|------| | 480x360 | 25 | | 640x480 | 25 | | 960x720 | 14 | | 1280x960 | 13 | | 1920x1440 | 13 | | 摄像头分辨率(16:9) | FPS | |----------------------|------| | 480x270 | 25 | | 640x360 | 25 | | 960x540 | 25 | | 1280x720 | 15 | | 1920x1080 | 12 | ###### 2.1.2.1.4 打开摄像头设备 ```cpp cap.open(0); ``` * **参数**:0表示默认摄像头设备,也可以指定其他设备编号 * **返回值**:成功打开返回 true ,否则返回 false ###### 2.1.2.1.5 读取视频帧 ```cpp cap >> frame; ``` * **说明**:读取下一帧图像,如果当前帧为空,则返回 false ##### 2.1.2.2 lockzhiner_vision_module::edit::Edit类 ###### 2.1.2.2.1 依赖头文件 ```cpp #include ``` ###### 2.1.2.2.2 初始化模块 ```cpp Edit edit; ``` * **说明**:创建Edit对象,用于后续图像传输操作 ###### 2.1.2.2.3 建立连接 ```cpp edit.StartAndAcceptConnection(); ``` * **参数**:无 * **返回值**:成功建立连接返回 true ,否则返回 false ###### 2.1.2.2.4 图像传输 ```cpp edit.Print(frame); ``` * **参数**: cv::Mat 对象,表示图像帧 * **返回值**:无 #### 2.1.3. 综合代码解析 ##### 2.1.3.1 基础摄像头读取 ###### 2.1.3.1.1 流程图 ``` 开始 | ├── 创建 `cv::VideoCapture` 对象 `cap` | ├── 设置摄像头分辨率(640x480) | ├── 打开摄像头设备(参数0表示默认摄像头) | ├── 成功 -> 继续执行 | └── 失败 -> | └── 输出 "Error: Could not open camera." | └── 返回错误码 `EXIT_FAILURE` | ├── 进入无限循环 | ├── 获取一帧图像 | | ├── 成功 -> 继续执行 | | └── 失败 -> | | └── 输出 "Warning: Couldn't read a frame from the camera." | | └── 继续下一次循环 | └── 释放摄像头资源 └── 程序正常退出(返回 0) ``` ###### 2.1.3.1.2 代码解析 * 初始化摄像头 ```cpp cv::VideoCapture cap; const int width = 640; const int height = 480; cap.set(cv::CAP_PROP_FRAME_WIDTH, width); cap.set(cv::CAP_PROP_FRAME_HEIGHT, height); ``` * 逐帧捕获图像 ```cpp while (true) { cv::Mat frame; cap >> frame; if (frame.empty()) { std::cerr << "Warning: Couldn't read a frame from the camera." << std::endl; continue; } } ``` ###### 2.1.3.1.3 完整代码实现 ```cpp #include #include int main() { cv::VideoCapture cap; cap.set(cv::CAP_PROP_FRAME_WIDTH, 640); cap.set(cv::CAP_PROP_FRAME_HEIGHT, 480); cap.open(0); // 参数0表示默认摄像头设备 if (!cap.isOpened()) { std::cerr << "Error: Could not open camera." << std::endl; return EXIT_FAILURE; } while (true) { cv::Mat frame; cap >> frame; if (frame.empty()) { std::cerr << "Warning: Couldn't read a frame from the camera." << std::endl; continue; } } cap.release(); return 0; } ``` ##### 2.1.3.2 摄像头图像传输 ###### 2.1.3.2.1 流程图 ``` 开始 │ ├── 创建 `cv::VideoCapture` 对象 `cap` │ ├── 定义常量 `width = 640` 和 `height = 480` │ ├── 设置摄像头分辨率 │ ├── 设置帧宽度为 640 │ └── 设置帧高度为 480 │ ├── 建立 `lockzhiner_vision_module::edit::Edit` 对象 `edit` │ ├── 启动并接受连接 │ ├── 成功 -> 继续执行 │ └── 失败 -> │ └── 输出 "Error: Failed to start and accept connection." │ └── 返回错误码 `EXIT_FAILURE` │ ├── 进入无限循环 │ ├── 获取一帧图像 │ | ├── 成功 -> 继续执行 │ | └── 失败 -> │ | └── 输出 "Warning: Couldn't read a frame from the camera." │ | └── 继续下一次循环 │ │ │ └── 传输当前帧(调用 `edit.Print(frame)`) │ └── 程序正常退出(返回 0) ``` ###### 2.1.3.2.2 代码解析 * 初始化摄像头和Edit模块 ```cpp cv::VideoCapture cap; const int width = 640; const int height = 480; cap.set(cv::CAP_PROP_FRAME_WIDTH, width); cap.set(cv::CAP_PROP_FRAME_HEIGHT, height); lockzhiner_vision_module::edit::Edit edit; ``` * 建立连接 ```cpp if (!edit.StartAndAcceptConnection()) { std::cerr << "Error: Failed to start and accept connection." << std::endl; return EXIT_FAILURE; } ``` * 逐帧捕获图像并传输 ```cpp while (true) { cv::Mat frame; cap >> frame; if (frame.empty()) { std::cerr << "Warning: Couldn't read a frame from the camera." << std::endl; continue; } edit.Print(frame); } ``` ###### 2.1.3.2.3 完整代码实现 ```cpp #include #include #include int main() { // 初始化 edit 模块 lockzhiner_vision_module::edit::Edit edit; if (!edit.StartAndAcceptConnection()) { std::cerr << "Error: Failed to start and accept connection." << std::endl; return EXIT_FAILURE; } std::cout << "Device connected successfully." << std::endl; // 初始化摄像头 cv::VideoCapture cap; int width = 640; // 设置摄像头分辨率宽度 int height = 480; // 设置摄像头分辨率高度 cap.set(cv::CAP_PROP_FRAME_WIDTH, width); cap.set(cv::CAP_PROP_FRAME_HEIGHT, height); // 打开摄像头设备 cap.open(0); // 参数 0 表示默认摄像头设备 if (!cap.isOpened()) { std::cerr << "Error: Could not open camera." << std::endl; return EXIT_FAILURE; } // 主循环:读取摄像头帧并传递给 edit 模块 while (true) { cv::Mat frame; // 存储每一帧图像 cap >> frame; // 获取新的一帧 // 检查是否成功读取帧 if (frame.empty()) { std::cerr << "Warning: Couldn't read a frame from the camera." << std::endl; continue; } // 使用 edit 模块处理帧 edit.Print(frame); } // 释放摄像头资源 cap.release(); return 0; } ``` #### 2.1.4 编译过程 ##### 2.1.4.1 Cmake介绍 ```cmake # CMake最低版本要求 cmake_minimum_required(VERSION 3.10) project(test_capture) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # 定义项目根目录路径 set(PROJECT_ROOT_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../..") message("PROJECT_ROOT_PATH = " ${PROJECT_ROOT_PATH}) include("${PROJECT_ROOT_PATH}/toolchains/arm-rockchip830-linux-uclibcgnueabihf.toolchain.cmake") # 定义 OpenCV SDK 路径 set(OpenCV_ROOT_PATH "${PROJECT_ROOT_PATH}/third_party/opencv-mobile-4.10.0-lockzhiner-vision-module") set(OpenCV_DIR "${OpenCV_ROOT_PATH}/lib/cmake/opencv4") find_package(OpenCV REQUIRED) set(OPENCV_LIBRARIES "${OpenCV_LIBS}") # 定义 LockzhinerVisionModule SDK 路径 set(LockzhinerVisionModule_ROOT_PATH "${PROJECT_ROOT_PATH}/third_party/lockzhiner_vision_module_sdk") set(LockzhinerVisionModule_DIR "${LockzhinerVisionModule_ROOT_PATH}/lib/cmake/lockzhiner_vision_module") find_package(LockzhinerVisionModule REQUIRED) # 配置摄像头数据 add_executable(Test-Capture test_capture.cc) target_include_directories(Test-Capture PRIVATE ${LOCKZHINER_VISION_MODULE_INCLUDE_DIRS}) target_link_libraries(Test-Capture PRIVATE ${OPENCV_LIBRARIES} ${LOCKZHINER_VISION_MODULE_LIBRARIES}) install( TARGETS Test-Capture RUNTIME DESTINATION . ) ``` ##### 2.1.4.2 编译项目 使用 Docker Destop 打开 LockzhinerVisionModule 容器并执行以下命令来编译项目 ```bash # 进入Demo所在目录 cd /LockzhinerVisionModuleWorkSpace/LockzhinerVisionModule/Cpp_example/A01_capture # 创建编译目录 rm -rf build && mkdir build && cd build # 配置交叉编译工具链 export TOOLCHAIN_ROOT_PATH="/LockzhinerVisionModuleWorkSpace/arm-rockchip830-linux-uclibcgnueabihf" # 使用cmake配置项目 cmake .. # 执行编译项目 make -j8 && make install ``` 在执行完上述命令后,会在build目录下生成可执行文件。 #### 2.1.5 例程运行示例 ##### 2.1.5.1 运行过程 在凌智视觉模块中输入以下命令: ```bash chmod 777 Test_Capture ./Test_Capture ``` ##### 2.1.5.2 运行效果 ![示例图片](./images/A01_image_1.png) #### 2.1.6 总结 本文档介绍了如何使用 LockAI 和 OpenCV 实现摄像头模块的视频流读取与图像传输。核心步骤包括: 初始化摄像头并设置分辨率; 打开摄像头并逐帧捕获图像; 使用 Edit 模块进行图像传输。 **注意事项**: 1. 推荐使用 640x480 分辨率以平衡性能和画质; 2. 确保 Edit 模块连接成功后再进行图像传输; ### 2.2 GPIO使用指南 在外设的使用过程中,最基本的就是GPIO的使用。在本章节,我们将介绍GPIO的使用。 #### 2.2.1 GPIO基本知识 ##### 2.2.1.1 什么是GPIO GPIO(General Purpose Input/Output,通用输入输出)是微控制器或处理器上的一种引脚,可以由用户配置为输入模式或输出模式,用于与外部设备进行交互。 - 输入模式:GPIO引脚可以读取外部信号的状态(高电平或低电平)。 - 输出模式:GPIO引脚可以输出高电平或低电平信号,驱动外部设备。 ##### 2.2.1.2 GPIO的工作模式 GPIO通常支持以下两种工作模式: - 输入模式:用于检测外部信号的状态,例如按键输入。 - 输出模式:用于控制外部设备,例如点亮LED灯。 ##### 2.2.1.3 GPIO的状态 GPIO的状态可以是以下三种之一: - 低电平(LOW):表示逻辑0,通常对应于0V。 - 高电平(HIGH):表示逻辑1,通常对应于供电电压(例如3.3V或5V)。 - 错误状态(ERROR):表示GPIO操作失败。 #### 2.2.2 API文档 ##### 2.2.2.1 头文件 ```c++ #include ``` ##### 2.2.2.2 GPIOMode枚举类型 ```c++ enum class GPIOMode { IN, ///< 输入模式(信号检测) OUT ///< 输出模式(设备驱动) }; ``` - 作用:定义GPIO引脚的工作模式 - 成员说明: - IN:表示输入模式。 - OUT:表示输出模式。 ##### 2.2.2.3 GPIOState枚举类型 ```c++ enum class GPIOState { LOW = 0, ///< 低电平(0V) HIGH = 1, ///< 高电平(3.3V/5V) ERROR = 2 ///< 操作异常状态 }; ``` - 作用:定义GPIO引脚的状态。 - 成员说明: - LOW:表示低电平(0V)。 - HIGH:表示高电平(3.3V/5V)。 - ERROR:操作异常状态 ##### 2.2.2.4 GPIOBase类 ###### 2.2.2.4.1 头文件 ```cpp #include ``` ###### 2.2.2.4.2 配置GPIO工作模式 ```c++ bool config(GPIOMode mode); ``` - 参数: - mode:GPIO的工作模式,可以是GPIOMode::IN或GPIOMode::OUT。 - 返回值: - true:表示配置成功。 - false:表示配置失败。 ###### 2.2.2.4.3 设置GPIO输出状态 ```c++ bool Write(GPIOState state); ``` - 参数: - state:GPIO的状态,可以是GPIOState::LOW或GPIOState::HIGH。 - 返回值: - true:表示设置成功。 - false':表示设置失败。 ###### 2.2.2.4.4 读取GPIO状态 ```c++ bool Read(GPIOState& state); ``` - 参数: - state:GPIO的状态,用于存储读取到的状态。 - 返回值: - true:表示读取成功。 - false:表示读取失败。 ##### 2.2.2.5 GPIO硬件映射 ```c++ /* - GPIO0系列 -*/ using GPIO0A0 = GPIOBase<0, 'A', 0>; ///< 端口0A的第0脚 /* - GPIO1系列 -*/ using GPIO1C7 = GPIOBase<1, 'C', 7>; ///< 端口1C的第7脚 /* - GPIO2系列 -*/ using GPIO2A0 = GPIOBase<2, 'A', 0>; ///< 端口2A的第0脚 using GPIO2A1 = GPIOBase<2, 'A', 1>; ///< 端口2A的第1脚 ``` - 说明: - GPIO0A0: 表示端口组0,端口A的第0脚 - GPIO1C7: 表示端口组1,端口C的第7脚 - GPIO2A0: 表示端口组2,端口A的第0脚 #### 2.2.3 综合代码解析 ##### 2.2.3.1 GPIO输入模式使用示例 ###### 2.2.3.1.1 流程图 ``` 开始 │ ├── 包含必要的头文件 │ ├── 主函数入口 │ ├── 创建 `lockzhiner_vision_module::periphery::GPIO0A0` 对象 `gpio` │ ├── 配置 GPIO 模式为输入模式 (`GPIOMode::IN`) │ ├── 成功 -> 继续执行 │ └── 失败 -> │ └── 输出 "Failed to config gpio mode" │ └── 返回错误码 1 │ ├── 定义 GPIO 状态变量 `state` │ ├── 读取 GPIO 状态 │ ├── 成功 -> 继续执行 │ └── 失败 -> │ └── 输出 "Failed to read gpio mode" │ └── 返回错误码 1 │ ├── 输出 GPIO 的状态值(转换为 `uint8_t` 类型) │ └── 程序正常退出(返回 0) ``` ###### 2.2.3.1.2 代码解析 - 创建GPIO0A0对象gpio ```c++ lockzhiner_vision_module::periphery::GPIO0A0 gpio; ``` - 配置GPIOState变量state ```c++ lockzhiner_vision_module::periphery::GPIOState state; ``` - 配置GPIO为输入模式 ```c++ if (!gpio.Config(lockzhiner_vision_module::periphery::GPIOMode::IN)) { std::cout << "Failed to config gpio mode" << std::endl; return 1; } ``` - 读取GPIO状态,如果无法读取的话打印 Failed to read gpio mode。 ```c++ if (!gpio.Read(state)) { std::cout << "Failed to read gpio mode" << std::endl; return 1; } ``` - 输出结果 ```c++ td::cout << "state is " << static_cast(state) << std::endl; ``` ###### 2.2.3.1.3 代码实现 ```c++ # include # include # include int main() { lockzhiner_vision_module::periphery::GPIO0A0 gpio; if (!gpio.Config(lockzhiner_vision_module::periphery::GPIOMode::IN)) { std::cout << "Failed to config gpio mode" << std::endl; return 1; } lockzhiner_vision_module::periphery::GPIOState state; if (!gpio.Read(state)) { std::cout << "Failed to read gpio mode" << std::endl; return 1; } std::cout << "state is " << static_cast(state) << std::endl; return 0; } ``` ##### 2.2.3.2 GPIO输出模式使用示例 ###### 2.2.3.2.1 流程图 ``` 开始 │ ├── 包含必要的头文件 │ ├── 主函数入口 │ ├── 创建 `lockzhiner_vision_module::periphery::GPIO0A0` 对象 `gpio` │ ├── 配置 GPIO 模式为输出模式 (`GPIOMode::OUT`) │ ├── 成功 -> 继续执行 │ └── 失败 -> │ └── 输出 "Failed to config gpio mode" │ └── 返回错误码 1 │ ├── 设置 GPIO 状态为高电平 (`GPIOState::HIGH`) │ ├── 成功 -> 继续执行 │ └── 失败 -> │ └── 输出 "Failed to config gpio mode" │ └── 返回错误码 1 │ ├── 循环等待 (计数器 i 从 0 到 9) │ ├── 输出当前等待进度(i 值) │ ├── 线程休眠 1 秒 │ └── 计数器 i 自增 │ ├── 设置 GPIO 状态为低电平 (`GPIOState::LOW`) │ ├── 成功 -> 继续执行 │ └── 失败 -> │ └── 输出 "Failed to config gpio mode" │ └── 返回错误码 1 │ └── 程序正常退出(返回 0) ``` ###### 2.2.3.2.2 代码解析 - 创建GPIO0A0对象gpio ```c++ lockzhiner_vision_module::periphery::GPIO0A0 gpio; ``` - 配置GPIOState变量state ```c++ lockzhiner_vision_module::periphery::GPIOState state; ``` - 配置GPIO为输出模式 ```c++ if (!gpio.Config(lockzhiner_vision_module::periphery::GPIOMode::OUT)) { std::cout << "Failed to config gpio mode" << std::endl; return 1; } ``` - 输出高电平信号 ```c++ if (!gpio.Write(lockzhiner_vision_module::periphery::GPIOState::HIGH)) { std::cout << "Failed to write gpio mode" << std::endl; return 1; } ``` ###### 2.2.3.2.3 代码实现 ```c++ #include #include #include int main() { lockzhiner_vision_module::periphery::GPIO0A0 gpio; if (!gpio.Config(lockzhiner_vision_module::periphery::GPIOMode::OUT)) { std::cout << "Failed to config gpio mode" << std::endl; return 1; } if (!gpio.Write(lockzhiner_vision_module::periphery::GPIOState::HIGH)) { std::cout << "Failed to config gpio mode" << std::endl; return 1; } for (int i = 0; i < 10; i++) { std::cout << "Wait: " << i << "/" << 10 << std::endl; std::this_thread::sleep_for(std::chrono::seconds(1)); } if (!gpio.Write(lockzhiner_vision_module::periphery::GPIOState::LOW)) { std::cout << "Failed to config gpio mode" << std::endl; return 1; } return 0; } ``` #### 2.2.4 编译过程 ##### 2.2.4.1 Cmake介绍 ```cmake # CMake最低版本要求 cmake_minimum_required(VERSION 3.10) project(test_gpio) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # 定义项目根目录路径 set(PROJECT_ROOT_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../..") message("PROJECT_ROOT_PATH = " ${PROJECT_ROOT_PATH}) include("${PROJECT_ROOT_PATH}/toolchains/arm-rockchip830-linux-uclibcgnueabihf.toolchain.cmake") # 定义 LockzhinerVisionModule SDK 路径 set(LockzhinerVisionModule_ROOT_PATH "${PROJECT_ROOT_PATH}/third_party/lockzhiner_vision_module_sdk") set(LockzhinerVisionModule_DIR "${LockzhinerVisionModule_ROOT_PATH}/lib/cmake/lockzhiner_vision_module") find_package(LockzhinerVisionModule REQUIRED) # 配置 GPIO 输出 Demo add_executable(Test-GPIO-Write GPIO_Write.cc) target_include_directories(Test-GPIO-Write PRIVATE ${LOCKZHINER_VISION_MODULE_INCLUDE_DIRS}) target_link_libraries(Test-GPIO-Write PRIVATE ${LOCKZHINER_VISION_MODULE_LIBRARIES}) # 配置 GPIO 读取 Demo add_executable(Test-GPIO-Read GPIO_Read.cc) target_include_directories(Test-GPIO-Read PRIVATE ${LOCKZHINER_VISION_MODULE_INCLUDE_DIRS}) target_link_libraries(Test-GPIO-Read PRIVATE ${LOCKZHINER_VISION_MODULE_LIBRARIES}) install( TARGETS Test-GPIO-Read TARGETS Test-GPIO-Write RUNTIME DESTINATION . ) ``` ##### 2.2.4.2 编译项目 使用 Docker Destop 打开 LockzhinerVisionModule 容器并执行以下命令来编译项目 ```bash ## 进入Demo所在目录 cd /LockzhinerVisionModuleWorkSpace/LockzhinerVisionModule/Cpp_example/A02_GPIO ## 创建编译目录 rm -rf build && mkdir build && cd build ## 配置交叉编译工具链 export TOOLCHAIN_ROOT_PATH="/LockzhinerVisionModuleWorkSpace/arm-rockchip830-linux-uclibcgnueabihf" ## 使用cmake配置项目 cmake .. ## 执行编译项目 make -j8 && make install ``` #### 2.2.5 运行效果展示 ##### 2.2.5.1 准备工作 1. 准备一个在3.3 v 左右的一个信号输出源。 ##### 2.2.5.2 运行过程 在凌智视觉模块中输入以下命令: ```bash chmod 777 Test-GPIO-Read ./Test-GPIO-Read chmod 777 Test-GPIO-Write ./Test-GPIO-Write ``` ##### 2.2.5.3 运行结果 - 运行GPIO输出例程 我们可以查看示波器看到,GPIO_0A0输出了3.4 V 左右的电压 ![](./images/A02_image_1.png) 电压持续10 s 后回复了正常 ![](./images/A02_image_2.png) - 运行 GPIO 输入例程 可以看到,在接高电平引脚的情况下,引脚的状态信息为 GPIOState.LOW ![](./images/A02_image_3.png) 在接低电平引脚的情况下,引脚的状态信息为 GPIOState.HIGH ![](./images/A02_image_4.png) #### 2.2.6 总结 通过上述内容,我们介绍了GPIO的基本概念、API定义以及具体的使用示例。按照以下步骤,您可以轻松地使用GPIO: - 配置GPIO的工作模式(输入或输出)。 - 根据需求读取或写入GPIO状态。 - 检查操作是否成功,并根据返回值处理异常。 ### 2.3 PWM使用指南 在电子工程和嵌入式系统开发中,脉冲宽度调制(PWM, Pulse Width Modulation)是一项关键技术,它允许我们通过调整脉冲信号的占空比来控制模拟信号的平均电平。 #### 2.3.1 PWM基本知识 ##### 2.3.1.1 什么是PWM PWM(Pulse Width Modulation,脉冲宽度调制)是一种通过改变脉冲信号的占空比来控制输出信号的技术。 - 频率:表示每秒产生脉冲信号的次数,单位为Hz。 - 占空比:表示高电平时间与整个周期的比例,范围为0.0到1.0(或0%到100%)。例如: - 占空比为0.5表示高电平占一半时间,低电平占一半时间。 - 占空比为1.0表示始终为高电平。 - 占空比为0.0表示始终为低电平。 ##### 2.3.1.2 PWM的应用场景 PWM广泛应用于以下领域: - 电机控制:通过调节占空比控制电机转速。 - LED亮度调节:通过改变占空比控制LED的亮度。 - 信号生成:用于生成特定波形的信号。 #### 2.3.2 API文档 ##### 2.3.2.1 头文件 ```c++ #include ``` ##### 2.3.2.2 配置PWM输出参数 ```c++ bool Config(uint32_t frequency, float duty_cycle); ``` - 参数: - frequency:表示每秒产生脉冲信号的次数,单位为Hz。 - duty_cycle:表示高电平时间与整个周期的比例,范围为0.0到1.0(或0%到100%)。 - 返回值: - true:表示配置成功。 - false:表示配置失败。 ##### 2.3.2.3 打开PWM输出 ```c++ bool Open(); ``` - 参数:无 - 返回值: - true:表示打开成功。 - false:表示打开失败。 ##### 2.3.2.4 关闭PWM输出 ```c++ bool Close(); ``` - 参数:无 - 返回值: - true:表示关闭成功。 - false:表示关闭失败。 #### 2.3.3 综合代码解析 ##### 2.3.3.1 流程图 ``` 开始 │ ├── 包含必要的头文件 │ ├── 主函数入口,并检查命令行参数数量(argc) │ ├── 如果 argc 等于 3 │ ├─ 解析频率和占空比参数 │ └─ 频率 = atoi(argv[1]) │ └─ 占空比 = strtof(argv[2], nullptr) │ └── 否则使用默认值(频率=1000000, 占空比=0.5) │ ├── 输出频率和占空比信息 │ ├── 创建 `lockzhiner_vision_module::periphery::PWM9` 对象 `pwm` │ ├── 打开 PWM 并设置频率和占空比 │ ├── 成功 -> 继续执行 │ └── 失败 -> │ └── 输出 "Failed to open adc." │ └── 返回错误码 1 │ ├── 循环等待 (计数器 i 从 0 到 9) │ ├── 输出当前等待进度(i 值) │ ├── 线程休眠 1 秒 │ └── 计数器 i 自增 │ ├── 关闭 PWM │ ├── 成功 -> 继续执行 │ └── 失败 -> │ └── 输出 "Failed to open adc."(注意:这里应该为 "Failed to close pwm." 更准确) │ └── 返回错误码 1 │ └── 程序正常退出(返回 0) ``` ##### 2.3.3.2 代码解析 - 解析命令行参数 ```c++ if (argc == 3) { frequency = std::atoi(argv[1]); duty_cycle = std::strtof(argv[2], nullptr); } ``` - 实例化PWM9对象 ```c++ lockzhiner_vision_module::periphery::PWM9 pwm; ``` - 打开PWM通道 ```c++ pwm.Open() ``` ##### 2.3.3.3 完整代码 ```c++ #include #include #include #include #include int main(int argc, char *argv[]) { uint32_t frequency = 1000000; float duty_cycle = 0.5; if (argc == 3) { frequency = std::atoi(argv[1]); duty_cycle = std::strtof(argv[2], nullptr); } std::cout << "frequency is " << frequency << std::endl; std::cout << "duty_cycle is " << duty_cycle << std::endl; lockzhiner_vision_module::periphery::PWM9 pwm; if (!pwm.Open(frequency, duty_cycle)) { std::cout << "Failed to open adc." << std::endl; return 1; } for (int i = 0; i < 10; i++) { std::cout << "Wait: " << i << "/" << 10 << std::endl; std::this_thread::sleep_for(std::chrono::seconds(1)); } // 关闭 PWM if (!pwm.Close()) { std::cout << "Failed to open adc." << std::endl; return 1; } return 0; } ``` #### 2.3.4 编译过程 ##### 2.3.4.1 编译环境搭建 - 请确保你已经按照第三章开发环境搭建指南正确配置了开发环境。 - 同时以正确连接开发板。 ##### 2.3.4.2 Cmake介绍 ```cmake # CMake最低版本要求 cmake_minimum_required(VERSION 3.10) project(test_pwm) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) #定义项目根目录路径 set(PROJECT_ROOT_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../..") message("PROJECT_ROOT_PATH = " ${PROJECT_ROOT_PATH}) include("${PROJECT_ROOT_PATH}/toolchains/arm-rockchip830-linux-uclibcgnueabihf.toolchain.cmake") #义 LockzhinerVisionModule SDK 路径 set(LockzhinerVisionModule_ROOT_PATH "${PROJECT_ROOT_PATH}/third_party/lockzhiner_vision_module_sdk") set(LockzhinerVisionModule_DIR "${LockzhinerVisionModule_ROOT_PATH}/lib/cmake/lockzhiner_vision_module") find_package(LockzhinerVisionModule REQUIRED) # 配置 PWM 输出 Demo add_executable(Test-PWM PWM.cc) target_include_directories(Test-PWM PRIVATE ${LOCKZHINER_VISION_MODULE_INCLUDE_DIRS}) target_link_libraries(Test-PWM PRIVATE ${LOCKZHINER_VISION_MODULE_LIBRARIES}) install( TARGETS Test-PWM RUNTIME DESTINATION . ) ``` ##### 2.3.4.3 编译项目 使用 Docker Destop 打开 LockzhinerVisionModule 容器并执行以下命令来编译项目 ```bash # 进入Demo所在目录 cd /LockzhinerVisionModuleWorkSpace/LockzhinerVisionModule/Cpp_example/A03_PWM # 创建编译目录 rm -rf build && mkdir build && cd build # 配置交叉编译工具链 export TOOLCHAIN_ROOT_PATH="/LockzhinerVisionModuleWorkSpace/arm-rockchip830-linux-uclibcgnueabihf" # 使用cmake配置项目 cmake .. # 执行编译项目 make -j8 && make install ``` 在执行完上述命令后,会在build目录下生成可执行文件。 #### 2.3.5 例程运行示例 ##### 2.3.5.1 运行过程 在凌智视觉模块中输入以下命令: ```bash chmod 777 Test-PWM ./Test-PWM 1000 0.5 ``` ##### 2.3.5.2 运行效果 - 运行代码后,我们在示波器上可以看到如下波形 ![](./images/A03_images_1.png) #### 2.3.6 总结 通过上述内容,我们介绍了PWM的基本概念、API定义以及具体的使用示例。按照以下步骤,您可以轻松地使用PWM功能: - 初始化PWM通道:通过Open()函数设置PWM的频率和占空比。 - 运行PWM输出:根据需求保持PWM输出一段时间。 - 关闭PWM通道:操作完成后,务必调用Close()函数释放资源,避免PWM持续输出。 ### 2.4 ADC使用指南 在嵌入式系统开发中,读取模拟信号是常见的需求。例如,测量电池电量、温度传感器数据等场景都需要用到ADC(Analog-to-Digital Converter,模数转换器)。LockAI提供了ADC引脚,方便用户获取模拟信号。 #### 2.4.1 ADC基本知识 ##### 2.4.1.1 什么是ADC ADC(Analog-to-Digital Converter,模数转换器)是一种将模拟信号转换为数字信号的硬件模块。 - 模拟信号:连续变化的信号,例如电压或电流。 - 数字信号:离散的数值,通常以二进制形式表示。 - 分辨率:ADC的精度,通常以位数表示(如12位ADC可以表示0到4095之间的值)。 ##### 2.4.1.2 ADC应用场景 ADC广泛应用于以下领域: - 电池电量检测:通过测量电池电压估算电量。 - 环境监测:读取温度、湿度等传感器的模拟输出。 - 音频处理:将模拟音频信号转换为数字信号进行处理。 #### 2.4.2 API 文档 ##### 2.4.2.1 使用ADC类需要引用头文件 ```c++ #include ``` ##### 2.4.2.2 读取模拟信号(转换后的) ```c++ bool Read(float& adc_data); ``` - 参数: - [out] adc_data: 存储转换后的电压值(单位:伏特) - 返回值 - true:表示读取成功。 - false:表示读取失败。 ##### 2.4.2.3 读取模拟信号(原始的) ```c++ float Read(); ``` - 参数:无 - 返回值: - 返回模拟信号值(单位:mV) - 失败时返回0.0f #### 2.4.3 综合代码解析 ##### 2.4.3.1 流程图 ``` 开始 │ ├── 包含必要的头文件 │ ├── 主函数入口 │ ├── 创建 `lockzhiner_vision_module::periphery::ADCIN1` 对象 `adc` │ ├── 定义浮点型变量 `adc_data` │ ├── 读取 ADC 数据 │ ├── 成功 -> 继续执行 │ └── 失败 -> │ └── 输出 "Failed to read adc data." │ └── 返回错误码 1 │ ├── 输出 ADC 数据(单位:mV) │ └── 程序正常退出(返回 0) ``` ##### 2.4.3.2 代码解析 - 初始化ADC引脚 ```c++ lockzhiner_vision_module::periphery::ADCIN1 adc; ``` - 读取模拟信号 ```c++ adc.Read(adc_data) ``` ##### 2.4.3.3 完整代码实现 ```c++ #include #include int main() { lockzhiner_vision_module::periphery::ADCIN1 adc; float adc_data; if (!adc.Read(adc_data)) { std::cout << "Failed to read adc data." << std::endl; return 1; } std::cout << "adc_data is " << adc_data << "mV" << std::endl; return 0; } ``` #### 2.4.4 编译过程 ##### 2.4.4.1 Cmake介绍 ```cmake # CMake最低版本要求 cmake_minimum_required(VERSION 3.10) project(test_adc) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # 定义项目根目录路径 set(PROJECT_ROOT_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../..") message("PROJECT_ROOT_PATH = " ${PROJECT_ROOT_PATH}) include("${PROJECT_ROOT_PATH}/toolchains/arm-rockchip830-linux-uclibcgnueabihf.toolchain.cmake") # 定义 LockzhinerVisionModule SDK 路径 set(LockzhinerVisionModule_ROOT_PATH "${PROJECT_ROOT_PATH}/third_party/lockzhiner_vision_module_sdk") set(LockzhinerVisionModule_DIR "${LockzhinerVisionModule_ROOT_PATH}/lib/cmake/lockzhiner_vision_module") find_package(LockzhinerVisionModule REQUIRED) # 配置 ADC 读取 Demo add_executable(Test-ADC ADC.cc) target_include_directories(Test-ADC PRIVATE ${LOCKZHINER_VISION_MODULE_INCLUDE_DIRS}) target_link_libraries(Test-ADC PRIVATE ${LOCKZHINER_VISION_MODULE_LIBRARIES}) install( TARGETS Test-ADC RUNTIME DESTINATION . ) ``` ##### 2.4.4.2 编译项目 使用 Docker Destop 打开 LockzhinerVisionModule 容器并执行以下命令来编译项目 ```bash # 进入Demo所在目录 cd /LockzhinerVisionModuleWorkSpace/LockzhinerVisionModule/Cpp_example/A01_adc # 创建编译目录 rm -rf build && mkdir build && cd build # 配置交叉编译工具链 export TOOLCHAIN_ROOT_PATH="/LockzhinerVisionModuleWorkSpace/arm-rockchip830-linux-uclibcgnueabihf" # 使用cmake配置项目 cmake .. # 执行编译项目 make -j8 && make install ``` 在执行完上述命令后,会在build目录下生成可执行文件。 #### 2.4.5 例程运行示例 ##### 2.4.5.1 运行过程 在凌智视觉模块中输入以下命令: ```bash chmod 777 Test-ADC ./Test-ADC ``` ##### 2.4.5.2 运行效果 - 外部输入电压如下图所示 ![](./images/A04.png) - 程序运行结果如下图所示 ![](./images/A04_image_2.png) 可以看到有一定的误差,误差一般在10mv以内 #### 2.4.6 总结 通过上述内容,我们介绍了ADC的基本概念、API定义以及具体的使用示例。按照以下步骤,您可以轻松地使用ADC功能: - 初始化ADC引脚:创建对应的ADC对象(如ADCIN1)。 - 读取模拟信号:调用Read()函数获取模拟信号值。 - 处理读取结果:检查返回值是否成功,并根据需要处理读取到的数据。 ### 2.5 串口通讯 串口(Serial Port),也称为串行接口或串行通信接口,是一种用于连接计算机与外部设备并进行数据传输的接口技术。它使用较少的导线(通常只需要几根线),并且可以在较长的距离上可靠地传输数据,尽管速度相对较慢。 在本章节中,我们将介绍如何使用 Lockzhiner Vision Module 上的串口进行数据传输。为了方便调试,我们使用 CH340 USB 转串口模块(以下简称 CH340) 进行调试,请正确将模块的引脚按照以下方式连接: - LockzhinerVisionModule RX1 <-> CH340 TXD - LockzhinerVisionModule TX1 <-> CH340 RXD - LockzhinerVisionModule GND <-> CH340 GND #### 2.5.1 串口通讯基本知识 ##### 2.5.1.1 什么是串口通讯 串口通讯是一种通过串行方式传输数据的技术,数据以位为单位逐位发送和接收。相比并行通讯,串口通讯具有以下特点: - 优点: - 使用较少的导线,适合远距离传输。 - 实现简单,成本较低。 - 缺点: - 数据传输速度较慢。 ##### 2.5.1.2 常见参数 串口通讯的关键参数包括: - 波特率(Baud Rate):表示每秒传输的位数,通常为 9600、38400、57600、115200 等。 - 数据位(Data Bits):表示每位传输的数据位数,通常为 8 位。 - 停止位(Stop Bits):表示发送和接收的停止位,通常为 1 位。 - 校验位(Parity Bit):表示校验位,通常为无校验位、奇校验位和偶校验位。 * 在LockAI中我们主要关心波特率即可。 #### 2.5.2 API文档 ##### 2.5.2.1 头文件 ```c++ #include ``` ##### 2.5.2.2 打开串口 ```c++ bool Open(uint32_t baud_rate = 115200); ``` - 参数: - baud_rate:波特率,默认为115200。(支持标准波特率值) - 返回值: - true:打开串口成功。 - false:打开串口失败。 ##### 2.5.2.3 关闭串口 ```c++ bool Close(); ``` - 返回值: - true:关闭串口成功。 - false:关闭串口失败。 ##### 2.5.2.4 发送数据 ```c++ bool Write(const std::string& data); ``` - 参数: - data:要发送的数据。 - 返回值: - true:发送数据成功。 - false:发送数据失败。 ##### 2.5.2.5 读取数据 ```c++ bool Read(std::string& data, size_t size); ``` - 参数: - data:用于存储读取数据的字符串。 - size:要读取的数据大小。 - 返回值: - true:读取数据成功。 - false:读取数据失败。 #### 2.5.3 综合代码解析 ##### 2.5.3.1 串口读数据示例 ###### 2.5.3.1.1 流程图 ``` 开始 │ ├── 包含必要的头文件 │ ├── 主函数入口 │ ├── 创建 `lockzhiner_vision_module::periphery::USART1` 对象 `usart` │ ├── 打开串口并设置波特率为 115200 │ ├── 成功 -> 继续执行 │ └── 失败 -> │ └── 输出 "Failed to open usart." │ └── 返回错误码 1 │ ├── 输出提示信息:"Start receiving serial port data." │ ├── 进入无限循环等待接收数据 │ ├── 从串口读取数据到缓冲区 `buffer`(最多 1024 字节) │ │ ├── 缓冲区非空 -> 输出接收到的数据 │ │ └── 缓冲区为空 -> 直接继续下一次循环 │ │ │ └── 持续监听串口输入 │ └── 程序正常退出(返回 0) ``` ###### 2.5.3.1.2 代码解释 - 初始化USART模块,创建USART1对象。 ```c++ lockzhiner_vision_module::periphery::USART1 usart; ``` - 打开串口 ```c++ usart.Open(115200) ``` - 串口读取 ```c++ usart.Read(buffer, 1024); ``` ###### 2.5.3.1.3 代码实现 ```c++ #include #include int main() { lockzhiner_vision_module::periphery::USART1 usart; if (!usart.Open(115200)) { std::cout << "Failed to open usart." << std::endl; return 1; } std::cout << "Start receiving serial port data." << std::endl; while (1) { std::string buffer; usart.Read(buffer, 1024); if (!buffer.empty()) { std::cout << buffer << std::endl; } } return 0; } ``` ##### 2.5.3.2 串口写数据示例 ###### 2.5.3.2.1 流程图 ``` 开始 │ ├── 包含必要的头文件 (`usart.h`, ``) │ ├── 主函数入口 │ ├── 创建 `lockzhiner_vision_module::periphery::USART1` 对象 `usart` │ ├── 打开串口并设置波特率为 115200 │ ├── 成功 -> 继续执行 │ └── 失败 -> │ └── 输出 "Failed to open usart." │ └── 返回错误码 1 │ ├── 向串口发送数据 "Hello World\n" │ ├── 成功 -> 继续执行 │ └── 失败 -> │ └── 输出 "Failed to send data." │ └── 返回错误码 1 │ └── 程序正常退出(返回 0) ``` ###### 2.5.3.2.2 代码解释 - 初始化USART模块, ```c++ lockzhiner_vision_module::periphery::USART1 usart; ``` - 打开串口 ```c++ usart.Open(115200) ``` - 发送数据 ```c++ usart.Write("Hello World\n"); ``` ###### 2.5.3.2.3 代码实现 ```c++ #include #include int main() { lockzhiner_vision_module::periphery::USART1 usart; if (!usart.Open(115200)) { std::cout << "Failed to open usart." << std::endl; return 1; } if (!usart.Write("Hello World\n")) { std::cout << "Failed to send data." << std::endl; return 1; } return 0; } ``` #### 2.5.4 编译过程 ##### 2.5.4.1 编译环境搭建 - 请确保你已经按照 [开发环境搭建指南](https://gitee.com/LockzhinerAI/LockzhinerVisionModule/blob/master/docs/introductory_tutorial/cpp_development_environment.md) 正确配置了开发环境。 - 同时已正确连接开发板。 ##### 2.5.4.2 Cmake介绍 ```cmake # CMake最低版本要求 cmake_minimum_required(VERSION 3.10) project(test_usart) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # 定义项目根目录路径 set(PROJECT_ROOT_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../..") message("PROJECT_ROOT_PATH = " ${PROJECT_ROOT_PATH}) include("${PROJECT_ROOT_PATH}/toolchains/arm-rockchip830-linux-uclibcgnueabihf.toolchain.cmake") # 定义 LockzhinerVisionModule SDK 路径 set(LockzhinerVisionModule_ROOT_PATH "${PROJECT_ROOT_PATH}/third_party/lockzhiner_vision_module_sdk") set(LockzhinerVisionModule_DIR "${LockzhinerVisionModule_ROOT_PATH}/lib/cmake/lockzhiner_vision_module") find_package(LockzhinerVisionModule REQUIRED) # 配置 USART 输出 Demo add_executable(Test-USART-Write USART_Write.cc) target_include_directories(Test-USART-Write PRIVATE ${LOCKZHINER_VISION_MODULE_INCLUDE_DIRS}) target_link_libraries(Test-USART-Write PRIVATE ${LOCKZHINER_VISION_MODULE_LIBRARIES}) # 配置 USART 读取 Demo add_executable(Test-USART-Read USART_Read.cc) target_include_directories(Test-USART-Read PRIVATE ${LOCKZHINER_VISION_MODULE_INCLUDE_DIRS}) target_link_libraries(Test-USART-Read PRIVATE ${LOCKZHINER_VISION_MODULE_LIBRARIES}) install( TARGETS Test-USART-Read TARGETS Test-USART-Write RUNTIME DESTINATION . ) ``` ##### 2.5.4.3 编译项目 使用 Docker Destop 打开 LockzhinerVisionModule 容器并执行以下命令来编译项目 ```bash # 进入Demo所在目录 cd /LockzhinerVisionModuleWorkSpace/LockzhinerVisionModule/Cpp_example/A05_USART # 创建编译目录 rm -rf build && mkdir build && cd build # 配置交叉编译工具链 export TOOLCHAIN_ROOT_PATH="/LockzhinerVisionModuleWorkSpace/arm-rockchip830-linux-uclibcgnueabihf" # 使用cmake配置项目 cmake .. # 执行编译项目 make -j8 && make install ``` 在执行完上述命令后,会在build目录下生成可执行文件。 #### 2.5.5 例程运行示例 ##### 2.5.5.1 运行过程 在凌智视觉模块中输入以下命令: ```shell chmod 777 Test-USART-Read ./Test-USART-Read chmod 777 Test-USART-Write ./Test-USART-Write ``` ##### 2.5.5.2 运行结果 - 在电脑端打开串口调试工具,选择对应串口,波特率为115200,数据位为8,停止位为1,校验位为无校验位。 - 电脑端串口配置如下所示: ![](./images/A05_image_1.png) - 运行串口写数据示例后,我们在电脑端可以收到如下数据。 ![](./images/A05_image_2.png) - 运行串口读数据示例后,我们在电脑端发送Hello World,然后我们收到如下数据。 ![](./images/A05_image_3.png) #### 2.5.6 总结 通过上述内容,我们介绍了串口通讯的基本概念、API定义以及具体的使用示例。按照以下步骤,您可以轻松地使用串口功能: - 初始化串口:调用Open()函数设置波特率并打开串口。 - 读取数据:调用Read()函数从串口接收数据。 - 写入数据:调用Write()函数向串口发送数据。 - 关闭串口:操作完成后,调用Close()函数释放资源。 ## 3. OpenCV 基础介绍 ### 3.1 图像的基本运算 在图像处理中,理解图像的基本操作是掌握计算机视觉技术的关键。本章节将介绍 OpenCV 中图像的基本运算方法,包括像素操作、逻辑运算和差值运算,并通过一个综合示例展示其实际应用。 #### 3.1.1 基本知识讲解 ##### 3.1.1.1 图像坐标系 在图像处理中,坐标是一个非常重要的概念: - 原点:图像的左上角为原点 (0, 0)。 - x 轴:从左到右递增。 - y 轴:从上到下递增。 ##### 3.1.1.2 图像的基本属性 每张图像都有以下基本属性: - 宽度(Width):图像的列数。 - 高度(Height):图像的行数。 - 通道数(Channels): - 灰度图:1 个通道。 - 彩色图:通常为 3 个通道(BGR)。 ##### 3.1.1.3 图像的基本操作 图像的基本操作包括: - 获取和设置像素值:访问和修改图像中的像素值。 - 逻辑运算:如按位与、或、异或等。 - 差值运算:计算两张图像之间的差异。 #### 3.1.2 API文档 ##### 3.1.2.1 头文件 ```cpp #include ``` ##### 3.1.2.2 获取设置像素点 ```c++ uchar cv::Mat::at(int row, int col) const; // 灰度图 cv::Vec3b cv::Mat::at(int row, int col) const; // 彩色图 ``` - 参数: - row:行索引(y坐标) - col:列索引(x坐标) - 返回值: - 对于灰度图,返回单通道值(unchar) - 对于彩色图,返回三通道值(cv::Vec3b) ##### 3.1.2.3 设置像素点 ```c++ void cv::Mat::at(int row, int col) = value; // 灰度图 void cv::Mat::at(int row, int col) = value; // 彩色图 ``` - 参数: - row:行索引(y坐标) - col:列索引(x坐标) - value:要设置的像素值(uchar或cv::Vec3b) ##### 3.1.2.4 获取图像的宽度和高度 ###### 3.1.2.4.1 获取宽度 ```c++ int cv::Mat::cols; ``` - 返回值 - 返回图像的宽度(列数) ###### 3.1.2.4.2 获取高度 ```c++ int cv::Mat::rows; ``` - 返回值 - 返回图像的高度(行数) ##### 3.1.2.5 获取图像的格式和大小 ###### 3.1.2.5.1 判断是否为灰度图 ```c++ int cv::Mat::channels(); ``` - 返回值 - 1表示灰度图 - 3表示彩色图 ###### 3.1.2.5.2 获取图像大小(字节数) ```c++ size_t cv::Mat::total(); // 总像素数 size_t cv::Mat::elemSize(); // 每个像素的字节数 size_t totalBytes = img.total() * img.elemSize();//计算公式 ``` - 返回值 - 返回图像的总字节数 ##### 3.1.2.6 图像取反 ```c++ void cv::bitwise_not(InputArray src, OutputArray dst); ``` - 参数: - src:输入图像(cv::Mat) - dst:输出图像(cv::Mat) - 返回值: - 结果储存在dst中 ##### 3.1.2.7 图像逻辑运算 ###### 3.1.2.7.1 按位与(AND) ```c++ void cv::bitwise_and(InputArray src1, InputArray src2, OutputArray dst); ``` -参数: - src1:输入图像1(cv::Mat) - src2:输入图像2(cv::Mat) - dst:输出图像(cv::Mat) - 返回值: - 结果储存在dst中 ###### 3.1.2.7.2 按位或(OR) ```c++ void cv::bitwise_or(InputArray src1, InputArray src2, OutputArray dst); ``` - 参数: - src1:输入图像1(cv::Mat) - src2:输入图像2(cv::Mat) - dst:输出图像(cv::Mat) - 返回值: - 结果储存在dst中 ###### 3.1.2.7.3 按位异或(XOR) ```c++ void cv::bitwise_xor(InputArray src1, InputArray src2, OutputArray dst); ``` - 参数: - src1:输入图像1(cv::Mat) - src2:输入图像2(cv::Mat) - dst:输出图像(cv::Mat) - 返回值: - 结果储存在dst中 ###### 3.1.2.7.4 按位取反(NOT) ```c++ void cv::bitwise_not(InputArray src, OutputArray dst); ``` - 参数: - src:输入图像(cv::Mat) - dst:输出图像(cv::Mat) - 返回值: - 结果储存在dst中 ###### 3.1.2.7.5 复杂的图像逻辑运算 - 如果需要实现复杂的逻辑运算(如NAND、NOR、NXOR等),可以通过组合上述基本函数来完成。例如: **NAND 与非运算 ```c++ cv::Mat nandResult; cv::bitwise_and(img1, img2, nandResult); // 计算 AND cv::bitwise_not(nandResult, nandResult); // 取反得到 NAND ``` - 通过这样的组合我们就可以实现更为复杂的逻辑运算了。 ##### 3.1.2.8 绝对差值 ```c++ void cv::absdiff(InputArray src1, InputArray src2, OutputArray dst); ``` - 参数: - src1:输入图像1(cv::Mat) - src2:输入图像2(cv::Mat) - dst:输出图像(cv::Mat) - 返回值: - 结果储存在dst中 #### 3.1.3 综合代码解析 ##### 3.1.3.1 流程图 ``` 开始 │ ├── 包含必要的头文件 │ ├── 主函数入口 │ ├── 检查命令行参数数量是否为3(程序名+两个图像路径) │ ├── 是 -> 继续执行 │ └── 否 -> 输出使用说明并返回错误码 -1 │ ├── 从命令行参数中读取两个图像的路径 │ ├── 加载两个图像 │ ├── 成功 -> 继续执行 │ └── 失败 -> 输出错误信息并返回错误码 -1 │ ├── 获取第一个图像的尺寸并输出到控制台 │ ├── 在第一个图像上设置感兴趣区域(ROI),并将其颜色设置为蓝色 │ ├── 对第一个图像进行取反操作,生成新图像 │ ├── 计算两个原始图像之间的差异,生成差异图像 │ ├── 显示原始图像、取反后的图像以及差异图像 │ └── 等待用户按键后退出程序(正常结束) ``` ##### 3.1.3.2 代码解析 - 像素操作 ```c++ // 从(10,10)开始,宽40,高40,设置为蓝色 cv::Rect roi(10, 10, 40, 40); image1(roi).setTo(cv::Scalar(255, 0, 0)); ``` - 图像变换 ```c++ // 图像取反 cv::Mat invertedImage; cv::bitwise_not(image1, invertedImage); // 图像差分 cv::Mat diffImage; cv::absdiff(image1, image2, diffImage); ``` - 显示结果 ```c++ // 显示结果 cv::imshow("Original Image", image1); cv::imshow("Inverted Image", invertedImage); cv::imshow("Difference Image", diffImage); ``` ##### 3.1.3.3 代码实现 ```c++ #include #include #include int main(int argc, char *argv[]) { // 检查命令行参数数量是否正确 if (argc != 3) { std::cerr << "Usage: " << argv[0] << " " << std::endl; return -1; } // 从命令行参数中读取图像路径 std::string image1Path = argv[1]; std::string image2Path = argv[2]; // 加载图像 cv::Mat image1 = cv::imread(image1Path); cv::Mat image2 = cv::imread(image2Path); // 检查图像是否加载成功 if (image1.empty() || image2.empty()) { std::cerr << "Error: Could not load images!" << std::endl; return -1; } // 获取图像尺寸 int width = image1.cols; int height = image1.rows; std::cout << "Image size: " << width << "x" << height << std::endl; // 从(10,10)开始,宽40,高40,设置为蓝色 cv::Rect roi(10, 10, 40, 40); image1(roi).setTo(cv::Scalar(255, 0, 0)); // 图像取反 cv::Mat invertedImage; cv::bitwise_not(image1, invertedImage); // 图像差分 cv::Mat diffImage; cv::absdiff(image1, image2, diffImage); // 显示结果 cv::imshow("Original Image", image1); cv::imshow("Inverted Image", invertedImage); cv::imshow("Difference Image", diffImage); cv::waitKey(0); return 0; } ``` #### 3.1.4. 编译过程 ##### 3.1.4.1 编译环境搭建 - 请确保你已经按照 [开发环境搭建指南](https://gitee.com/LockzhinerAI/LockzhinerVisionModule/blob/master/docs/introductory_tutorial/cpp_development_environment.md) 正确配置了开发环境。 - 同时以正确连接开发板。 ##### 3.1.4.2 Cmake介绍 ```cmake ## CMake最低版本要求 cmake_minimum_required(VERSION 3.10) project(test-basic-method) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) ## 定义项目根目录路径 set(PROJECT_ROOT_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../..") message("PROJECT_ROOT_PATH = " ${PROJECT_ROOT_PATH}) include("${PROJECT_ROOT_PATH}/toolchains/arm-rockchip830-linux-uclibcgnueabihf.toolchain.cmake") ## 定义 OpenCV SDK 路径 set(OpenCV_ROOT_PATH "${PROJECT_ROOT_PATH}/third_party/opencv-mobile-4.10.0-lockzhiner-vision-module") set(OpenCV_DIR "${OpenCV_ROOT_PATH}/lib/cmake/opencv4") find_package(OpenCV REQUIRED) set(OPENCV_LIBRARIES "${OpenCV_LIBS}") ## 定义 LockzhinerVisionModule SDK 路径 set(LockzhinerVisionModule_ROOT_PATH "${PROJECT_ROOT_PATH}/third_party/lockzhiner_vision_module_sdk") set(LockzhinerVisionModule_DIR "${LockzhinerVisionModule_ROOT_PATH}/lib/cmake/lockzhiner_vision_module") find_package(LockzhinerVisionModule REQUIRED) ## 基本图像处理示例 add_executable(Test-basic-method basic_method.cc) target_include_directories(Test-basic-method PRIVATE ${LOCKZHINER_VISION_MODULE_INCLUDE_DIRS}) target_link_libraries(Test-basic-method PRIVATE ${OPENCV_LIBRARIES} ${LOCKZHINER_VISION_MODULE_LIBRARIES}) install( TARGETS Test-basic-method RUNTIME DESTINATION . ) ``` ##### 3.1.4.3 编译项目 使用 Docker Destop 打开 LockzhinerVisionModule 容器并执行以下命令来编译项目 ```bash ## 进入Demo所在目录 cd /LockzhinerVisionModuleWorkSpace/LockzhinerVisionModule/Cpp_example/B01_basic_method ## 创建编译目录 rm -rf build && mkdir build && cd build ## 配置交叉编译工具链 export TOOLCHAIN_ROOT_PATH="/LockzhinerVisionModuleWorkSpace/arm-rockchip830-linux-uclibcgnueabihf" ## 使用cmake配置项目 cmake .. ## 执行编译项目 make -j8 && make install ``` 在执行完上述命令后,会在build目录下生成可执行文件。 #### 3.1.5 例程运行示例 ##### 3.1.5.1 运行过程 在凌智视觉模块中输入以下命令: ```shell chmod 777 Test-basic-method ## 需要输入两张大小一样的图片 ./Test-basic-method image1_path image2_path ``` ##### 3.1.5.2 运行结果 - 原始图像: ![](./images/B01_2.png) - 原始图像左上角加上一个40*40的矩形蓝像素点 ![](./images/B01_Original.png) - 图像取反结果: ![](./images/B01_Inverted.png) - 图像差分结果: ![](./images/B01_Difference.png) #### 3.1.6 总结 通过上述内容,我们介绍了图像的基本操作及其对应的 OpenCV API。按照以下步骤,您可以轻松地进行图像的基本运算: - 获取和设置像素值:使用 cv::Mat::at 方法访问和修改像素。 - 逻辑运算:使用 cv::bitwise_and、cv::bitwise_or 等函数实现逻辑运算。 - 差值运算:使用 cv::absdiff 计算图像之间的差异。 - 复杂运算:通过组合基本函数实现更复杂的逻辑运算。 ### 3.2 使用图像的统计信息 在图像处理中,统计信息可以帮助我们了解图像的特性,例如区域内的像素分布、颜色转换以及特定区域的分析。本章节将介绍如何提取兴趣区域(ROI)、转换颜色通道、计算均值和标准差,以及查找最小值和最大值,并通过一个综合示例展示其实际应用。 #### 3.2.1 基本知识讲解 ##### 3.2.1.1 图像的兴趣区域(ROI) - ROI(Region of Interest):指图像中感兴趣的区域,通常用于局部分析或处理。 - 提取 ROI 的目的是减少数据量并专注于特定区域,从而提高处理效率。 ##### 3.2.1.2 颜色空间转换 - 不同的颜色空间适用于不同的任务。例如: - 灰度图:简化图像处理,常用于边缘检测等任务。 - HSV:用于颜色分割任务,分离色调、饱和度和亮度。 - LAB:更接近人类视觉感知,适合颜色校正。 ##### 3.2.1.3 图像统计信息 - 均值和标准差:反映图像整体亮度及亮度变化情况。 - 最小值和最大值:帮助识别图像中的极端像素值及其位置。 #### 3.2.2 API文档 ##### 3.2.2.1 头文件 ```c++ #include ``` ##### 3.2.2.2 提取兴趣区域(ROI) ```c++ cv::Mat roi = image(cv::Rect(x, y, w, h)); ``` - 功能: - 从图像中提取一个矩形区域。 - 参数: - image:输入图像(cv::Mat类型)。 - (x, y):ROI左上角的坐标。 - (w, h):ROI的宽高。 - 返回值: - 提取出的ROI图像(cv::Mat类型)。 ##### 3.2.2.3 转换为灰度图 ```c++ cv::cvtColor(image, grayImage, cv::COLOR_BGR2GRAY); ``` - 功能: - 将彩色图像转换为灰度图像。 - 参数: - image:输入图像(cv::Mat类型)。 - grayImage:输出灰度图像(cv::Mat类型)。 - COLOR_BGR2GRAY:将BGR图像转换为灰度图像。 - 返回值: - 无。最后结果储存在grayImage中。 **注意:** 其中根据不同的转换要求可以使用不同的转换代码,具体如下所示。 | 转换方向 | 转换代码 | 描述 | |-------------------------|----------------------------|---------------------------------------| | **BGR ↔ Grayscale** | `cv::COLOR_BGR2GRAY` | 将 BGR 图像转换为灰度图像 | | | `cv::COLOR_GRAY2BGR` | 将灰度图像转换为 BGR 图像 | | **BGR ↔ RGB** | `cv::COLOR_BGR2RGB` | 将 BGR 图像转换为 RGB 图像 | | | `cv::COLOR_RGB2BGR` | 将 RGB 图像转换为 BGR 图像 | | **BGR ↔ HSV** | `cv::COLOR_BGR2HSV` | 将 BGR 图像转换为 HSV 图像 | | | `cv::COLOR_HSV2BGR` | 将 HSV 图像转换为 BGR 图像 | | **BGR ↔ LAB** | `cv::COLOR_BGR2LAB` | 将 BGR 图像转换为 LAB 图像 | | | `cv::COLOR_LAB2BGR` | 将 LAB 图像转换为 BGR 图像 | | **BGR ↔ YUV** | `cv::COLOR_BGR2YUV` | 将 BGR 图像转换为 YUV 图像 | | | `cv::COLOR_YUV2BGR` | 将 YUV 图像转换为 BGR 图像 | | **BGR ↔ XYZ** | `cv::COLOR_BGR2XYZ` | 将 BGR 图像转换为 CIE XYZ 图像 | | | `cv::COLOR_XYZ2BGR` | 将 CIE XYZ 图像转换为 BGR 图像 | | **BGR ↔ YCrCb** | `cv::COLOR_BGR2YCrCb` | 将 BGR 图像转换为 YCrCb 图像 | | | `cv::COLOR_YCrCb2BGR` | 将 YCrCb 图像转换为 BGR 图像 | | **BGR ↔ HLS** | `cv::COLOR_BGR2HLS` | 将 BGR 图像转换为 HLS 图像 | | | `cv::COLOR_HLS2BGR` | 将 HLS 图像转换为 BGR 图像 | | **BGR ↔ Luv** | `cv::COLOR_BGR2LUV` | 将 BGR 图像转换为 Luv 图像 | | | `cv::COLOR_LUV2BGR` | 将 Luv 图像转换为 BGR 图像 | | **BGR ↔ Bayer** | `cv::COLOR_BayerBG2BGR` | 将 Bayer 格式图像转换为 BGR 图像 | | **BGR ↔ RGBA** | `cv::COLOR_BGR2RGBA` | 将 BGR 图像转换为 RGBA 图像(添加 Alpha 通道) | | | `cv::COLOR_RGBA2BGR` | 将 RGBA 图像转换为 BGR 图像 | ##### 3.2.2.4 计算均值和标准差 ```c++ cv::meanStdDev(src, mean, stddev); ``` - 功能: - 计算图像或矩阵元素的平均值和标准偏差。 - 参数: - src:输入图像或矩阵(cv::Mat类型)。 - mean:输出平均值(cv::Scalar类型)。 - stddev:输出标准偏差(cv::Scalar类型)。 - 返回值: - 无。最后结果储存在mean和stddev中。 ##### 3.2.2.5 计算最小值和最大值 ```c++ cv::minMaxLoc(src, &minVal, &maxVal, &minLoc, &maxLoc); ``` - 功能: - 在输入图像或矩阵中找到最小值和最大值。 - 参数: - src:输入图像或矩阵(cv::Mat类型)。 - minVal:输出最小值(double类型)。 - maxVal:输出最大值(double类型)。 - minLoc:输出最小值对应的位置(cv::Point类型)。 - maxLoc:输出最大值对应的位置(cv::Point类型)。 - 返回值: - 无。最后结果储存在minVal、maxVal、minLoc和maxLoc中。 #### 3.2.3 综合代码解析 ##### 3.2.3.1 流程图 ``` 开始 │ ├── 包含必要的头文件 (``, ``) │ ├── 主函数入口 │ ├── 读取图像("example.jpg") │ ├── 成功 -> 继续执行 │ └── 失败 -> │ └── 输出 "Error: Could not open image!" │ └── 返回错误码 -1 │ ├── 定义感兴趣区域(ROI)并提取(从点(50,50)开始,宽度和高度均为200像素) │ ├── 将 ROI 转换为灰度图像 │ ├── 计算灰度图像的均值和标准差 │ ├── 计算灰度图像的最小值和最大值及其位置 │ ├── 输出灰度图像的统计信息 │ ├── 均值 │ ├── 标准差 │ ├── 最小值及其位置 │ └── 最大值及其位置 │ └── 程序正常退出(返回 0) ``` ##### 3.2.3.2 代码解释 - 读取图像文件 ```c++ cv::Mat image = cv::imread("2.jpg"); if (image.empty()) { std::cerr << "Error: Could not open image!" << std::endl; return -1; } ``` - 设定ROI区域 ```c++ // 定义 ROI 并提取 cv::Rect roiRect(50, 50, 200, 200); cv::Mat roi = image(roiRect); ``` - 转换为灰度图 ```c++ cv::Mat grayRoi; cv::cvtColor(roi, grayRoi, cv::COLOR_BGR2GRAY); ``` - 均值和标准差计算 ```c++ // 计算均值和标准差 cv::Scalar mean, stddev; cv::meanStdDev(grayRoi, mean, stddev); ``` ##### 3.2.3.3 代码实现 ```c++ #include #include int main() { // 读取图像 cv::Mat image = cv::imread("example.jpg"); if (image.empty()) { std::cerr << "Error: Could not open image!" << std::endl; return -1; } // 定义 ROI 并提取 cv::Rect roiRect(50, 50, 200, 200); cv::Mat roi = image(roiRect); // 转换为灰度图 cv::Mat grayRoi; cv::cvtColor(roi, grayRoi, cv::COLOR_BGR2GRAY); // 计算均值和标准差 cv::Scalar mean, stddev; cv::meanStdDev(grayRoi, mean, stddev); // 计算最小值和最大值 double minVal, maxVal; cv::Point minLoc, maxLoc; cv::minMaxLoc(grayRoi, &minVal, &maxVal, &minLoc, &maxLoc); // 输出结果 std::cout << "Mean: " << mean[0] << std::endl; std::cout << "Standard Deviation: " << stddev[0] << std::endl; std::cout << "Min Value: " << minVal << " at " << minLoc << std::endl; std::cout << "Max Value: " << maxVal << " at " << maxLoc << std::endl; return 0; } ``` #### 3.2.4 编译过程 ##### 3.2.4.1 编译环境搭建 - 请确保你已经按照 [开发环境搭建指南](https://gitee.com/LockzhinerAI/LockzhinerVisionModule/blob/master/docs/introductory_tutorial/cpp_development_environment.md) 正确配置了开发环境。 - 同时以正确连接开发板。 ##### 3.2.4.2 Cmake介绍 ```cmake # CMake最低版本要求 cmake_minimum_required(VERSION 3.10) project(test-Image-information-statistics) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # 定义项目根目录路径 set(PROJECT_ROOT_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../..") message("PROJECT_ROOT_PATH = " ${PROJECT_ROOT_PATH}) include("${PROJECT_ROOT_PATH}/toolchains/arm-rockchip830-linux-uclibcgnueabihf.toolchain.cmake") # 定义 OpenCV SDK 路径 set(OpenCV_ROOT_PATH "${PROJECT_ROOT_PATH}/third_party/opencv-mobile-4.10.0-lockzhiner-vision-module") set(OpenCV_DIR "${OpenCV_ROOT_PATH}/lib/cmake/opencv4") find_package(OpenCV REQUIRED) set(OPENCV_LIBRARIES "${OpenCV_LIBS}") # 定义 LockzhinerVisionModule SDK 路径 set(LockzhinerVisionModule_ROOT_PATH "${PROJECT_ROOT_PATH}/third_party/lockzhiner_vision_module_sdk") set(LockzhinerVisionModule_DIR "${LockzhinerVisionModule_ROOT_PATH}/lib/cmake/lockzhiner_vision_module") find_package(LockzhinerVisionModule REQUIRED) # 图像信息处理 add_executable(Test-Image-information-statistics Image_information_statistics.cc) target_include_directories(Test-Image-information-statistics PRIVATE ${LOCKZHINER_VISION_MODULE_INCLUDE_DIRS}) target_link_libraries(Test-Image-information-statistics PRIVATE ${OPENCV_LIBRARIES} ${LOCKZHINER_VISION_MODULE_LIBRARIES}) install( TARGETS Test-Image-information-statistics RUNTIME DESTINATION . ) ``` ##### 3.2.4.3 编译项目 使用 Docker Destop 打开 LockzhinerVisionModule 容器并执行以下命令来编译项目 ```bash # 进入Demo所在目录 cd /LockzhinerVisionModuleWorkSpace/LockzhinerVisionModule/Cpp_example/B02_Image_information_statistics # 创建编译目录 rm -rf build && mkdir build && cd build # 配置交叉编译工具链 export TOOLCHAIN_ROOT_PATH="/LockzhinerVisionModuleWorkSpace/arm-rockchip830-linux-uclibcgnueabihf" # 使用cmake配置项目 cmake .. # 执行编译项目 make -j8 && make install ``` 在执行完上述命令后,会在build目录下生成可执行文件。 #### 3.2.5 例程运行示例 ##### 3.2.5.1 运行过程 在凌智视觉模块中输入以下命令: ```shell chmod 777 Test-Image-information-statistics ./Test-Image-information-statistics ``` ##### 3.2.5.2 运行效果 在运行上述代码时,会输出以下结果: ![](./images/B02_images.png) #### 3.2.6 总结 通过上述内容,我们介绍了如何使用 OpenCV 提取图像的 ROI、转换颜色空间、计算统计信息等操作。按照以下步骤,您可以轻松地进行图像的统计分析: - 提取 ROI:使用 cv::Rect 提取感兴趣区域。 - 颜色转换:使用 cv::cvtColor 转换颜色空间。 - 计算统计信息: - 使用 cv::meanStdDev 计算均值和标准差。 - 使用 cv::minMaxLoc 查找最小值和最大值及其位置。 - 综合应用:结合上述方法对图像进行局部分析和全局统计。 ### 3.3 画图功能 在计算机视觉和图像处理中,画图功能是非常重要的工具,尤其是在需要向用户反馈信息时。通过绘制线条、矩形框、圆形以及文字等图形元素,可以直观地展示结果或标注关键信息。本章节将介绍 OpenCV 中常用的画图功能,并通过一个综合示例展示其实际应用。 #### 3.3.1 基本知识讲解 ##### 3.3.1.1 图像画图的重要性 - 视觉反馈:画图功能可以帮助开发者或用户直观地理解算法的输出结果。 - 标注目标:例如在目标检测中,可以用矩形框标注检测到的目标。 - 调试与验证:通过绘制中间结果,可以快速验证算法是否正常工作。 ##### 3.3.1.2 OpenCV 的画图函数 OpenCV 提供了多种画图函数,用于在图像上绘制不同的几何图形和文字。这些函数的核心思想是操作像素矩阵,从而生成所需的图形。 #### 3.3.2 API文档 ##### 3.3.2.1 依赖的头文件 ```c++ #include ``` ##### 3.3.2.2 画线 ```c++ cv::line(image, cv::Point(x0, y0), cv::Point(x1, y1), color, thickness); ``` - 参数说明: - image:画图的图像 - cv::Point(x0, y0):起点 - cv::Point(x1, y1):终点 - color:线的颜色 - thickness:线的粗细 ##### 3.3.2.3 画矩形框 ```c++ cv::rectangle(image, cv::Rect(x, y, w, h), color, thickness); ``` - 参数说明: - image:画图的图像 - cv::Rect(x, y, w, h):矩形的左上角坐标和宽高 - color:线的颜色 - thickness:线的粗细 ##### 3.3.2.4 画圆 ```c++ cv::circle(image, cv::Point(x, y), radius, color, thickness); ``` - 参数说明: - image:画图的图像 - cv::Point(x, y):圆心坐标 - radius:圆的半径 - color:线的颜色 - thickness:线的粗细 ##### 3.3.2.5 写字 ```c++ cv::putText(image, text, cv::Point(x, y), font, fontScale, color, thickness); ``` - 参数说明: - image:画图的图像 - text:要写的文字 - cv::Point(x, y):文字的左上角坐标 - font:字体 - fontScale:字体大小 - color:线的颜色 - thickness:线的粗细 #### 3.3.3 综合代码解析 ##### 3.3.3.1 流程图 ``` 开始 | ├── 创建一个空白图像 (512x512,3通道) | ├── 画线 | ├── 起点: (10, 10) | ├── 终点: (100, 100) | ├── 颜色: (255, 0, 0) | ├── 线宽: 2 | ├── 画矩形 | ├── 左上角: (50, 50) | ├── 宽度: 100 | ├── 高度: 100 | ├── 颜色: (0, 255, 0) | ├── 线宽: 2 | ├── 画圆 | ├── 圆心: (200, 200) | ├── 半径: 30 | ├── 颜色: (0, 0, 255) | ├── 填充: -1 | ├── 画十字 | ├── 中心: (300, 300) | ├── 大小: 10 | ├── 水平线 | ├── 起点: (290, 300) | ├── 终点: (310, 300) | ├── 颜色: (255, 255, 0) | ├── 线宽: 2 | ├── 垂直线 | ├── 起点: (300, 290) | ├── 终点: (300, 310) | ├── 颜色: (255, 255, 0) | ├── 线宽: 2 | ├── 写字 | ├── 文本: "Hello, OpenCV!" | ├── 位置: (10, 400) | ├── 字体: cv::FONT_HERSHEY_SIMPLEX | ├── 尺寸: 1.0 | ├── 颜色: (0, 0, 0) | ├── 线宽: 2 | ├── 显示图像 | ├── 窗口名: "Drawing Example" | ├── 图像: image | ├── 等待按键事件 | ├── 键盘输入: cv::waitKey(0) | └── 程序正常退出 (返回 0) ``` ##### 3.3.3.2 代码解析 - 创建512*512黑色画布 ```c++ // 创建一个空白图像 (512x512,3通道) cv::Mat image = cv::Mat::zeros(512, 512, CV_8UC3); ``` - 绘制蓝色线条 ```c++ // 画线 cv::line(image, cv::Point(10, 10), cv::Point(100, 100), cv::Scalar(255, 0, 0), 2); ``` - 绘制绿色矩形 ```c++ // 画矩形 cv::rectangle(image, cv::Rect(50, 50, 100, 100), cv::Scalar(0, 255, 0), 2); ``` - 绘制红色实心圆 ```c++ // 画圆 cv::circle(image, cv::Point(200, 200), 30, cv::Scalar(0, 0, 255), -1); ``` - 绘制黄色十字 ```c++ int cross_x = 300, cross_y = 300, cross_size = 10; cv::line(image, cv::Point(cross_x - cross_size, cross_y), cv::Point(cross_x + cross_size, cross_y), cv::Scalar(255, 255, 0), 2); cv::line(image, cv::Point(cross_x, cross_y - cross_size), cv::Point(cross_x, cross_y + cross_size), cv::Scalar(255, 255, 0), 2); ``` - 添加黑色文字 ```c++ // 写字 cv::putText(image, "Hello, OpenCV!", cv::Point(10, 400), cv::FONT_HERSHEY_SIMPLEX, 1.0, cv::Scalar(0, 0, 0), 2); ``` ##### 3.3.3.3 代码实现 ```c++ #include #include int main() { // 创建一个空白图像 (512x512,3通道) cv::Mat image = cv::Mat::zeros(512, 512, CV_8UC3); // 画线 cv::line(image, cv::Point(10, 10), cv::Point(100, 100), cv::Scalar(255, 0, 0), 2); // 画矩形 cv::rectangle(image, cv::Rect(50, 50, 100, 100), cv::Scalar(0, 255, 0), 2); // 画圆 cv::circle(image, cv::Point(200, 200), 30, cv::Scalar(0, 0, 255), -1); // 画十字 int cross_x = 300, cross_y = 300, cross_size = 10; cv::line(image, cv::Point(cross_x - cross_size, cross_y), cv::Point(cross_x + cross_size, cross_y), cv::Scalar(255, 255, 0), 2); cv::line(image, cv::Point(cross_x, cross_y - cross_size), cv::Point(cross_x, cross_y + cross_size), cv::Scalar(255, 255, 0), 2); // 写字 cv::putText(image, "Hello, OpenCV!", cv::Point(10, 400), cv::FONT_HERSHEY_SIMPLEX, 1.0, cv::Scalar(0, 0, 0), 2); // 显示图像 cv::imshow("Drawing Example", image); cv::waitKey(0); return 0; } ``` #### 3.3.4 编译过程 ##### 3.3.4.1 编译环境搭建 - 请确保你已经按照 [开发环境搭建指南](https://gitee.com/LockzhinerAI/LockzhinerVisionModule/blob/master/docs/introductory_tutorial/cpp_development_environment.md) 正确配置了开发环境。 - 同时以正确连接开发板。 ##### 3.3.4.2 Cmake介绍 ```cmake ## CMake最低版本要求 cmake_minimum_required(VERSION 3.10) project(test_draw) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) ## 定义项目根目录路径 set(PROJECT_ROOT_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../..") message("PROJECT_ROOT_PATH = " ${PROJECT_ROOT_PATH}) include("${PROJECT_ROOT_PATH}/toolchains/arm-rockchip830-linux-uclibcgnueabihf.toolchain.cmake") ## 定义 OpenCV SDK 路径 set(OpenCV_ROOT_PATH "${PROJECT_ROOT_PATH}/third_party/opencv-mobile-4.10.0-lockzhiner-vision-module") set(OpenCV_DIR "${OpenCV_ROOT_PATH}/lib/cmake/opencv4") find_package(OpenCV REQUIRED) set(OPENCV_LIBRARIES "${OpenCV_LIBS}") ## 定义 LockzhinerVisionModule SDK 路径 set(LockzhinerVisionModule_ROOT_PATH "${PROJECT_ROOT_PATH}/third_party/lockzhiner_vision_module_sdk") set(LockzhinerVisionModule_DIR "${LockzhinerVisionModule_ROOT_PATH}/lib/cmake/lockzhiner_vision_module") find_package(LockzhinerVisionModule REQUIRED) ## 画图示例 add_executable(Test-Draw Draw.cc) target_include_directories(Test-Draw PRIVATE ${LOCKZHINER_VISION_MODULE_INCLUDE_DIRS}) target_link_libraries(Test-Draw PRIVATE ${OPENCV_LIBRARIES} ${LOCKZHINER_VISION_MODULE_LIBRARIES}) install( TARGETS Test-Draw RUNTIME DESTINATION . ) ``` ##### 3.3.4.3 编译项目 使用 Docker Destop 打开 LockzhinerVisionModule 容器并执行以下命令来编译项目 ```bash # 进入Demo所在目录 cd /LockzhinerVisionModuleWorkSpace/LockzhinerVisionModule/Cpp_example/B03_Draw # 创建编译目录 rm -rf build && mkdir build && cd build # 配置交叉编译工具链 export TOOLCHAIN_ROOT_PATH="/LockzhinerVisionModuleWorkSpace/arm-rockchip830-linux-uclibcgnueabihf" # 使用cmake配置项目 cmake .. # 执行编译项目 make -j8 && make install ``` 在执行完上述命令后,会在build目录下生成可执行文件。 #### 3.3.5 例程运行展示 ##### 3.3.5.1 运行过程 在凌智视觉模块中输入以下命令: ```shell chmod 777 Test-Draw ./Test-Draw ``` ##### 3.3.5.2 运行结果 运行例程代码,我们可以得到下面的图片。 ![](./images/B03_Drawing.png) 可以看见其中就包含了画图的基本元素存在。 #### 3.3.6 总结 通过上述内容,我们介绍了 OpenCV 中常用的画图功能及其参数说明,包括: - 画线:绘制直线。 - 画矩形框:绘制矩形框并支持填充。 - 画圆:绘制圆形并支持填充。 - 写字:在图像上绘制文字。 结合综合示例,您可以轻松地在图像上绘制各种几何图形和文字,从而实现更直观的视觉反馈。这些功能在目标检测、图像标注、调试等场景中非常实用。 ## 4. OpenCV 传统视觉识别 ### 4.1 寻找色块 在传统计算机视觉场景中,颜色识别是目标检测和分割的重要手段之一。通过识别特定颜色的色块,可以在相对纯净的背景下快速定位目标区域。本章节提供了一个简单的色块识别案例,并将其封装为一个自定义函数 find_blobs,方便快速移植和使用。 #### 4.1.1 基本知识讲解 ##### 4.1.1.1 色块识别的重要性 - 颜色特征提取:颜色是一种重要的视觉特征,尤其在背景较为单一的情况下,能够快速区分目标区域。 - 应用场景:广泛应用于机器人导航、工业自动化、物体跟踪等领域。 - HSV 颜色空间:相比于 RGB 颜色空间,HSV 更适合用于颜色识别,因为它可以将颜色信息(Hue)、饱和度(Saturation)和亮度(Value)分离,便于设置阈值。 ##### 4.1.1.2 色块识别的流程 - 获取图像。 - 将图像从 BGR 转换为 HSV 颜色空间。 - 创建二值掩码,筛选出符合颜色范围的像素。 - 使用形态学操作清除噪声。 - 查找轮廓并筛选符合条件的色块。 - 计算外接矩形和中心点。 - 绘制结果并输出。 #### 4.1.2 API文档 ##### 4.1.2.1 头文件 ```c++ #include ``` ##### 4.1.2.2 生成掩码 ```c++ cv::inRange(src, lowerb, upperb, dst); ``` - 参数说明: - src:输入图像,可以是单通道或三通道的图像。 - lowerb:颜色下界,是一个Scalar对象,表示要查找的颜色的下限。 - upperb:颜色上界,是一个Scalar对象,表示要查找的颜色的上限。 - dst:输出图像,是一个单通道的8位无符号整数图像,表示生成的掩码。 - 返回值: - 无 ##### 4.1.2.3 创建形态学操作所需的结构元素核 ```c++ cv::getStructuringElement(shape, ksize, anchor); ``` - 参数说明: - shape:核形状,可以是RECT、CROSS、ELLIPSE等。 - ksize:核大小,是一个Size对象,表示核的宽度和高度。 - anchor:锚点,是一个Point对象,表示核的锚点位置。 - 返回值: - 返回一个核,是一个Mat对象。 ##### 4.1.2.4 形态学操作:清除噪声 ```c++ cv::morphologyEx(src, dst, op, kernel, anchor, iterations, borderType, borderValue); ``` - 参数说明: - src:输入图像,可以是单通道或三通道的图像。 - dst:输出图像,是一个单通道的8位无符号整数图像,表示生成的掩码。 - op:操作类型,可以是OPEN、CLOSE、GRADIENT、TOPHAT、BLACKHAT等。 - kernel:核,是一个Mat对象,表示形态学操作的核。 - anchor:锚点,是一个Point对象,表示核的锚点位置。 - iterations:迭代次数,是一个整数,表示形态学操作的迭代次数。 - borderType:边界类型,可以是BORDER_CONSTANT、BORDER_REPLICATE、BORDER_REFLECT、BORDER_WRAP、BORDER_REFLECT_101等。 - borderValue:边界值,是一个Scalar对象,表示边界区域的值。 - 返回值: - 无 ##### 4.1.2.5 查找轮廓 ```c++ cv::findContours(image, contours, hierarchy, mode, method, offset); ``` - 参数说明: - image:输入图像,可以是单通道或三通道的图像。 - contours:输出参数,是一个vector > 对象,表示轮廓的集合。 - hierarchy:输出参数,是一个vector对象,表示轮廓的层级关系。 - mode:轮廓发现模式,可以是RETR_EXTERNAL、RETR_LIST、RETR_CCOMP、RETR_TREE等。 - method:轮廓 approximation 方法,可以是CHAIN_APPROX_NONE、CHAIN_APPROX_SIMPLE、CHAIN_APPROX_TC89_L1、CHAIN_APPROX_TC89_KCOS等。 - offset:轮廓偏移量,是一个Point对象,表示轮廓的偏移量。 - 返回值: - 返回一个整数,表示轮廓的数量。 ##### 4.1.2.6 获取轮廓的外接矩形 ```c++ cv::boundingRect(points); ``` - 参数说明: - points:输入参数,是一个vector对象,表示轮廓的点集合。 - 返回值: - 返回一个Rect对象,表示轮廓的外接矩形。 ##### 4.1.2.7 计算矩阵矩 ```c++ cv::moments(array, binaryImage); ``` - 参数说明: - array:输入参数,是一个Mat对象,表示输入的矩阵。 - binaryImage:输入参数,是一个布尔值,表示是否将输入的矩阵转换为二值矩阵。 - 返回值: - 返回一个 Moments对象,表示矩阵的矩。 ##### 4.1.2.8 绘制矩形框 ```c++ cv::rectangle(img, pt1, pt2, color, thickness, lineType, shift); ``` - 参数说明: - img:输入参数,是一个Mat对象,表示输入的图像。 - pt1:输入参数,是一个Point对象,表示矩形的左上角点。 - pt2:输入参数,是一个Point对象,表示矩形的右下角点。 - color:输入参数,是一个Scalar对象,表示矩形的颜色。 - thickness:输入参数,是一个整数,表示矩形的线宽。 - lineType:输入参数,是一个整数,表示矩形的线类型。 - shift:输入参数,是一个整数,表示坐标的精度。 - 返回值: - 无 ##### 4.1.2.9 绘制圆 ```c++ cv::circle(img, center, radius, color, thickness, lineType, shift); ``` - 参数说明: - img:输入参数,是一个Mat对象,表示输入的图像。 - center:输入参数,是一个Point对象,表示圆心。 - radius:输入参数,是一个整数,表示圆的半径。 - color:输入参数,是一个Scalar对象,表示圆的颜色。 - thickness:输入参数,是一个整数,表示圆的线宽。 - lineType:输入参数,是一个整数,表示圆的线类型。 - shift:输入参数,是一个整数,表示坐标的精度。 - 返回值: - 无 #### 4.1.3 综合代码介绍 ##### 4.1.3.1 流程图 ``` 开始 | |-- 创建 Edit 对象 edit 并启动连接 | |-- 成功 -> 继续执行 | |-- 失败 -> | |-- 输出 "Error: Failed to start and accept connection." | |-- 返回错误码 1 | |-- 设置摄像头分辨率并打开摄像头设备 | |-- 成功 -> 继续执行 | |-- 失败 -> | |-- 输出 "Error: Could not open camera." | |-- 返回错误码 1 | |-- 进入无限循环 | |-- 获取新的一帧图像 | |-- 定义颜色阈值(红色) | |-- 调用 find_blobs 函数查找色块 | |-- 遍历检测到的色块 | |-- 计算外接矩形框并绘制 | |-- 计算中心点并绘制 | |-- 打印色块信息 | |-- 使用 edit.Print 显示图像 | |-- 程序正常退出(返回 0) ``` ##### 4.1.3.2 核心代码解析 - BGR转HSV ```c++ cv::cvtColor(image, hsv_image, cv::COLOR_BGR2HSV); ``` - 阈值分割 ```c++ cv::inRange(hsv_image, lower_bound, upper_bound, mask);); ``` - 形态学处理 ```c++ cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(kernel_size, kernel_size)); cv::morphologyEx(mask, mask, cv::MORPH_OPEN, kernel); ``` - 查找轮廓 ```c++ cv::findContours(mask, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE); ``` 自定义函数参数如下所示 ```c++ std::vector> find_blobs( const cv::Mat &image, const cv::Scalar &lower_bound, const cv::Scalar &upper_bound, int min_area = 100, int kernel_size = 5); ``` - 参数说明: - image:输入参数,是一个Mat对象,表示输入的图像。 - lower_bound:输入参数,是一个Scalar对象,表示颜色下界。 - upper_bound:输入参数,是一个Scalar对象,表示颜色上界。 - min_area:输入参数,是一个整数,表示最小面积。 - kernel_size:输入参数,是一个整数,表示核大小。 - 返回值: - 返回一个vector>对象,表示找到的色块的点集合。 ##### 4.1.3.3 完整代码实现 ```c++ #include #include #include #include std::vector> find_blobs( const cv::Mat &image, const cv::Scalar &lower_bound, const cv::Scalar &upper_bound, int min_area = 100, int kernel_size = 5) { // 转换为 HSV 颜色空间 cv::Mat hsv_image; cv::cvtColor(image, hsv_image, cv::COLOR_BGR2HSV); // 创建二值掩码 cv::Mat mask; cv::inRange(hsv_image, lower_bound, upper_bound, mask); // 形态学操作:清除噪声 cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(kernel_size, kernel_size)); cv::morphologyEx(mask, mask, cv::MORPH_OPEN, kernel); // 查找轮廓 std::vector> contours; cv::findContours(mask, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE); // 筛选符合条件的色块 std::vector> filtered_contours; for (const auto &contour : contours) { cv::Rect bounding_rect = cv::boundingRect(contour); if (bounding_rect.area() >= min_area) { filtered_contours.push_back(contour); } } return filtered_contours; } int main() { lockzhiner_vision_module::edit::Edit edit; if (!edit.StartAndAcceptConnection()) { std::cerr << "Error: Failed to start and accept connection." << std::endl; return EXIT_FAILURE; } std::cout << "Device connected successfully." << std::endl; cv::VideoCapture cap; int width = 640; // 设置摄像头分辨率宽度 int height = 480; // 设置摄像头分辨率高度 cap.set(cv::CAP_PROP_FRAME_WIDTH, width); cap.set(cv::CAP_PROP_FRAME_HEIGHT, height); // 打开摄像头设备 cap.open(0); // 参数 0 表示默认摄像头设备 if (!cap.isOpened()) { std::cerr << "Error: Could not open camera." << std::endl; return EXIT_FAILURE; } while (true) { cv::Mat image; // 存储每一帧图像 cap >> image; // 获取新的一帧 // 定义颜色阈值(例如红色) cv::Scalar lower_red(170, 100, 100); // 红色下界 cv::Scalar upper_red(179, 255, 255); // 红色上界 // 调用 find_blobs 函数 int min_area = 100; // 最小面积阈值 int kernel_size = 1; // 形态学操作核大小 std::vector> blobs = find_blobs(image, lower_red, upper_red, min_area, kernel_size); // 绘制和打印检测到的色块 for (const auto &contour : blobs) { // 计算外接矩形框 cv::Rect bounding_rect = cv::boundingRect(contour); // 绘制矩形框 cv::rectangle(image, bounding_rect, cv::Scalar(0, 255, 0), 2); // 计算中心点 cv::Moments moments = cv::moments(contour); int cx = moments.m10 / moments.m00; int cy = moments.m01 / moments.m00; // 绘制中心点 cv::circle(image, cv::Point(cx, cy), 5, cv::Scalar(0, 0, 255), -1); // 打印信息 std::cout << "Blob detected at (" << cx << ", " << cy << ") with area " << bounding_rect.area() << std::endl; } edit.Print(image); } return 0; } ``` #### 4.1.4 编译过程 ##### 4.1.4.1 编译环境搭建 - 请确保你已经按照 [开发环境搭建指南](https://gitee.com/LockzhinerAI/LockzhinerVisionModule/blob/master/docs/introductory_tutorial/cpp_development_environment.md) 正确配置了开发环境。 - 同时以正确连接开发板。 ##### 4.1.4.2 Cmake介绍 ```cmake #CMake最低版本要求 cmake_minimum_required(VERSION 3.10) project(test-find-blobs) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) #定义项目根目录路径 set(PROJECT_ROOT_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../..") message("PROJECT_ROOT_PATH = " ${PROJECT_ROOT_PATH}) include("${PROJECT_ROOT_PATH}/toolchains/arm-rockchip830-linux-uclibcgnueabihf.toolchain.cmake") #定义 OpenCV SDK 路径 set(OpenCV_ROOT_PATH "${PROJECT_ROOT_PATH}/third_party/opencv-mobile-4.10.0-lockzhiner-vision-module") set(OpenCV_DIR "${OpenCV_ROOT_PATH}/lib/cmake/opencv4") find_package(OpenCV REQUIRED) set(OPENCV_LIBRARIES "${OpenCV_LIBS}") #定义 LockzhinerVisionModule SDK 路径 set(LockzhinerVisionModule_ROOT_PATH "${PROJECT_ROOT_PATH}/third_party/lockzhiner_vision_module_sdk") set(LockzhinerVisionModule_DIR "${LockzhinerVisionModule_ROOT_PATH}/lib/cmake/lockzhiner_vision_module") find_package(LockzhinerVisionModule REQUIRED) #基本图像处理示例 add_executable(Test-find-blobs find_blobs.cc) target_include_directories(Test-find-blobs PRIVATE ${LOCKZHINER_VISION_MODULE_INCLUDE_DIRS}) target_link_libraries(Test-find-blobs PRIVATE ${OPENCV_LIBRARIES} ${LOCKZHINER_VISION_MODULE_LIBRARIES}) install( TARGETS Test-find-blobs RUNTIME DESTINATION . ) ``` ##### 4.1.4.3 编译项目 使用 Docker Destop 打开 LockzhinerVisionModule 容器并执行以下命令来编译项目 ```bash # 进入Demo所在目录 cd /LockzhinerVisionModuleWorkSpace/LockzhinerVisionModule/Cpp_example/C01_find_blobs # 创建编译目录 rm -rf build && mkdir build && cd build # 配置交叉编译工具链 export TOOLCHAIN_ROOT_PATH="/LockzhinerVisionModuleWorkSpace/arm-rockchip830-linux-uclibcgnueabihf" # 使用cmake配置项目 cmake .. # 执行编译项目 make -j8 && make install ``` 在执行完上述命令后,会在build目录下生成可执行文件。 #### 4.1.5 例程运行示例 ##### 4.1.5.1 运行过程 ```shell chmod 777 Test-find-blobs ./Test-find-blobs ``` ##### 4.1.5.2 运行效果 ![识别效果](./images/C01_findblob.png) #### 4.1.6 总结 通过上述内容,我们详细介绍了色块识别的流程及相关 API 的使用方法,包括: - 生成掩码:筛选符合颜色范围的像素。 - 形态学操作:清除噪声。 - 查找轮廓:获取目标区域的轮廓。 - 筛选与绘制:筛选符合条件的色块并绘制外接矩形和中心点。 ### 4.2 模板匹配 模板匹配是一种在图像中寻找特定模式的技术。它通过滑动一个模板图像(较小的图像)在输入图像上进行比较,找到最相似的区域。本章节提供了一个简单的模板匹配案例,并将其封装为一个自定义函数 performTemplateMatching,方便快速移植和使用。 #### 4.2.1 基本知识讲解 ##### 4.2.1.1 模板匹配的重要性 - 目标检测:模板匹配可以用于检测图像中的特定对象。 - 应用场景:广泛应用于物体识别、工业自动化、机器人导航等领域。 - 局限性:模板匹配对旋转、缩放和光照变化较为敏感,因此通常需要结合其他技术来提高鲁棒性。 ##### 4.2.1.2 模板匹配的流程 - 获取输入图像和模板图像。 - 使用模板匹配算法(如归一化互相关 NCC)计算相似度。 - 找到匹配结果中的最大值及其位置。 - 根据相似度阈值判断匹配是否成功。 - 绘制矩形框标记匹配区域并显示结果。 #### 4.2.2 API 文档 ##### 4.2.2.1 头文件 ```c++ #include ``` ##### 4.2.2.2 在输入图像中搜索模板图像的位置 ```c++ void matchTemplate(InputArray image, InputArray templ, OutputArray result, int method); ``` - 参数: - image:待搜索的图像。 - templ:模板图像。 - result:搜索结果。 - method:搜索方法,可以是 TM_SQDIFF、TM_SQDIFF_NORMED、TM_CCORR、TM_CCORR_NORMED、TM_CCOEFF、TM_CCOEFF_NORMED 之一。 - 返回值: - 无 ##### 4.2.2.3 获取匹配结果中的最大值及其位置 ```c++ void minMaxLoc(InputArray src, double *minVal, double *maxVal, Point *minLoc, Point *maxLoc, InputArray mask = noArray()); ``` - 参数: - src:输入矩阵。 - minVal:最小值。 - maxVal:最大值。 - minLoc:最小值位置。 - maxLoc:最大值位置。 - mask:可选的掩码矩阵,用于指定要搜索的像素范围。 - 返回值: - 无 #### 4.2.3 综合代码解析 ##### 4.2.3.1 流程图 ``` 开始 | |-- 检查命令行参数数量是否正确 | |-- 正确 -> 继续执行 | |-- 错误 -> 输出用法信息并退出 | |-- 创建 Edit 对象 edit 并启动连接 | |-- 成功 -> 继续执行 | |-- 失败 -> 输出错误信息并退出 | |-- 初始化摄像头设置和打开摄像头设备 | |-- 成功 -> 继续执行 | |-- 失败 -> 输出错误信息并退出 | |-- 加载模板图像 | |-- 成功 -> 继续执行 | |-- 失败 -> 输出错误信息并退出 | |-- 进入无限循环 | |-- 读取输入图像 | |-- 成功 -> 继续执行 | |-- 失败 -> 输出错误信息并退出循环 | |-- 调用 performTemplateMatching 函数进行模板匹配 | |-- 成功匹配 -> 在图像上绘制矩形框标记匹配区域 | |-- 匹配失败 -> 不做任何操作 | |-- 显示处理后的图像 | |-- 按 ESC 键退出循环 | |-- 使用 edit.Print 输出图像 | |-- 结束程序 ``` ##### 4.2.3.2 代码解释 - 模板匹配函数应用 ```c++ double similarityThreshold = 0.7; // 相似度阈值 bool useGrayscale = false; // 是否使用灰度处理 bool matchSuccess = performTemplateMatching(img, templ, similarityThreshold, img, useGrayscale); ``` 模板匹配函数具体参数定义如下所示。 ```c++ bool performTemplateMatching(const Mat &inputImage, const Mat &templateImage, double threshold, Mat &outputImage,bool isGrayscale = false); ``` - 参数: - inputImage:输入图像。 - templateImage:模板图像。 - threshold:相似度阈值。 - outputImage:输出图像。 - isGrayscale:是否进行灰度处理。 - 返回值: - true:匹配成功。 - false:匹配失败。 - 输出结果 ```c++ edit.Print(img); ``` ##### 4.2.3.3 代码实现 ```c++ #include #include #include using namespace cv; using namespace std; // 模板匹配函数 bool performTemplateMatching(const Mat &inputImage, const Mat &templateImage, double threshold, Mat &outputImage, bool isGrayscale = false) { // 确保模板图像比输入图像小 if (templateImage.rows > inputImage.rows || templateImage.cols > inputImage.cols) { cout << "模板图像不能大于输入图像!" << endl; return false; } // 创建用于匹配的图像副本 Mat templ = templateImage.clone(); Mat img = inputImage.clone(); // 如果选择灰度处理,则将输入图像和模板图像转换为灰度 if (isGrayscale) { if (img.channels() == 3) { cvtColor(img, img, COLOR_BGR2GRAY); } if (templ.channels() == 3) { cvtColor(templ, templ, COLOR_BGR2GRAY); } } // 打印调试信息 cout << "输入图像尺寸: " << img.size() << ", 通道数: " << img.channels() << endl; cout << "模板图像尺寸: " << templ.size() << ", 通道数: " << templ.channels() << endl; // 创建结果矩阵,用于存储匹配结果 int resultRows = img.rows - templ.rows + 1; int resultCols = img.cols - templ.cols + 1; if (resultRows <= 0 || resultCols <= 0) { cout << "结果矩阵尺寸无效!请检查输入图像和模板图像的尺寸。" << endl; return false; } Mat result(resultRows, resultCols, CV_32FC1); // 使用归一化互相关(NCC)方法进行模板匹配 double start = static_cast(getTickCount()); matchTemplate(img, templ, result, TM_CCOEFF_NORMED); double end = static_cast(getTickCount()); double elapsedTime = (end - start) / getTickFrequency(); cout << "matchTemplate 运行时间: " << elapsedTime << " 秒" << endl; // 找到匹配结果中的最大值及其位置 double minVal, maxVal; Point minLoc, maxLoc; minMaxLoc(result, &minVal, &maxVal, &minLoc, &maxLoc); // 输出相似度阈值判断 if (maxVal >= threshold) { cout << "匹配成功!最大相似度: " << maxVal << endl; // 绘制矩形框标记匹配区域 rectangle(outputImage, maxLoc, Point(maxLoc.x + templ.cols, maxLoc.y + templ.rows), Scalar(0, 255, 0), 2); return true; } else { cout << "匹配失败!最大相似度: " << maxVal << endl; return false; } } int main(int argc, char **argv) { // 检查命令行参数数量是否正确 if (argc != 2) { cout << "用法: " << argv[0] << " <模板图像路径>" << endl; return -1; } // 声明并初始化变量 string templateImagePath = argv[1]; // 模板图像路径 lockzhiner_vision_module::edit::Edit edit; if (!edit.StartAndAcceptConnection()) { std::cerr << "Error: Failed to start and accept connection." << std::endl; return EXIT_FAILURE; } std::cout << "Device connected successfully." << std::endl; // 初始化摄像头 cv::VideoCapture cap; int width = 320; // 设置摄像头分辨率宽度 int height = 240; // 设置摄像头分辨率高度 cap.set(cv::CAP_PROP_FRAME_WIDTH, width); cap.set(cv::CAP_PROP_FRAME_HEIGHT, height); // 打开摄像头设备 cap.open(0); // 参数 0 表示默认摄像头设备 if (!cap.isOpened()) { std::cerr << "Error: Could not open camera." << std::endl; return EXIT_FAILURE; } // 加载模板图像 Mat templ = imread(templateImagePath, IMREAD_COLOR); // 默认加载彩色模板 if (templ.empty()) { std::cerr << "Error: Could not load template image." << std::endl; return EXIT_FAILURE; } while (true) { // 读取输入图像 cv::Mat img; cap >> img; if (img.empty()) { std::cerr << "Error: Captured frame is empty." << std::endl; break; } // 调用模板匹配函数 double similarityThreshold = 0.7; // 相似度阈值 bool useGrayscale = false; // 是否使用灰度处理 bool matchSuccess = performTemplateMatching(img, templ, similarityThreshold, img, useGrayscale); // 显示结果 imshow("Template Matching Result", img); if (waitKey(1) == 27) { // 按 ESC 键退出 break; } // 输出图像 edit.Print(img); } return 0; } ``` #### 4.2.4 编译过程 ##### 4.2.4.1 编译环境搭建 - 请确保你已经按照 [开发环境搭建指南](https://gitee.com/LockzhinerAI/LockzhinerVisionModule/blob/master/docs/introductory_tutorial/cpp_development_environment.md) 正确配置了开发环境。 - 同时以正确连接开发板。 ##### 4.2.4.2 Cmake介绍 ```cmake # CMake最低版本要求 cmake_minimum_required(VERSION 3.10) project(test-TemplateMatching) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # 定义项目根目录路径 set(PROJECT_ROOT_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../..") message("PROJECT_ROOT_PATH = " ${PROJECT_ROOT_PATH}) include("${PROJECT_ROOT_PATH}/toolchains/arm-rockchip830-linux-uclibcgnueabihf.toolchain.cmake") # 定义 OpenCV SDK 路径 set(OpenCV_ROOT_PATH "${PROJECT_ROOT_PATH}/third_party/opencv-mobile-4.10.0-lockzhiner-vision-module") set(OpenCV_DIR "${OpenCV_ROOT_PATH}/lib/cmake/opencv4") find_package(OpenCV REQUIRED) set(OPENCV_LIBRARIES "${OpenCV_LIBS}") ## 定义 LockzhinerVisionModule SDK 路径 set(LockzhinerVisionModule_ROOT_PATH "${PROJECT_ROOT_PATH}/third_party/lockzhiner_vision_module_sdk") set(LockzhinerVisionModule_DIR "${LockzhinerVisionModule_ROOT_PATH}/lib/cmake/lockzhiner_vision_module") find_package(LockzhinerVisionModule REQUIRED) # 基本图像处理示例 add_executable(Test-TemplateMatching TemplateMatching.cc) target_include_directories(Test-TemplateMatching PRIVATE ${LOCKZHINER_VISION_MODULE_INCLUDE_DIRS}) target_link_libraries(Test-TemplateMatching PRIVATE ${OPENCV_LIBRARIES} ${LOCKZHINER_VISION_MODULE_LIBRARIES}) install( TARGETS Test-TemplateMatching RUNTIME DESTINATION . ) ``` ##### 4.2.4.3 编译项目 使用 Docker Destop 打开 LockzhinerVisionModule 容器并执行以下命令来编译项目 ```bash # 进入Demo所在目录 cd /LockzhinerVisionModuleWorkSpace/LockzhinerVisionModule/Cpp_example/A01_capture # 创建编译目录 rm -rf build && mkdir build && cd build # 配置交叉编译工具链 export TOOLCHAIN_ROOT_PATH="/LockzhinerVisionModuleWorkSpace/arm-rockchip830-linux-uclibcgnueabihf" # 使用cmake配置项目 cmake .. # 执行编译项目 make -j8 && make install ``` 在执行完上述命令后,会在build目录下生成可执行文件。 #### 4.2.5 例程运行示例 ##### 4.2.5.1 准备工作 - 下载凌智视觉模块图片传输助手:[点击下载](https://gitee.com/LockzhinerAI/LockzhinerVisionModule/releases/download/v0.0.0/LockzhinerVisionModuleImageFetcher.exe) - 我们首先需要进行图像采集,采集一个480*320分辨率下的模板照片。 ##### 4.2.5.2 运行过程 在凌智视觉模块中输入以下命令: ```shell chmod 777 Test-TemplateMatching template_0.png ./Test-TemplateMatching ``` ##### 4.2.5.3 运行结果 - 运行结果如下: ![](./images/C02.png) - 模板照片如下: ![](./images/template_1.png) #### 4.2.6 总结 通过上述内容,我们详细介绍了模板匹配的流程及相关 API 的使用方法,包括: - 图像读取:加载输入图像和模板图像。 - 模板匹配:使用归一化互相关方法计算相似度。 - 查找极值:获取最佳匹配位置。 - 绘制与显示:标记匹配区域并显示结果。 希望这份文档能帮助您更好地理解和实现模板匹配功能! 注意事项: - 在本次例程中摄像头需要运行在480*320分辨率下,否则会出现帧率过低的情况。如需更高帧率,可酌情再降低分辨率。 ### 4.3 多模板匹配 多模板匹配是一种在图像中同时寻找多个模板的技术。通过对每个模板逐一进行匹配,找到与输入图像最相似的区域,并标记出匹配度最高的结果。本章节提供了一个简单的多模板匹配案例,并将其封装为一个自定义函数 multiTemplateMatching,方便快速移植和使用。 #### 4.3.1 基本知识讲解 ##### 4.3.1.1 多模板匹配的重要幸 - 目标检测:多模板匹配可以用于检测图像中的多个特定对象。 - 应用场景:广泛应用于物体识别、工业自动化、机器人导航等领域。 - 优势:支持多个模板的同时匹配,能够灵活处理多种目标。 - 局限性:对旋转、缩放和光照变化较为敏感,因此通常需要结合其他技术来提高鲁棒性。 ##### 4.3.1.2 多模板匹配的流程 - 获取输入图像和多个模板图像。 - 遍历每个模板,逐一执行模板匹配算法(如归一化互相关 NCC)。 - 找到每个模板匹配结果中的最大值及其位置。 - 根据相似度阈值筛选匹配结果,并记录匹配度最高的模板。 - 绘制矩形框标记匹配区域并显示结果。 #### 4.3.2 API文档 ##### 4.3.2.1 头文件 ```c++ #include ``` ##### 4.3.2.2 模板匹配 ```c++ cv::matchTemplate(image, templ, result, method); ``` - 功能:在输入图像中搜索模板图像的最佳匹配位置。 - 参数: - image:输入图像。 - templ:模板图像。 - result:匹配结果图像,输出参数。 - method:匹配方法,可选值有: - CV_TM_SQDIFF:平方差匹配。 - 返回值:无。 ##### 4.3.2.3 查找机制 ```c++ cv::minMaxLoc(src, minVal, maxVal, minLoc, maxLoc, mask); ``` - 功能:查找匹配结果图像中的最小值和最大值及其位置。 - 参数: - src:输入矩阵。 - minVal:输出的最小值。 - maxVal:输出的最大值。 - minLoc:最小值的位置。 - maxLoc:最大值的位置。 - mask:可选的掩码矩阵。 - 返回值:无。 #### 4.3.3 综合代码解析 ##### 4.3.3.1 流程图 ``` 开始 | |-- 创建 Edit 对象并启动连接 | |-- 初始化摄像头设置(分辨率) | |-- 打开摄像头设备 | |-- 从命令行读取模板路径列表 | |-- 进入无限循环 | |-- 获取新的一帧图像 | | | |-- 执行多模板匹配算法 | |-- 遍历每个模板路径 | |-- 加载模板图像 | |-- 如果需要,转换图像颜色(灰度或彩色) | |-- 执行模板匹配 | |-- 更新最佳匹配记录(如果当前匹配更好) | |-- 在图像上绘制最高匹配度的矩形框(如果有匹配) | | | |-- 显示处理后的图像 | |-- 结束程序 ``` ##### 4.3.3.2 代码解释 - 使用多模板匹配函数 ```c++ multiTemplateMatching(img, templates, 0.7, true); ``` 自定义多模板匹配函数具体参数如下所示。 ```c++ void multiTemplateMatching(const Mat& img, const vector& templatePaths, double threshold = 0.7, bool isGrayscale = false); ``` - 功能:对多个模板逐一执行匹配,并标记匹配度最高的区域。 - 参数: - img:输入图像。 - templatePaths:模板图像路径列表。 - threshold:相似度阈值,默认为 0.7。 - isGrayscale:是否将输入图像转换为灰度图像,默认为 false。 - 返回值:无。 ```c++ edit.Print(img); ``` ##### 4.3.3.3 完整代码实现 ```c++ #include #include #include #include #include using namespace cv; using namespace std; // 多模板匹配函数(支持彩色或灰度图像,仅绘制匹配度最高的框) void multiTemplateMatching(const Mat &img, const vector &templatePaths, double threshold = 0.7, bool isGrayscale = false) { // 初始化最高匹配度和对应的模板路径、位置 double bestMatchValue = 0.0; string bestMatchTemplatePath = ""; Rect bestMatchRect; // 遍历每个模板路径 for (const auto &templatePath : templatePaths) { // 加载模板图像(根据 isGrayscale 决定是灰度还是彩色) Mat templ = imread(templatePath, isGrayscale ? IMREAD_GRAYSCALE : IMREAD_COLOR); if (templ.empty()) { cerr << "无法加载模板图像: " << templatePath << endl; continue; } // 如果输入图像是灰度图像,则将彩色图像转换为灰度 Mat inputImage = img.clone(); if (isGrayscale && inputImage.channels() == 3) { cvtColor(inputImage, inputImage, COLOR_BGR2GRAY); } // 创建结果矩阵 int result_cols = inputImage.cols - templ.cols + 1; int result_rows = inputImage.rows - templ.rows + 1; Mat result(result_rows, result_cols, CV_32FC1); // 执行模板匹配 matchTemplate(inputImage, templ, result, TM_CCOEFF_NORMED); // 查找最佳匹配位置 double minVal, maxVal; Point minLoc, maxLoc; minMaxLoc(result, &minVal, &maxVal, &minLoc, &maxLoc); // 如果当前模板的最大匹配度高于之前的记录,则更新最佳匹配信息 if (maxVal > bestMatchValue && maxVal >= threshold) { bestMatchValue = maxVal; bestMatchTemplatePath = templatePath; bestMatchRect = Rect(maxLoc.x, maxLoc.y, templ.cols, templ.rows); } } // 如果找到匹配度高于阈值的最佳匹配,则绘制矩形框 if (!bestMatchTemplatePath.empty()) { rectangle(img, bestMatchRect, Scalar(0, 255, 0), 2); // 绿色矩形框 cout << "匹配到模板: " << bestMatchTemplatePath << ", 匹配度: " << bestMatchValue << endl; } else { cout << "未找到匹配度高于阈值的模板。" << endl; } } int main(int argc, char *argv[]) { lockzhiner_vision_module::edit::Edit edit; if (!edit.StartAndAcceptConnection()) { std::cerr << "Error: Failed to start and accept connection." << std::endl; return EXIT_FAILURE; } std::cout << "Device connected successfully." << std::endl; cv::VideoCapture cap; int width = 320; // 设置摄像头分辨率宽度 int height = 240; // 设置摄像头分辨率高度 cap.set(cv::CAP_PROP_FRAME_WIDTH, width); cap.set(cv::CAP_PROP_FRAME_HEIGHT, height); // 打开摄像头设备 cap.open(0); // 参数 0 表示默认摄像头设备 if (!cap.isOpened()) { std::cerr << "Error: Could not open camera." << std::endl; return EXIT_FAILURE; } // 检查命令行参数是否提供了模板路径 vector templates; if (argc < 2) { cerr << "Usage: " << argv[0] << " template_path1 [template_path2 ...]" << endl; return EXIT_FAILURE; } // 从命令行读取模板路径 for (int i = 1; i < argc; ++i) { templates.push_back(argv[i]); } while (true) { cv::Mat img; // 存储每一帧图像 cap >> img; // 获取新的一帧 // 检查是否成功读取帧 if (img.empty()) { std::cerr << "Warning: Couldn't read a frame from the camera." << std::endl; continue; } // 执行多模板匹配(示例中仍使用彩色图像) multiTemplateMatching(img, templates, 0.7, true); // 显示结果 edit.Print(img); } return 0; } ``` #### 4.3.4 编译结果 ##### 4.3.4.1 编译环境搭建 - 请确保你已经按照 [开发环境搭建指南](https://gitee.com/LockzhinerAI/LockzhinerVisionModule/blob/master/docs/introductory_tutorial/cpp_development_environment.md) 正确配置了开发环境。 - 同时已正确连接开发板。 ##### 4.3.4.2 Cmake介绍 ```cmake #CMake最低版本要求 cmake_minimum_required(VERSION 3.10) project(test-TemplateMatching-more) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) #定义项目根目录路径 set(PROJECT_ROOT_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../..") message("PROJECT_ROOT_PATH = " ${PROJECT_ROOT_PATH}) include("${PROJECT_ROOT_PATH}/toolchains/arm-rockchip830-linux-uclibcgnueabihf.toolchain.cmake") #定义 OpenCV SDK 路径 set(OpenCV_ROOT_PATH "${PROJECT_ROOT_PATH}/third_party/opencv-mobile-4.10.0-lockzhiner-vision-module") set(OpenCV_DIR "${OpenCV_ROOT_PATH}/lib/cmake/opencv4") find_package(OpenCV REQUIRED) set(OPENCV_LIBRARIES "${OpenCV_LIBS}") #定义 LockzhinerVisionModule SDK 路径 set(LockzhinerVisionModule_ROOT_PATH "${PROJECT_ROOT_PATH}/third_party/lockzhiner_vision_module_sdk") set(LockzhinerVisionModule_DIR "${LockzhinerVisionModule_ROOT_PATH}/lib/cmake/lockzhiner_vision_module") find_package(LockzhinerVisionModule REQUIRED) #基本图像处理示例 add_executable(Test-TemplateMatching-more Template_Matching_more.cc) target_include_directories(Test-TemplateMatching-more PRIVATE ${LOCKZHINER_VISION_MODULE_INCLUDE_DIRS}) target_link_libraries(Test-TemplateMatching-more PRIVATE ${OPENCV_LIBRARIES} ${LOCKZHINER_VISION_MODULE_LIBRARIES}) install( TARGETS Test-TemplateMatching-more RUNTIME DESTINATION . ) ``` ##### 4.3.4.3 编译项目 使用 Docker Destop 打开 LockzhinerVisionModule 容器并执行以下命令来编译项目 ```bash #进入Demo所在目录 cd /LockzhinerVisionModuleWorkSpace/LockzhinerVisionModule/Cpp_example/C03_TemplateMatching_more #创建编译目录 rm -rf build && mkdir build && cd build #配置交叉编译工具链 export TOOLCHAIN_ROOT_PATH="/LockzhinerVisionModuleWorkSpace/arm-rockchip830-linux-uclibcgnueabihf" #使用cmake配置项目 cmake .. #执行编译项目 make -j8 && make install ``` 在执行完上述命令后,会在build目录下生成可执行文件。 #### 4.3.5 例程运行示例 ##### 4.3.5.1 准备工作 1. 下载凌智视觉模块图片传输助手:[点击下载](https://gitee.com/LockzhinerAI/LockzhinerVisionModule/releases/download/v0.0.0/LockzhinerVisionModuleImageFetcher.exe) ##### 4.3.5.2 运行过程 在凌智视觉模块中输入以下命令: ```shell chmod 777 Test-TemplateMatching-more #在实际运行中,模板数量越少,相对来说运行的帧率越高,如需更高帧率请自行降低分辨率。 ./Test-TemplateMatching-more template_0.png template_1.png template_2.png template_3.png ``` ##### 4.3.5.3 运行效果 - 运行程序后,您将看到实时视频流中匹配度最高的区域被绿色矩形框标记出来。如果未找到匹配度高于阈值的模板,则会输出提示信息。 - 输出结果 ![](./images/C03.png) - 模板照片如下 ![](./images/template_0.png) ![](./images/template_1.png) ![](./images/template_2.png) ![](./images/template_3.png) #### 4.3.6 总结 通过上述内容,我们详细介绍了多模板匹配的流程及相关 API 的使用方法,包括: - 图像读取:加载输入图像和多个模板图像。 - 模板匹配:使用归一化互相关方法计算相似度。 - 查找极值:获取每个模板的最佳匹配位置。 - 绘制与显示:标记匹配度最高的区域并显示结果。 ### 4.4 轮廓检测 本文档展示了如何使用 OpenCV 进行图像处理和特征检测,包括边缘检测、直线检测、圆检测以及多边形拟合。通过这些技术,可以实现对摄像头捕获的实时视频流进行分析,并标记出检测到的特征。 #### 4.4.1 基本知识讲解 ##### 4.4.1.1 图像处理的重要性 - 目标检测:图像处理技术可以用于检测图像中的特定对象或特征。 - 应用场景:广泛应用于物体识别、工业自动化、机器人导航、自动驾驶等领域。 - 常见任务: - 边缘检测:提取图像中的边界信息。 - 直线检测:识别图像中的直线结构。 - 圆检测:识别图像中的圆形结构。 - 多边形拟合:将轮廓拟合成多边形以简化形状描述。 ##### 4.4.1.2 图像处理的基本流程 - 初始化摄像头:打开摄像头设备并设置分辨率。 - 读取图像帧:从摄像头中获取实时视频帧。 - 预处理:将图像转换为灰度图、降噪等操作。 - 特征检测:执行边缘检测、霍夫变换等算法。 - 结果绘制:在原图上绘制检测到的特征。 - 显示结果:将处理后的图像输出到屏幕。 #### 4.4.2 API文档 ##### 4.4.2.1 头文件 ```c++ #include ``` ##### 4.4.2.2 高斯模糊 ```c++ cv::GaussianBlur(src, dst, Size(3, 3), 0); ``` - 参数: - src:输入图像。 - dst:输出图像。 - Size(3, 3):卷积核大小。 - 0:标准差。 - 返回值: - 无。 ##### 4.4.2.3 边缘检测 ```c++ cv::Canny(src, dst, 50, 150); ``` - 参数: - src:输入图像。 - dst:输出图像。 - 50:低阈值。 - 150:高阈值。 - apertureSize:Sobel 算子的孔径大小(默认为 3)。 - L2gradient:是否使用 L2 范数计算梯度(默认为 false)。 - 返回值: - 无。 ##### 4.4.2.4 查找图像中的轮廓 ```c++ cv::findContours(src, contours, hierarchy, mode, method); ``` - 参数: - src:输入图像。 - contours:轮廓列表。 - hierarchy:轮廓层级信息。 - mode:轮廓查找模式(默认为 CV_RETR_EXTERNAL)。 - method:轮廓 approximation 方法(默认为 CV_CHAIN_APPROX_SIMPLE)。 - 返回值: - 无 ##### 4.4.2.5 对轮廓进行多边形拟合 ```c++ cv::approxPolyDP(contours[i], approx, epsilon, closed); ``` - 参数: - contours[i]:轮廓。 - approx:多边形顶点列表。 - epsilon:精度参数,表示最大距离,用于控制多边形拟合的精度。 - closed:是否闭合多边形(默认为 false)。 - 返回值: - 无 ##### 4.4.2.6 使用概率霍夫变换检测直线 ```c++ cv::HoughLinesP(src, lines, 1, CV_PI / 180, 50, 50, 10); ``` - 参数: - src:输入图像。 - lines:检测到的直线列表。 - 1:rho 分辨率。 - CV_PI / 180:theta 分辨率。 - 50:最小线段长度。 - 50:最大线段间隔。 - 10:线段阈值。 - 返回值: - 无 ##### 4.4.2.7 使用霍夫变化检测圆型 ```c++ cv::HoughCircles(gray, circles, cv::HOUGH_GRADIENT, 1, 50, 50, 30, 120, 250); ``` - 参数: - src:输入图像。 - circles:检测到的圆列表。 - CV_HOUGH_GRADIENT:检测方法。 - 1:rho 分辨率。 - 50:​最小圆心距。 - 50:​​Canny 高阈值。 - 30:​累加器阈值。 - 120:待检测圆的最小半径。 - 250:待检测圆的最大半径。 - 返回值: - 无 #### 4.4.3. 综合代码解析 ##### 4.4.3.1 识别圆 ###### 4.4.3.1.1 流程图 ``` 开始 | |-- 创建 Edit 对象并启动连接 | |-- 初始化摄像头设置(分辨率640x480) | |-- 打开摄像头设备 | |-- 进入无限循环 | |-- 获取新的一帧图像 (src) | | | |-- 转换为灰度图像 (gray = cvtColor(src, COLOR_BGR2GRAY)) | | | |-- 应用高斯模糊降噪 (GaussianBlur(gray, gray, Size(9, 9), 2, 2)) | | | |-- 使用霍夫圆变换检测圆 | |-- 参数设定:dp=1, minDist=50, param1=50, param2=30, minRadius=120, maxRadius=250 | |-- 遍历检测到的每个圆 | |-- 计算圆心位置和半径 | |-- 在原图上绘制圆 (circle(src, center, radius, Scalar(0, 255, 255), 2)) | | | |-- 显示处理后的图像 (edit.Print(src)) | |-- 结束程序 (释放摄像头资源) ``` ###### 4.4.3.1.2 核心代码解析 - 灰度转换 ```c++ cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY); ``` - 高斯模糊 ```c++ cv::GaussianBlur(gray, gray, cv::Size(9, 9), 2, 2); ``` - 霍夫圆检测并绘制圆 ```c++ std::vector circles; cv::HoughCircles(gray, circles, cv::HOUGH_GRADIENT, 1, 50, 50, 30, 120, 250); for (const cv::Vec3f &circle : circles) { cv::Point center(cvRound(circle[0]), cvRound(circle[1])); int radius = cvRound(circle[2]); cv::circle(src, center, radius, cv::Scalar(0, 255, 255), 2); // 绘制圆 } ``` ###### 4.4.3.1.3 完整代码实现 ```c++ #include #include #include int main() { lockzhiner_vision_module::edit::Edit edit; if (!edit.StartAndAcceptConnection()) { std::cerr << "Error: Failed to start and accept connection." << std::endl; return EXIT_FAILURE; } std::cout << "Device connected successfully." << std::endl; // 初始化摄像头 cv::VideoCapture cap; int width = 640; // 设置摄像头分辨率宽度 int height = 480; // 设置摄像头分辨率高度 cap.set(cv::CAP_PROP_FRAME_WIDTH, width); cap.set(cv::CAP_PROP_FRAME_HEIGHT, height); cap.open(0); // 参数 0 表示默认摄像头设备 if (!cap.isOpened()) { std::cerr << "Error: Could not open camera." << std::endl; return EXIT_FAILURE; } while (true) { // 读取输入图像 cv::Mat src; cap >> src; // 获取新的一帧 if (src.empty()) { std::cerr << "Warning: Couldn't read a frame from the camera." << std::endl; continue; } // 转换为灰度图像 cv::Mat gray; cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY); // 高斯模糊降噪 cv::GaussianBlur(gray, gray, cv::Size(9, 9), 2, 2); // 圆检测(霍夫圆变换) std::vector circles; cv::HoughCircles(gray, circles, cv::HOUGH_GRADIENT, 1, 50, 50, 30, 120, 250); for (const cv::Vec3f &circle : circles) { cv::Point center(cvRound(circle[0]), cvRound(circle[1])); int radius = cvRound(circle[2]); cv::circle(src, center, radius, cv::Scalar(0, 255, 255), 2); // 绘制圆 } edit.Print(src); } cap.release(); return 0; } ``` ##### 4.4.3.2 识别直线 ###### 4.4.3.2.1 流程图 ``` 开始 | |-- 创建 Edit 对象并启动连接 | |-- 初始化摄像头设置(分辨率640x480) | |-- 打开摄像头设备 | |-- 进入无限循环 | |-- 获取新的一帧图像 (src) | | | |-- 转换为灰度图像 (gray = cvtColor(src, COLOR_BGR2GRAY)) | | | |-- 应用Canny边缘检测 | |-- 参数设定:低阈值=50, 高阈值=150 | |-- 生成边缘图像 (edges) | | | |-- 使用概率霍夫变换检测直线 | |-- 参数设定:rho=1, theta=CV_PI/180, threshold=50, minLineLength=50, maxLineGap=10 | |-- 遍历检测到的每条直线 | |-- 在原图上绘制直线 (line(src, Point(x1, y1), Point(x2, y2), Scalar(255, 0, 0), 2)) | | | |-- 显示处理后的图像 (edit.Print(src)) | |-- 结束程序 (释放摄像头资源) ``` ###### 4.4.3.2.2 核心代码解析 - 将原始图像转换为灰度图像 ```c++ cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY); ``` - Canny边缘检测 ```c++ cv::Canny(gray, edges, 50, 150); ``` - 利用霍夫变换检测直线并在原图像中绘制 ```c++ cv::HoughLinesP(edges, lines, 1, CV_PI / 180, 50, 50, 10); for (const cv::Vec4i &line : lines) { cv::line(src, cv::Point(line[0], line[1]), cv::Point(line[2], line[3]), cv::Scalar(255, 0, 0), 2); } ``` ###### 4.4.3.2.3 完整代码实现 ```c++ #include #include #include int main() { lockzhiner_vision_module::edit::Edit edit; if (!edit.StartAndAcceptConnection()) { std::cerr << "Error: Failed to start and accept connection." << std::endl; return EXIT_FAILURE; } std::cout << "Device connected successfully." << std::endl; // 初始化摄像头 cv::VideoCapture cap; int width = 640; // 设置摄像头分辨率宽度 int height = 480; // 设置摄像头分辨率高度 cap.set(cv::CAP_PROP_FRAME_WIDTH, width); cap.set(cv::CAP_PROP_FRAME_HEIGHT, height); cap.open(0); // 参数 0 表示默认摄像头设备 if (!cap.isOpened()) { std::cerr << "Error: Could not open camera." << std::endl; return EXIT_FAILURE; } while (true) { // 读取输入图像 cv::Mat src; cap >> src; // 获取新的一帧 if (src.empty()) { std::cerr << "Warning: Couldn't read a frame from the camera." << std::endl; continue; } // 转换为灰度图像 cv::Mat gray; cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY); // 边缘检测(Canny) cv::Mat edges; cv::Canny(gray, edges, 50, 150); // 直线检测(霍夫变换) std::vector lines; cv::HoughLinesP(edges, lines, 1, CV_PI / 180, 50, 50, 10); for (const cv::Vec4i &line : lines) { cv::line(src, cv::Point(line[0], line[1]), cv::Point(line[2], line[3]), cv::Scalar(255, 0, 0), 2); } edit.Print(src); } cap.release(); return 0; } ``` ##### 4.4.3.3 识别多边形 ###### 4.4.3.3.1 流程图 ``` 开始 | |-- 创建 Edit 对象并启动连接 | |-- 初始化摄像头设置(分辨率640x480) | |-- 打开摄像头设备 | |-- 进入无限循环 | |-- 获取新的一帧图像 (src) | | | |-- 转换为灰度图像 (gray = cvtColor(src, COLOR_BGR2GRAY)) | | | |-- 应用高斯模糊降噪 (GaussianBlur(gray, gray, Size(5, 5), 0)) | | | |-- 使用Canny边缘检测 (Canny(gray, edges, 50, 150)) | | | |-- 查找轮廓 (findContours(edges, contours, RETR_LIST, CHAIN_APPROX_SIMPLE)) | | | |-- 多边形拟合及绘制 | |-- 遍历每个轮廓 | |-- 使用approxPolyDP函数对轮廓进行多边形拟合 | |-- 在原图上绘制拟合后的多边形轮廓 (drawContours(polygonImage, approx, -1, Scalar(0, 0, 255), 2)) | | | |-- 显示处理后的图像 (edit.Print(polygonImage)) | |-- 结束程序 (释放摄像头资源) ``` ###### 4.4.3.3.2 核心代码解析 - 预处理:包括灰度转换、高斯模糊、Canny边缘检测 ```c++ cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY); cv::GaussianBlur(gray, gray, cv::Size(5, 5), 0); cv::Canny(gray, edges, 50, 150); ``` - 轮廓分析 ```c++ std::vector> contours; cv::findContours(edges, contours, cv::RETR_LIST, cv::CHAIN_APPROX_SIMPLE); ``` - 多边形拟合 ```c++ for (size_t i = 0; i < contours.size(); i++) { std::vector approx; cv::approxPolyDP(contours[i], approx, cv::arcLength(contours[i], true) * 0.02, true); cv::drawContours(polygonImage, std::vector>{approx}, -1, cv::Scalar(0, 0, 255), 2); } ``` ###### 4.4.3.3.3 完整代码实现 ```c++ #include #include #include int main() { lockzhiner_vision_module::edit::Edit edit; if (!edit.StartAndAcceptConnection()) { std::cerr << "Error: Failed to start and accept connection." << std::endl; return EXIT_FAILURE; } std::cout << "Device connected successfully." << std::endl; // 初始化摄像头 cv::VideoCapture cap; int width = 640; // 设置摄像头分辨率宽度 int height = 480; // 设置摄像头分辨率高度 cap.set(cv::CAP_PROP_FRAME_WIDTH, width); cap.set(cv::CAP_PROP_FRAME_HEIGHT, height); cap.open(0); // 参数 0 表示默认摄像头设备 if (!cap.isOpened()) { std::cerr << "Error: Could not open camera." << std::endl; return EXIT_FAILURE; } while (true) { // 读取输入图像 cv::Mat src; cap >> src; // 获取新的一帧 if (src.empty()) { std::cerr << "Warning: Couldn't read a frame from the camera." << std::endl; continue; } // 转换为灰度图像 cv::Mat gray; cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY); // 高斯模糊降噪 cv::GaussianBlur(gray, gray, cv::Size(5, 5), 0); // 边缘检测(Canny) cv::Mat edges; cv::Canny(gray, edges, 50, 150); // 查找轮廓 std::vector> contours; cv::findContours(edges, contours, cv::RETR_LIST, cv::CHAIN_APPROX_SIMPLE); // 多边形拟合 cv::Mat polygonImage = src.clone(); for (size_t i = 0; i < contours.size(); i++) { std::vector approx; cv::approxPolyDP(contours[i], approx, cv::arcLength(contours[i], true) * 0.02, true); cv::drawContours(polygonImage, std::vector>{approx}, -1, cv::Scalar(0, 0, 255), 2); } edit.Print(polygonImage); } cap.release(); return 0; } ``` #### 4.4.4 编译过程 ##### 4.4.4.1 编译环境搭建 - 请确保你已经按照 [开发环境搭建指南](https://gitee.com/LockzhinerAI/LockzhinerVisionModule/blob/master/docs/introductory_tutorial/cpp_development_environment.md) 正确配置了开发环境。 - 同时已正确连接开发板。 ##### 4.4.4.2 Cmake介绍 ```cmake ## CMake最低版本要求 cmake_minimum_required(VERSION 3.10) project(test_find_contours) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) ## 定义项目根目录路径 set(PROJECT_ROOT_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../..") message("PROJECT_ROOT_PATH = " ${PROJECT_ROOT_PATH}) include("${PROJECT_ROOT_PATH}/toolchains/arm-rockchip830-linux-uclibcgnueabihf.toolchain.cmake") ## 定义 OpenCV SDK 路径 set(OpenCV_ROOT_PATH "${PROJECT_ROOT_PATH}/third_party/opencv-mobile-4.10.0-lockzhiner-vision-module") set(OpenCV_DIR "${OpenCV_ROOT_PATH}/lib/cmake/opencv4") find_package(OpenCV REQUIRED) set(OPENCV_LIBRARIES "${OpenCV_LIBS}") ## 定义 LockzhinerVisionModule SDK 路径 set(LockzhinerVisionModule_ROOT_PATH "${PROJECT_ROOT_PATH}/third_party/lockzhiner_vision_module_sdk") set(LockzhinerVisionModule_DIR "${LockzhinerVisionModule_ROOT_PATH}/lib/cmake/lockzhiner_vision_module") find_package(LockzhinerVisionModule REQUIRED) ## 寻找圆型轮廓 add_executable(Test-find-circle find_circle.cc) target_include_directories(Test-find-circle PRIVATE ${LOCKZHINER_VISION_MODULE_INCLUDE_DIRS}) target_link_libraries(Test-find-circle PRIVATE ${OPENCV_LIBRARIES} ${LOCKZHINER_VISION_MODULE_LIBRARIES}) ## 寻找线 add_executable(Test-find-line find_line.cc) target_include_directories(Test-find-line PRIVATE ${LOCKZHINER_VISION_MODULE_INCLUDE_DIRS}) target_link_libraries(Test-find-line PRIVATE ${OPENCV_LIBRARIES} ${LOCKZHINER_VISION_MODULE_LIBRARIES}) ## 寻找多边形 add_executable(Test-find-polygon find_polygon.cc) target_include_directories(Test-find-polygon PRIVATE ${LOCKZHINER_VISION_MODULE_INCLUDE_DIRS}) target_link_libraries(Test-find-polygon PRIVATE ${OPENCV_LIBRARIES} ${LOCKZHINER_VISION_MODULE_LIBRARIES}) install( TARGETS Test-find-circle TARGETS Test-find-line TARGETS Test-find-polygon RUNTIME DESTINATION . ) ``` ##### 4.4.4.3 编译项目 使用 Docker Destop 打开 LockzhinerVisionModule 容器并执行以下命令来编译项目 ```bash ## 进入Demo所在目录 cd /LockzhinerVisionModuleWorkSpace/LockzhinerVisionModule/Cpp_example/C04_find_contours ## 创建编译目录 rm -rf build && mkdir build && cd build ## 配置交叉编译工具链 export TOOLCHAIN_ROOT_PATH="/LockzhinerVisionModuleWorkSpace/arm-rockchip830-linux-uclibcgnueabihf" ## 使用cmake配置项目 cmake .. ## 执行编译项目 make -j8 && make install ``` 在执行完上述命令后,会在build目录下生成可执行文件。 #### 4.4.5 例程运行示例 ##### 4.4.5.1 圆识别 ```shell chmod find_circle ./find_circle ``` - 识别结果: ![](./images/C04_circle.png) ##### 4.4.5.2 直线识别 ```shell chmod find_line ./find_line ``` - 识别结果: ![](./images/C04_line.png) ##### 4.4.5.3 多边形识别 ```shell chmod find_polygon ./find_polygon ``` - 多边形识别: ![](./images/C04_polygon.png) #### 4.4.6 总结 - 本文档深入探讨了使用 OpenCV 进行实时图像处理与特征检测的多种方法,展示了如何通过边缘检测、直线检测、圆检测以及多边形拟合等技术对摄像头捕获的视频流进行分析。同时使用传统视觉方法进行图像识别,对环境非常敏感,推荐使用LockAI目标检测方法进行不同物体的识别以取得更好的效果。 ### 4.5 同时识别轮廓和色块 #### 4.5.1 项目简介 ##### 4.5.1.1 色块识别的重要性 - 颜色特征提取:颜色是一种重要的视觉特征,尤其在背景较为单一的情况下,能够快速区分目标区域。 - 应用场景:广泛应用于机器人导航、工业自动化、物体跟踪等领域。 - HSV 颜色空间:相比于 RGB 颜色空间,HSV 更适合用于颜色识别,因为它可以将颜色信息(Hue)、饱和度(Saturation)和亮度(Value)分离,便于设置阈值。 ##### 4.5.1.2 色块识别的流程 - 获取图像。 - 将图像从 BGR 转换为 HSV 颜色空间。 - 创建二值掩码,筛选出符合颜色范围的像素。 - 使用形态学操作清除噪声。 - 查找轮廓并筛选符合条件的色块。 - 计算外接矩形和中心点。 - 绘制结果并输出。 ##### 4.5.1.3 图像处理的重要性 - 目标检测:图像处理技术可以用于检测图像中的特定对象或特征。 - 应用场景:广泛应用于物体识别、工业自动化、机器人导航、自动驾驶等领域。 - 常见任务: - 边缘检测:提取图像中的边界信息。 - 直线检测:识别图像中的直线结构。 - 圆检测:识别图像中的圆形结构。 - 多边形拟合:将轮廓拟合成多边形以简化形状描述。 ##### 4.5.1.4 图像处理的基本流程 - 初始化摄像头:打开摄像头设备并设置分辨率。 - 读取图像帧:从摄像头中获取实时视频帧。 - 预处理:将图像转换为灰度图、降噪等操作。 - 特征检测:执行边缘检测、霍夫变换等算法。 - 结果绘制:在原图上绘制检测到的特征。 - 显示结果:将处理后的图像输出到屏幕。 #### 4.5.2 API 文档 ##### 4.5.2.1 头文件 ```c++ #include ``` ##### 4.5.2.2 生成掩码 ```c++ cv::inRange(src, lowerb, upperb, dst); ``` - 参数说明: - src:输入图像,可以是单通道或三通道的图像。 - lowerb:颜色下界,是一个Scalar对象,表示要查找的颜色的下限。 - upperb:颜色上界,是一个Scalar对象,表示要查找的颜色的上限。 - dst:输出图像,是一个单通道的8位无符号整数图像,表示生成的掩码。 - 返回值: - 无 ##### 4.5.2.3 创建形态学操作所需的结构元素核 ```c++ cv::getStructuringElement(shape, ksize, anchor); ``` - 参数说明: - shape:核形状,可以是RECT、CROSS、ELLIPSE等。 - ksize:核大小,是一个Size对象,表示核的宽度和高度。 - anchor:锚点,是一个Point对象,表示核的锚点位置。 - 返回值: - 返回一个核,是一个Mat对象。 ##### 4.5.2.4 形态学操作:清除噪声 ```c++ cv::morphologyEx(src, dst, op, kernel, anchor, iterations, borderType, borderValue); ``` - 参数说明: - src:输入图像,可以是单通道或三通道的图像。 - dst:输出图像,是一个单通道的8位无符号整数图像,表示生成的掩码。 - op:操作类型,可以是OPEN、CLOSE、GRADIENT、TOPHAT、BLACKHAT等。 - kernel:核,是一个Mat对象,表示形态学操作的核。 - anchor:锚点,是一个Point对象,表示核的锚点位置。 - iterations:迭代次数,是一个整数,表示形态学操作的迭代次数。 - borderType:边界类型,可以是BORDER_CONSTANT、BORDER_REPLICATE、BORDER_REFLECT、BORDER_WRAP、BORDER_REFLECT_101等。 - borderValue:边界值,是一个Scalar对象,表示边界区域的值。 - 返回值: - 无 ##### 4.5.2.5 查找轮廓 ```c++ cv::findContours(image, contours, hierarchy, mode, method, offset); ``` - 参数说明: - image:输入图像,可以是单通道或三通道的图像。 - contours:输出参数,是一个vector>对象,表示轮廓的集合。 - hierarchy:输出参数,是一个vector对象,表示轮廓的层级关系。 - mode:轮廓发现模式,可以是RETR_EXTERNAL、RETR_LIST、RETR_CCOMP、RETR_TREE等。 - method:轮廓 approximation 方法,可以是CHAIN_APPROX_NONE、CHAIN_APPROX_SIMPLE、CHAIN_APPROX_TC89_L1、CHAIN_APPROX_TC89_KCOS等。 - offset:轮廓偏移量,是一个Point对象,表示轮廓的偏移量。 - 返回值: - 返回一个整数,表示轮廓的数量。 ##### 4.5.2.6 获取轮廓的外接矩形 ```c++ cv::boundingRect(points); ``` - 参数说明: - points:输入参数,是一个vector对象,表示轮廓的点集合。 - 返回值: - 返回一个Rect对象,表示轮廓的外接矩形。 ##### 4.5.2.7 计算矩阵矩 ```c++ cv::moments(array, binaryImage); ``` - 参数说明: - array:输入参数,是一个Mat对象,表示输入的矩阵。 - binaryImage:输入参数,是一个布尔值,表示是否将输入的矩阵转换为二值矩阵。 - 返回值: - 返回一个 Moments对象,表示矩阵的矩。 ##### 4.5.2.8 绘制矩形框 ```c++ cv::rectangle(img, pt1, pt2, color, thickness, lineType, shift); ``` - 参数说明: - img:输入参数,是一个Mat对象,表示输入的图像。 - pt1:输入参数,是一个Point对象,表示矩形的左上角点。 - pt2:输入参数,是一个Point对象,表示矩形的右下角点。 - color:输入参数,是一个Scalar对象,表示矩形的颜色。 - thickness:输入参数,是一个整数,表示矩形的线宽。 - lineType:输入参数,是一个整数,表示矩形的线类型。 - shift:输入参数,是一个整数,表示坐标的精度。 - 返回值: - 无 ##### 4.5.2.9 绘制圆 ```c++ cv::circle(img, center, radius, color, thickness, lineType, shift); ``` - 参数说明: - img:输入参数,是一个Mat对象,表示输入的图像。 - center:输入参数,是一个Point对象,表示圆心。 - radius:输入参数,是一个整数,表示圆的半径。 - color:输入参数,是一个Scalar对象,表示圆的颜色。 - thickness:输入参数,是一个整数,表示圆的线宽。 - lineType:输入参数,是一个整数,表示圆的线类型。 - shift:输入参数,是一个整数,表示坐标的精度。 - 返回值: - 无 ##### 4.5.2.10 查找色块函数(自定义) ```c++ std::vector> find_blobs( const cv::Mat &image, const cv::Scalar &lower_bound, const cv::Scalar &upper_bound, int min_area = 100, int kernel_size = 5); ``` - 参数说明: - image:输入参数,是一个Mat对象,表示输入的图像。 - lower_bound:输入参数,是一个Scalar对象,表示颜色下界。 - upper_bound:输入参数,是一个Scalar对象,表示颜色上界。 - min_area:输入参数,是一个整数,表示最小面积。 - kernel_size:输入参数,是一个整数,表示核大小。 - 返回值: - 返回一个vector>对象,表示找到的色块的点集合。 ##### 4.5.2.11 高斯模糊 ```c++ cv::GaussianBlur(src, dst, Size(3, 3), 0); ``` - 参数: - src:输入图像。 - dst:输出图像。 - Size(3, 3):卷积核大小。 - 0:标准差。 - 返回值: - 无。 ##### 4.5.2.12 边缘检测 ```c++ cv::Canny(src, dst, 50, 150); ``` - 参数: - src:输入图像。 - dst:输出图像。 - 50:低阈值。 - 150:高阈值。 - apertureSize:Sobel 算子的孔径大小(默认为 3)。 - L2gradient:是否使用 L2 范数计算梯度(默认为 false)。 - 返回值: - 无。 ##### 4.5.2.13 对轮廓进行多边形拟合 ```c++ cv::approxPolyDP(contours[i], approx, epsilon, closed); ``` - 参数: - contours[i]:轮廓。 - approx:多边形顶点列表。 - epsilon:精度参数,表示最大距离,用于控制多边形拟合的精度。 - closed:是否闭合多边形(默认为 false)。 - 返回值: - 无 ##### 4.5.2.14 使用概率霍夫变换检测直线 ```c++ cv::HoughLinesP(src, lines, 1, CV_PI / 180, 50, 50, 10); ``` - 参数: - src:输入图像。 - lines:检测到的直线列表。 - 1:rho 分辨率。 - CV_PI / 180:theta 分辨率。 - 50:最小线段长度。 - 50:最大线段间隔。 - 10:线段阈值。 - 返回值: - 无 ##### 4.5.2.15 使用霍夫变化检测圆型 ```c++ cv::HoughCircles(gray, circles, cv::HOUGH_GRADIENT, 1, 50, 50, 30, 120, 250); ``` - 参数: - src:输入图像。 - circles:检测到的圆列表。 - CV_HOUGH_GRADIENT:检测方法。 - 1:rho 分辨率。 - 50:​最小圆心距。 - 50:​​Canny 高阈值。 - 30:​累加器阈值。 - 120:待检测圆的最小半径。 - 250:待检测圆的最大半径。 - 返回值: - 无 #### 4.5.3 综合代码介绍 ##### 4.5.3.1 流程图 ``` 开始 | |-- 创建 Edit 对象并启动连接 | |-- 初始化摄像头设置(分辨率640x480) | |-- 打开摄像头设备 | |-- 进入无限循环 | |-- 获取新的一帧图像 (image) | | | |-- 定义颜色阈值(例如红色) | |-- lower_red = Scalar(170, 100, 100) | |-- upper_red = Scalar(179, 255, 255) | | | |-- 调用 find_blobs 函数查找色块 | |-- 转换为 HSV 颜色空间 (cvtColor(image, hsv_image, COLOR_BGR2HSV)) | |-- 创建二值掩码 (inRange(hsv_image, lower_bound, upper_bound, mask)) | |-- 形态学操作:清除噪声 (morphologyEx(mask, mask, MORPH_OPEN, kernel)) | |-- 查找轮廓 (findContours(mask, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE)) | |-- 筛选符合条件的色块 (面积 >= min_area) | | | |-- 绘制和处理检测到的色块 | |-- 遍历每个色块(contour) | |-- 计算外接矩形框 (boundingRect(contour)) | |-- 近似多边形拟合 (approxPolyDP(contour, approx, arcLength(contour, true) * 0.02, true)) | |-- 判断是否为四边形 (approx.size() == 4) | |-- 绘制矩形框 (rectangle(image, bounding_rect, Scalar(0, 255, 0), 2)) | |-- 计算中心点 (moments(contour)) | |-- 绘制中心点 (circle(image, Point(cx, cy), 5, Scalar(0, 0, 255), -1)) | |-- 打印信息 (输出色块的位置和面积) | | | |-- 显示处理后的图像 (edit.Print(image)) | |-- 结束程序 (释放摄像头资源) ``` ##### 4.5.3.2 核心代码解析 - 阈值分割 ```c++ cv::inRange(hsv_image, lower_bound, upper_bound, mask); ``` - 形态学开运算 ```c++ cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(kernel_size, kernel_size)); cv::morphologyEx(mask, mask, cv::MORPH_OPEN, kernel); ``` - 轮廓查找 ```c++ std::vector> contours; cv::findContours(mask, contours, cv::RETR_EXTERNAL,cv::CHAIN_APPROX_SIMPLE); ``` - 筛选色块 ```c++ std::vector> filtered_contours; for (const auto &contour : contours) { cv::Rect bounding_rect = cv::boundingRect(contour); if (bounding_rect.area() >= min_area) { filtered_contours.push_back(contour); } } ``` ##### 4.5.3.3 完整代码实现 ```c++ #include #include #include #include std::vector> find_blobs( const cv::Mat &image, const cv::Scalar &lower_bound, const cv::Scalar &upper_bound, int min_area = 100, int kernel_size = 5) { // 转换为 HSV 颜色空间 cv::Mat hsv_image; cv::cvtColor(image, hsv_image, cv::COLOR_BGR2HSV); // 创建二值掩码 cv::Mat mask; cv::inRange(hsv_image, lower_bound, upper_bound, mask); // 形态学操作:清除噪声 cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(kernel_size, kernel_size)); cv::morphologyEx(mask, mask, cv::MORPH_OPEN, kernel); // 查找轮廓 std::vector> contours; cv::findContours(mask, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE); // 筛选符合条件的色块 std::vector> filtered_contours; for (const auto &contour : contours) { cv::Rect bounding_rect = cv::boundingRect(contour); if (bounding_rect.area() >= min_area) { filtered_contours.push_back(contour); } } return filtered_contours; } int main() { lockzhiner_vision_module::edit::Edit edit; if (!edit.StartAndAcceptConnection()) { std::cerr << "Error: Failed to start and accept connection." << std::endl; return EXIT_FAILURE; } std::cout << "Device connected successfully." << std::endl; cv::VideoCapture cap; int width = 640; // 设置摄像头分辨率宽度 int height = 480; // 设置摄像头分辨率高度 cap.set(cv::CAP_PROP_FRAME_WIDTH, width); cap.set(cv::CAP_PROP_FRAME_HEIGHT, height); // 打开摄像头设备 cap.open(0); // 参数 0 表示默认摄像头设备 if (!cap.isOpened()) { std::cerr << "Error: Could not open camera." << std::endl; return EXIT_FAILURE; } while (true) { cv::Mat image; // 存储每一帧图像 cap >> image; // 获取新的一帧 if (image.empty()) { std::cerr << "Warning: Couldn't read a frame from the camera." << std::endl; continue; } // 定义颜色阈值(例如红色) cv::Scalar lower_red(170, 100, 100); // 红色下界 cv::Scalar upper_red(179, 255, 255); // 红色上界 // 调用 find_blobs 函数 int min_area = 100; // 最小面积阈值 int kernel_size = 1; // 形态学操作核大小 std::vector> blobs = find_blobs(image, lower_red, upper_red, min_area, kernel_size); // 绘制和打印检测到的色块,并筛选矩形 for (const auto &contour : blobs) { // 计算外接矩形框 cv::Rect bounding_rect = cv::boundingRect(contour); // 近似多边形拟合 std::vector approx; cv::approxPolyDP(contour, approx, cv::arcLength(contour, true) * 0.02, true); // 判断是否为四边形 if (approx.size() == 4) { // 绘制矩形框 cv::rectangle(image, bounding_rect, cv::Scalar(0, 255, 0), 2); // 计算中心点 cv::Moments moments = cv::moments(contour); int cx = moments.m10 / moments.m00; int cy = moments.m01 / moments.m00; // 绘制中心点 cv::circle(image, cv::Point(cx, cy), 5, cv::Scalar(0, 0, 255), -1); // 打印信息 std::cout << "Red quadrilateral detected at (" << cx << ", " << cy << ") with area " << bounding_rect.area() << std::endl; } } // 显示结果 edit.Print(image); } cap.release(); return 0; } ``` #### 4.5.4 编译过程 ##### 4.5.4.1 编译环境搭建 - 请确保你已经按照 [开发环境搭建指南](https://gitee.com/LockzhinerAI/LockzhinerVisionModule/blob/master/docs/introductory_tutorial/cpp_development_environment.md) 正确配置了开发环境。 - 同时已正确连接开发板。 ##### 4.5.4.2 Cmake介绍 ```cmake # CMake最低版本要求 cmake_minimum_required(VERSION 3.10) project(test-Finecolorandshape) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # 定义项目根目录路径 set(PROJECT_ROOT_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../..") message("PROJECT_ROOT_PATH = " ${PROJECT_ROOT_PATH}) include("${PROJECT_ROOT_PATH}/toolchains/arm-rockchip830-linux-uclibcgnueabihf.toolchain.cmake") # 定义 OpenCV SDK 路径 set(OpenCV_ROOT_PATH "${PROJECT_ROOT_PATH}/third_party/opencv-mobile-4.10.0-lockzhiner-vision-module") set(OpenCV_DIR "${OpenCV_ROOT_PATH}/lib/cmake/opencv4") find_package(OpenCV REQUIRED) set(OPENCV_LIBRARIES "${OpenCV_LIBS}") # 定义 LockzhinerVisionModule SDK 路径 set(LockzhinerVisionModule_ROOT_PATH "${PROJECT_ROOT_PATH}/third_party/lockzhiner_vision_module_sdk") set(LockzhinerVisionModule_DIR "${LockzhinerVisionModule_ROOT_PATH}/lib/cmake/lockzhiner_vision_module") find_package(LockzhinerVisionModule REQUIRED) # 寻找色块和轮廓 add_executable(Test-Finecolorandshape Finecolorandshape.cc) target_include_directories(Test-Finecolorandshape PRIVATE ${LOCKZHINER_VISION_MODULE_INCLUDE_DIRS}) target_link_libraries(Test-Finecolorandshape PRIVATE ${OPENCV_LIBRARIES} ${LOCKZHINER_VISION_MODULE_LIBRARIES}) install( TARGETS Test-Finecolorandshape RUNTIME DESTINATION . ) ``` ##### 4.5.4.3 编译项目 使用 Docker Destop 打开 LockzhinerVisionModule 容器并执行以下命令来编译项目 ```bash # 进入Demo所在目录 cd /LockzhinerVisionModuleWorkSpace/LockzhinerVisionModule/Cpp_example/C05_Find_color_and_shape # 创建编译目录 rm -rf build && mkdir build && cd build # 配置交叉编译工具链 export TOOLCHAIN_ROOT_PATH="/LockzhinerVisionModuleWorkSpace/arm-rockchip830-linux-uclibcgnueabihf" # 使用cmake配置项目 cmake .. # 执行编译项目 make -j8 && make install ``` 在执行完上述命令后,会在build目录下生成可执行文件。 #### 4.5.5 例程运行示例 ##### 4.5.5.1 运行过程 ```shell chmod 777 Test-Finecolorandshape ./Test-Finecolorandshape ``` ##### 4.5.5.2 结果展示 ![](./images/C05.png) #### 4.5.6 总结 本程序实现了基于 OpenCV 的红色四边形检测功能,具有以下特点: - 高效性:通过颜色过滤、形态学处理和轮廓筛选,快速定位目标。 - 灵活性:支持自定义颜色阈值、最小面积和形态学核大小,适应不同场景需求。 - 易用性:代码结构清晰,模块化设计便于扩展和维护。 该程序可作为基础框架,进一步应用于更复杂的视觉任务,例如多目标检测、动态跟踪等。通过调整颜色阈值和形状筛选条件,还可扩展到其他颜色和形状的检测任务。 ### 4.6 二维码识别 二维码识别是视觉模块经常使用到的功能之一,本章节中,我们将教会你如何使用 Lockzhiner Vision Module 进行二维码识别。 #### 4.6.1 基本知识讲解 ##### 4.6.1.1 二维码简介 二维码(QR Code)是一种高效的二维条码,能快速存储和读取信息,即使部分损坏也能准确识别。它广泛应用于移动支付、广告、物流、票务等领域,用户只需用智能手机扫描即可获取信息或完成操作,极大提升了效率和便利性。 ##### 4.6.1.2 二维码识别步骤 二维码识别主要通过两个步骤完成:图像捕捉和解码。 - 图像捕捉:使用设备摄像头拍摄包含二维码的图像。 - 解码:软件处理图像,定位并读取二维码中的数据,转换为原始信息。 常用工具如ZXing和OpenCV支持快速集成到应用中,使用户能轻松扫描并获取二维码信息。 #### 4.6.2 API 文档 ##### 4.6.2.1 QRCodeDetector类 ###### 4.6.2.1.1 头文件 ```c++ #include ``` - 作用:用于声明QRCodeDetector类,使得QRCodeDetector类可以在当前源文件中使用。 ###### 4.6.2.1.2 构造类对象 ```c++ lockzhiner_vision_module::vision::QRCodeDetector model; ``` - 作用:用于实现二维码识别。 - 参数说明: - 无 - 返回值: - 无 ###### 4.6.2.1.3 Predict函数 ```c++ auto results = model.Predict(input_mat); ``` - 作用:QRCodeDetector类中的一个函数,用于实现二维码识别。 - 参数说明: - input_mat: 输入参数,类型为cv::Mat,表示要分析的输入图像。 - 返回值: - 返回一个包含二维码检测结果的对象集合。每个Result对象包含二维码的位置信息和解码后的文本内容。 ##### 4.6.2.2 Visualize函数 ###### 4.6.2.2.1 头文件 ```c++ #include ``` - 作用:用于声明Visualize函数,使得Visualize函数可以在当前源文件中使用。 ###### 4.6.2.2.2 结果可视化 ```c++ lockzhiner_vision_module::vision::Visualize(input_image, output_image, results); ``` - 参数说明: - input_image: 输入参数,表示原始输入图像。 - output_image: 输出参数,用于存储带有可视化结果的输出图像。 - results: 输入参数,表示二维码检测的结果集。每个Result对象包含二维码的位置信息和解码后的文本内容。 - 返回值: - 无 #### 4.6.3 综合代码介绍 ##### 4.6.3.1 流程图 ``` 开始 | |-- 初始化 `edit` 模块并启动连接 | |-- 设置摄像头参数(分辨率640x480) | |-- 打开摄像头设备 | |-- 进入无限循环 | |-- 初始化计数器 (read_index = 0, time_ms = 0) | | | |-- 循环30次处理帧 | |-- 记录开始时间 | |-- 从摄像头读取一帧 (cap >> input_mat) | |-- 使用模型对图像进行二维码检测 (model.Predict(input_mat)) | |-- 记录结束时间 | |-- 计算处理该帧所需时间并累加到 time_ms | |-- 增加 read_index 计数 | | | |-- 调用 Visualize 函数对原始图像和检测结果进行可视化处理 | |-- 将可视化结果存储在 output_image 中 | |-- 显示或输出处理后的图像 (edit.Print(output_image)) | | |-- 输出当前FPS (Frames per second: 1000.0 / time_ms * read_index) | |-- 结束程序 (释放摄像头资源) ``` ##### 4.6.3.2 核心代码解析 - 定义检测模型 ```c++ lockzhiner_vision_module::vision::QRCodeDetector model; ``` - 调用摄像头捕获图像 ```c++ cv::VideoCapture cap; // 设置摄像头获取帧的宽高 cap.set(cv::CAP_PROP_FRAME_WIDTH, 640); cap.set(cv::CAP_PROP_FRAME_HEIGHT, 480); cap.open(0); // wihile循环中的以下代码用于捕获图像帧 cap >> input_mat; if (input_mat.empty()) { continue; } ``` - 检测二维码 ```c++ auto results = model.Predict(input_mat); ``` ##### 4.6.3.3 完整代码实现 ```c++ #include #include #include #include #include #include #include #include using namespace std::chrono; lockzhiner_vision_module::vision::QRCodeDetector model; int main() { // 初始化 edit 模块 lockzhiner_vision_module::edit::Edit edit; if (!edit.StartAndAcceptConnection()) { std::cerr << "Error: Failed to start and accept connection." << std::endl; return EXIT_FAILURE; } std::cout << "Device connected successfully." << std::endl; cv::VideoCapture cap; // 设置摄像头获取帧的宽高 cap.set(cv::CAP_PROP_FRAME_WIDTH, 640); cap.set(cv::CAP_PROP_FRAME_HEIGHT, 480); cap.open(0); if (!cap.isOpened()) { std::cerr << "Error: Could not open camera." << std::endl; return EXIT_FAILURE; } cv::Mat input_mat; while (true) { int read_index = 0; int time_ms = 0; for (int i = 0; i < 30; i++) { high_resolution_clock::time_point start_time = high_resolution_clock::now(); cap >> input_mat; if (input_mat.empty()) { continue; } // 使用 model 对象的 Predict 方法对输入图像进行预测,获取二维码检测结果 auto results = model.Predict(input_mat); high_resolution_clock::time_point end_time = high_resolution_clock::now(); auto time_span = duration_cast(end_time - start_time); time_ms += time_span.count(); read_index += 1; cv::Mat output_image; // 调用 Visualize 函数对原始图像和检测结果进行可视化处理,并将结果存储在 output_image 中 lockzhiner_vision_module::vision::Visualize(input_mat, output_image, results); edit.Print(output_image); } std::cout << "Frames per second: " << 1000.0 / time_ms * read_index << std::endl; } cap.release(); return 0; } ``` #### 4.6.4 编译调试 ##### 4.6.4.1 编译环境搭建 - 请确保你已经按照 [开发环境搭建指南](https://gitee.com/LockzhinerAI/LockzhinerVisionModule/blob/master/docs/introductory_tutorial/cpp_development_environment.md) 正确配置了开发环境。 - 同时以正确连接开发板。 ##### 4.6.4.2 Cmake介绍 ```cmake # CMake最低版本要求 cmake_minimum_required(VERSION 3.10) project(test_qr_code_detector) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # 定义项目根目录路径 set(PROJECT_ROOT_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../..") message("PROJECT_ROOT_PATH = " ${PROJECT_ROOT_PATH}) include("${PROJECT_ROOT_PATH}/toolchains/arm-rockchip830-linux-uclibcgnueabihf.toolchain.cmake") # 定义 OpenCV SDK 路径 set(OpenCV_ROOT_PATH "${PROJECT_ROOT_PATH}/third_party/opencv-mobile-4.10.0-lockzhiner-vision-module") set(OpenCV_DIR "${OpenCV_ROOT_PATH}/lib/cmake/opencv4") find_package(OpenCV REQUIRED) set(OPENCV_LIBRARIES "${OpenCV_LIBS}") # 定义 LockzhinerVisionModule SDK 路径 set(LockzhinerVisionModule_ROOT_PATH "${PROJECT_ROOT_PATH}/third_party/lockzhiner_vision_module_sdk") set(LockzhinerVisionModule_DIR "${LockzhinerVisionModule_ROOT_PATH}/lib/cmake/lockzhiner_vision_module") find_package(LockzhinerVisionModule REQUIRED) # 定义 ZXing SDK 路径 set(ZXing_ROOT_PATH "${PROJECT_ROOT_PATH}/third_party/zxing-cpp-v2.2.1-lockzhiner-vision-module") set(ZXing_DIR "${ZXing_ROOT_PATH}/lib/cmake/ZXing") set(ZXing_INCLUDE_DIRS "${ZXing_ROOT_PATH}/include") find_package(ZXing REQUIRED) set(ZXing_LIBRARIES "${ZXing_LIBS}") # 基本图像处理示例 add_executable(Test-qr_code-detector test_qr_code_detector.cc) target_include_directories(Test-qr_code-detector PRIVATE ${ZXing_INCLUDE_DIRS} ${LOCKZHINER_VISION_MODULE_INCLUDE_DIRS} ) target_link_libraries(Test-qr_code-detector PRIVATE ${OPENCV_LIBRARIES} ${LOCKZHINER_VISION_MODULE_LIBRARIES} ${ZXing_LIBRARIES} ) install( TARGETS Test-qr_code-detector RUNTIME DESTINATION . ) ``` ##### 4.6.4.3 编译项目 使用 Docker Destop 打开 LockzhinerVisionModule 容器并执行以下命令来编译项目 ```bash ## 进入Demo所在目录 cd /LockzhinerVisionModuleWorkSpace/LockzhinerVisionModule/Cpp_example/C06_test_qr_code_detector ## 创建编译目录 rm -rf build && mkdir build && cd build ## 配置交叉编译工具链 export TOOLCHAIN_ROOT_PATH="/LockzhinerVisionModuleWorkSpace/arm-rockchip830-linux-uclibcgnueabihf" ## 使用cmake配置项目 cmake .. ## 执行编译项目 make -j8 && make install ``` 在执行完上述命令后,会在build目录下生成可执行文件。 #### 4.6.5 例程运行示例 ##### 4.6.5.1 运行前准备 - 请确保你已经参考 [凌智视觉模块摄像头部署指南](../../../periphery/capture/README.md) 正确下载了凌智视觉模块图片传输助手。 ##### 4.6.5.2 运行过程 在凌智视觉模块输入以下命令: ```shell chmod 777 Test-qr_code-detector ./Test-qr_code-detector ``` ##### 4.6.5.3 运行效果 ![title](./images/C06_test_capture.png) #### 4.6.6 总结 通过上述内容,我们成功的实现了一个二维码识别系统,包括: - 获取并加载包含二维码的图像。 - 进行二维码的检测和解码,返回检测和解码后的结果。 - 可视化包含二维码图像的识别结果。 ### 4.7 条码识别 条码识别是视觉模块经常使用到的功能之一,经常用于识别超市的货物信息。本章节中,我们将教会你如何使用 Lockzhiner Vision Module 进行条码识别。 #### 4.7.1 基本知识讲解 ##### 4.7.1.1 条码简介 条码是一种通过宽度不同的平行线条和间隔来表示数据的机器可读形式,能够被扫描设备快速读取并转换为数字信号。被广泛应用于零售、物流、医疗和制造等行业。条码技术提高了数据输入的速度和准确性,降低了成本,并且由于其高效性和兼容性强的特点,成为商品标识、库存管理、货物追踪等操作中不可或缺的一部分。无论是简单的产品编码还是一些复杂的包含文字、网址的信息,条码都能提供可靠的支持。 ##### 4.7.1.2 条码识别步骤 条码码识别主要通过两个步骤完成:图像捕捉和解码。 - 图像捕捉:使用设备摄像头拍摄包含条码的图像。 - 解码:软件处理图像,定位并读取条码中的数据,转换为原始信息。 常用工具如ZXing和ZBar提供了便捷的方法来集成条码识别功能,使得开发者可以轻松实现从图像捕捉到数据解码的过程。用户只需简单操作即可快速获取条码中的信息,极大提高了效率和便利性。 #### 4.7.2 API 文档 ##### 4.7.2.1 Code128Detector类 ###### 4.7.2.1.1 头文件 ```c++ #include ``` - 作用:用于声明Code128Detector类,使得Code128Detector类可以在当前源文件中使用。 ###### 4.7.2.1.2 构造类对象 ```c++ lockzhiner_vision_module::vision::Code128Detector model; ``` - 作用:用于实现条码识别。 - 参数说明: - 无 - 返回值: - 无 ###### 4.7.2.1.3 Predict函数 ```c++ auto results = model.Predict(input_mat); ``` - 作用:Code128Detector类中的一个函数,用于实现条码识别。 - 参数说明: - input_mat: 要识别的图像。 - 返回值: - 返回一个包含Code 128格式的条码检测结果的对象集合。每个Result对象包含条码的位置信息和解码后的文本内容。 ##### 4.7.2.2 Visualize函数 ###### 4.7.2.2.1 头文件 ```c++ #include ``` - 作用:用于声明Visualize函数,使得Visualize函数可以在当前源文件中使用。 ###### 4.7.2.2.2 结果可视化 ```c++ lockzhiner_vision_module::vision::Visualize(input_image, output_image, results); ``` - 参数说明: - input_image: 输入参数,表示原始输入图像。 - output_image: 输出参数,用于存储带有可视化结果的输出图像。 - results: 输入参数,表示条码码检测的结果集。每个Result对象包含条码的位置信息和解码后的文本内容。 - 返回值: - 无 #### 4.7.3 综合代码介绍 ##### 4.7.3.1 流程图 ``` 开始 | |-- 初始化 `edit` 模块并启动连接 | |-- 设置摄像头参数(分辨率640x480) | |-- 打开摄像头设备 | |-- 进入无限循环 | |-- 初始化计数器 (read_index = 0, time_ms = 0) | | | |-- 循环30次处理帧 | |-- 获取当前时间点作为开始时间 (start_time) | |-- 从摄像头读取一帧 (cap >> input_mat) | |-- 使用模型对图像进行条码检测 (model.Predict(input_mat)) | |-- 获取当前时间点作为结束时间 (end_time) | |-- 计算处理该帧所需时间 (time_span = end_time - start_time),并将结果累加到 time_ms | |-- 增加 read_index 计数 | | | |-- 调用 Visualize 函数对原始图像和检测结果进行可视化处理 | |-- 将可视化结果存储在 output_image 中 | |-- 显示或输出处理后的图像 (edit.Print(output_image)) | | |-- 输出当前FPS (Frames per second: 1000.0 / time_ms * read_index) | |-- 结束程序 (释放摄像头资源) ``` ##### 4.7.3.2 核心代码解析 - 初始化 ```c++ lockzhiner_vision_module::vision::Code128Detector model; lockzhiner_vision_module::edit::Edit edit; ``` - 调用摄像头捕获图像 ```c++ cv::VideoCapture cap; // 设置摄像头获取帧的宽高 cap.set(cv::CAP_PROP_FRAME_WIDTH, 640); cap.set(cv::CAP_PROP_FRAME_HEIGHT, 480); cap.open(0); // wihile循环中的以下代码用于捕获图像帧 cap >> input_mat; if (input_mat.empty()) { continue; } ``` - 条码检测 ```c++ auto results = model.Predict(input_mat); ``` ##### 4.7.3.3 完整代码实现 ```c++ #include #include #include #include #include #include #include #include using namespace std::chrono; lockzhiner_vision_module::vision::Code128Detector model; int main() { // 初始化 edit 模块 lockzhiner_vision_module::edit::Edit edit; if (!edit.StartAndAcceptConnection()) { std::cerr << "Error: Failed to start and accept connection." << std::endl; return EXIT_FAILURE; } std::cout << "Device connected successfully." << std::endl; cv::VideoCapture cap; // 设置摄像头获取帧宽高 cap.set(cv::CAP_PROP_FRAME_WIDTH, 640); cap.set(cv::CAP_PROP_FRAME_HEIGHT, 480); cap.open(0); if (!cap.isOpened()) { std::cerr << "Error: Could not open camera." << std::endl; return EXIT_FAILURE; } cv::Mat input_mat; while (true) { int read_index = 0; int time_ms = 0; for (int i = 0; i < 30; i++) { // 获取当前时间点作为开始时间 high_resolution_clock::time_point start_time = high_resolution_clock::now(); cap >> input_mat; if (input_mat.empty()) { continue; } // 使用 model 对象的 Predict 方法对输入图像进行预测,获取条码检测结果 auto results = model.Predict(input_mat); // 获取当前时间点作为结束时间 high_resolution_clock::time_point end_time = high_resolution_clock::now(); auto time_span = duration_cast(end_time - start_time); time_ms += time_span.count(); read_index += 1; cv::Mat output_image; // 调用 Visualize 函数对原始图像和检测结果进行可视化处理,并将结果存储在 output_image 中 lockzhiner_vision_module::vision::Visualize(input_mat, output_image, results); edit.Print(output_image); } std::cout << "Frames per second: " << 1000.0 / time_ms * read_index << std::endl; } cap.release(); return 0; } ``` #### 4.7.4 编译调试 ##### 4.7.4.1 编译环境搭建 - 请确保你已经按照 [开发环境搭建指南](https://gitee.com/LockzhinerAI/LockzhinerVisionModule/blob/master/docs/introductory_tutorial/cpp_development_environment.md) 正确配置了开发环境。 - 同时已正确连接开发板。 ##### 4.7.4.2 Cmake介绍 ```cmake # CMake最低版本要求 cmake_minimum_required(VERSION 3.10) project(test_bar_codeDetector) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # 定义项目根目录路径 set(PROJECT_ROOT_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../..") message("PROJECT_ROOT_PATH = " ${PROJECT_ROOT_PATH}) include("${PROJECT_ROOT_PATH}/toolchains/arm-rockchip830-linux-uclibcgnueabihf.toolchain.cmake") # 定义 OpenCV SDK 路径 set(OpenCV_ROOT_PATH "${PROJECT_ROOT_PATH}/third_party/opencv-mobile-4.10.0-lockzhiner-vision-module") set(OpenCV_DIR "${OpenCV_ROOT_PATH}/lib/cmake/opencv4") find_package(OpenCV REQUIRED) set(OPENCV_LIBRARIES "${OpenCV_LIBS}") # 定义 LockzhinerVisionModule SDK 路径 set(LockzhinerVisionModule_ROOT_PATH "${PROJECT_ROOT_PATH}/third_party/lockzhiner_vision_module_sdk") set(LockzhinerVisionModule_DIR "${LockzhinerVisionModule_ROOT_PATH}/lib/cmake/lockzhiner_vision_module") find_package(LockzhinerVisionModule REQUIRED) # 定义 ZXing SDK 路径 set(ZXing_ROOT_PATH "${PROJECT_ROOT_PATH}/third_party/zxing-cpp-v2.2.1-lockzhiner-vision-module") set(ZXing_DIR "${ZXing_ROOT_PATH}/lib/cmake/ZXing") set(ZXing_INCLUDE_DIRS "${ZXing_ROOT_PATH}/include") find_package(ZXing REQUIRED) set(ZXing_LIBRARIES "${ZXing_LIBS}") # 基本图像处理示例 add_executable(test-bar-codeDetector test_bar_codeDetector.cc) target_include_directories(test-bar-codeDetector PRIVATE ${ZXing_INCLUDE_DIRS} ${LOCKZHINER_VISION_MODULE_INCLUDE_DIRS} ) target_link_libraries(test-bar-codeDetector PRIVATE ${OPENCV_LIBRARIES} ${LOCKZHINER_VISION_MODULE_LIBRARIES} ${ZXing_LIBRARIES} ) install( TARGETS test-bar-codeDetector RUNTIME DESTINATION . ) ``` ##### 4.7.4.3 编译项目 使用 Docker Destop 打开 LockzhinerVisionModule 容器并执行以下命令来编译项目 ```bash ## 进入Demo所在目录 cd /LockzhinerVisionModuleWorkSpace/LockzhinerVisionModule/Cpp_example/C07_test_bar_codeDetector ## 创建编译目录 rm -rf build && mkdir build && cd build ## 配置交叉编译工具链 export TOOLCHAIN_ROOT_PATH="/LockzhinerVisionModuleWorkSpace/arm-rockchip830-linux-uclibcgnueabihf" ## 使用cmake配置项目 cmake .. ## 执行编译项目 make -j8 && make install ``` 在执行完上述命令后,会在build目录下生成可执行文件。 #### 4.7.5 例程运行示例 ##### 4.7.5.1 运行前准备 - 请确保你已经参考 [凌智视觉模块摄像头部署指南](../../../periphery/capture/README.md) 正确下载了凌智视觉模块图片传输助手。 ##### 4.7.5.2 运行过程 在凌智视觉模块输入以下命令: ```shell chmod 777 test-bar-codeDetector ./test-bar-codeDetector ``` ##### 4.7.5.3 运行效果 ![title](./images/C07_Bar_Code_result.png) #### 4.7.6 总结 通过上述内容,我们成功的实现了一个条码识别系统,包括: - 获取并加载包含code 128格式的条码图像。 - 进行条码的检测和解码,返回检测和解码后的结果。 - 可视化包含条码图像的识别结果。 ## 5. 神经网络相关应用 ### 5.1 目标检测 本文档展示了如何使用 lockzhiner_vision_module::vision::PaddleDet 类进行目标检测,并通过 lockzhiner_vision_module::vision::Visualize 函数将检测结果可视化。 #### 5.1.1 基础知识讲解 ##### 5.1.1.1 目标检测的基本介绍 目标检测是计算机视觉领域中的一个关键任务,它不仅需要识别图像中存在哪些对象,还需要定位这些对象的位置。具体来说,目标检测算法会输出每个检测到的对象的边界框(Bounding Box)以及其所属类别的概率或置信度得分。 - 应用场景:目标检测技术广泛应用于多个领域,包括但不限于安全监控、自动驾驶汽车、智能零售和医疗影像分析。 ##### 5.1.1.2 PaddleDetection 的基本介绍 PaddleDetection 是基于百度飞桨深度学习框架开发的一个高效的目标检测库,支持多种先进的目标检测模型,如 YOLO 系列、SSD、Faster R-CNN、Mask R-CNN 等。它提供了简单易用的接口,使得开发者能够快速部署高性能的目标检测应用。 - 特点: - 高性能:优化了推理速度,在保持高精度的同时实现了快速响应。 - 灵活性:支持多种预训练模型,可以根据具体需求选择合适的模型架构。 - 易于集成:提供 C++ API,便于嵌入式系统或桌面应用程序中使用。 - 丰富的模型库:涵盖单阶段(One-stage)和双阶段(Two-stage)检测模型,满足不同场景的需求。 - 适用场景:适用于需要对视频流或图像进行实时分析的应用场景,例如安防监控、智能交通系统、工业自动化等。 #### 5.1.2 API 文档 ##### 5.1.2.1 PaddleDetection 类 ###### 5.1.2.1.1 头文件 ```cpp #include ``` ###### 5.1.2.1.2 构造函数 ```cpp lockzhiner_vision_module::vision::PaddleDetection(); ``` - 作用: - 创建一个 PaddleDetection 对象,并初始化相关成员变量。 - 参数: - 无 - 返回值: - 无 ###### 5.1.2.1.3 Initialize函数 ```cpp bool Initialize(const std::string& model_path); ``` - 作用: - 加载预训练的 PaddleDetection 模型。 - 参数: - model_path:模型路径,包含模型文件和参数文件。 - 返回值: - true:模型加载成功。 - false:模型加载失败。 ###### 5.1.2.1.4 SetThreshold函数 ```cpp void SetThreshold(float score_threshold = 0.5, float nms_threshold = 0.3); ``` - 作用: - 设置目标检测的置信度阈值和NMS阈值。 - 参数: - score_threshold:置信度阈值,默认值为0.5。 - nms_threshold:NMS阈值,默认值为0.3。 - 返回值: - 无 ###### 5.1.2.1.5 Predict函数 ```cpp std::vector Predict(const cv::Mat& image); ``` - 作用: - 使用加载的模型对输入图像进行目标检测,返回检测结果。 - 参数: - input_mat (const cv::Mat&): 输入的图像数据,通常是一个 cv::Mat 变量。 - 返回值: - 返回一个包含多个 DetectionResult 对象的向量,每个对象表示一个检测结果。 ##### 5.1.2.2 DetectionResult 类 ###### 5.1.2.2.1 头文件 ```cpp #include ``` ###### 5.1.2.2.2 box函数 ```cpp lockzhiner_vision_module::vision::Rect box() const; ``` - 作用: - 获取目标检测结果的边界框。 - 参数: - 无 - 返回值: - 返回一个 lockzhiner_vision_module::vision::Rect 对象,表示目标检测结果的边界框。 ###### 5.1.2.2.3 score函数 ```cpp float score() const; ``` - 作用: - 获取目标检测结果的置信度得分。 - 参数: - 无 - 返回值: - 返回一个 float 类型的置信度得分。 ###### 5.1.2.2.4 label_id函数 - 作用: - 获取目标检测结果的标签ID。 - 参数: - 无 - 返回值: - 返回一个整数,表示目标检测结果的标签ID。 ##### 5.1.2.3 Visualize 函数 ###### 5.1.2.3.1 头文件 ```cpp #include ``` ###### 5.1.2.3.2 函数定义 ```cpp void lockzhiner_vision_module::vision::Visualize( const cv::Mat& input_mat, cv::Mat& output_image, const std::vector& results, const std::vector& labels = {}, float font_scale = 0.4 ); ``` - 作用: - 将目标检测结果可视化到输入图像上,并返回可视化后的图像。 - 参数: - input_mat (const cv::Mat&): 输入图像。 - output_image (cv::Mat&): 输出图像,包含标注后的结果。 - results (const std::vector&): 检测结果列表。 - labels (const std::vector&): 可选的标签列表,用于标注类别名称,默认为空。 - font_scale (float): 字体大小比例,默认为 0.4。 - 返回值: - 无 #### 5.1.3 示例代码解析 ##### 5.1.3.1 流程图 ``` 开始 | |-- 检查参数个数是否为2 | |-- 不是 -> 输出 "Usage: Test-PaddleDet model_path" 并返回1 | |-- 初始化模型 | |-- 失败 -> 输出 "Failed to initialize model." 并返回1 | |-- 初始化编辑模块 | |-- 失败 -> 输出 "Error: Failed to start and accept connection." 并返回EXIT_FAILURE | |-- 成功 -> 输出 "Device connected successfully." | |-- 打开摄像头 | |-- 设置分辨率 (640x480) | |-- 打开摄像头失败 -> 输出 "Error: Could not open camera." 并返回1 | |-- 进入无限循环 | | | |-- 捕获一帧图像 | | |-- 图像为空 -> 输出 "Warning: Captured an empty frame." 并继续下一次循环 | | | |-- 调用模型进行预测 | | |-- 记录开始时间 | | |-- 获取预测结果 | | |-- 记录结束时间 | | | |-- 计算推理时间 | | |-- 输出 "Inference time: X ms" | | | |-- 可视化结果 | | |-- 创建输出图像 | | |-- 调用可视化函数 | | | |-- 打印输出图像 | |-- 释放摄像头资源 | |-- 程序正常退出 (返回0) ``` ##### 5.1.3.2 核心代码解析 - 初始化模型 ```cpp lockzhiner_vision_module::vision::PaddleDet model; if (!model.Initialize(argv[1])) { std::cout << "Failed to initialize model." << std::endl; return 1; } ``` - 模型推理 ```cpp auto results = model.Predict(input_mat); ``` - 可视化推理结果 ```cpp cv::Mat output_image; lockzhiner_vision_module::vision::Visualize(input_mat, output_image, results); edit.Print(output_image); ``` ##### 5.1.3.3 完整代码实现 ```cpp #include #include #include #include #include #include using namespace std::chrono; int main(int argc, char* argv[]) { if (argc != 2) { std::cerr << "Usage: Test-PaddleDet model_path" << std::endl; return 1; } // 初始化模型 lockzhiner_vision_module::vision::PaddleDet model; if (!model.Initialize(argv[1])) { std::cout << "Failed to initialize model." << std::endl; return 1; } lockzhiner_vision_module::edit::Edit edit; if (!edit.StartAndAcceptConnection()) { std::cerr << "Error: Failed to start and accept connection." << std::endl; return EXIT_FAILURE; } std::cout << "Device connected successfully." << std::endl; // 打开摄像头 cv::VideoCapture cap; cap.set(cv::CAP_PROP_FRAME_WIDTH, 640); cap.set(cv::CAP_PROP_FRAME_HEIGHT, 480); cap.open(0); if (!cap.isOpened()) { std::cerr << "Error: Could not open camera." << std::endl; return 1; } cv::Mat input_mat; while (true) { // 捕获一帧图像 cap >> input_mat; if (input_mat.empty()) { std::cerr << "Warning: Captured an empty frame." << std::endl; continue; } // 调用模型进行预测 high_resolution_clock::time_point start_time = high_resolution_clock::now(); auto results = model.Predict(input_mat); high_resolution_clock::time_point end_time = high_resolution_clock::now(); // 计算推理时间 auto time_span = duration_cast(end_time - start_time); std::cout << "Inference time: " << time_span.count() << " ms" << std::endl; // 可视化结果 cv::Mat output_image; lockzhiner_vision_module::vision::Visualize(input_mat, output_image, results); edit.Print(output_image); } cap.release(); return 0; } ``` #### 5.1.4 编译过程 ##### 5.1.4.1 编译环境搭建 - 请确保你已经按照 [开发环境搭建指南](https://gitee.com/LockzhinerAI/LockzhinerVisionModule/blob/master/docs/introductory_tutorial/cpp_development_environment.md) 正确配置了开发环境。 - 同时已正确连接开发板。 ##### 5.1.4.2 Cmake介绍 ```cmake cmake_minimum_required(VERSION 3.10) project(D01_test_detection) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # 定义项目根目录路径 set(PROJECT_ROOT_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../..") message("PROJECT_ROOT_PATH = " ${PROJECT_ROOT_PATH}) include("${PROJECT_ROOT_PATH}/toolchains/arm-rockchip830-linux-uclibcgnueabihf.toolchain.cmake") # 定义 OpenCV SDK 路径 set(OpenCV_ROOT_PATH "${PROJECT_ROOT_PATH}/third_party/opencv-mobile-4.10.0-lockzhiner-vision-module") set(OpenCV_DIR "${OpenCV_ROOT_PATH}/lib/cmake/opencv4") find_package(OpenCV REQUIRED) set(OPENCV_LIBRARIES "${OpenCV_LIBS}") # 定义 LockzhinerVisionModule SDK 路径 set(LockzhinerVisionModule_ROOT_PATH "${PROJECT_ROOT_PATH}/third_party/lockzhiner_vision_module_sdk") set(LockzhinerVisionModule_DIR "${LockzhinerVisionModule_ROOT_PATH}/lib/cmake/lockzhiner_vision_module") find_package(LockzhinerVisionModule REQUIRED) add_executable(Test-detection test_detection.cc) target_include_directories(Test-detection PRIVATE ${LOCKZHINER_VISION_MODULE_INCLUDE_DIRS}) target_link_libraries(Test-detection PRIVATE ${OPENCV_LIBRARIES} ${LOCKZHINER_VISION_MODULE_LIBRARIES}) install( TARGETS Test-detection RUNTIME DESTINATION . ) ``` ##### 5.1.4.3 编译项目 使用 Docker Destop 打开 LockzhinerVisionModule 容器并执行以下命令来编译项目 ```bash ## 进入Demo所在目录 cd /LockzhinerVisionModuleWorkSpace/LockzhinerVisionModule/Cpp_example/D01_test_detection ## 创建编译目录 rm -rf build && mkdir build && cd build ## 配置交叉编译工具链 export TOOLCHAIN_ROOT_PATH="/LockzhinerVisionModuleWorkSpace/arm-rockchip830-linux-uclibcgnueabihf" ## 使用cmake配置项目 cmake .. ## 执行编译项目 make -j8 && make install ``` 在执行完上述命令后,会在build目录下生成可执行文件。 #### 5.1.5 例程运行示例 ##### 5.1.5.1 运行 ```shell chmod 777 Test-detection ## 在实际应用的过程中LZ-Picodet需要替换为下载的或者你的rknn模型 ./Test-detection LZ-Picodet ``` ##### 5.1.5.2 结果展示 - 可以看到我们正确识别了绿色的方块,同时打印了标签和置信度。 ![](./images/D01.png) #### 5.1.6 总结 本文档详细介绍了目标检测的基础知识及 PaddleDetection 的基本概念,并提供了详细的API文档说明,帮助开发者理解和实现目标检测与可视化功能。通过上述流程,可以构建高效的实时目标检测系统,满足多种应用场景的需求。 ### 5.2 手写数字识别 手写数字识别是一种经典的模式识别和图像处理问题,旨在通过计算机自动识别用户手写的数字。本章节中,我们将教会你如何使用 Lockzhiner Vision Module 进行手写数字识别。 #### 5.2.1 基本知识讲解 ##### 5.2.1.1 手写数字识别简介 手写数字识别是一种利用计算机视觉和机器学习技术自动识别手写数字的过程。它通过图像预处理、特征提取和模型训练来实现高效准确的数字识别。被广泛应用于银行支票处理、邮政编码识别及考试评分等场景。这项技术不仅提高了数据处理的速度和准确性,还极大地简化了输入流程,为金融、邮政和教育等行业带来了显著的便利。 ##### 5.2.1.2 手写数字识别常用方法 目前,实现手写数字识别方法有很多,常用的方法如下: - 卷积神经网络(CNN):最流行的方法之一,能够自动从图像中学习特征。适用于复杂背景和不同书写风格的手写数字识别。 - 支持向量机(SVM):一种传统的机器学习方法,通过提取图像的特征(如HOG特征)进行分类,适合处理较为规范的手写数字。 - K近邻算法(KNN):基于相似度的分类方法,通过比较待识别数字与训练样本的距离来进行分类,简单但计算成本较高。 #### 5.2.2 API 文档 ##### 5.2.2.1 PaddleClas类 ###### 5.2.2.1.1 头文件 ```c++ #include ``` - 作用:用于声明PaddleClas类,使得PaddleClas类可以在当前源文件中使用。 ###### 5.2.2.1.2 构造类函数 ```c++ lockzhiner_vision_module::vision::PaddleClas model; ``` - 作用:用于实现手写数字识别。 - 参数说明: - 无 - 返回值: - 无 ###### 5.2.2.1.3 Predict函数 ```c++ auto result = model.Predict(input_mat); ``` - 作用:PaddleClas类中的一个函数,用于实现手写数字识别。 - 参数说明: - input_mat:要识别的图像。 - 返回值: - 返回一个包含手写数字分类结果的对象。该Result对象包含预测得分(score)和对应的标签ID(label_id),即识别出的手写数字。 ##### 5.2.2.2 Visualize函数 ###### 5.2.2.2.1 头文件 ```c++ #include ``` - 作用:用于声明Visualize函数,使得Visualize函数可以在当前源文件中使用。 ###### 5.2.2.2.2 结果可视化 ```c++ lockzhiner_vision_module::vision::Visualize(input_mat, output_image, result); ``` - 参数说明: - input_mat:表示原始输入图像。 - output_image:用于存储带有可视化结果的输出图像。 - result:输入参数,表示手写数字识别的结果。该Result对象包含预测得分(score)和对应的标签ID(label_id)。 - 返回值: - 无 #### 5.2.3 综合代码解析 ##### 5.2.3.1 流程图 ``` 开始 | |--- 初始化 PaddleClas 模型对象 model | |--- 判断参数数量 | |--- 参数数量不等于3 -> 输出使用说明并退出(返回1) | |--- 使用第一个参数初始化 model | |--- 成功 -> 继续执行 | |--- 失败 -> 输出 "Failed to initialize model" 并退出(返回1) | |--- 判断第二个参数 | |--- 第二个参数是 "Capture" | | |--- 调用 TestCapture() 函数 | | | |--- 初始化 Edit 模块 | | | | |--- 成功 -> 继续执行 | | | | |--- 失败 -> 输出错误信息并退出(返回EXIT_FAILURE) | | | |--- 打开摄像头 | | | | |--- 成功 -> 继续执行 | | | | |--- 失败 -> 输出错误信息并退出(返回EXIT_FAILURE) | | | |--- 进入循环读取帧 | | | | |--- 获取当前时间点作为开始时间 | | | | |--- 读取帧 | | | | | |--- 成功 -> 继续执行 | | | | | |--- 失败 -> 跳过本次循环 | | | | |--- 使用 model 对帧进行预测 | | | | |--- 获取当前时间点作为结束时间 | | | | |--- 计算并累加处理时间 | | | | |--- 输出预测结果 | | | | |--- 可视化结果并使用 edit 模块处理帧 | | | | |--- 输出帧率 | | | |--- 释放摄像头资源 | | | |--- 退出循环(返回0) | |--- 否则,调用 TestImage() 函数 | | |--- 读取图像文件 | | |--- 使用 model 对图像进行预测 | | |--- 输出预测结果 | | |--- 可视化结果并保存到文件 | | |--- 退出函数(返回0) | |--- 结束 |--- 程序正常退出(返回0) ``` ##### 5.2.3.2 核心代码解析 - 初始化分类模型 ```cpp lockzhiner_vision_module::vision::PaddleClas model; ``` 自定义函数参数如下 - 图片手写数字识别 ```c++ int TestImage(const std::string& image_path) ``` - 参数说明: - image_path:输入参数,表示包含手写数字的图像文件路径。 - 返回值: - 返回0表示成功执行,并保存执行结果为"cls_result.png"。 - 摄像头实时手写数字识别 ```c++ int TestCapture() ``` - 参数说明: - 无 - 返回值: - 返回0表示执行成功,并将检测结果绘制在原始图像上。程序会持续从摄像头读取帧并进行处理,直到手动终止程序。 ##### 5.2.3.3 完整代码实现 ```c++ #include #include #include #include #include #include #include #include using namespace std::chrono; lockzhiner_vision_module::vision::PaddleClas model; int TestCapture() { // 初始化 edit 模块 lockzhiner_vision_module::edit::Edit edit; if (!edit.StartAndAcceptConnection()) { std::cerr << "Error: Failed to start and accept connection." << std::endl; return EXIT_FAILURE; } std::cout << "Device connected successfully." << std::endl; cv::VideoCapture cap; // 设置摄像头长宽 cap.set(cv::CAP_PROP_FRAME_WIDTH, 640); cap.set(cv::CAP_PROP_FRAME_HEIGHT, 480); cap.open(0); if (!cap.isOpened()) { std::cerr << "Error: Could not open camera." << std::endl; return EXIT_FAILURE; } cv::Mat input_mat; while (true) { int read_index = 0; int time_ms = 0; for (int i = 0; i < 30; i++) { // 获取当前时间点作为开始时间 high_resolution_clock::time_point start_time = high_resolution_clock::now(); cap >> input_mat; if (input_mat.empty()) { continue; } // 使用 model 对象的 Predict 方法对输入图像进行预测 auto result = model.Predict(input_mat); // 获取当前时间点作为结束时间 high_resolution_clock::time_point end_time = high_resolution_clock::now(); auto time_span = duration_cast(end_time - start_time); time_ms += time_span.count(); read_index += 1; std::cout << "score is " << result.score << ";label_id is " << result.label_id << std::endl; cv::Mat output_image; lockzhiner_vision_module::vision::Visualize(input_mat, output_image, result); // 使用 edit 模块处理帧 edit.Print(output_image); } std::cout << "Frames per second: " << 1000.0 / time_ms * read_index << std::endl; } cap.release(); return 0; } int TestImage(const std::string &image_path) { cv::Mat input_image = cv::imread(image_path); auto result = model.Predict(input_image); std::cout << "score is " << result.score << ";label_id is " << result.label_id << std::endl; cv::Mat output_image; lockzhiner_vision_module::vision::Visualize(input_image, output_image, result); cv::imwrite("cls_result.png", output_image); return 0; } int main(int argc, char *argv[]) { if (argc != 3) { std::cerr << "Usage: Test-PaddleClas model_path " << std::endl; return 1; } if (!model.Initialize(argv[1])) { std::cout << "Failed to initialize model." << std::endl; return 1; } std::string argument(argv[2]); if (argument == "Capture") { return TestCapture(); } else { return TestImage(argument); } return 0; } ``` #### 5.2.4 编译调试 ##### 5.2.4.1 编译环境搭建 - 请确保你已经按照 [开发环境搭建指南](https://gitee.com/LockzhinerAI/LockzhinerVisionModule/blob/master/docs/introductory_tutorial/cpp_development_environment.md) 正确配置了开发环境。 - 同时已经正确连接开发板。 ##### 5.2.4.2 Cmake介绍 ```cmake cmake_minimum_required(VERSION 3.10) project(test_DigitHandRecog) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # 定义项目根目录路径 set(PROJECT_ROOT_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../..") message("PROJECT_ROOT_PATH = " ${PROJECT_ROOT_PATH}) include("${PROJECT_ROOT_PATH}/toolchains/arm-rockchip830-linux-uclibcgnueabihf.toolchain.cmake") # 定义 OpenCV SDK 路径 set(OpenCV_ROOT_PATH "${PROJECT_ROOT_PATH}/third_party/opencv-mobile-4.10.0-lockzhiner-vision-module") set(OpenCV_DIR "${OpenCV_ROOT_PATH}/lib/cmake/opencv4") find_package(OpenCV REQUIRED) set(OPENCV_LIBRARIES "${OpenCV_LIBS}") # 定义 LockzhinerVisionModule SDK 路径 set(LockzhinerVisionModule_ROOT_PATH "${PROJECT_ROOT_PATH}/third_party/lockzhiner_vision_module_sdk") set(LockzhinerVisionModule_DIR "${LockzhinerVisionModule_ROOT_PATH}/lib/cmake/lockzhiner_vision_module") find_package(LockzhinerVisionModule REQUIRED) add_executable(Test-DigitHandRecog DigitHandRecog.cc) target_include_directories(Test-DigitHandRecog PRIVATE ${LOCKZHINER_VISION_MODULE_INCLUDE_DIRS}) target_link_libraries(Test-DigitHandRecog PRIVATE ${OPENCV_LIBRARIES} ${LOCKZHINER_VISION_MODULE_LIBRARIES}) install( TARGETS Test-DigitHandRecog RUNTIME DESTINATION . ) ``` ##### 5.2.4.3 编译项目 使用 Docker Destop 打开 LockzhinerVisionModule 容器并执行以下命令来编译项目 ```bash # 进入Demo所在目录 cd /LockzhinerVisionModuleWorkSpace/LockzhinerVisionModule/Cpp_example/D02_DigitHandRecog # 创建编译目录 rm -rf build && mkdir build && cd build # 配置交叉编译工具链 export TOOLCHAIN_ROOT_PATH="/LockzhinerVisionModuleWorkSpace/arm-rockchip830-linux-uclibcgnueabihf" # 使用cmake配置项目 cmake .. # 执行编译项目 make -j8 && make install ``` 在执行完上述命令后,会在build目录下生成可执行文件。 #### 5.2.5 例程运行示例 ##### 5.2.5.1 运行前准备 - 请确保你已经下载了 [凌智视觉模块手写数字分类模型](https://gitee.com/LockzhinerAI/LockzhinerVisionModule/releases/download/v0.0.0/LZ-DigitHandRecog.rknn) ##### 5.2.5.2 运行过程 在凌智视觉模块输入以下命令: ```shell chmod 777 Test-DigitHandRecog ## 调用摄像头实时识别 ./Test-DigitHandRecog LZ-DigitHandRecog.rknn Capture ## 单张图像识别 ./Test-DigitHandRecog LZ-DigitHandRecog.rknn image_path ``` ##### 5.2.5.3 运行效果 - 图像识别效果图 ![title](./images/D02_cls_result.png) - 摄像头实时识别效果图 ![title](./images/D02_Capture_7.png) #### 5.2.6 总结 通过上述内容,我们成功的实现了一个手写数字识别系统,包括: - 获取并加载包含手写数字的图像。 - 进行手写数字的检测和分类,返回检测和分类后的结果。 - 可视化包含手写数字图像的识别结果。 ### 5.3 人脸识别系统 本章节基于 Lockzhiner Vision Module 的 LZ-Picodet 模型训练的人脸检测模型 LZ-Face,以及ArcFace人脸识别模型,实现了一个人脸识别系统。 #### 5.3.1 基本知识讲解 ##### 5.3.1.1 人脸识别简介 人脸识别是一种利用人的脸部特征进行身份识别的生物识别技术。它通过检测图像或视频中的人脸,提取如眼睛、鼻子和嘴巴等关键特征点,并将这些信息转化为面部特征向量,进而与已知人脸数据库中的数据比对来确认个人身份。被广泛应用于安全监控、门禁系统、移动设备解锁及社交媒体等领域。 ##### 5.3.1.2 人脸识别常用方法 人脸识别主要涉及到以下几个关键步骤:人脸检测、特征提取和匹配识别。以下是实现人脸识别的常用方法: - 深度学习方法:现代的人脸识别系统大多采用深度学习方法,并结合大规模人脸数据库和高性能计算资源,实现了非常高的识别精度。 - 基于模板匹配的方法:通过将待识别人脸与预定义的标准人脸模板进行比较来实现识别。 #### 5.3.2 API 文档 ##### 5.3.2.1 FaceRecognitionSystem类 ###### 5.3.2.1.1 头文件 ```c++ #include ``` - 作用:用于声明FaceRecognitionSystem类,使得FaceRecognitionSystem类可以在当前文件中使用。 ###### 5.3.2.1.2 构造类函数 ```c++ lockzhiner_vision_module::vision::FaceRecognitionSystem face_system; ``` - 作用:用于实现人脸识别。 - 参数说明: - 无 - 返回值: - 无 ###### 5.3.2.1.3 Predict函数 ```c++ auto result = face_system.Predict(input_mat); ``` - 作用:FaceRecognitionSystem类中的一个函数,用于实现人脸识别。 - 参数说明: - input_mat:要识别的图像。 - 返回值: - 返回一个包含人脸识别结果的对象。该对象包含人脸的id,置信度和人脸的位置信息。 ##### 5.3.2.2 Visualize函数 ###### 5.3.2.2.1 头文件 ```c++ #include ``` - 作用:用于声明Visualize函数,使得Visualize函数可以在当前源文件中使用。 ###### 5.3.2.2.2 结果可视化 ```c++ lockzhiner_vision_module::vision::Visualize(input_mat, output_image, result); ``` - 参数说明: - input_mat:原始输入图像。 - output_image:用于存储带有可视化结果的输出图像。 - result:输入参数,表示人脸识别的结果。该result对象包含人脸的id,置信度和人脸的位置信息。 - 返回值: - 无 #### 5.3.3 综合代码解析 ##### 5.3.3.1 流程图 ``` 开始 | |--- 初始化 FaceRecognitionSystem 模型对象 face_system | |--- 判断参数数量 | |--- 参数数量不等于5 -> 输出使用说明并退出(返回1) | |--- 使用第一个和第二个参数初始化 face_system | |--- 成功 -> 继续执行 | |--- 失败 -> 输出 "Failed to initialize face system" 并退出(返回1) | |--- 使用第三个和第四个参数构建数据库 | |--- 成功 -> 继续执行 | |--- 失败 -> 输出 "Failed to build database" 并退出(返回1) | |--- 初始化 Edit 模块 | |--- 成功 -> 继续执行 | |--- 失败 -> 输出错误信息并退出(返回EXIT_FAILURE) | |--- 打开摄像头 | |--- 成功 -> 继续执行 | |--- 失败 -> 输出错误信息并退出(返回EXIT_FAILURE) | |--- 进入循环读取帧 | |--- 获取当前时间点作为开始时间 | |--- 读取帧 | | |--- 成功 -> 继续执行 | | |--- 失败 -> 跳过本次循环 | |--- 使用 face_system 对帧进行预测 | |--- 获取当前时间点作为结束时间 | |--- 计算并累加处理时间 | |--- 可视化结果并使用 edit 模块处理帧 | |--- 输出帧率 | |--- 释放摄像头资源 |--- 程序正常退出(返回0) ``` ##### 5.3.3.2 核心代码解析 - 初始化人脸识别模型 ```cpp lockzhiner_vision_module::vision::FaceRecognitionSystem face_system; ``` - 构建人脸数据库 ```cpp if (!face_system.BuildDatabase(argv[3], argv[4])) { std::cout << "Failed to build database." << std::endl; return 1; } ``` - 调用摄像头捕获图像 ```c++ cv::VideoCapture cap; // 设置摄像头获取帧的宽高 cap.set(cv::CAP_PROP_FRAME_WIDTH, 640); cap.set(cv::CAP_PROP_FRAME_HEIGHT, 480); cap.open(0); // wihile循环中的以下代码用于捕获图像帧 cap >> input_mat; if (input_mat.empty()) { continue; } ``` - 模型推理 ```cpp auto result = face_system.Predict(input_mat); ``` ##### 5.3.3.3 完整代码实现 ```c++ #include #include #include #include #include #include #include #include using namespace std::chrono; lockzhiner_vision_module::vision::FaceRecognitionSystem face_system; int main(int argc, char *argv[]) { if (argc != 5) { std::cerr << "Usage: Test-Face-Recognition-System det_model_path " "rec_model_path database_root crop_root" << std::endl; return 1; } if (!face_system.Initialize(argv[1], argv[2])) { std::cout << "Failed to initialize face system." << std::endl; return 1; } if (!face_system.BuildDatabase(argv[3], argv[4])) { std::cout << "Failed to build database." << std::endl; return 1; } // 初始化 edit 模块 lockzhiner_vision_module::edit::Edit edit; if (!edit.StartAndAcceptConnection()) { std::cerr << "Error: Failed to start and accept connection." << std::endl; return EXIT_FAILURE; } std::cout << "Device connected successfully." << std::endl; cv::VideoCapture cap; // 设置摄像头捕获帧的宽高 cap.set(cv::CAP_PROP_FRAME_WIDTH, 640); cap.set(cv::CAP_PROP_FRAME_HEIGHT, 480); cap.open(0); if (!cap.isOpened()) { std::cerr << "Error: Could not open camera." << std::endl; return EXIT_FAILURE; } cv::Mat input_mat; while (true) { int read_index = 0; int time_ms = 0; for (int i = 0; i < 30; i++) { // 获取当前时间点作为开始时间 high_resolution_clock::time_point start_time = high_resolution_clock::now(); cap >> input_mat; if (input_mat.empty()) { continue; } // 使用 model 对象的 Predict 方法对输入图像进行预测 auto result = face_system.Predict(input_mat); // 获取当前时间点作为结束时间 high_resolution_clock::time_point end_time = high_resolution_clock::now(); auto time_span = duration_cast(end_time - start_time); time_ms += time_span.count(); read_index += 1; cv::Mat output_image; lockzhiner_vision_module::vision::Visualize(input_mat, output_image, result); // 使用 edit 模块处理帧 edit.Print(output_image); } std::cout << "Frames per second: " << 1000.0 / time_ms * read_index << std::endl; } // 释放摄像头资源 cap.release(); return 0; } ``` #### 5.3.4 编译调试 ##### 5.3.4.1 编译环境搭建 - 请确保你已经按照 [开发环境搭建指南](https://gitee.com/LockzhinerAI/LockzhinerVisionModule/blob/master/docs/introductory_tutorial/cpp_development_environment.md) 正确配置了开发环境。 - 同时已经正确连接开发板。 ##### 5.3.4.2 Cmake介绍 ```cmake cmake_minimum_required(VERSION 3.10) project(D03_face_recognition_system) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) ## 定义项目根目录路径 set(PROJECT_ROOT_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../..") message("PROJECT_ROOT_PATH = " ${PROJECT_ROOT_PATH}) include("${PROJECT_ROOT_PATH}/toolchains/arm-rockchip830-linux-uclibcgnueabihf.toolchain.cmake") ## 定义 OpenCV SDK 路径 set(OpenCV_ROOT_PATH "${PROJECT_ROOT_PATH}/third_party/opencv-mobile-4.10.0-lockzhiner-vision-module") set(OpenCV_DIR "${OpenCV_ROOT_PATH}/lib/cmake/opencv4") find_package(OpenCV REQUIRED) set(OPENCV_LIBRARIES "${OpenCV_LIBS}") ## 定义 LockzhinerVisionModule SDK 路径 set(LockzhinerVisionModule_ROOT_PATH "${PROJECT_ROOT_PATH}/third_party/lockzhiner_vision_module_sdk") set(LockzhinerVisionModule_DIR "${LockzhinerVisionModule_ROOT_PATH}/lib/cmake/lockzhiner_vision_module") find_package(LockzhinerVisionModule REQUIRED) add_executable(Test-face-recognition-system face_recognition_system.cc) target_include_directories(Test-face-recognition-system PRIVATE ${LOCKZHINER_VISION_MODULE_INCLUDE_DIRS}) target_link_libraries(Test-face-recognition-system PRIVATE ${OPENCV_LIBRARIES} ${LOCKZHINER_VISION_MODULE_LIBRARIES}) install( TARGETS Test-face-recognition-system RUNTIME DESTINATION . ) ``` ##### 5.3.4.3 编译项目 使用 Docker Destop 打开 LockzhinerVisionModule 容器并执行以下命令来编译项目 ```bash ## 进入Demo所在目录 cd /LockzhinerVisionModuleWorkSpace/LockzhinerVisionModule/Cpp_example/D03_face_recognition_system ## 创建编译目录 rm -rf build && mkdir build && cd build ## 配置交叉编译工具链 export TOOLCHAIN_ROOT_PATH="/LockzhinerVisionModuleWorkSpace/arm-rockchip830-linux-uclibcgnueabihf" ## 使用cmake配置项目 cmake .. ## 执行编译项目 make -j8 && make install ``` 在执行完上述命令后,会在build目录下生成可执行文件。 #### 5.3.5. 例程运行示例 ##### 5.3.5.1 运行前准备 - 请确保你已经下载了 [凌智视觉模块人脸检测模型](https://gitee.com/LockzhinerAI/LockzhinerVisionModule/releases/download/v0.0.3/LZ-Face.rknn) - 请确保你已经下载了 [凌智视觉模块人脸识别模型](https://gitee.com/LockzhinerAI/LockzhinerVisionModule/releases/download/v0.0.0/LZ-ArcFace.rknn) ##### 5.3.5.2 运行过程 在凌智视觉模块输入以下命令: ```shell chmod 777 Test-face-recognition-system ./Test-face-recognition-system LZ-Face LZ-ArcFace BaseDataset CropDataset ``` ##### 5.3.5.3 运行效果 ![title](./images/D03_result.png) ##### 5.3.5.4 注意事项 上面提到的BaseDataset和CropDataset需要提前创建。BaseDataset用于存储已有的人脸图像,同一个人的人脸图像保存在BaseDataset的一个子文件夹下。CropDataset文件夹创建时为空,用于保存裁剪后的人脸图像,目录结构和BaseDataset相同。 #### 5.3.6 总结 通过上述内容,我们成功实现了一个高效的人脸识别系统,包括: - 获取并加载包含人脸的图像。 - 进行人脸检测和识别。 - 可视化人脸识别结果。 ### 5.4 测距 本系统基于单目摄像头和目标检测模型,通过目标的真实物理尺寸和其在图像中的像素大小之间的关系,计算目标与摄像头之间的距离。该方案适用于嵌入式设备或高性能计算平台,并支持实时测距和可视化显示。 #### 5.4.1 基础知识 ##### 5.4.1.1 测距原理 利用单目摄像头成像几何关系,测距公式如下: $ 距离 = \frac{真实物体大小 \times 相机焦距}{目标在图像中的像素大小} $ - 真实物体大小:目标的实际物理尺寸(单位:米)。 - 相机焦距:需要通过标定获得,或者通过实验调整得到合适的值。 - 目标在图像中的像素大小:通过目标检测模型获取目标的边界框宽度和高度,并取其平均值作为目标的像素大小。 ##### 5.4.1.2 实现步骤 在实现测距系统的过程中,最重要的一步就是先检测到目标,通过判断目标的像素和实际大小的一个转换关系,从而得到目标与摄像头之间的距离。在本次实验中,我们就综合目标检测来做测距实验。 - 初始化目标检测模型: - 加载预训练的目标检测模型(如 PaddleDet)。 - 初始化外部设备连接(如串口通信模块)。 - 捕获视频流: - 打开摄像头,设置分辨率(如 640x480)。 - 捕获每一帧图像。 - 目标检测与测距: - 使用目标检测模型对每一帧图像进行预测,获取目标的边界框信息。 - 根据边界框的宽度和高度计算目标的像素大小。 - 使用测距公式计算目标与摄像头的距离。 - 结果可视化: - 在图像上绘制目标的边界框。 - 将计算出的距离信息标注在图像上。 - 将处理后的图像发送到外部设备显示。 - 循环运行: - 实时处理每一帧图像,直到用户退出程序。 #### 5.4.2 API 文档 ##### 5.4.2.1 PaddleDetection 类 ###### 5.4.2.1.1 头文件 ```cpp #include ``` ###### 5.4.2.1.2 构造函数 ```cpp lockzhiner_vision_module::vision::PaddleDetection(); ``` - 作用: - 创建一个 PaddleDetection 对象,并初始化相关成员变量。 - 参数: - 无 - 返回值: - 无 ###### 5.4.2.1.3 Initialize函数 ```cpp bool Initialize(const std::string& model_path); ``` - 作用: - 加载预训练的 PaddleDetection 模型。 - 参数: - model_path:模型路径,包含模型文件和参数文件。 - 返回值: - true:模型加载成功。 - false:模型加载失败。 ###### 5.4.2.1.4 SetThreshold函数 ```cpp void SetThreshold(float score_threshold = 0.5, float nms_threshold = 0.3); ``` - 作用: - 设置目标检测的置信度阈值和NMS阈值。 - 参数: - score_threshold:置信度阈值,默认值为0.5。 - nms_threshold:NMS阈值,默认值为0.3。 - 返回值: - 无 ###### 5.4.2.1.5 Predict函数 ```cpp std::vector Predict(const cv::Mat& image); ``` - 作用: - 使用加载的模型对输入图像进行目标检测,返回检测结果。 - 参数: - input_mat (const cv::Mat&): 输入的图像数据,通常是一个 cv::Mat 变量。 - 返回值: - 返回一个包含多个 DetectionResult 对象的向量,每个对象表示一个检测结果。 ##### 5.4.2.2 DetectionResult 类 ###### 5.4.2.2.1 头文件 ```cpp #include ``` ###### 5.4.2.2.2 box函数 ```cpp lockzhiner_vision_module::vision::Rect box() const; ``` - 作用: - 获取目标检测结果的边界框。 - 参数: - 无 - 返回值: - 返回一个 lockzhiner_vision_module::vision::Rect 对象,表示目标检测结果的边界框。 ###### 5.4.2.2.3 score函数 ```cpp float score() const; ``` - 作用: - 获取目标检测结果的置信度得分。 - 参数: - 无 - 返回值: - 返回一个 float 类型的置信度得分。 ###### 5.4.2.2.4 label_id函数 - 作用: - 获取目标检测结果的标签ID。 - 参数: - 无 - 返回值: - 返回一个整数,表示目标检测结果的标签ID。 ##### 5.4.2.3 Visualize 函数 ```cpp void lockzhiner_vision_module::vision::Visualize( const cv::Mat& input_mat, cv::Mat& output_image, const std::vector& results, const std::vector& labels = {}, float font_scale = 0.4 ); ``` - 作用: - 将目标检测结果可视化到输入图像上,并返回可视化后的图像。 - 参数: - input_mat (const cv::Mat&): 输入图像。 - output_image (cv::Mat&): 输出图像,包含标注后的结果。 - results (const std::vector&): 检测结果列表。 - labels (const std::vector&): 可选的标签列表,用于标注类别名称,默认为空。 - font_scale (float): 字体大小比例,默认为 0.4。 - 返回值: - 无 #### 5.4.3 综合代码解析 ##### 5.4.3.1 流程图 ``` 开始 | |--- 初始化 PaddleDet 模型对象 model | |--- 判断参数数量 | |--- 参数数量不等于2 -> 输出使用说明并退出(返回1) | |--- 使用第一个参数初始化 model | |--- 成功 -> 继续执行 | |--- 失败 -> 输出 "Failed to initialize model" 并退出(返回1) | |--- 初始化 Edit 模块 | |--- 成功 -> 继续执行 | |--- 失败 -> 输出错误信息并退出(返回EXIT_FAILURE) | |--- 打开摄像头 | |--- 设置摄像头捕获帧的宽高为640x480 | |--- 打开摄像头 | |--- 成功 -> 继续执行 | |--- 失败 -> 输出错误信息并退出(返回1) | |--- 进入循环读取帧 | |--- 从摄像头读取帧 | | |--- 帧为空 -> 输出警告信息并跳过本次循环 | | |--- 否则 -> 继续执行 | |--- 预测并可视化结果 | | |--- 获取当前时间作为开始时间 | | |--- 对输入图像进行预测 | | |--- 获取当前时间作为结束时间 | | |--- 计算并输出推理时间 | | |--- 可视化预测结果 | |--- 在每个检测框上绘制距离信息 | | |--- 计算像素尺寸和距离 | | |--- 格式化距离文本 | | |--- 在检测框左上方绘制距离 | |--- 显示结果到外部设备 | |--- 释放摄像头资源 |--- 程序正常退出(返回0) ``` ##### 5.4.3.2 核心代码解析 - 初始化模型 ```cpp lockzhiner_vision_module::vision::PaddleDet model; if (!model.Initialize(argv[1])) { std::cout << "Failed to initialize model." << std::endl; return 1; } ``` - 模型推理 ```cpp auto results = model.Predict(input_mat); ``` - 计算物体距离 ```cpp for (size_t i = 0; i < results.size(); ++i) { int width = results[i].box.width; int height = results[i].box.height; // 计算目标的平均像素大小 float pixel_size = (width + height) / 2.0f; // 计算距离 float distance = CalculateDistance(pixel_size, REAL_OBJECT_SIZE, FOCAL_LENGTH); // 将距离信息存储到 label_id 中(需要转换为整数) results[i].label_id = static_cast(distance * 100); // 单位:厘米 } ``` CalculateDistance函数具体参数定义如下所示: ```c++ float CalculateDistance(float pixel_size, float real_size, float focal_length) ``` - 参数: - pixel_size:目标在图像中的像素大小,单位为像素。 - real_size:目标的实际物理尺寸,单位为米。 - focal_length:相机焦距,单位为像素。 - 返回值:目标与摄像头之间的距离,单位为米。 ##### 5.4.3.3 完整代码实现 ```c++ #include #include #include #include #include #include #include #include #include #include const float REAL_OBJECT_SIZE = 0.02f; const float FOCAL_LENGTH = 800.0f; float CalculateDistance(float pixel_size, float real_size, float focal_length) { return (real_size * focal_length) / pixel_size; } int main(int argc, char *argv[]) { if (argc != 2) { std::cerr << "Usage: Test-PaddleDet model_path" << std::endl; return 1; } // 初始化模型和设备连接 lockzhiner_vision_module::vision::PaddleDet model; if (!model.Initialize(argv[1])) { std::cout << "Failed to initialize model." << std::endl; return 1; } lockzhiner_vision_module::edit::Edit edit; if (!edit.StartAndAcceptConnection()) { std::cerr << "Error: Failed to start and accept connection." << std::endl; return EXIT_FAILURE; } cv::VideoCapture cap; cap.set(cv::CAP_PROP_FRAME_WIDTH, 640); cap.set(cv::CAP_PROP_FRAME_HEIGHT, 480); cap.open(0); if (!cap.isOpened()) { std::cerr << "Error: Could not open camera." << std::endl; return 1; } cv::Mat input_mat; while (true) { cap >> input_mat; if (input_mat.empty()) { std::cerr << "Warning: Captured an empty frame." << std::endl; continue; } // 预测并可视化结果 auto start_time = std::chrono::high_resolution_clock::now(); auto results = model.Predict(input_mat); auto end_time = std::chrono::high_resolution_clock::now(); auto time_span = std::chrono::duration_cast(end_time - start_time); std::cout << "Inference time: " << time_span.count() << " ms" << std::endl; cv::Mat output_image; lockzhiner_vision_module::vision::Visualize(input_mat, output_image, results); // 在每个检测框上绘制距离信息 for (size_t i = 0; i < results.size(); ++i) { int width = results[i].box.width; int height = results[i].box.height; float pixel_size = (width + height) / 2.0f; float distance = CalculateDistance(pixel_size, REAL_OBJECT_SIZE, FOCAL_LENGTH); // 格式化距离文本(保留两位小数) std::stringstream ss; ss << std::fixed << std::setprecision(2) << distance << " m"; std::string distance_text = ss.str(); // 在检测框左上方绘制距离 cv::Rect box = results[i].box; cv::putText( output_image, distance_text, cv::Point(box.x, box.y - 25), // 在框上方5像素处显示 cv::FONT_HERSHEY_SIMPLEX, 0.5, // 字体大小 cv::Scalar(0, 255, 0), // 绿色文本 2 // 线宽 ); } // 显示结果到外部设备 edit.Print(output_image); } cap.release(); return 0; } ``` #### 5.4.4 编译过程 ##### 5.4.4.1 编译环境搭建 - 请确保你已经按照 [开发环境搭建指南](https://gitee.com/LockzhinerAI/LockzhinerVisionModule/blob/master/docs/introductory_tutorial/cpp_development_environment.md) 正确配置了开发环境。 - 同时以正确连接开发板。 ##### 5.4.4.2 Cmake介绍 ```cmake cmake_minimum_required(VERSION 3.10) project(test_distance) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) ## 定义项目根目录路径 set(PROJECT_ROOT_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../..") message("PROJECT_ROOT_PATH = " ${PROJECT_ROOT_PATH}) include("${PROJECT_ROOT_PATH}/toolchains/arm-rockchip830-linux-uclibcgnueabihf.toolchain.cmake") ## 定义 OpenCV SDK 路径 set(OpenCV_ROOT_PATH "${PROJECT_ROOT_PATH}/third_party/opencv-mobile-4.10.0-lockzhiner-vision-module") set(OpenCV_DIR "${OpenCV_ROOT_PATH}/lib/cmake/opencv4") find_package(OpenCV REQUIRED) set(OPENCV_LIBRARIES "${OpenCV_LIBS}") ## 定义 LockzhinerVisionModule SDK 路径 set(LockzhinerVisionModule_ROOT_PATH "${PROJECT_ROOT_PATH}/third_party/lockzhiner_vision_module_sdk") set(LockzhinerVisionModule_DIR "${LockzhinerVisionModule_ROOT_PATH}/lib/cmake/lockzhiner_vision_module") find_package(LockzhinerVisionModule REQUIRED) add_executable(Test-distance distance.cc) target_include_directories(Test-distance PRIVATE ${LOCKZHINER_VISION_MODULE_INCLUDE_DIRS}) target_link_libraries(Test-distance PRIVATE ${OPENCV_LIBRARIES} ${LOCKZHINER_VISION_MODULE_LIBRARIES}) install( TARGETS Test-distance RUNTIME DESTINATION . ) ``` ##### 5.4.4.3 编译项目 使用 Docker Destop 打开 LockzhinerVisionModule 容器并执行以下命令来编译项目 ```bash ## 进入Demo所在目录 cd /LockzhinerVisionModuleWorkSpace/LockzhinerVisionModule/Cpp_example/D04_distance ## 创建编译目录 rm -rf build && mkdir build && cd build ## 配置交叉编译工具链 export TOOLCHAIN_ROOT_PATH="/LockzhinerVisionModuleWorkSpace/arm-rockchip830-linux-uclibcgnueabihf" ## 使用cmake配置项目 cmake .. ## 执行编译项目 make -j8 && make install ``` 在执行完上述命令后,会在build目录下生成可执行文件。 #### 5.4.5 例程运行示例 ##### 5.4.5.1 运行 ```shell chmod 777 Test-distance ## 实际使用过程中 LZ-Picodet 需要替换为你需要的rknn模型。 ./Test-distance LZ-Picodet ``` ##### 5.4.5.2 结果展示 - 我们可以看见,在label_id中显示了实时距离,同时正确识别了绿色方块。 ![](./images/D04.png) #### 5.4.6 总结 本系统通过结合目标检测模型和测距公式,实现了基于单目摄像头的实时测距功能。但是需要注意的是单目摄像头的测距只是大致测距,收到环境和检测质量的影响非常大,同时误差也几乎是不可控的。如有精确的测距需求,建议采用双目摄像头或其他测距方案实现。 ### 5.5 OCR 文字识别 本章节在 Lockzhiner Vision Module 上基于OcrLiteNcnn模型, 实现了一个OCR文字识别系统。 #### 5.5.1 基本知识讲解 ##### 5.5.1.1 文字识别简介 OCR(光学字符识别)是指通过电子设备读取并转换纸质文档或图像中的文字为可编辑和处理的数字文本的技术。它涉及图像预处理、字符分割、特征提取、字符识别及后处理等步骤,以实现高准确度的文字转换。OCR技术极大提升了信息数字化的效率,广泛应用于数字化图书馆、自动化数据录入、车牌识别系统及辅助阅读工具等领域,是现代办公与生活中不可或缺的一部分。 ##### 5.5.1.2 文字识别常用方法 - 模板匹配:通过与预定义字符模板比较来识别字符,适用于固定字体和字号。 - 特征提取:从字符中提取关键特征(如线条、端点)并使用分类器识别,适应字体变化。 - 神经网络:利用卷积神经网络自动学习字符特征,特别适合复杂背景和多变字体,提供高准确率。 这些方法各有优势,选择取决于具体应用需求和文档特性。随着技术发展,基于神经网络的方法因其高性能而得到广泛应用。 #### 5.5.2 API 文档 ##### 5.5.2.1 Net类 ###### 5.5.2.1.1 头文件 ```cpp #include ``` - 作用:用于声明Net类,使得Net类可以在当前文件中使用。 ###### 5.5.2.1.2 构造类函数 ```cpp ncnn::Net net; ``` - 作用:创建一个Net类型的对象实例,用于实现文字识别。 - 参数说明: - 无 - 返回值: - 无 ###### 5.5.2.1.3 load_param函数 ```cpp int load_param(const DataReader& dr); ``` - 参数说明: - dr:传入的参数文件路径。 - 返回值: - 返回值为0表示加载参数文件成功。 ###### 5.5.2.1.4 load_model函数 ```cpp int load_model(const DataReader& dr); ``` - 参数说明: - dr:传入的模型文件路径。 - 返回值:返回值为0表示加载模型成功。 ###### 5.5.2.1.5 from_pixels函数 ```cpp ncnn::Mat::from_pixels(srcResize.data, ncnn::Mat::PIXEL_BGR, srcResize.cols, srcResize.rows); ``` - 参数说明: - srcResize.data:输入图像的像素数据指针。 - ncnn::Mat::PIXEL_BGR:输入像素数据的颜色格式。 - srcResize.cols:输入图像的宽度。 - srcResize.rows:输入图像的高度。 - 返回值:适配成 NCNN 所需的格式的包含图像数据的新对象。 ##### 5.5.2.2 Extractor类 ###### 5.5.2.2.1 头文件 ```cpp #include ``` - 作用:用于声明Extractor类,使得Extractor类可以在当前文件中使用。 ###### 5.5.2.2.2 构造类函数 ```cpp ncnn::Extractor extractor = net.create_extractor(); ``` - 作用:从已经加载了神经网络模型的 net 中创建一个 Extractor 实例,用于执行文字识别的推理任务。 - 参数说明: - 无 - 返回值: - 无 #### 5.5.3 OCR 字符识别代码解析 ##### 5.5.3.1 流程图 ``` 开始 │ ├── 判断参数数量是否为5 │ └── 不符合 → 输出Usage并退出 │ ├── 加载NCNN模型 (param + bin) │ ├── 读取字符集 keys.txt │ └── keys 数量 != 5531 → 错误退出 │ ├── 读取输入图像 │ └── 图像为空 → 错误退出 │ ├── processFrame() │ ├── 图像预处理(Resize、RGB转换、归一化) │ ├── 模型推理(Extractor) │ ├── 解析输出(Softmax + argmax) │ ├── 过滤无效字符 + 构建识别结果字符串 │ └── 输出识别结果和耗时 │ └── 程序结束 ``` ##### 5.5.3.2 核心代码解析 - 加载模型参数和权重 ```cpp net.load_param(argv[2]); net.load_model(argv[3]); ``` - 读取字符集文件 ```cpp std::ifstream in(argv[4]); std::string line; if (in) { while (getline(in, line)) { // line中不包括每行的换行符 keys.push_back(line); } } else { printf("The keys.txt file was not found\n"); return false; } if (keys.size() != 5531) { fprintf(stderr, "missing keys\n"); return false; } ``` - 对输入图像进行识别 ```cpp for (int i = 0; i < out.h; i++) { int maxIndex = 0; float maxValue = -1000.f; // Softmax 计算 std::vector exps(out.w); for (int j = 0; j < out.w; j++) { float expSingle = exp(outputData[i * out.w + j]); exps.at(j) = expSingle; } float partition = accumulate(exps.begin(), exps.end(), 0.0); // 行总和 // 找到最大值及其索引 auto maxElementIt = std::max_element(exps.begin(), exps.end()); maxIndex = std::distance(exps.begin(), maxElementIt); maxValue = *maxElementIt / partition; // 检测到有效字符 if (maxIndex > 0 && maxIndex < keySize && (!(i > 0 && maxIndex == lastIndex))) { scores.emplace_back(maxValue); strRes.append(keys[maxIndex - 1]); // 将字符追加到结果字符串中 } lastIndex = maxIndex; } ``` 自定义函数说明 - OCR 文字识别 ```cpp void processFrame(Mat &src, ncnn::Net &net, const std::vector &keys, const float meanValues[], const float normValues[], const int dstHeight) ``` - 作用: - 执行图像预处理,模型推理,Softmax解码,字符拼接全流程。 - 参数说明: - src:待识别的文本区域图像。 - ocr_net:OCR识别模型。 - keys:字符表(字符到索引映射)。 - ocr_mean:图像归一化均值。 - ocr_norm:图像归一化标准差。 - dstHeight:目标高度。 - 返回值: - 无 ##### 5.5.3.3 完整代码实现 ```cpp #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace cv; using namespace std::chrono; template inline static size_t argmax(ForwardIterator first, ForwardIterator last) { return std::distance(first, std::max_element(first, last)); } // 后续处理函数 void processFrame(Mat &src, ncnn::Net &net, const std::vector &keys, const float meanValues[], const float normValues[], const int dstHeight) { float scale = (float)dstHeight / (float)src.rows; int dstWidth = int((float)src.cols * scale); std::cout << "resize" << std::endl; cv::Mat srcResize; resize(src, srcResize, cv::Size(dstWidth, dstHeight)); std::cout << "resize success" << std::endl; ncnn::Mat input = ncnn::Mat::from_pixels(srcResize.data, ncnn::Mat::PIXEL_RGB, srcResize.cols, srcResize.rows); std::cout << "input success" << std::endl; input.substract_mean_normalize(meanValues, normValues); ncnn::Extractor extractor = net.create_extractor(); // net.num_threads = 6; extractor.input("input", input); std::cout << "extract success" << std::endl; ncnn::Mat out; extractor.extract("out", out); float *floatArray = (float *)out.data; std::vector outputData(floatArray, floatArray + out.h * out.w); int keySize = keys.size(); std::string strRes; std::vector scores; int lastIndex = 0; int maxIndex; float maxValue; std::cout << "开始检测" << std::endl; high_resolution_clock::time_point start_time = high_resolution_clock::now(); for (int i = 0; i < out.h; i++) { int maxIndex = 0; float maxValue = -1000.f; // Softmax 计算 std::vector exps(out.w); for (int j = 0; j < out.w; j++) { float expSingle = exp(outputData[i * out.w + j]); exps.at(j) = expSingle; } // 行总和 float partition = accumulate(exps.begin(), exps.end(), 0.0); // 找到最大值及其索引 auto maxElementIt = std::max_element(exps.begin(), exps.end()); maxIndex = std::distance(exps.begin(), maxElementIt); maxValue = *maxElementIt / partition; // 检测到有效字符 if (maxIndex > 0 && maxIndex < keySize && (!(i > 0 && maxIndex == lastIndex))) { scores.emplace_back(maxValue); // 将字符追加到结果字符串中 strRes.append(keys[maxIndex - 1]); } lastIndex = maxIndex; } high_resolution_clock::time_point end_time = high_resolution_clock::now(); auto time_span = duration_cast(end_time - start_time); // 所有检测完成后,一次性输出结果 std::cout << "检测完成,最终结果:" << std::endl; std::cout << "识别的文本: " << strRes << std::endl; std::cout << "单张图片推理时间(ms): " << time_span.count() << std::endl; std::cout << std::endl; } int main(int argc, char *argv[]) { if (argc != 5) { std::cerr << "Usage: ./ncnn_ocr " << std::endl; } const float meanValues[3] = {127.5, 127.5, 127.5}; const float normValues[3] = {1.0 / 127.5, 1.0 / 127.5, 1.0 / 127.5}; const int dstHeight = 32; ncnn::Net net; std::vector keys; // Mat src = imread(argv[1]); // default : BGR // Step 2: 加载 .param 和 .bin 文件 net.load_param(argv[2]); net.load_model(argv[3]); std::cout << "load model success" << std::endl; std::ifstream in(argv[4]); std::string line; if (in) { // line中不包括每行的换行符 while (getline(in, line)) { keys.push_back(line); } } else { printf("The keys.txt file was not found\n"); return false; } if (keys.size() != 5531) { fprintf(stderr, "missing keys\n"); return false; } printf("total keys size(%lu)\n", keys.size()); std::string argument(argv[1]); // Default: BGR Mat src = imread(argv[1]); if (src.empty()) { std::cerr << "Error opening image file" << std::endl; return -1; } processFrame(src, net, keys, meanValues, normValues, dstHeight); return 0; } ``` #### 5.5.4 编译调试 ##### 5.5.4.1 编译环境搭建 - 请确保你已经按照 [开发环境搭建指南](https://gitee.com/LockzhinerAI/LockzhinerVisionModule/blob/master/docs/introductory_tutorial/cpp_development_environment.md) 正确配置了开发环境。 - 同时已经正确连接开发板。 ##### 5.5.4.2 Cmake介绍 ```cmake cmake_minimum_required(VERSION 3.10) project(test_distance) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) ## 定义项目根目录路径 set(PROJECT_ROOT_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../..") message("PROJECT_ROOT_PATH = " ${PROJECT_ROOT_PATH}) include("${PROJECT_ROOT_PATH}/toolchains/arm-rockchip830-linux-uclibcgnueabihf.toolchain.cmake") ## 定义 OpenCV SDK 路径 set(OpenCV_ROOT_PATH "${PROJECT_ROOT_PATH}/third_party/opencv-mobile-4.10.0-lockzhiner-vision-module") set(OpenCV_DIR "${OpenCV_ROOT_PATH}/lib/cmake/opencv4") find_package(OpenCV REQUIRED) set(OPENCV_LIBRARIES "${OpenCV_LIBS}") ## 定义 LockzhinerVisionModule SDK 路径 set(LockzhinerVisionModule_ROOT_PATH "${PROJECT_ROOT_PATH}/third_party/lockzhiner_vision_module_sdk") set(LockzhinerVisionModule_DIR "${LockzhinerVisionModule_ROOT_PATH}/lib/cmake/lockzhiner_vision_module") find_package(LockzhinerVisionModule REQUIRED) ## ncnn配置 set(NCNN_ROOT_DIR "${PROJECT_ROOT_PATH}/third_party/ncnn-20240820-lockzhiner-vision-module") # 确保third_party层级存在 message(STATUS "Checking ncnn headers in: ${NCNN_ROOT_DIR}/include/ncnn") ## 验证头文件存在 if(NOT EXISTS "${NCNN_ROOT_DIR}/include/ncnn/net.h") message(FATAL_ERROR "ncnn headers not found. Confirm the directory contains ncnn: ${NCNN_ROOT_DIR}") endif() set(NCNN_INCLUDE_DIRS "${NCNN_ROOT_DIR}/include") set(NCNN_LIBRARIES "${NCNN_ROOT_DIR}/lib/libncnn.a") add_executable(Test-ncnn_rec ncnn_rec.cc) target_include_directories(Test-ncnn_rec PRIVATE ${LOCKZHINER_VISION_MODULE_INCLUDE_DIRS} ${NCNN_INCLUDE_DIRS}) target_link_libraries(Test-ncnn_rec PRIVATE ${OPENCV_LIBRARIES} ${NCNN_LIBRARIES} ${LOCKZHINER_VISION_MODULE_LIBRARIES}) install( TARGETS Test-ncnn_rec RUNTIME DESTINATION . ) ``` ##### 5.5.4.3 编译项目 使用 Docker Destop 打开 LockzhinerVisionModule 容器并执行以下命令来编译项目 ```bash ## 进入Demo所在目录 cd /LockzhinerVisionModuleWorkSpace/LockzhinerVisionModule/Cpp_example/D05_ocr_text_recognition ## 创建编译目录 rm -rf build && mkdir build && cd build ## 配置交叉编译工具链 export TOOLCHAIN_ROOT_PATH="/LockzhinerVisionModuleWorkSpace/arm-rockchip830-linux-uclibcgnueabihf" ## 使用cmake配置项目 cmake .. ## 执行编译项目 make -j8 && make install ``` 在执行完上述命令后,会在build目录下生成可执行文件。 #### 5.5.5 执行结果 ##### 5.5.5.1 运行前准备 - 请确保你已经下载了 [凌智视觉模块文字识别参数文件](https://gitee.com/LockzhinerAI/LockzhinerVisionModule/releases/download/v0.0.6/crnn_lite_op.param) - 请确保你已经下载了 [凌智视觉模块文字识别bin文件](https://gitee.com/LockzhinerAI/LockzhinerVisionModule/releases/download/v0.0.6/crnn_lite_op.bin) - 请确保你已经下载了 [凌智视觉模块文字识别keys文件](https://gitee.com/LockzhinerAI/LockzhinerVisionModule/releases/download/v0.0.6/keys.txt) ##### 5.5.5.2 运行过程 ```shell chmod 777 Test-ncnn_rec ## 对图像进行识别 ./Test-ncnn_rec image_path crnn_lite_op.param crnn_lite_op.bin keys.txt ``` ##### 5.5.5.3 运行效果 ###### 5.5.5.2.1 图像OCR识别 - 原始图像 ![title](./images/D5_Test.jpg) - 识别结果 ![title](./images/D5_result.png) ###### 5.5.5.2.2 注意事项 由于本章节只训练了一个识别模型,并没有训练检测模型,所有只针对包含单行文本的图像效果比较好,对于包含多行文本的识别,效果并不是很好。 #### 5.5.6 总结 通过上述内容,我们成功实现了一个简单的OCR字符识别系统,包括: - 加载识别模型和检测图像。 - 进行字符识别。 - 将识别结果打印出来。 ### 5.6 OCR 文本框检测 本章节在Lockzhiner Vision Module 上基于OcrLiteNcnn模型, 实现了一个OCR文本框检测系统。 #### 5.6.1 基本知识讲解 ##### 5.6.1.1 文本检测简介 文本检测是一种识别图像中文字位置的技术,作为光学字符识别(OCR)的一部分,它能准确找出并定位图片或视频中的文字区域。适用于文档数字化、车牌识别、实时翻译和辅助视障人士等场景,通过自动化提取文本信息,极大提升了信息处理的效率与便捷性。 ##### 5.6.1.2 文本检测常用方法 - 连通域分析:适用于前景与背景对比明显的图像,通过识别连通区域定位文本。 - 边缘检测:利用Canny算子找到文本边缘,适合处理倾斜或弯曲的文本。 - 深度学习:使用卷积神经网络(CNN)等模型自动提取特征,特别擅长自然场景中的多方向和多尺度文本检测。 #### 5.6.2 API 文档 ##### 5.6.2.1 Net类 ###### 5.6.2.1.1 头文件 ```cpp #include ``` - 作用:用于声明Net类,使得Net类可以在当前文件中使用。 ###### 5.6.2.1.2 构造类函数 ```cpp ncnn::Net net; ``` - 作用:创建一个Net类型的对象实例,用于实现文字区域的检测。 - 参数说明: - 无 - 返回值: - 无 ###### 5.6.2.1.3 load_param函数 ```cpp int load_param(const DataReader& dr); ``` - 参数说明: - dr:传入的参数文件路径。 - 返回值: - 返回值为0表示加载参数文件成功。 ###### 5.6.2.1.4 load_model函数 ```cpp int load_model(const DataReader& dr); ``` - 参数说明: - dr:传入的模型文件路径。 - 返回值:返回值为0表示加载模型成功。 ###### 5.6.2.1.5 from_pixels函数 ```cpp ncnn::Mat::from_pixels(srcResize.data, ncnn::Mat::PIXEL_BGR, srcResize.cols, srcResize.rows); ``` - 参数说明: - srcResize.data:输入图像的像素数据指针。 - ncnn::Mat::PIXEL_BGR:输入像素数据的颜色格式。 - srcResize.cols:输入图像的宽度。 - srcResize.rows:输入图像的高度。 - 返回值:适配成 NCNN 所需的格式的包含图像数据的新对象。 ##### 5.6.2.2 Extractor类 ###### 5.6.2.2.1 头文件 ```cpp #include ``` - 作用:用于声明Extractor类,使得Extractor类可以在当前文件中使用。 ###### 5.6.2.2.2 构造类函数 ```cpp ncnn::Extractor extractor = net.create_extractor(); ``` - 作用:从已经加载了神经网络模型的 net 中创建一个 Extractor 实例,用于执行文本区域框检测的推理任务。 - 参数说明: - 无 - 返回值: - 无 #### 5.6.3 OCR 文本检测代码解析 ##### 5.6.3.1 流程图 ``` 开始 │ ├── 参数检查 (argc == 4) │ └── 错误 → 输出Usage并退出 │ ├── 图像读取 (imread) │ └── 失败 → 错误退出 │ ├── 模型加载 (load_param + load_model) │ └── 失败 → 错误退出 │ ├── 计算缩放参数 getScaleParam() │ ├── 图像预处理 │ ├── resize │ ├── from_pixels (BGR) │ └── substract_mean_normalize │ ├── 模型推理 (extractor.input + extract) │ └── 输出推理时间 │ ├── 后处理 findRsBoxes() │ ├── findContours │ ├── 过滤小区域 │ ├── expandPolygon 扩展文本框 │ └── 坐标还原回原图尺寸 │ ├── 绘制文本框 drawTextBox() │ ├── 显示结果 imshow + waitKey │ └── 程序结束 ``` ##### 5.6.3.2 核心代码解析 - 加载模型参数和权重 ```cpp net.load_param(argv[2]); net.load_model(argv[3]) ``` 自定义函数说明 - 计算图像缩放比例以适应目标尺寸 ```cpp ScaleParam getScaleParam(const cv::Mat &src, const int targetSize); ``` - 参数说明: - src:输入的原始图像。 - targetSize:目标尺寸大小。 - 返回值: - 返回一个 ScaleParam 结构体,该结构体包含了原图尺寸、缩放后的尺寸以及缩放比例等信息。 - 向量归一化函数 ```cpp cv::Point2f normalize(const cv::Point2f& v); ``` - 作用: - 计算向量长度并归一化,用于后续几何变换(如多边形扩展方向计算)。 - 参数说明: - v:待归一化的二维向量。 - 返回值: - 返回单位长度的向量,若输入向量为零向量则返回(0, 0)。 - 多边形扩张函数:用于扩大文本框边界 ```cpp std::vector expandPolygon(const std::vector& inBox, float distance) ``` - 作用: - 基于邻边法线方向计算角平分线,向外扩展多边形边界,增强文本检测框的包容性。 - 参数说明: - inBox:输入的多边形顶点集合。 - distance:扩展距离,控制文本框膨胀程度。 - 返回值: - 返回扩展后的新多边形顶点集合。 - 文本检测框提取 ```cpp std::vector findRsBoxes(const cv::Mat &fMapMat, const cv::Mat &norfMapMat, ScaleParam &s, const float boxScoreThresh, const float unClipRatio); ``` - 作用: - 结合轮廓检测与UNet式后处理,生成最终文本检测框。 - 参数说明: - fMapMat:DBNet输出的概率图。 - norfMapMat:二值化后的概率图。 - s:图像缩放参数结构体。 - boxScoreThresh:文本框置信度阈值。 - unClipRatio:边界扩展系数。 - 返回值: - 返回包含文本框坐标、置信度的结构体列表。 ##### 5.6.3.3 完整代码实现 ```cpp #include #include #include #include #include #include #include #include #include using namespace cv; using namespace std::chrono; // 定义必要的参数 const float meanValues[3] = {0.485f * 255, 0.456f * 255, 0.406f * 255}; const float normValues[3] = {1.0f / 0.229f / 255.0f, 1.0f / 0.224f / 255.0f, 1.0f / 0.225f / 255.0f}; // 图像缩放参数定义 struct ScaleParam { int srcWidth; int srcHeight; int dstWidth; int dstHeight; float ratioWidth; float ratioHeight; }; // 文本框结构体的定义 struct TextBox { std::vector boxPoint; float score; }; // 计算图像缩放比例以适应目标尺寸 ScaleParam getScaleParam(const cv::Mat &src, const int targetSize) { int imgHeight = src.rows; int imgWidth = src.cols; float ratio = std::min(static_cast(targetSize) / imgHeight, static_cast(targetSize) / imgWidth); ScaleParam scaleParam; scaleParam.srcHeight = imgHeight; scaleParam.srcWidth = imgWidth; scaleParam.dstHeight = static_cast(imgHeight * ratio); scaleParam.dstWidth = static_cast(imgWidth * ratio); scaleParam.ratioHeight = ratio; scaleParam.ratioWidth = ratio; return scaleParam; } // 向量归一化函数 cv::Point2f normalize(const cv::Point2f& v) { float len = std::sqrt(v.x * v.x + v.y * v.y); // 防止除以零 if (len == 0) return cv::Point2f(0, 0); return cv::Point2f(v.x / len, v.y / len); } // 多边形扩张函数:用于扩大文本框边界 std::vector expandPolygon(const std::vector& inBox, float distance) { std::vector outBox; int n = inBox.size(); // 确保输入至少是一个三角形 if (n < 3) return outBox; for (int i = 0; i < n; ++i) { cv::Point2f prev = inBox[(i + n - 1) % n]; cv::Point2f curr = inBox[i]; cv::Point2f next = inBox[(i + 1) % n]; cv::Point2f v1 = cv::Point2f(curr.x - prev.x, curr.y - prev.y); cv::Point2f v2 = cv::Point2f(next.x - curr.x, next.y - curr.y); cv::Point2f normal1(-v1.y, v1.x); cv::Point2f normal2(-v2.y, v2.x); normal1 = normalize(normal1); normal2 = normalize(normal2); cv::Point2f bisectorNormal = normal1 + normal2; bisectorNormal = normalize(bisectorNormal); cv::Point2f newPoint = curr + bisectorNormal * distance; outBox.push_back(cv::Point(newPoint.x, newPoint.y)); } return outBox; } // 查找并处理文本框区域 std::vector findRsBoxes(const cv::Mat &fMapMat, const cv::Mat &norfMapMat, ScaleParam &s, const float boxScoreThresh, const float unClipRatio) { float minArea = 3; std::vector rsBoxes; std::vector> contours; cv::findContours(norfMapMat, contours, cv::RETR_LIST, cv::CHAIN_APPROX_SIMPLE); for (size_t i = 0; i < contours.size(); ++i) { double perimeter = cv::arcLength(contours[i], true); cv::RotatedRect minRect = cv::minAreaRect(contours[i]); float minSideLen = std::min(minRect.size.width, minRect.size.height); if (minSideLen < minArea) continue; // 创建一个掩码图像 cv::Mat mask = cv::Mat::zeros(fMapMat.size(), CV_8UC1); cv::drawContours(mask, contours, static_cast(i), cv::Scalar(255), cv::FILLED); // 计算掩码内部的平均得分 cv::Scalar meanScore = cv::mean(fMapMat, mask); float score = static_cast(meanScore[0]); if (score < boxScoreThresh) continue; // 使用expandPolygon实现多边形扩张 double area = cv::contourArea(contours[i]); float distance = unClipRatio * area / static_cast(perimeter); std::vector clipBox = expandPolygon(contours[i], distance); if (minSideLen < minArea + 2) continue; for (auto &point : clipBox) { point.x = std::max(0, std::min(static_cast(point.x / s.ratioWidth), s.srcWidth - 1)); point.y = std::max(0, std::min(static_cast(point.y / s.ratioHeight), s.srcHeight - 1)); } rsBoxes.emplace_back(TextBox{clipBox, score}); } return rsBoxes; } // 绘制文本框到图像上 void drawTextBox(cv::Mat &boxImg, const std::vector &box, int thickness) { for (size_t i = 0; i < box.size(); ++i) { cv::line(boxImg, box[i], box[(i + 1) % box.size()], cv::Scalar(0, 255, 0), thickness); } } int main(int argc, char** argv) { if(argc != 4) { std::cerr << "Usage: " << argv[0] << " " << std::endl; return -1; } // 读取输入图像 cv::Mat src = cv::imread(argv[1], cv::IMREAD_COLOR); if(src.empty()) { std::cerr << "Failed to read image: " << argv[1] << std::endl; return -1; } ncnn::Net net; // 禁用一些可能不被支持的功能 net.opt.use_vulkan_compute = false; net.opt.use_bf16_storage = false; net.opt.use_fp16_packed = false; net.opt.use_fp16_storage = false; net.opt.use_fp16_arithmetic = false; // 加载模型参数和权重文件 if (net.load_param(argv[2]) != 0 || net.load_model(argv[3]) != 0) { std::cerr << "Failed to load model from " << argv[2] << " and " << argv[3] << std::endl; return -1; } std::cout << "Model loaded successfully." << std::endl; // 736是自定义的参数,可以自己设置 ScaleParam scaleParam = getScaleParam(src, 736); std::cout << " scaleParam successfully." << std::endl; float boxScoreThresh = 0.5f; float boxThresh = 0.3f; float unClipRatio = 3.5f; cv::Mat srcResize; resize(src, srcResize, cv::Size(scaleParam.dstWidth, scaleParam.dstHeight)); std::cout << " resize successfully." << std::endl; // 准备输入数据,并进行均值标准化 ncnn::Mat input = ncnn::Mat::from_pixels(srcResize.data, ncnn::Mat::PIXEL_BGR, srcResize.cols, srcResize.rows); input.substract_mean_normalize(meanValues, normValues); // 创建推理器并执行推理 high_resolution_clock::time_point start_time = high_resolution_clock::now(); ncnn::Extractor extractor = net.create_extractor(); extractor.input("input0", input); ncnn::Mat out; extractor.extract("out1", out); high_resolution_clock::time_point end_time = high_resolution_clock::now(); // 计算推理时间 auto time_span = duration_cast(end_time - start_time); std::cout << "单张图片推理时间(ms): " << time_span.count() << std::endl; // 创建一个单通道的 cv::Mat 来存储第一个通道的数据 cv::Mat fMapMat(srcResize.rows, srcResize.cols, CV_32FC1); memcpy(fMapMat.data, (float *) out.data, srcResize.rows * srcResize.cols * sizeof(float)); cv::Mat norfMapMat; norfMapMat = fMapMat > boxThresh; // 查找文本框并绘制在原图上 std::vector textBoxes = findRsBoxes(fMapMat, norfMapMat, scaleParam, boxScoreThresh, unClipRatio); for (const auto &textBox : textBoxes) { drawTextBox(src, textBox.boxPoint, 1); } cv::imshow("Detected Text Boxes", src); cv::waitKey(0); return 0; } ``` #### 5.6.4 编译调试 ##### 5.6.4.1 编译环境搭建 - 请确保你已经按照 [开发环境搭建指南](https://gitee.com/LockzhinerAI/LockzhinerVisionModule/blob/master/docs/introductory_tutorial/cpp_development_environment.md) 正确配置了开发环境。 - 同时已经正确连接开发板。 ##### 5.6.4.2 Cmake介绍 ```cmake cmake_minimum_required(VERSION 3.10) project(ocr_text_detection) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) ## 定义项目根目录路径 set(PROJECT_ROOT_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../..") message("PROJECT_ROOT_PATH = " ${PROJECT_ROOT_PATH}) include("${PROJECT_ROOT_PATH}/toolchains/arm-rockchip830-linux-uclibcgnueabihf.toolchain.cmake") ## 定义 OpenCV SDK 路径 set(OpenCV_ROOT_PATH "${PROJECT_ROOT_PATH}/third_party/opencv-mobile-4.10.0-lockzhiner-vision-module") set(OpenCV_DIR "${OpenCV_ROOT_PATH}/lib/cmake/opencv4") find_package(OpenCV REQUIRED) set(OPENCV_LIBRARIES "${OpenCV_LIBS}") ## 定义 LockzhinerVisionModule SDK 路径 set(LockzhinerVisionModule_ROOT_PATH "${PROJECT_ROOT_PATH}/third_party/lockzhiner_vision_module_sdk") set(LockzhinerVisionModule_DIR "${LockzhinerVisionModule_ROOT_PATH}/lib/cmake/lockzhiner_vision_module") find_package(LockzhinerVisionModule REQUIRED) ## ncnn配置 set(NCNN_ROOT_DIR "${PROJECT_ROOT_PATH}/third_party/ncnn-20240820-lockzhiner-vision-module") # 确保third_party层级存在 message(STATUS "Checking ncnn headers in: ${NCNN_ROOT_DIR}/include/ncnn") ## 验证头文件存在 if(NOT EXISTS "${NCNN_ROOT_DIR}/include/ncnn/net.h") message(FATAL_ERROR "ncnn headers not found. Confirm the directory contains ncnn: ${NCNN_ROOT_DIR}") endif() set(NCNN_INCLUDE_DIRS "${NCNN_ROOT_DIR}/include") set(NCNN_LIBRARIES "${NCNN_ROOT_DIR}/lib/libncnn.a") add_executable(Test-ncnn_dbnet ncnn_dbnet.cc) target_include_directories(Test-ncnn_dbnet PRIVATE ${LOCKZHINER_VISION_MODULE_INCLUDE_DIRS} ${NCNN_INCLUDE_DIRS}) target_link_libraries(Test-ncnn_dbnet PRIVATE ${OPENCV_LIBRARIES} ${NCNN_LIBRARIES} ${LOCKZHINER_VISION_MODULE_LIBRARIES}) install( TARGETS Test-ncnn_dbnet RUNTIME DESTINATION . ) ``` ##### 5.6.4.3 编译项目 使用 Docker Destop 打开 LockzhinerVisionModule 容器并执行以下命令来编译项目 ```bash ## 进入Demo所在目录 cd /LockzhinerVisionModuleWorkSpace/LockzhinerVisionModule/Cpp_example/D06_ocr_text_detection ## 创建编译目录 rm -rf build && mkdir build && cd build ## 配置交叉编译工具链 export TOOLCHAIN_ROOT_PATH="/LockzhinerVisionModuleWorkSpace/arm-rockchip830-linux-uclibcgnueabihf" ## 使用cmake配置项目 cmake .. ## 执行编译项目 make -j8 && make install ``` 在执行完上述命令后,会在build目录下生成可执行文件。 #### 5.6.5 执行结果 ##### 5.6.5.1 运行前准备 - 请确保你已经下载了 [凌智视觉模块文本检测参数文件](https://gitee.com/LockzhinerAI/LockzhinerVisionModule/releases/download/v0.0.6/dbnet_op.param) - 请确保你已经下载了 [凌智视觉模块文本检测bin文件](https://gitee.com/LockzhinerAI/LockzhinerVisionModule/releases/download/v0.0.6/dbnet_op.bin) ##### 5.6.5.2 运行过程 ```shell chmod 777 Test-ncnn_dbnet ./Test-ncnn_dbnet dbnet_op.param dbnet_op.bin ``` ##### 5.6.5.3 运行效果 - 原始图像 ![title](./images/D6_1.png) - 检测结果 ![title](./images/D6_result1.png) #### 5.6.6 总结 通过上述内容,我们成功实现了一个简单的OCR文本检测系统,包括: - 加载检测模型和检测图像。 - 进行文本检测推理。 - 在原图上绘制检测结果并保存。 ### 5.7 OCR 中文字符识别 本章节在 Lockzhiner Vision Module 上基于OcrLiteNcnn模型, 实现了一个OCR中文字符识别系统。 #### 5.7.1 基本知识讲解 ##### 5.7.1.1 OCR中文字符识别简介 光学字符识别(OCR)是一种将图像中的文字转换为可编辑文本的技术,其中中文OCR因汉字结构复杂、字形多样而更具挑战性。通过图像预处理、深度学习模型(如CNN+RNN)及语言校正,系统能精准识别印刷体或手写中文,并适应不同字体、排版与复杂背景。该技术广泛应用于文档数字化、证件识别、智能办公等领域,尤其在处理海量纸质资料、提升信息处理效率方面具有重要意义。 ##### 5.7.1.2 OCR中文字符识别的核心步骤 要实现一个OCR中文字符识别,主要包含以下几个步骤: - 图像预处理: - 灰度化与二值化:将彩色图像转换为黑白图像,增强文字与背景的对比度。 - 降噪与倾斜校正:去除噪声,校正因拍摄导致的倾斜角度。 - 文字定位与区域检测: - 一般利用深度学习模型来检测图像中的文字区域。 - 字符分割: - 将连续文字块拆分为单个字符,解决粘连字符问题。 - 特征提取与识别: - 提取字符的几何特征或通过深度学习模型生成抽象特征。 - 后处理与校正 #### 5.7.2 API 文档 ##### 5.7.2.1 Net类 ###### 5.7.2.1.1 头文件 ```cpp #include ``` - 作用:用于声明Net类,使得Net类可以在当前文件中使用。 ###### 5.7.2.1.2 构造类函数 ```cpp ncnn::Net dbnet; ``` - 作用:创建一个Net类型的对象实例,用于实现文字区域的检测。 - 参数说明: - 无 - 返回值: - 无 ```cpp ncnn::Net ocr_net; ``` - 作用:创建一个Net类型的对象实例,用于实现文字的识别。 - 参数说明: - 无 - 返回值: - 无 ###### 5.7.2.1.3 load_param函数 ```cpp int load_param(const DataReader& dr); ``` - 参数说明: - dr:传入的参数文件路径。 - 返回值: - 返回值为0表示加载参数文件成功。 ###### 5.7.2.1.4 load_model函数 ```cpp int load_model(const DataReader& dr); ``` - 参数说明: - dr:传入的模型文件路径。 - 返回值:返回值为0表示加载模型成功。 ###### 5.7.2.1.5 from_pixels函数 ```cpp ncnn::Mat::from_pixels(srcResize.data, ncnn::Mat::PIXEL_BGR, srcResize.cols, srcResize.rows); ``` - 参数说明: - srcResize.data:输入图像的像素数据指针。 - ncnn::Mat::PIXEL_BGR:输入像素数据的颜色格式。 - srcResize.cols:输入图像的宽度。 - srcResize.rows:输入图像的高度。 - 返回值:适配成 NCNN 所需的格式的包含图像数据的新对象。 ##### 5.7.2.2 Extractor类 ###### 5.7.2.2.1 头文件 ```cpp #include ``` - 作用:用于声明Extractor类,使得Extractor类可以在当前文件中使用。 ###### 5.7.2.2.2 构造类函数 ```cpp ncnn::Extractor dbnet_extractor = dbnet.create_extractor(); ``` - 作用:从已经加载了神经网络模型的 dbnet 中创建一个 Extractor 实例,用于执行文本区域检测的推理任务。 - 参数说明: - 无 - 返回值: - 无 ```cpp ncnn::Extractor ocr_extractor = ocr_net.create_extractor(); ``` - 作用:从已经加载了神经网络模型的 ocr_net 中创建一个 Extractor 实例,用于执行文本识别的推理任务。 - 参数说明: - 无 - 返回值: - 无 #### 5.7.3 OCR 中文字符识别代码解析 ##### 5.7.3.1 流程图 ``` 开始 │ ├── 参数检查 (argc == 7) │ └── 错误 → 输出Usage并退出 │ ├── 加载字符集 keys.txt 到 keys 向量 │ └── 文件未找到或格式错误 → 错误退出 │ ├── 初始化 DBNet 和 OCR 模型 │ ├── 禁用不支持的功能 │ ├── 加载模型参数和权重文件 │ └── 任一模型加载失败 → 错误退出 │ ├── 读取并预处理输入图像 │ ├── 计算缩放参数 │ ├── resize 图像 │ └── 准备 NCNN 输入数据 │ ├── 文本检测 │ ├── 模型推理 │ ├── findRsBoxes() 查找文本框 │ └── 坐标还原回原图尺寸 │ ├── 文本识别 │ ├── 裁剪文本区域 │ ├── processFrame() 进行OCR识别 │ └── 结果保存至文本框结构体 │ ├── 结果排序与可视化 │ ├── 根据中心点排序文本框 │ ├── 绘制文本框边界 │ ├── 输出识别文本 │ └── 显示结果图像 │ └── 程序结束 ``` ##### 5.7.3.2 核心代码解析 ###### 5.7.3.2.1 向量归一化 ```cpp cv::Point2f normalize(const cv::Point2f& v); ``` - 作用: - 计算向量长度并归一化,用于后续几何变换(如多边形扩展方向计算)。 - 参数说明: - v:待归一化的二维向量。 - 返回值: - 返回单位长度的向量,若输入向量为零向量则返回(0, 0)。 ###### 5.7.3.2.2 多边形边界扩张 ```cpp std::vector expandPolygon(const std::vector& inBox, float distance); ``` - 作用: - 基于邻边法线方向计算角平分线,向外扩展多边形边界,增强文本检测框的包容性。 - 参数说明: - inBox:输入的多边形顶点集合。 - distance:扩展距离,控制文本框膨胀程度。 - 返回值: - 返回扩展后的新多边形顶点集合。 ###### 5.7.3.2.3 文本检测框提取 ```cpp std::vector findRsBoxes(const cv::Mat &fMapMat, const cv::Mat &norfMapMat, ScaleParam &s, const float boxScoreThresh, const float unClipRatio); ``` - 作用: - 结合轮廓检测与UNet式后处理,生成最终文本检测框。 - 参数说明: - fMapMat:DBNet输出的概率图。 - norfMapMat:二值化后的概率图。 - s:图像缩放参数结构体。 - boxScoreThresh:文本框置信度阈值。 - unClipRatio:边界扩展系数。 - 返回值: - 返回包含文本框坐标、置信度的结构体列表。 ###### 5.7.3.2.4 OCR文本识别 ```cpp void processFrame(Mat &src, ncnn::Net &ocr_net, const std::vector &keys, const float ocr_mean[], const float ocr_norm[], int dstHeight, std::string &outputText); ``` - 作用: - 执行图像预处理,模型推理,Softmax解码,字符拼接全流程。 - 参数说明: - src:待识别的文本区域图像。 - ocr_net:OCR识别模型。 - keys:字符表(字符到索引映射)。 - ocr_mean:图像归一化均值。 - ocr_norm:图像归一化标准差。 - dstHeight:目标高度。 - outputText:输出识别结果字符串。 - 返回值: - 无 ##### 5.7.3.3 完整代码实现 ```cpp #include #include #include #include #include #include #include #include using namespace cv; using namespace std::chrono; // 定义必要的参数 const float dbnet_meanValues[3] = {0.485f * 255, 0.456f * 255, 0.406f * 255}; const float dbnet_normValues[3] = {1.0f / 0.229f / 255.0f, 1.0f / 0.224f / 255.0f, 1.0f / 0.225f / 255.0f}; // 文本检测结构体 struct TextBox { std::vector boxPoint; float score; std::string text; }; // 图像缩放参数定义 struct ScaleParam { int srcWidth; int srcHeight; int dstWidth; int dstHeight; float ratioWidth; float ratioHeight; }; // 文本检测相关函数 ScaleParam getScaleParam(const cv::Mat &src, const int targetSize) { int imgHeight = src.rows; int imgWidth = src.cols; float ratio = std::min(static_cast(targetSize) / imgHeight, static_cast(targetSize) / imgWidth); ScaleParam scaleParam; scaleParam.srcHeight = imgHeight; scaleParam.srcWidth = imgWidth; scaleParam.dstHeight = static_cast(imgHeight * ratio); scaleParam.dstWidth = static_cast(imgWidth * ratio); scaleParam.ratioHeight = ratio; scaleParam.ratioWidth = ratio; return scaleParam; } cv::Point2f normalize(const cv::Point2f& v) { float len = std::sqrt(v.x * v.x + v.y * v.y); return (len == 0) ? cv::Point2f(0, 0) : cv::Point2f(v.x / len, v.y / len); } // 多边形边界扩展 std::vector expandPolygon(const std::vector& inBox, float distance) { std::vector outBox; int n = inBox.size(); if (n < 3) return outBox; for (int i = 0; i < n; ++i) { cv::Point2f prev = inBox[(i + n - 1) % n]; cv::Point2f curr = inBox[i]; cv::Point2f next = inBox[(i + 1) % n]; cv::Point2f v1 = curr - prev; cv::Point2f v2 = next - curr; cv::Point2f normal1(-v1.y, v1.x); cv::Point2f normal2(-v2.y, v2.x); normal1 = normalize(normal1); normal2 = normalize(normal2); cv::Point2f bisectorNormal = normalize(normal1 + normal2); cv::Point2f newPoint = curr + bisectorNormal * distance; outBox.push_back(cv::Point(newPoint.x, newPoint.y)); } return outBox; } // 提取检测到文本的检测框 std::vector findRsBoxes(const cv::Mat &fMapMat, const cv::Mat &norfMapMat, ScaleParam &s, const float boxScoreThresh, const float unClipRatio) { float minArea = 3; std::vector rsBoxes; std::vector> contours; cv::findContours(norfMapMat, contours, cv::RETR_LIST, cv::CHAIN_APPROX_SIMPLE); for (const auto& contour : contours) { double perimeter = cv::arcLength(contour, true); cv::RotatedRect minRect = cv::minAreaRect(contour); float minSideLen = std::min(minRect.size.width, minRect.size.height); if (minSideLen < minArea) continue; cv::Mat mask = cv::Mat::zeros(fMapMat.size(), CV_8UC1); for (int contour_idx = 0; contour_idx < contours.size(); contour_idx++) { cv::drawContours(mask, contours, contour_idx, cv::Scalar(255), cv::FILLED); } cv::Scalar meanScore = cv::mean(fMapMat, mask); float score = static_cast(meanScore[0]); if (score < boxScoreThresh) continue; double area = cv::contourArea(contour); float distance = unClipRatio * static_cast(area) / static_cast(perimeter); std::vector clipBox = expandPolygon(contour, distance); for (auto &point : clipBox) { point.x = std::max(0, std::min(static_cast(point.x / s.ratioWidth), s.srcWidth - 1)); point.y = std::max(0, std::min(static_cast(point.y / s.ratioHeight), s.srcHeight - 1)); } rsBoxes.emplace_back(TextBox{clipBox, score}); } return rsBoxes; } void drawTextBox(cv::Mat &boxImg, const std::vector &box, int thickness) { for (size_t i = 0; i < box.size(); ++i) { cv::line(boxImg, box[i], box[(i + 1) % box.size()], cv::Scalar(0, 255, 0), thickness); } } // 辅助函数 template inline static size_t argmax(ForwardIterator first, ForwardIterator last) { return std::distance(first, std::max_element(first, last)); } // 文本识别相关函数 void processFrame(Mat &src, ncnn::Net &ocr_net, const std::vector &keys, const float ocr_mean[], const float ocr_norm[], int dstHeight, std::string &outputText) { float scale = (float)dstHeight / (float)src.rows; int dstWidth = int((float)src.cols * scale); Mat resized_patch; cv::resize(src, resized_patch, cv::Size(dstWidth, dstHeight)); ncnn::Mat input = ncnn::Mat::from_pixels(resized_patch.data, ncnn::Mat::PIXEL_BGR, resized_patch.cols, resized_patch.rows); input.substract_mean_normalize(ocr_mean, ocr_norm); ncnn::Extractor ocr_extractor = ocr_net.create_extractor(); ocr_extractor.input("input", input); ncnn::Mat ocr_output; ocr_extractor.extract("out", ocr_output); float *floatArray = (float *)ocr_output.data; std::vector outputData(floatArray, floatArray + ocr_output.h * ocr_output.w); int keySize = keys.size(); std::string strRes; std::vector scores; int lastIndex = 0; int maxIndex; float maxValue; for (int i = 0; i < ocr_output.h; i++) { int maxIndex = 0; float maxValue = -1000.f; // Softmax 计算 std::vector exps(ocr_output.w); for (int j = 0; j < ocr_output.w; j++) { float expSingle = exp(outputData[i * ocr_output.w + j]); exps.at(j) = expSingle; } // 行总和 float partition = accumulate(exps.begin(), exps.end(), 0.0); // 找到最大值及其索引 auto maxElementIt = std::max_element(exps.begin(), exps.end()); maxIndex = std::distance(exps.begin(), maxElementIt); maxValue = *maxElementIt / partition; // 检测到有效字符 if (maxIndex > 0 && maxIndex < keySize && (!(i > 0 && maxIndex == lastIndex))) { scores.emplace_back(maxValue); // 将字符追加到结果字符串中 strRes.append(keys[maxIndex - 1]); } lastIndex = maxIndex; } outputText = strRes; } int main(int argc, char *argv[]) { if (argc != 7) { std::cerr << "Usage: " << argv[0] << " " << std::endl; return -1; } // 读取输入图像 Mat src = imread(argv[1], IMREAD_COLOR); if (src.empty()) { std::cerr << "Failed to read image." << std::endl; return -1; } // 加载OCR字符表 std::vector keys; std::ifstream in(argv[6]); std::string line; if (in) { while (getline(in, line)) { keys.push_back(line); } } else { std::cerr << "The keys.txt file was not found." << std::endl; return -1; } if (keys.size() != 5531) { std::cerr << "Invalid keys.txt format." << std::endl; return -1; } // 初始化DBNet检测模型 ncnn::Net dbnet; dbnet.opt.use_vulkan_compute = false; if (dbnet.load_param(argv[2]) != 0 || dbnet.load_model(argv[3]) != 0) { std::cerr << "Failed to load DBNet model." << std::endl; return -1; } // 初始化OCR识别模型 ncnn::Net ocr_net; ocr_net.opt.use_vulkan_compute = false; if (ocr_net.load_param(argv[4]) != 0 || ocr_net.load_model(argv[5]) != 0) { std::cerr << "Failed to load OCR model." << std::endl; return -1; } // 执行文本检测 ScaleParam scaleParam = getScaleParam(src, 736); Mat srcResize; resize(src, srcResize, cv::Size(scaleParam.dstWidth, scaleParam.dstHeight)); ncnn::Mat dbnet_input = ncnn::Mat::from_pixels(srcResize.data, ncnn::Mat::PIXEL_BGR, srcResize.cols, srcResize.rows); dbnet_input.substract_mean_normalize(dbnet_meanValues, dbnet_normValues); ncnn::Extractor dbnet_extractor = dbnet.create_extractor(); dbnet_extractor.input("input0", dbnet_input); ncnn::Mat dbnet_output; dbnet_extractor.extract("out1", dbnet_output); // 解析检测输出 Mat fMapMat(srcResize.rows, srcResize.cols, CV_32FC1); memcpy(fMapMat.data, (float *)dbnet_output.data, srcResize.rows * srcResize.cols * sizeof(float)); Mat norfMapMat = fMapMat > 0.3f; std::vector text_boxes = findRsBoxes(fMapMat, norfMapMat, scaleParam, 0.5f, 4.0f); // 初始化计数器 int save_count = 0; // 执行文本识别 for (auto& box : text_boxes) { // 裁剪文本区域 Rect rect = cv::boundingRect(box.boxPoint); Mat text_patch = src(rect).clone(); // 保存裁剪后的原始区域 std::string filename = "cropped_" + std::to_string(save_count) + ".png"; // 保存为PNG格式 imwrite(filename, text_patch); save_count++; // 执行OCR识别 const float ocr_mean[3] = {127.5f, 127.5f, 127.5f}; const float ocr_norm[3] = {1.0f / 127.5f, 1.0f / 127.5f, 1.0f / 127.5f}; std::string textResult; processFrame(text_patch, ocr_net, keys, ocr_mean, ocr_norm, 32, textResult); box.text = textResult; } // 对文本框进行排序 std::sort(text_boxes.begin(), text_boxes.end(), [](const TextBox &a, const TextBox &b) { cv::Rect rectA = cv::boundingRect(a.boxPoint); cv::Rect rectB = cv::boundingRect(b.boxPoint); int centerYA = rectA.y + rectA.height / 2; int centerYB = rectB.y + rectB.height / 2; if (centerYA != centerYB) { return centerYA < centerYB; } return (rectA.x + rectA.width / 2) < (rectB.x + rectB.width / 2); }); // 可视化结果 for (const auto& box : text_boxes) { drawTextBox(src, box.boxPoint, 1); std::cout << box.text << std::endl; // 由于opencv并不支持绘制中文,所以一下代码可以注释掉,感兴趣的可以自己尝试一下。 // putText(src, box.text, box.boxPoint[0], // FONT_HERSHEY_SIMPLEX, 0.8, // Scalar(0, 0, 255), 1); } // 显示结果 imshow("Text_result", src); waitKey(0); return 0; } ``` #### 5.7.4 编译调试 ##### 5.7.4.1 编译环境搭建 - 请确保你已经按照 [开发环境搭建指南](https://gitee.com/LockzhinerAI/LockzhinerVisionModule/blob/master/docs/introductory_tutorial/cpp_development_environment.md) 正确配置了开发环境。 - 同时已经正确连接开发板。 ##### 5.7.4.2 Cmake介绍 ```cmake cmake_minimum_required(VERSION 3.10) project(ocr_synthesis) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) ## 定义项目根目录路径 set(PROJECT_ROOT_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../..") message("PROJECT_ROOT_PATH = " ${PROJECT_ROOT_PATH}) include("${PROJECT_ROOT_PATH}/toolchains/arm-rockchip830-linux-uclibcgnueabihf.toolchain.cmake") ## 定义 OpenCV SDK 路径 set(OpenCV_ROOT_PATH "${PROJECT_ROOT_PATH}/third_party/opencv-mobile-4.10.0-lockzhiner-vision-module") set(OpenCV_DIR "${OpenCV_ROOT_PATH}/lib/cmake/opencv4") find_package(OpenCV REQUIRED) set(OPENCV_LIBRARIES "${OpenCV_LIBS}") ## 定义 LockzhinerVisionModule SDK 路径 set(LockzhinerVisionModule_ROOT_PATH "${PROJECT_ROOT_PATH}/third_party/lockzhiner_vision_module_sdk") set(LockzhinerVisionModule_DIR "${LockzhinerVisionModule_ROOT_PATH}/lib/cmake/lockzhiner_vision_module") find_package(LockzhinerVisionModule REQUIRED) ## ncnn配置 set(NCNN_ROOT_DIR "${PROJECT_ROOT_PATH}/third_party/ncnn-20240820-lockzhiner-vision-module") # 确保third_party层级存在 message(STATUS "Checking ncnn headers in: ${NCNN_ROOT_DIR}/include/ncnn") ## 验证头文件存在 if(NOT EXISTS "${NCNN_ROOT_DIR}/include/ncnn/net.h") message(FATAL_ERROR "ncnn headers not found. Confirm the directory contains ncnn: ${NCNN_ROOT_DIR}") endif() set(NCNN_INCLUDE_DIRS "${NCNN_ROOT_DIR}/include") set(NCNN_LIBRARIES "${NCNN_ROOT_DIR}/lib/libncnn.a") add_executable(Test-OcrLite OcrLite.cc) target_include_directories(Test-OcrLite PRIVATE ${LOCKZHINER_VISION_MODULE_INCLUDE_DIRS} ${NCNN_INCLUDE_DIRS}) target_link_libraries(Test-OcrLite PRIVATE ${OPENCV_LIBRARIES} ${NCNN_LIBRARIES} ${LOCKZHINER_VISION_MODULE_LIBRARIES}) install( TARGETS Test-OcrLite RUNTIME DESTINATION . ) ``` ##### 5.7.4.3 编译项目 使用 Docker Destop 打开 LockzhinerVisionModule 容器并执行以下命令来编译项目 ```bash ## 进入Demo所在目录 cd /LockzhinerVisionModuleWorkSpace/LockzhinerVisionModule/Cpp_example/D07_ocr_synthesis ## 创建编译目录 rm -rf build && mkdir build && cd build ## 配置交叉编译工具链 export TOOLCHAIN_ROOT_PATH="/LockzhinerVisionModuleWorkSpace/arm-rockchip830-linux-uclibcgnueabihf" ## 使用cmake配置项目 cmake .. ## 执行编译项目 make -j8 && make install ``` 在执行完上述命令后,会在build目录下生成可执行文件。 #### 5.7.5 执行结果 ##### 5.7.5.1 运行前准备 - 请确保已经下载了模型,并保存在当前目录下。 ##### 5.7.5.2 运行过程 ```shell chmod 777 Test-OcrLite ./Test-OcrLite dbnet_op.param dbnet_op.bin crnn_lite_op.param crnn_lite_op.bin keys.txt ``` ##### 5.7.5.3 运行结果 - 原始图像 ![title](./images/D07_test.png) - 识别结果 ![title](./images/D07_Result_Rec.png) #### 5.7.6 总结 通过上述内容,我们成功实现了一个简单的OCR中文字符识别系统,包括: - 加载模型和待识别图像。 - 进行文本区域检测推理和文本识别推理。 - 打印并保存推理结果。 ### 5.8 图像分割 本章节在 Lockzhiner Vision Module 上基于paddleseg模型, 实现了一个PP-HumanSeg人像分割系统。 #### 5.8.1. 基本知识简介 ##### 5.8.1.1 人像分割简介 人像分割是一种基于计算机视觉的技术,通过深度学习算法精准识别图像或视频中的人物主体,将其与背景进行像素级分离。该技术可实时运行于移动端及嵌入式设备,广泛应用于虚拟背景、智能抠图、视频会议美颜等场景,支持复杂光照、多样姿态和遮挡情况下的高精度分割,兼顾处理速度与效果。 ##### 5.8.1.2 人像分割常用方法 目前对于实现人像分割任务的方法有很多,下面介绍几种常用的人像分割实现方法。 - 传统算法(如GrabCut)​:基于颜色直方图与图割优化,适合简单背景,计算量小但精度有限。 - U-Net系列:编码器-解码器结构,医学图像起家,适合精细边缘,需较高算力。 - DeepLab系列:采用空洞卷积扩大感受野,擅长复杂场景,模型较大。 - BiSeNet​:双分支结构平衡速度与精度,实时分割首选,移动端友好。 - PP-HumanSeg​:百度自研轻量模型,专为人像优化,支持半监督训练,RKNN部署效率高。 这些方法各有优势,其中在工业部署方面PP-HumanSeg(精度与速度平衡)和BiSeNet(高性价比)更适合,配合OpenCV后处理优化边缘。 #### 5.8.2. API 文档 ##### 5.8.2.1 RKNPU2Backend 类 ###### 5.8.2.1.1 头文件 ```cpp #include "rknpu2_backend/rknpu2_backend.h" ``` - 作用:创建一个RKNPU2Backend类,用于实现对rknn模型的处理。 ###### 5.8.2.1.2 构造类函数 ```cpp ockzhiner_vision_module::vision::RKNPU2Backend backend; ``` - 作用:创建一个RKNPU2Backen类型的对象实例,用于实现人像分割。 - 参数说明: - 无 - 返回值: - 无 ###### 5.8.2.1.3 Initialize 函数 ```cpp bool Initialize(const std::string &model_path, const std::string ¶m_path = "") override; ``` - 作用:初始化 RKNN 模型,加载模型文件和可选参数文件,完成推理引擎的准备工作。 - 参数说明: - model_path:必需参数,RKNN 模型文件路径(.rknn 格式)。 - param_path:可选参数,额外参数文件路径(某些场景下用于补充模型配置,默认空字符串)。 - 返回值:返回ture/false,表示模型初始化是否成功。 ###### 5.8.2.1.4 Run 函数 ```cpp bool Run(); ``` - 作用:执行模型推理计算,驱动输入数据通过模型计算得到输出结果。 - 参数说明: - 无 - 返回值: - true:推理执行成功。 - false:推理失败(可能原因:输入数据未准备、内存不足等)。 ###### 5.8.2.1.5 GetInputAttrs 函数 ```cpp const std::vector& GetInputAttrs() const; ``` - 作用:获取模型所有输入张量的属性信息(维度/形状、数据类型、量化参数等)。 - 参数说明: - 无 - 返回值:常量引用形式的 rknn_tensor_attr 向量,包含输入张量属性。 ###### 5.8.2.1.6 GetOutputAttrs 函数 ```cpp const std::vector& GetInputMemories() const; ``` - 作用:获取模型所有输出张量的属性信息。 - 参数说明:无 - 返回值:常量引用形式的 rknn_tensor_attr 向量,包含输出张量属性。 #### 5.8.3. PP-Humanseg人像分割代码解析 ##### 5.8.3.1 流程图 ``` 开始 │ ├── 参数检查 (argc == 3) │ └── 错误 → 输出Usage并退出 │ ├── 初始化 RKNN 后端 │ ├── 加载模型 │ └── 初始化失败 → 错误退出 │ ├── 加载与预处理输入图像 │ ├── 读取图像文件 │ ├── 获取输入属性 │ ├── 调用 preprocess() 函数 │ ├── 调整尺寸和颜色空间 │ ├── 量化图像数据 │ └── 验证尺寸匹配 │ └── 预处理失败 → 错误退出 │ ├── 执行推理 │ ├── 拷贝预处理后的图像数据到输入内存 │ ├── 执行推理 │ └── 推理失败 → 错误退出 │ ├── 后处理 │ ├── 获取输出属性及内存 │ ├── 调用 postprocess() 函数 │ ├── 解析输出数据生成概率图 │ ├── 自适应阈值分割 │ ├── 多尺度形态学处理 │ ├── 智能边缘优化 │ └── 多模态结果融合 │ ├── 结果展示与保存 │ ├── 计算推理时间 │ ├── 生成并保存结果图像 │ ├── 显示原始图像、掩膜及结果图像 │ └── 程序结束 ``` ##### 5.8.3.2 核心代码解析 - 初始化模型 ```cpp backend.Initialize(model_path) ``` - 获取输入输出属性 ```cpp const auto& input_attrs = backend.GetInputAttrs(); const auto& output_attrs = backend.GetOutputAttrs(); ``` - 对输入图像进行推理 ```cpp backend.Run() ``` 自定义函数说明 - pp-humanseg输入预处理 ```cpp cv::Mat preprocess(const cv::Mat& image, const std::vector& input_dims) ``` - 作用:对输入图像进行预处理操作,包括 ​​尺寸调整​​、​​颜色空间转换​​ 和 ​​量化处理​​,使其符合 RKNN 模型的输入要求。 - 参数说明: - image:输入图像(BGR 格式的 cv::Mat 对象)。 - input_dims:模型输入张量的维度定义(需满足 [1, H, W, 3] 的 NHWC 格式)。 - 返回值: - 返回预处理后的量化张量(cv::Mat,数据类型为 CV_8S)。 - 若输入维度不合法,返回空矩阵(cv::Mat())并报错。 - pp-humanseg输入后处理 ```cpp cv::Mat postprocess(const rknn_tensor_mem* output_mem, const std::vector& output_dims, const cv::Size& target_size) ``` - 作用:将模型输出的原始张量转换为高精度分割掩膜,包含 ​​概率解码​​、​​动态阈值分割​​、​​形态学优化​​和​​边缘增强​​等步骤,最终生成与原始图像尺寸匹配的二值化掩膜。 - 参数说明: - output_mem:模型输出的内存指针,包含量化后的原始数据。 - output_dims:模型输出的维度信息,需满足 [1, 2, H, W] 的 NCHW 格式。 - target_size:目标输出尺寸。 - 返回值:返回优化后的二值化掩膜。 ##### 5.8.3.3 完整代码实现 ```cpp #include #include #include #include "rknpu2_backend/rknpu2_backend.h" #include #include #include using namespace std::chrono; // 输入和输出量化的scale和zeropoint const float INPUT_SCALE = 1.0f / 255.0f; const int INPUT_ZP = -128; const float OUTPUT_SCALE = 0.034558f; const int OUTPUT_ZP = -128; // 预处理函数 cv::Mat preprocess(const cv::Mat& image, const std::vector& input_dims) { // 确保输入维度为NHWC [1, H, W, 3] if (input_dims.size() != 4 || input_dims[0] != 1 || input_dims[3] != 3) { std::cerr << "Invalid input dimensions" << std::endl; return cv::Mat(); } const size_t input_h = input_dims[1]; const size_t input_w = input_dims[2]; // Resize并转换颜色空间 cv::Mat resized, rgb; cv::resize(image, resized, cv::Size(input_w, input_h)); cv::cvtColor(resized, rgb, cv::COLOR_BGR2RGB); // 量化到INT8 cv::Mat quantized; rgb.convertTo(quantized, CV_8S, 255.0 * INPUT_SCALE, INPUT_ZP); return quantized; } // 后处理函数 cv::Mat postprocess(const rknn_tensor_mem* output_mem, const std::vector& output_dims, const cv::Size& target_size) { // 验证输出维度为NCHW [1, 2, H, W] if (output_dims.size() != 4 || output_dims[0] != 1 || output_dims[1] != 2) { std::cerr << "Invalid output dimensions" << std::endl; return cv::Mat(); } const int8_t* data = static_cast(output_mem->virt_addr); const int h = output_dims[2]; const int w = output_dims[3]; // ================= 1. 概率图生成优化 ================= cv::Mat prob_map(h, w, CV_32FC1); // 基于192x192模型的缩放补偿 float spatial_weight = 1.0f - (h * w) / (192.0f * 192.0f); for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { const int bg_idx = 0 * h * w + y * w + x; const int fg_idx = 1 * h * w + y * w + x; // 带饱和保护的反量化 float bg_logit = std::clamp((data[bg_idx] - OUTPUT_ZP) * OUTPUT_SCALE, -10.0f, 10.0f); float fg_logit = std::clamp((data[fg_idx] - OUTPUT_ZP) * OUTPUT_SCALE, -10.0f, 10.0f); // 空间注意力加权(中心区域增强) float center_weight = 1.0f - (std::abs(x - w/2.0f)/(w/2.0f) + std::abs(y - h/2.0f)/(h/2.0f))/2.0f; fg_logit *= (1.2f + 0.3f * center_weight * spatial_weight); // 稳健的Softmax计算 float max_logit = std::max(bg_logit, fg_logit); float exp_sum = expf(bg_logit - max_logit) + expf(fg_logit - max_logit); prob_map.at(y, x) = expf(fg_logit - max_logit) / (exp_sum + 1e-8f); } } // ================= 2. 自适应阈值优化 ================= cv::Mat binary_mask; // 重点区域检测 cv::Mat prob_roi = prob_map(cv::Rect(w/4, h/4, w/2, h/2)); float center_mean = cv::mean(prob_roi)[0]; float dynamic_thresh = std::clamp(0.45f - (center_mean - 0.5f) * 0.3f, 0.25f, 0.65f); cv::threshold(prob_map, binary_mask, dynamic_thresh, 255, cv::THRESH_BINARY); binary_mask.convertTo(binary_mask, CV_8U); // ================= 3. 多尺度形态学处理 ================= std::vector mask_pyramid; // 构建金字塔 cv::buildPyramid(binary_mask, mask_pyramid, 2); // 小尺度去噪 cv::Mat kernel1 = cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(3,3)); cv::morphologyEx(mask_pyramid[1], mask_pyramid[1], cv::MORPH_OPEN, kernel1); // 中尺度填充 cv::Mat kernel2 = cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(5,5)); cv::morphologyEx(mask_pyramid[0], mask_pyramid[0], cv::MORPH_CLOSE, kernel2); // 金字塔重建 cv::Mat refined_mask; cv::pyrUp(mask_pyramid[1], refined_mask, mask_pyramid[0].size()); cv::bitwise_and(refined_mask, mask_pyramid[0], refined_mask); // ================= 4. 智能边缘优化 ================= cv::Mat edge_weights; cv::distanceTransform(refined_mask, edge_weights, cv::DIST_L2, 3); cv::normalize(edge_weights, edge_weights, 0, 1.0, cv::NORM_MINMAX); cv::Mat probabilistic_edges; cv::Canny(refined_mask, probabilistic_edges, 50, 150); probabilistic_edges.convertTo(probabilistic_edges, CV_32F, 1.0/255.0); cv::Mat final_edges; cv::multiply(probabilistic_edges, edge_weights, final_edges); final_edges.convertTo(final_edges, CV_8U, 255.0); // ================= 5. 多模态融合输出 ================= cv::Mat resized_mask; cv::resize(refined_mask, resized_mask, target_size, 0, 0, cv::INTER_LANCZOS4); cv::Mat final_mask; cv::bilateralFilter(resized_mask, final_mask, 5, 15, 15); // 最终保边处理 cv::Mat edge_mask_hr; cv::resize(final_edges, edge_mask_hr, target_size, 0, 0, cv::INTER_NEAREST); final_mask.setTo(255, edge_mask_hr > 128); return final_mask; } int main(int argc, char* argv[]) { if (argc != 3) { std::cerr << "Usage: " << argv[0] << " " << std::endl; return 1; } const std::string model_path = argv[1]; const std::string image_path = argv[2]; // 初始化RKNN后端 lockzhiner_vision_module::vision::RKNPU2Backend backend; if (!backend.Initialize(model_path)) { std::cerr << "Failed to initialize RKNN backend" << std::endl; return -1; } // 加载图像 cv::Mat image = cv::imread(image_path); if (image.empty()) { std::cerr << "Failed to read image: " << image_path << std::endl; return -1; } // 获取输入属性 const auto& input_attrs = backend.GetInputAttrs(); if (input_attrs.empty()) { std::cerr << "No input attributes found" << std::endl; return -1; } const auto& input_attr = input_attrs[0]; std::vector input_dims(input_attr.dims, input_attr.dims + input_attr.n_dims); // 预处理 cv::Mat preprocessed = preprocess(image, input_dims); if (preprocessed.empty()) { std::cerr << "Preprocessing failed" << std::endl; return -1; } // 验证输入数据尺寸 const size_t expected_input_size = input_attr.size_with_stride; const size_t actual_input_size = preprocessed.total() * preprocessed.elemSize(); if (expected_input_size != actual_input_size) { std::cerr << "Input size mismatch! Expected: " << expected_input_size << ", Actual: " << actual_input_size << std::endl; return -1; } // 拷贝输入数据 const auto& input_memories = backend.GetInputMemories(); if (input_memories.empty() || !input_memories[0]) { std::cerr << "Invalid input memory" << std::endl; return -1; } memcpy(input_memories[0]->virt_addr, preprocessed.data, actual_input_size); // 执行推理 high_resolution_clock::time_point start_time = high_resolution_clock::now(); if (!backend.Run()) { std::cerr << "Inference failed" << std::endl; return -1; } // 获取输出 const auto& output_attrs = backend.GetOutputAttrs(); if (output_attrs.empty()) { std::cerr << "No output attributes found" << std::endl; return -1; } const auto& output_memories = backend.GetOutputMemories(); if (output_memories.empty() || !output_memories[0]) { std::cerr << "Invalid output memory" << std::endl; return -1; } // 后处理 const auto& output_attr = output_attrs[0]; std::vector output_dims(output_attr.dims, output_attr.dims + output_attr.n_dims); cv::Mat mask = postprocess(output_memories[0], output_dims, image.size()); high_resolution_clock::time_point end_time = high_resolution_clock::now(); auto time_span = duration_cast(end_time - start_time); std::cout << "单张图片推理时间(ms): " << time_span.count() << std::endl; // 生成结果 cv::Mat result; cv::bitwise_and(image, image, result, mask); // 保存结果 const std::string output_path = "result.jpg"; cv::imwrite(output_path, result); std::cout << "Result saved to: " << output_path << std::endl; // 显示调试视图 cv::imshow("Original", image); cv::imshow("Mask", mask); cv::imshow("Result", result); cv::waitKey(0); return 0; } ``` #### 5.8.4. 编译调试 ##### 5.8.4.1 编译环境搭建 - 请确保你已经按照 [开发环境搭建指南](https://gitee.com/LockzhinerAI/LockzhinerVisionModule/blob/master/docs/introductory_tutorial/cpp_development_environment.md) 正确配置了开发环境。 - 同时已经正确连接开发板。 ##### 5.8.4.2 Cmake介绍 ```cmake cmake_minimum_required(VERSION 3.10) project(pp_humanseg) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # 定义项目根目录路径 set(PROJECT_ROOT_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../..") message("PROJECT_ROOT_PATH = " ${PROJECT_ROOT_PATH}) include("${PROJECT_ROOT_PATH}/toolchains/arm-rockchip830-linux-uclibcgnueabihf.toolchain.cmake") # 定义 OpenCV SDK 路径 set(OpenCV_ROOT_PATH "${PROJECT_ROOT_PATH}/third_party/opencv-mobile-4.10.0-lockzhiner-vision-module") set(OpenCV_DIR "${OpenCV_ROOT_PATH}/lib/cmake/opencv4") find_package(OpenCV REQUIRED) set(OPENCV_LIBRARIES "${OpenCV_LIBS}") # 定义 LockzhinerVisionModule SDK 路径 set(LockzhinerVisionModule_ROOT_PATH "${PROJECT_ROOT_PATH}/third_party/lockzhiner_vision_module_sdk") set(LockzhinerVisionModule_DIR "${LockzhinerVisionModule_ROOT_PATH}/lib/cmake/lockzhiner_vision_module") find_package(LockzhinerVisionModule REQUIRED) # 配置rknpu2 set(RKNPU2_BACKEND_BASE_DIR "${LockzhinerVisionModule_ROOT_PATH}/include/lockzhiner_vision_module/vision/deep_learning/runtime") if(NOT EXISTS ${RKNPU2_BACKEND_BASE_DIR}) message(FATAL_ERROR "RKNPU2 backend base dir missing: ${RKNPU2_BACKEND_BASE_DIR}") endif() add_executable(Test-pp_humanseg pp_humanseg.cc) target_include_directories(Test-pp_humanseg PRIVATE ${LOCKZHINER_VISION_MODULE_INCLUDE_DIRS} ${rknpu2_INCLUDE_DIRS} ${RKNPU2_BACKEND_BASE_DIR}) target_link_libraries(Test-pp_humanseg PRIVATE ${OPENCV_LIBRARIES} ${NCNN_LIBRARIES} ${LOCKZHINER_VISION_MODULE_LIBRARIES}) install( TARGETS Test-pp_humanseg RUNTIME DESTINATION . ) ``` ##### 5.8.4.3 编译项目 使用 Docker Destop 打开 LockzhinerVisionModule 容器并执行以下命令来编译项目。 ```bash # 进入Demo所在目录 cd /LockzhinerVisionModuleWorkSpace/LockzhinerVisionModule/Cpp_example/D08_pp_humanseg # 创建编译目录 rm -rf build && mkdir build && cd build # 配置交叉编译工具链 export TOOLCHAIN_ROOT_PATH="/LockzhinerVisionModuleWorkSpace/arm-rockchip830-linux-uclibcgnueabihf" # 使用cmake配置项目 cmake .. # 执行编译项目 make -j8 && make install ``` 在执行完上述命令后,会在build目录下生成可执行文件。 #### 5.8.5. 执行结果 ##### 5.8.5.1 运行前准备 - 请确保你已经下载了 [凌智视觉模块人像分割模型](https://gitee.com/LockzhinerAI/LockzhinerVisionModule/releases/download/v0.0.6/pp-humanseg.rknn) ##### 5.8.5.2 运行过程 ```shell chmod 777 Test-pp_humanseg # 对人像进行分割 ./Test-pp_humanseg pp-humanseg.rknn image_path ``` ##### 5.8.5.3 运行效果 ###### 5.8.5.3.1 人像分割结果 - 原始图像 ![title](./images/D08_2.png) - 分割结果 ![title](./images/D08_3.jpg) ###### 5.8.5.3.2 注意事项 由于分割的模型很小,并且在模型转换过程中会有精度损失,所以在测试的时候尽量选择背景比较纯净的图像效果比较好。 #### 5.8.6. 总结 通过上述内容,我们成功实现了一个简单的人像分割的例子,包括: - 加载图像分割的rknn模型和待分割图像。 - 图像预处理和模型推理。 - 图像后处理并保存分割结果。 ### 5.9 车牌识别 本章节在 Lockzhiner Vision Module 上基于 PaddleDet 目标检测类和rcnn文本识别模型,实现了一个车牌识别案例。 #### 5.9.1. 基本知识简介 ##### 5.9.1.1 车牌识别简介 车牌识别是一种基于计算机视觉和深度学习的技术,通过图像处理、字符分割和光学字符识别算法,自动提取车辆牌照中的文字与数字信息。该技术可实时识别不同光照、角度和复杂背景下的车牌,广泛应用于智能交通管理、停车场收费系统、电子警察执法等场景。 ##### 5.9.1.2 车牌识别流程 - 车牌检测: - 深度学习​​:YOLO、Faster R-CNN等检测模型定位车牌区域。 - 传统方法:颜色空间分析(蓝色/黄色车牌)+边缘检测。 - 车牌矫正:透视变换调整倾斜角度,Hough直线检测旋转角度。 - 字符分割:​​垂直投影法分割字符,连通域分析处理粘连字符。 - 字符识别: - CRNN​​:CNN+RNN+CTC结构,端到端识别。 - OCR模型​​:Tesseract、PaddleOCR等开源工具。 #### 5.9.2. API 文档 ##### 5.9.2.1 PaddleDetection 类 ###### 5.9.2.1.1 头文件 ```cpp #include ``` ###### 5.9.2.1.2 构造函数 ```cpp lockzhiner_vision_module::vision::PaddleDetection(); ``` - 作用: - 创建一个 PaddleDetection 对象,并初始化相关成员变量。 - 参数: - 无 - 返回值: - 无 ###### 5.9.2.2.1.3 Initialize函数 ```cpp bool Initialize(const std::string& model_path); ``` - 作用: - 加载预训练的 PaddleDetection 模型。 - 参数: - model_path:模型路径,包含模型文件和参数文件。 - 返回值: - true:模型加载成功。 - false:模型加载失败。 ###### 5.9.2.1.4 SetThreshold函数 ```cpp void SetThreshold(float score_threshold = 0.5, float nms_threshold = 0.3); ``` - 作用: - 设置目标检测的置信度阈值和NMS阈值。 - 参数: - score_threshold:置信度阈值,默认值为0.5。 - nms_threshold:NMS阈值,默认值为0.3。 - 返回值: - 无 ###### 5.9.2.1.5 Predict函数 ```cpp std::vector Predict(const cv::Mat& image); ``` - 作用: - 使用加载的模型对输入图像进行目标检测,返回检测结果。 - 参数: - input_mat (const cv::Mat&): 输入的图像数据,通常是一个 cv::Mat 变量。 - 返回值: - 返回一个包含多个 DetectionResult 对象的向量,每个对象表示一个检测结果。 ##### 5.9.2.2 Net类 ###### 5.9.2.2.1 头文件 ```cpp #include ``` - 作用:用于声明Net类,使得Net类可以在当前文件中使用。 ###### 5.9.2.2.2 构造类函数 ```cpp ncnn::Net ocr_net; ``` - 作用:创建一个Net类型的对象实例,用于加载模型和参数等。 - 参数说明:无 - 返回值:无 ###### 5.9.2.3.3 load_param函数 ```cpp int load_param(const DataReader& dr); ``` - 参数说明: - dr:传入的参数文件路径。 - 返回值: - 返回值为0表示加载参数文件成功。 ###### 5.9.2.2.4 load_model函数 ```cpp int load_model(const DataReader& dr); ``` - 参数说明: - dr:传入的模型文件路径。 - 返回值:返回值为0表示加载模型成功。 ###### 5.9.2.2.5 from_pixels函数 ```cpp ncnn::Mat::from_pixels(plate_img.data, ncnn::Mat::PIXEL_BGR, plate_img.cols, plate_img.rows); ``` - 作用​​:将原始图像像素数据转换为 ncnn::Mat 对象,​​同时进行缩放操作​​,适配神经网络的输入尺寸要求。 - 参数说明: - plate_img.data:输入图像的像素数据指针。 - ncnn::Mat::PIXEL_BGR:输入像素的颜色格式。 - plate_img.cols, plate_img.rows:原始图像的宽度和高度。 - 返回值​​:返回一个 ncnn::Mat 对象,包含缩放后的图像数据,格式为 ​​CHW 排列。 ##### 5.9.2.3 Extractor类 ###### 5.9.2.3.1 头文件 ```cpp #include ``` - 作用:用于声明Extractor类,使得Extractor类可以在当前文件中使用。 ###### 5.9.2.3.2 构造类函数 ```cpp ncnn::Extractor ex = ocr_net.create_extractor(); ``` - 作用:从已经加载了神经网络模型的 net 中创建一个 Extractor 实例,用于执行车牌识别的推理任务。 - 参数说明:无 - 返回值:无 #### 5.9.3. 车牌识别代码解析 ##### 5.9.3.1 流程图 ``` 开始 │ ├── 参数检查 (argc == 4 或 5) │ └── 错误 → 输出Usage并退出 │ ├── 初始化车牌检测模型 (PaddleDet) │ ├── 使用 argv[1] 加载模型 │ └── 失败 → 错误退出 │ ├── 初始化OCR模型 (ncnn) │ ├── 使用 argv[2] 和 argv[3] 加载 param 和 bin 文件 │ └── 失败 → 错误退出 │ ├── 设置字体与显示参数 │ ├── 判断运行模式 │ ├── argc == 5 → 图片处理模式 │ │ ├── 读取图像文件 │ │ ├── 执行车牌检测 │ │ ├── 绘制检测框 │ │ ├── 对每个车牌调用 RecognizePlate() │ │ ├── 叠加识别结果并显示 │ │ ├── 控制台输出详细信息 │ │ └── 等待按键退出 │ │ │ └── argc == 4 → 实时摄像头模式 │ ├── 启动设备连接服务 │ ├── 打开摄像头 │ │ │ └── 实时处理循环 │ ├── 读取一帧图像 │ ├── 执行车牌检测 │ ├── 绘制检测框 │ ├── 对每个车牌识别字符 │ ├── 叠加识别结果 │ ├── 控制台输出 + 时间戳 │ ├── 显示图像 │ ├── 按 ESC 退出循环 │ └── 程序结束 ``` ##### 5.9.3.2 核心代码解析 - 初始化车牌检测模型 ```cpp lockzhiner_vision_module::vision::PaddleDet detector; if (!detector.Initialize(argv[1])) { cerr << "Failed to load detection model: " << argv[1] << endl; return 1; } ``` - 车牌检测模型推理 ```cpp auto results = detector.Predict(image); ``` - 可视化并显示推理结果 ```cpp cv::Mat output_image = image.clone(); for (const auto& det : results) { cv::Rect rect( static_cast(det.box.x), static_cast(det.box.y), static_cast(det.box.width), static_cast(det.box.height) ); cv::rectangle( output_image, rect, cv::Scalar(0, 255, 0), 1, cv::LINE_AA ); } cv::imshow("Detection Result", output_image); ``` - 加载字符识别参数和模型 ```cpp ocr_net.load_param(param_path.c_str()) ocr_net.load_model(model_path.c_str()) ``` - 归一化处理 ```cpp const float mean[3] = {127.5f, 127.5f, 127.5f}; const float norm[3] = {0.0078125f, 0.0078125f, 0.0078125f}; in.substract_mean_normalize(mean, norm); ``` - 解码预测结果 ```cpp string license; vector preb; for (int c = 0; c < feat.c; c++) { const float* data = feat.channel(c); for (int w = 0; w < feat.w; w++) { float max_val = -FLT_MAX; int max_idx = 0; for (int h = 0; h < feat.h; h++) { float val = data[w + h * feat.w]; if (val > max_val) { max_val = val; max_idx = h; } } preb.push_back(max_idx); } } // 后处理去重 vector valid_labels; int prev = -1; for (size_t i = 0; i < preb.size(); ++i) { if (preb[i] != 67 && preb[i] != prev) { valid_labels.push_back(preb[i]); } prev = preb[i]; } for (int idx : valid_labels) { license += plate_chars[idx]; } ``` 自定义函数说明 - 车牌字符识别 ```cpp string RecognizePlate(cv::Mat plate_img); ``` - 作用:对分割出来的车牌进行识别。 - 参数说明: - plate_image:待识别的车牌图像。 - 返回值:返回一串字符串类型的数据,表示识别到的车牌是什么。 ##### 5.9.3.3 完整代码实现 ```cpp #include #include #include #include #include #include #include #include "myfontface.h" using namespace std; using namespace std::chrono; // OCR字符集配置 const string plate_chars[68] = { "京", "沪", "津", "渝", "冀", "晋", "蒙", "辽", "吉", "黑", "苏", "浙", "皖", "闽", "赣", "鲁", "豫", "鄂", "湘", "粤", "桂", "琼", "川", "贵", "云", "藏", "陕", "甘", "青", "宁", "新", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "I", "O", "-"}; ncnn::Net ocr_net; bool InitOCRModel(const string& param_path, const string& model_path) { return ocr_net.load_param(param_path.c_str()) == 0 && ocr_net.load_model(model_path.c_str()) == 0; } string RecognizePlate(cv::Mat plate_img) { cv::resize(plate_img, plate_img, cv::Size(94, 24)); ncnn::Mat in = ncnn::Mat::from_pixels(plate_img.data, ncnn::Mat::PIXEL_BGR, plate_img.cols, plate_img.rows); const float mean[3] = {127.5f, 127.5f, 127.5f}; const float norm[3] = {0.0078125f, 0.0078125f, 0.0078125f}; in.substract_mean_normalize(mean, norm); ncnn::Extractor ex = ocr_net.create_extractor(); ex.input("input.1", in); ncnn::Mat feat; ex.extract("131", feat); string license; vector preb; for (int c = 0; c < feat.c; c++) { const float* data = feat.channel(c); for (int w = 0; w < feat.w; w++) { float max_val = -FLT_MAX; int max_idx = 0; for (int h = 0; h < feat.h; h++) { float val = data[w + h * feat.w]; if (val > max_val) { max_val = val; max_idx = h; } } preb.push_back(max_idx); } } // 后处理去重 vector valid_labels; int prev = -1; for (size_t i = 0; i < preb.size(); ++i) { if (preb[i] != 67 && preb[i] != prev) { valid_labels.push_back(preb[i]); } prev = preb[i]; } for (int idx : valid_labels) { license += plate_chars[idx]; } return license.empty() ? "UNKNOWN" : license; } int main(int argc, char** argv) { // 参数验证 if (argc < 4 || argc > 5) { cerr << "Usage: " << argv[0] << " [image_path]\n" << "Example:\n" << " Realtime: " << argv[0] << " det_model ocr.param ocr.bin\n" << " Image: " << argv[0] << " det_model ocr.param ocr.bin test.jpg\n"; return 1; } // 初始化检测模型 lockzhiner_vision_module::vision::PaddleDet detector; if (!detector.Initialize(argv[1])) { cerr << "Failed to load detection model: " << argv[1] << endl; return 1; } // 初始化OCR模型 if (!InitOCRModel(argv[2], argv[3])) { cerr << "Failed to load OCR model: " << argv[2] << " and " << argv[3] << endl; return 1; } MyFontFace myfont; // 设置文字参数 double font_scale = 0.6; int thickness = 1; // 图片处理模式 if (argc == 5) { cv::Mat image = cv::imread(argv[4]); if (image.empty()) { cerr << "Failed to read image: " << argv[4] << endl; return 1; } auto results = detector.Predict(image); // 可视化并显示结果 cv::Mat output_image = image.clone(); for (const auto& det : results) { cv::Rect rect( static_cast(det.box.x), static_cast(det.box.y), static_cast(det.box.width), static_cast(det.box.height) ); cv::rectangle( output_image, rect, cv::Scalar(0, 255, 0), 1, cv::LINE_AA ); } cout << "\n===== 检测到 " << results.size() << " 个车牌 =====" << endl; for (size_t i = 0; i < results.size(); ++i) { const auto& det = results[i]; cv::Rect roi(det.box.x, det.box.y, det.box.width, det.box.height); roi &= cv::Rect(0, 0, image.cols, image.rows); if (roi.area() > 0) { cv::Mat plate_img = image(roi); cv::imshow("DetectionSeg Result", plate_img); string plate_num = RecognizePlate(plate_img); // 左上角偏移 cv::Point text_org(roi.x + 2, roi.y - 2); // 先绘制黑色背景提升可读性 cv::putText(output_image, plate_num, text_org, cv::Scalar(0, 0, 0), // 颜色 myfont, // 字体对象 font_scale, // 字体尺寸 thickness + 2, // 线宽 cv::PutTextFlags::PUT_TEXT_ALIGN_LEFT, // 对齐方式 cv::Range(0, 0)); // 自动换行范围(0表示不换行) cv::putText(output_image, plate_num, text_org, cv::Scalar(127, 0, 127), myfont, 10); cout << "车牌 " << i+1 << ":\n" << " 位置: [" << roi.x << ", " << roi.y << ", " << roi.width << "x" << roi.height << "]\n" << " 置信度: " << det.score << "\n" << " 识别结果: " << plate_num << "\n" << endl; cv::imshow("Detection Result", output_image); } } cv::waitKey(0); } // 实时摄像头模式 else { // 初始化设备连接 lockzhiner_vision_module::edit::Edit edit; if (!edit.StartAndAcceptConnection()) { std::cerr << "Error: Failed to start and accept connection." << std::endl; return EXIT_FAILURE; } std::cout << "Device connected successfully." << std::endl; cv::VideoCapture cap; cap.set(cv::CAP_PROP_FRAME_WIDTH, 640); cap.set(cv::CAP_PROP_FRAME_HEIGHT, 480); cap.open(0); if (!cap.isOpened()) { cerr << "Error: Could not open camera" << endl; return 1; } cout << "实时识别模式启动 (按ESC退出)..." << endl; cv::Mat frame; while (true) { cap >> frame; if (frame.empty()) continue; auto results = detector.Predict(frame); cv::Mat display_frame = frame.clone(); for (const auto& det : results) { cv::Rect rect( static_cast(det.box.x), static_cast(det.box.y), static_cast(det.box.width), static_cast(det.box.height) ); cv::rectangle( display_frame, rect, cv::Scalar(0, 255, 0), 1, cv::LINE_AA ); } // 添加时间戳 auto now = system_clock::now(); time_t now_time = system_clock::to_time_t(now); cout << "\n===== " << ctime(&now_time) << "检测到 " << results.size() << " 个车牌 =====" << endl; for (const auto& det : results) { cv::Rect roi(det.box.x, det.box.y, det.box.width, det.box.height); roi &= cv::Rect(0, 0, frame.cols, frame.rows); if (roi.area() > 0) { cv::Mat plate_img = frame(roi); string plate_num = RecognizePlate(plate_img); // 左上角偏移 cv::Point text_org(roi.x + 2, roi.y - 2); // 先绘制黑色背景提升可读性 cv::putText(display_frame, plate_num, text_org, cv::Scalar(0, 0, 0), // 颜色 myfont, // 字体对象 font_scale, // 字体尺寸 thickness + 2, // 线宽 cv::PutTextFlags::PUT_TEXT_ALIGN_LEFT, // 对齐方式 cv::Range(0, 0)); // 自动换行范围(0表示不换行) cv::putText(display_frame, plate_num, text_org, cv::Scalar(127, 0, 127), myfont, 10); cout << "[实时结果] 位置(" << roi.x << "," << roi.y << ") 识别: " << plate_num << " (置信度: " << det.score << ")" << endl; } } edit.Print(display_frame); // 退出检测 if (cv::waitKey(1) == 27) break; } } return 0; } ``` #### 5.9.4. 编译调试 ##### 5.9.4.1 编译环境搭建 - 请确保你已经按照 [开发环境搭建指南](https://gitee.com/LockzhinerAI/LockzhinerVisionModule/blob/master/docs/introductory_tutorial/cpp_development_environment.md) 正确配置了开发环境。 - 同时已经正确连接开发板。 ##### 5.9.4.2 Cmake介绍 ```cmake cmake_minimum_required(VERSION 3.10) project(plate_recognize) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # 定义项目根目录路径 set(PROJECT_ROOT_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../..") message("PROJECT_ROOT_PATH = " ${PROJECT_ROOT_PATH}) include("${PROJECT_ROOT_PATH}/toolchains/arm-rockchip830-linux-uclibcgnueabihf.toolchain.cmake") # 定义 OpenCV SDK 路径 set(OpenCV_ROOT_PATH "${PROJECT_ROOT_PATH}/third_party/opencv-mobile-4.10.0-lockzhiner-vision-module") set(OpenCV_DIR "${OpenCV_ROOT_PATH}/lib/cmake/opencv4") find_package(OpenCV REQUIRED) set(OPENCV_LIBRARIES "${OpenCV_LIBS}") # 定义 LockzhinerVisionModule SDK 路径 set(LockzhinerVisionModule_ROOT_PATH "${PROJECT_ROOT_PATH}/third_party/lockzhiner_vision_module_sdk") set(LockzhinerVisionModule_DIR "${LockzhinerVisionModule_ROOT_PATH}/lib/cmake/lockzhiner_vision_module") find_package(LockzhinerVisionModule REQUIRED) # ncnn配置 set(NCNN_ROOT_DIR "${PROJECT_ROOT_PATH}/third_party/ncnn-20240820-lockzhiner-vision-module") # 确保third_party层级存在 message(STATUS "Checking ncnn headers in: ${NCNN_ROOT_DIR}/include/ncnn") # 验证头文件存在 if(NOT EXISTS "${NCNN_ROOT_DIR}/include/ncnn/net.h") message(FATAL_ERROR "ncnn headers not found. Confirm the directory contains ncnn: ${NCNN_ROOT_DIR}") endif() set(NCNN_INCLUDE_DIRS "${NCNN_ROOT_DIR}/include") set(NCNN_LIBRARIES "${NCNN_ROOT_DIR}/lib/libncnn.a") add_executable(Test-plate_recognize plate_recognize.cc) target_include_directories(Test-plate_recognize PRIVATE ${LOCKZHINER_VISION_MODULE_INCLUDE_DIRS} ${NCNN_INCLUDE_DIRS}) target_link_libraries(Test-plate_recognize PRIVATE ${OPENCV_LIBRARIES} ${NCNN_LIBRARIES} ${LOCKZHINER_VISION_MODULE_LIBRARIES}) install( TARGETS Test-plate_recognize RUNTIME DESTINATION . ) ``` ##### 5.9.4.3 编译项目 使用 Docker Destop 打开 LockzhinerVisionModule 容器并执行以下命令来编译项目 ```bash # 进入Demo所在目录 cd /LockzhinerVisionModuleWorkSpace/LockzhinerVisionModule/Cpp_example/D09_plate_recognize # 创建编译目录 rm -rf build && mkdir build && cd build # 配置交叉编译工具链 export TOOLCHAIN_ROOT_PATH="/LockzhinerVisionModuleWorkSpace/arm-rockchip830-linux-uclibcgnueabihf" # 使用cmake配置项目 cmake .. # 执行编译项目 make -j8 && make install ``` 在执行完上述命令后,会在build目录下生成可执行文件。 #### 5.9.5. 执行结果 ##### 5.9.5.1 运行前准备 - 请确保你已经下载了 [凌智视觉模块车牌识别参数文件](https://gitee.com/LockzhinerAI/LockzhinerVisionModule/releases/download/v0.0.6/lpr2d.param) - 请确保你已经下载了 [凌智视觉模块车牌识别bin文件](https://gitee.com/LockzhinerAI/LockzhinerVisionModule/releases/download/v0.0.6/lpr2d.bin) - 请确保你已经下载了 [凌智视觉模块车牌检测模型](https://gitee.com/LockzhinerAI/LockzhinerVisionModule/releases/download/v0.0.6/Plate_recognize.rknn) ##### 5.9.5.2 运行过程 ```shell chmode 777 Test-plate_recognize # 对车牌图片进行识别 ./Test-plate_recognize Plate_recognize.rknn lpr2d.param lpr2d.bin image_path # 摄像头实时识别 ./Test-plate_recognize Plate_recognize.rknn lpr2d.param lpr2d.bin ``` ##### 5.9.5.3 运行效果 - 原始图像 ![](./images/D09_2.jpg) - 分割出来的车牌图像 ![](./images/D09_3.png) - 识别结果 ![](./images/D09_4.png) #### 5.9.6. 总结 通过上述内容,我们成功实现了一个车牌识别系统,包括: - 加载车牌检测模型和rcnn字符识别模型 - 进行车牌检测并将车牌分割出来。 - 将分割出来的车牌进行字符识别。 - 保存识别结果。 ### 5.10 YOLOV5目标检测 本章节基于 Lockzhiner Vision Module 和 YOLOv5 目标检测模型,实现实时目标检测功能。 #### 5.10.1 基本知识简介 ##### 5.10.1.1 目标检测简介 目标检测是计算机视觉领域中的一个关键任务,它不仅需要识别图像中存在哪些对象,还需要定位这些对象的位置。具体来说,目标检测算法会输出每个检测到的对象的边界框(Bounding Box)以及其所属类别的概率或置信度得分。 - 应用场景:目标检测技术广泛应用于多个领域,包括但不限于安全监控、自动驾驶汽车、智能零售和医疗影像分析。 ##### 5.10.1.2 YOLOv5简介 YOLOv5 是 Ultralytics 在 2020 年基于 PyTorch 开发的一种高效目标检测模型,属于 YOLO 系列。它通过一次前向传播即可预测目标的类别和位置,适用于实时检测任务。YOLOv5 提供了多种模型大小(如 YOLOv5s、m、l、x),适应不同硬件条件。其结构包括骨干网络 CSPDarknet、特征融合层和检测头,支持多尺度预测和自定义训练,广泛用于各种检测场景。 #### 5.10.2 API 文档 ##### 5.10.2.1 YOLOv5模型类 ###### 5.10.2.1.1 头文件 ```cpp #include "yolov5.h" ``` ###### 5.10.2.1.2 模型初始化函数 ```cpp int init_yolov5_model(const char* model_path, rknn_app_context_t* ctx); ``` - 作用:加载YOLOv5 RKNN模型并初始化推理上下文 - 参数 - model_path:RKNN模型文件路径 - ctx:模型上下文指针 - 返回值: - 0:初始化成功 - -1:初始化失败 ###### 5.10.2.1.3 模型推理函数 ```cpp int inference_yolov5_model(rknn_app_context_t* ctx, object_detect_result_list* od_results); ``` - 作用:执行模型推理并获取检测结果 - 参数: - ctx:已初始化的模型上下文 - od_results:检测结果存储结构体指针 - 返回值: - 0:推理成功 - -1 :推理失败 ###### 5.10.2.1.4 模型释放函数 ```cpp void release_yolov5_model(rknn_app_context_t* ctx); ``` - 作用:释放模型相关资源 - 参数: - ctx:待释放的模型上下文 - 返回值:无 ##### 5.10.2.2 图像处理函数 ###### 5.10.2.2.1 Letterbox处理 ```cpp cv::Mat letterbox(cv::Mat& image); ``` - 作用:保持图像比例进行缩放,添加灰边填充 - 参数: -image:输入图像(RGB格式) - 返回值: - 返回预处理图像 ###### 5.10.2.2.2 坐标映射函数 ```cpp void mapCoordinates(float& x, float& y); ``` - 作用:将模型输出坐标映射回原始图像坐标系 - 参数: - x/y:模型输出坐标(输入输出参数) - 返回值:无 ##### 5.10.2.3 结果处理函数 ###### 5.10.2.3.1 后处理初始化 ```cpp void init_post_process(); ``` - 作用:加载类别标签文件 - 参数:无 - 返回值:无 ###### 5.10.2.3.2 结果绘制函数 ```cpp void draw_detections(int count, object_detect_result* results, cv::Mat& frame, void (*mapFunc)(float&, float&)); ``` - 作用:在图像上绘制检测框和标签 - 参数: - count:检测结果数量 - results:检测结果数组 - frame:目标图像帧 - mapFunc:坐标映射函数指针 - 返回值:无 #### 5.10.3 代码解析 ##### 5.10.3.1 流程图 ``` main函数 ├─ 参数检查与提示 │ └─ 检查参数数量是否为4,否则打印用法 ├─ 初始化YOLOv5模型 │ ├─ 创建rknn_app_context结构体 │ ├─ 调用init_yolov5_model()加载RKNN模型 │ └─ 错误处理:模型加载失败时退出 ├─ 加载标签文件 │ └─ 调用init_post_process() ├─ 初始化视频输入 │ ├─ 创建Edit对象并建立连接 │ ├─ 设置摄像头参数(640x480) │ └─ 打开摄像头设备 ├─ 主处理循环 │ ├─ 帧计时开始 │ ├─ 读取视频帧 │ │ └─ 空帧检查 │ ├─ 图像预处理 │ │ ├─ 调整尺寸至320x320 │ │ └─ 执行letterbox处理 │ ├─ 模型推理 │ │ ├─ 将数据复制到模型输入内存 │ │ ├─ 调用inference_yolov5_model()执行推理 │ │ └─ 获取检测结果列表 │ ├─ 结果可视化 │ │ ├─ 调用draw_detections()绘制检测框 │ │ └─ 使用mapCoordinates坐标映射 │ ├─ 显示输出 │ │ └─ 调用edit.Print()显示处理后的帧 │ ├─ 性能计算 │ │ ├─ 记录结束时间 │ │ └─ 计算并打印帧处理时间 │ └─ 循环继续条件 └─ 资源释放 ├─ 调用release_yolov5_model()释放模型 ├─ 调用deinit_post_process()释放后处理 └─ 释放摄像头资源 ``` ##### 5.10.3.2核心代码解析 - 模型初始化 ```cpp rknn_app_context_t rknn_app_ctx; init_yolov5_model("yolov5s.rknn", &rknn_app_ctx); ``` - 图像预处理 ```cpp cv::Mat letterboxImage = letterbox(frame); // 保持比例缩放 memcpy(rknn_app_ctx.input_mems[0]->virt_addr, letterboxImage.data, model_width * model_height * 3); ``` - 模型推理 ```cpp object_detect_result_list od_results; inference_yolov5_model(&rknn_app_ctx, &od_results); ``` - 结果可视化 ```cpp draw_detections(od_results.count, od_results.results, frame, mapCoordinates); ``` ##### 5.10.3.3 完整代码实现 ```cpp #include #include #include #include "yolov5.h" #include "opencv2/core/core.hpp" #include "opencv2/highgui/highgui.hpp" #include "opencv2/imgproc/imgproc.hpp" #include #include // output img size #define DISP_WIDTH 320 #define DISP_HEIGHT 320 // disp size int width = DISP_WIDTH; int height = DISP_HEIGHT; // model size int model_width = 320; int model_height = 320; int leftPadding; int topPadding; // label size extern int obj_class_num; char *lable; int main(int argc, char *argv[]) { if (argc != 4) { LOGGER_INFO("Usage: %s ./yolov5_main model_path ./label size\n ./label_txt"); } obj_class_num = atoi(argv[2]); lable = argv[3]; // Rknn model char text[16]; // rknn上下文结构体 rknn_app_context_t rknn_app_ctx; object_detect_result_list od_results; int ret; const char *model_path = argv[1]; memset(&rknn_app_ctx, 0, sizeof(rknn_app_context_t)); // Step 1: Load RKNN model if (init_yolov5_model(model_path, &rknn_app_ctx) != 0) { printf("❌ Failed to load RKNN model!\n"); return -1; } printf("✅ RKNN model loaded successfully.\n"); // 加载标签文件 init_post_process(); // 打开摄像头 lockzhiner_vision_module::edit::Edit edit; if (!edit.StartAndAcceptConnection()) { std::cerr << "Error: Failed to start and accept connection." << std::endl; return EXIT_FAILURE; } cv::VideoCapture cap; cap.set(cv::CAP_PROP_FRAME_WIDTH, 640); cap.set(cv::CAP_PROP_FRAME_HEIGHT, 480); cap.open(0); if (!cap.isOpened()) { std::cerr << "Error: Could not open camera." << std::endl; return 1; } cv::Mat frame; // 在 while 循环外声明 start 和 end 变量 std::chrono::steady_clock::time_point start, end; while (true) { // 记录开始时间 start = std::chrono::steady_clock::now(); // Step 2: Load image from command line cap >> frame; if (frame.empty()) { LOGGER_INFO("❌ Failed to read frame from camera.\n"); continue; } cv::resize(frame, frame, cv::Size(width, height), 0, 0, cv::INTER_LINEAR); cv::Mat letterboxImage = letterbox(frame); if (letterboxImage.empty() || letterboxImage.total() * letterboxImage.elemSize() != model_width * model_height * 3) { LOGGER_ERROR("❌ Input image format or size mismatch!\n"); release_yolov5_model(&rknn_app_ctx); return -1; } if (rknn_app_ctx.input_mems == nullptr || rknn_app_ctx.input_mems[0] == nullptr) { LOGGER_ERROR("❌ RKNN input memory not allocated!\n"); release_yolov5_model(&rknn_app_ctx); return -1; } memcpy(rknn_app_ctx.input_mems[0]->virt_addr, letterboxImage.data, model_width * model_height * 3); if (inference_yolov5_model(&rknn_app_ctx, &od_results) != 0) { LOGGER_ERROR("inference_yolov5_model failed"); release_yolov5_model(&rknn_app_ctx); return -1; } draw_detections(od_results.count, // 传入结果数量 od_results.results, // 传入结果数组 frame, // 图像帧 mapCoordinates); // 直接使用现有坐标映射函数 edit.Print(frame); // 记录结束时间 end = std::chrono::steady_clock::now(); // 计算耗时(秒) double elapsed_time = std::chrono::duration(end - start).count(); printf("Frame processed in %.4f seconds\n", elapsed_time); } release_yolov5_model(&rknn_app_ctx); deinit_post_process(); cap.release(); return 0; } ``` #### 5.10.4 编译过程 ##### 5.10.4.1 编译环境搭建 - 请确保你已经按照 [开发环境搭建指南](https://gitee.com/LockzhinerAI/LockzhinerVisionModule/blob/master/docs/introductory_tutorial/cpp_development_environment.md) 正确配置了开发环境。 - 同时以正确连接开发板。 ##### 5.10.4.2 Cmake介绍 ```cmake # CMake最低版本要求 cmake_minimum_required(VERSION 3.10) project(test-find-blobs) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # 定义项目根目录路径 set(PROJECT_ROOT_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../..") message("PROJECT_ROOT_PATH = " ${PROJECT_ROOT_PATH}) include("${PROJECT_ROOT_PATH}/toolchains/arm-rockchip830-linux-uclibcgnueabihf.toolchain.cmake") # 定义 OpenCV SDK 路径 set(OpenCV_ROOT_PATH "${PROJECT_ROOT_PATH}/third_party/opencv-mobile-4.10.0-lockzhiner-vision-module") set(OpenCV_DIR "${OpenCV_ROOT_PATH}/lib/cmake/opencv4") find_package(OpenCV REQUIRED) set(OPENCV_LIBRARIES "${OpenCV_LIBS}") # 定义 LockzhinerVisionModule SDK 路径 set(LockzhinerVisionModule_ROOT_PATH "${PROJECT_ROOT_PATH}/third_party/lockzhiner_vision_module_sdk") set(LockzhinerVisionModule_DIR "${LockzhinerVisionModule_ROOT_PATH}/lib/cmake/lockzhiner_vision_module") find_package(LockzhinerVisionModule REQUIRED) set(RKNPU2_BACKEND_BASE_DIR "${LockzhinerVisionModule_ROOT_PATH}/include/lockzhiner_vision_module/vision/deep_learning/runtime") if(NOT EXISTS ${RKNPU2_BACKEND_BASE_DIR}) message(FATAL_ERROR "RKNPU2 backend base dir missing: ${RKNPU2_BACKEND_BASE_DIR}") endif() add_executable(yolov5_main main.cc postprocess.cc yolov5.cc yolov5.h postprocess.h ) target_include_directories(yolov5_main PRIVATE ${LOCKZHINER_VISION_MODULE_INCLUDE_DIRS} ${rknpu2_INCLUDE_DIRS} ${RKNPU2_BACKEND_BASE_DIR}) target_link_libraries(yolov5_main PRIVATE ${OPENCV_LIBRARIES} ${LOCKZHINER_VISION_MODULE_LIBRARIES}) install( TARGETS yolov5_main RUNTIME DESTINATION . ) ``` ##### 5.10.4.3 编译项目 使用 Docker Destop 打开 LockzhinerVisionModule 容器并执行以下命令来编译项目 ```bash # 进入Demo所在目录 cd /LockzhinerVisionModuleWorkSpace/LockzhinerVisionModule/Cpp_example/D10_yolov5 # 创建编译目录 rm -rf build && mkdir build && cd build # 配置交叉编译工具链 export TOOLCHAIN_ROOT_PATH="/LockzhinerVisionModuleWorkSpace/arm-rockchip830-linux-uclibcgnueabihf" # 使用cmake配置项目 cmake .. # 执行编译项目 make -j8 && make install ``` 在执行完上述命令后,会在build目录下生成可执行文件。 #### 5.10.5 例程运行示例 ##### 5.10.5.1 运行 ```shell chmod 777 yolov5_main # 在实际应用的过程中LZ-Picodet需要替换为下载的或者你的rknn模型 ./yolov5_main ./voc320.rknn 20 label ``` ##### 5.10.5.2 结果展示 - 可以看到我们可以正确识别多种类别的 ![](./images/D10_yolov5.png) ## 6 相关资源 开源是推动技术进步和创新的重要力量,我们采用了多个开源仓库来增强功能、提升效率。 * [OnnxSlim](https://github.com/inisis/OnnxSlim) * [OpenCV Mobile](https://github.com/nihui/opencv-mobile) * [Paddle2ONNX](https://github.com/PaddlePaddle/Paddle2ONNX) * [PaddleClas](https://github.com/PaddlePaddle/PaddleClas) * [PaddleDetection](https://github.com/PaddlePaddle/PaddleDetection) * [PaddleOCR](https://github.com/PaddlePaddle/PaddleOCR) * [PaddleSeg](https://github.com/PaddlePaddle/PaddleSeg) * [readerwriterqueue](https://github.com/cameron314/readerwriterqueue) * [RKNN Toolkit 2](https://github.com/airockchip/rknn-toolkit2) * [LockzhinerAI/LockzhinerVisionModule](https://gitee.com/LockzhinerAI/LockzhinerVisionModule) ## 7. 致谢 凌智视觉模块(Lockzhiner Vision Module) 离不开社区的支持,特别感谢以下个人(或团体): * 感谢 Rockchip NPU 团队帮忙调试 PaddlePaddle 模型到 RKNN 模型的推理和优化 * 感谢飞桨团队在适配 PaddleX 等套件上提供的帮助 * 感谢 nihui 大佬帮忙审核并将 Lockzhiner Vision Module 的 ISP 代码合入到 opencv-mobile