# CodingDocuments **Repository Path**: lut108/coding-documents ## Basic Information - **Project Name**: CodingDocuments - **Description**: 开发流程说明文档 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-02-22 - **Last Updated**: 2025-09-27 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 1.必要的软件和网址 - [Git](https://git-scm.com/): 分布式版本控制系统,可以用来完成软件版本的迭代开发及多人协作开发。 - [Github](https://github.com/): 开源代码平台。 - [Yolo](https://github.com/ultralytics/ultralytics): 机器视觉库。 - [OpenCV](https://github.com/opencv/opencv): 计算机视觉库。 - [vcpkg](https://github.com/microsoft/vcpkg): 快速安装 C++ 依赖库,且很容易集成到 Visual Studio 中完成开发工作。 - [Gitee](https://gitee.com/): 国内的开源代码平台。Github 由于其服务器位于国外,拉取/推送代码时很可能出现网络问题,而该平台服务器在国内,基本不会存在网络问题。 - [SQL Server](https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads): 数据库服务。 - [SQL Server Management Studio](https://learn.microsoft.com/en-us/ssms/download-sql-server-management-studio-ssms): 数据库管理工具。 - [Visual Studio](https://visualstudio.microsoft.com/zh-hans/): Windows 平台下开发 C++、C#、Python 等应用的集成编辑器(IDE)。 - [Visual Studio Code](https://code.visualstudio.com/download): 代码编辑器,可以安装各种插件来完成各种语言的开发。 - [Miniconda](https://docs.anaconda.com/miniconda/): Anaconda 的最小安装版本,Anaconda 通常用于数据科学等方面的处理,由于我们只是需要其多开发环境快速切换的功能,只安装其最小版本即可,其默认环境会安装最新版本的 Python 版本,用于完成 Python 程序的开发。由于某些情况下因为各种库依赖的问题,所以存在某些库依赖于不同的 Python 版本,使用该软件可以快速在多个 Python 版本之间进行切换。(Numpy、Pandas) - [Clash Verge](https://github.com/clash-verge-rev/clash-verge-rev): 代理软件。大多数时候代码资源都在国外,国内访问会很慢,因此代理软件是必需的。下面是几个机票的购买网址,一个月10元左右。按月买就行,有时候有些代理商服务会挂掉。 - [渔舍](https://cp.yushe.org/#/stage/dashboard) - [闪电](https://58sd.net/#/dashboard) - [EFCloud](https://user.efcloud1.com/login#/stage/dashboard) - [XFLTD](https://xftld.org/#/dashboard) - [Typora](https://typoraio.cn/): 使用 Markdown 语法进行做笔记的软件,支持各级标题、表格、加粗、斜体、下划线、无序列表、有序列表、LaTex公式、图片插入、代码块、重点区块、流程图等功能。在常见的代码仓库中,通常需要创建一些说明文档内容,这些代码仓库通常也是通过 Git 来进行管理的,Git 对 Word、二进制文件等内容难以支持,而很容易支持 Markdown 语法的文件,其以 `.md` 作为后缀名,因此通常作为程序界最为常用的文档格式。 - [串口调试助手](https://apps.microsoft.com/detail/9nblggh43hdm?hl=zh-cn&gl=GY): 微软商店中发布的一个串口调试工具,支持串口、TCP、UDP 调试。 - [Virtual Serial Port Driver](https://soft.3dmgame.com/down/204170.html): 虚拟串口创建工具。有时需要开发串口程序需要进行测试,可以通过该软件创建虚拟串口,再通过串口调试工具打开该虚拟串口,并向相应的串口发送数据完成测试。 - MVS: 海康相机 GUI 配置程序。 - TSC Console: TSC 打印机控制程序。 # 2.各软件的安装及使用 1. [Clash安装及使用](./clash.md) 2. [Git安装及使用](./Git.md) 3. [Markdown教程](https://www.runoob.com/markdown/md-tutorial.html) 4. [SqlServer数据库安装教程](https://blog.csdn.net/m0_51447373/article/details/143457782)(无需看远程连接配置部分) 5. [Visual Studio安装](./vs.md) 6. [vcpkg安装及使用](./vcpkg.md) 7. [Miniconda安装教程](https://blog.csdn.net/2401_87334241/article/details/145012378) 8. [VSCode安装及Python环境设置](./vscode.md) 9. [深度学习部署及训练](./dl.md) 10. [推荐书籍及视频](./books.md) 11. [如何构建一个完整的初始项目](./project.md) # 3.常见的开发流程 ## 3.1 开发环境的选择 > 具体的开发环境根据具体的项目需求和个人熟悉程度来选择。 - C/C++: **通常完成算法功能的开发**。包括结合 OpenCV 库完成图像处理算法、结合 PCL 库完成点云处理算法、结合 OpenGL 完成三维图像处理、结合 Eigen 完成矩阵运算。也可以与 Qt5 结合完成 GUI 界面的开发。推荐网站:[awesome-cpp](https://github.com/fffaraz/awesome-cpp),其中包含了 CPP 方面各种方向常用的第三方库汇总。 - C#: **通常完成上位机的开发**。通过结合 Winform 框架完成上位机界面的开发,也可以取代 WinCC 完成 PLC 的控制(这才是真正的上位机),通过这种开发手段来读取所有的传感器信息。 - Python: **通常完成脚本测试**。Python 开发便捷、语法简单、环境配置简单,可以用来快速开发脚本测试相关算法,也可以生成大量的统计图来分析算法存在的问题。此外,它通常也用来完成深度学习模型的训练。 ## 3.2 总览开发流程 1. **确定项目需求,分化模块功能**。根据具体的项目需求,划分不同的模块层级、为各个开发成员分配工作任务,制定信息的流动方式。同时制定编码规范、代码提交规范。 2. **选择开发语言及具体的开发环境、语言版本**。例如,.Net Framework 的版本、OpenCV 的版本、Python 的版本等,不同的版本在各个方面都会存在不同的差异,因此,对于不同的开发者之间的开发环境一定要一致,否则会出现在一台电脑上可以编译运行,但在另一台电脑上无法编译运行的情况。 3. **搭建数据库、数据表**。根据项目需求制定所需要的数据表,包括数据表名称、字段名称、字段类型、 字段含义、字段特性等。 4. **创建初始代码仓库**。通过 Git 创建初始的代码仓库,再将代码通过 Git 推送到远端服务器。 5. **开发成员拉取代码,完成功能开发**。各个开发成员从远端仓库拉取最新代码,各自完成各自功能的开发,完成开发后,将代码推送到远端,向仓库管理员提出代码合并请求。 6. **基本功能开发完成,打包发布**。由仓库管理员打包可执行程序,完成功能测试,发布正式版本。 # 4.视觉贴标代码解读 ## 4.1 程序总体流程 1. 程序初始化。 1. 开启子线程,清理过期缓存(针对软件重启问题,软件因为某些原因导致重启后清理缓存)。 2. 启动定时器,间隔固定的时间间隔清理过期缓存(针对软件长时间运行而不重启的问题)。 3. 连接视觉相机,设置视觉相机参数。 4. 连接机器人相机,设置机器人相机参数。 5. 连接打印机,设置打印机参数(由于打印机厂商给定接口的问题,同一时刻只能连接一台打印机,因此在代码中此处的连接任务并没有在初始化工作中进行,而是打印时才去连接打印机)。 6. 连接数据库。 2. 开启子线程,从数据库中读取控制任务。 3. 根据控制任务,执行相应操作: 1. 当未读取到任何任务时则不做处理,继续监听数据库中的控制信号。 2. 当读取到 ”位置识别“ 任务时: 1. 从数据库中查询最新的需要打印的标签信息。 2. 创建视觉子线程,调用视觉相机拍照接口,拍取镍摞照片,存储到本地磁盘,然后调用视觉算法接口,从最新的镍摞照片中计算出标签的坐标信息。最后将标签的坐标信息写入到数据库的控制表中。该子线程工作完成。 3. 先前的工作线程调用打印机的相应接口完成标签打印任务。完成后,等待视觉子线程工作完成。 3. 当读取到 ”拍取侧面照片“ 任务时,调用机器人相机的相应接口拍取照片,存储到本地磁盘即可。 4. 当读取到 ”扫描二维码“ 任务时,先调用机器人相机的相应接口拍取照片,存储到本地磁盘,然后使用第三方二维码扫描接口扫描刚才所存储的照片,与内存中的编号进行比对,查看贴标是否正确(会因为漏标、错标等问题发生标签与镍摞不匹配的情况)。 4. 任务执行完成,向数据库中写入 ”成功/失败“ 通知机器人完成下一步任务。 5. 重复 2-4。 ## 4.2 数据库搭建 使用软件: - 数据库软件:SQL Server - 数据库管理软件:SQL Server Management Studio 具体任务流程: 1. 通过 SQL Server Management Studio 创建数据库。 2. 在库中新建数据表,制定表名称、字段名称、字段类型、字段特征。 3. 软件代码测试数据库连接是否正常。 数据表示例1(控制表): | 字段 | 类型 | | --------------- | ------ | | CaptureVision | Int | | CaptureRobot | Int | | PrintLargeLabel | Int | | PrintSmallLabel | Int | | PrintSideLabel | Int | | LargeLabelPosX | Double | | LargeLabelPosY | Double | | Slope | Double | 数据表示例2(标签信息表): image-20250221193712522 ## 4.3 上位机开发 **开发环境**: - 软件:Visual Studio(编译器、调试器、版本控制、性能分析、数据库集成、代码编辑器等) - 编程语言:C# - 开发框架:.Net Framework 4.7.2(C#代码运行的基础、虚拟环境) > 对于代码而言,其需要经过如下四个过程才可以在计算机上正常运行: > > - **预编译**:处理一些以 `#` 开头的宏名称,在 C/C++ 中常见,例如根据不同的平台运行不同的代码,或者根据不同的依赖版本运行不同的代码。此外完成删除注释等工作。 > - **编译**:将代码编译为汇编代码。 > - **汇编**:将汇编代码转换为计算机可以认识的机器代码,也就是二进制文件。 > - **链接**:通常一个程序可能会依赖于一些动态库、静态库,此时就是完成动态库和静态库的链接工作,将可执行程序与库文件相链接。 > 对于平台而言,通常 C/C++ 语言生成的可执行文件可以直接在操作系统上运行,而 Java、C#、Python 等语言均需要依赖于特定的虚拟机才能正常运行,相当于在操作系统上又抽象出了一个层级。 **开发模式**: - Debug:调试版本,带有调试符号,编译出来的二进制包体积比较大,运行比较慢。开发时选择该版本。 - Release:发布版本,不带调试符号,运行速度快。打包部署时选择该版本。 **编译平台**: - Any CPU:各个平台通用,仅在 C# 下存在,在 C/C++ 下不存在。在开发依赖于其它语言的模块时,不要选择该编译平台,因为 64 位的程序需要链接 64 位的动态库,32 位的程序需要链接 32 位的动态库,但 C/C++ 并不支持 Any CPU,因此在链接时会产生问题。 - x64:64 位平台。 - x32:32 位平台。 --- 通常我们所运行的一个程序就是一个进程,例如微信、QQ、浏览器等,进程运行在操作系统之上。对于每一个进程而言,其内部有一个线程或多个线程。 未命名绘图.drawio 可以将操作系统看成一个餐厅,每个进程就是不同的部门,每个部门(进程)需要完成的工作需要基于餐厅(操作系统)才可以进行。而后厨部门(进程)里的厨师长,我们将它称为 ”主线程“。在最开始,客人少,厨师长一个人就可以完成后厨所有的工作,包括买菜、洗菜、炒菜、端菜、洗碗等。但是随着客人的增多,任务量逐渐增大,他一个任务完成这些全部的工作需要耗费大量的时间,客户需要不停的等待才能吃到菜。因此,后厨就需要招聘更多的人手(子线程),让这些人手(子线程)去分别处理买菜、洗菜、端菜、洗碗等任务,而厨师长(主线程)只需要负责炒菜就行。 而在界面程序中,主线程的主要任务就是刷新主界面,如果主线程负责了一个耗时比较长的动作,主界面得不到刷新,就会形成程序假死的现象。因此,对于长时间的耗时工作尽量不要放在主线程。可以开启多个子线程,让子线程在后台去完成这些工作。 对于多个子线程的运行,会存在所谓的 ”竞争“ 问题。好比在后厨现在有两个线程任务,一个是洗菜线程,一个是端菜线程,它们都需要用到盘子。当洗菜线程洗完菜将菜放到盘子里时,此时盘子里的菜还未被使用,此时端菜线程如果直接使用盘子就会出现问题。此处的盘子就是多个线程之间的竞争资源。为了防止多线程之间的竞争问题,需要使用互斥锁等机制去保护竞争区域,避免多线程的竞争问题,也就是同一时刻,只会有一个线程进入到竞争区域,其它线程先休眠,等到竞争区域内没有线程时,再唤醒休眠的线程进入到竞争区域内部。也就是,同一时刻只能有一个人使用盘子,洗菜线程使用盘子时,端菜线程就先进行休眠,等到洗菜线程用完盘子之后,再唤醒端菜线程,让端菜线程去使用盘子。 ## 4.4 Yolo与OpenCV集成代码解读 在 OpenCV 库中,提供了 [DNN](https://docs.opencv.org/4.x/d2/d58/tutorial_table_of_content_dnn.html) 模块用以集成神经网络模型,具体与 PyTorch 和 TensorFlow 神经网络模型框架的集成方法以及与 Python 和 C++ 的集成方法可以参考上述提供的官方文档链接。 这里提供的代码已经完成了与 Yolo 网络模型的集成,此处具体对其中的内容做出解读。 对于图像检测的整体流程而言,我们将待检测图像输入到神经网络中,经过神经网络中的各种运算可以得到一个多维矩阵输出,为了从该输出中得到具体目标框的信息,需要使用 OpenCV DNN 模块来完成,该部分所做的主要工作便是这部分。 ![](./images/dnn2.png) ### 4.4.1 模型输入与输出查看 通常我们通过 Python 构建的神经网络模型训练出来的是 **\*.pt** 文件,为了与 OpenCV 集成,需要将其转换为 **\*.onnx** 文件,这一部分不再赘述。 得到 ONNX 文件后,打开 [Netron](https://netron.app/),导入相应的模型文件。导入后,点击最上方的 image 输入模块,可以在右侧看到模型的输入结构和输出结构: ![](./images/dnn1.png) 可以观察到,该模型的输入是一个 $1\times3\times640\times640$ 的多维矩阵,输出是一个 $1\times7\times8400$ 的多维矩阵。 > 此处更标准的说法应该叫做 **张量**(Tensor),而非矩阵。为了简便,这里都将其称为多维矩阵。 对于输入而言,1 表示输入的图像数量,即 1 张图片,3 表示 3 个通道,即图像中的 R、G、B 通道,640x640 则表示输入图像的长和宽。 对于输出而言,其形状与具体的任务类型和标签数量有关。 ### 4.4.1 集成代码架构 具体的架构采用了面向对象编程中的 **继承** 关系来完成,*BaseYolo* 为所有检测任务中的基类,具体的检测任务由各个子类进行特化,根据目前 Yolo 所具有的检测任务,将其分为了五种:目标检测、位姿检测、旋转目标检测、语义分割、图像分类。检测完成的结果通过 Target 类型返回给用户,它包含了目标类别、置信度和包围盒位置信息。 ![](./images/dnn3.png) ### 4.4.2 模型加载 检测图像所要做的第一步工作便是加载网络模型,即加载 ONNX 文件。这部分具体的内容在如下代码中: ```cpp void BaseYolo::LoadModelFile(const std::string& filename, int width, int height) { assert(!filename.empty()); assert(std::filesystem::exists(filename)); assert(width > 0 && height > 0); model_ = std::make_unique(cv::dnn::readNet(filename)); SetInputSize(width, height); // 设置计算后台与计算设备 model_->setPreferableBackend(backend_id_); model_->setPreferableTarget(target_id_); } ``` 它主要完成如下步骤: 1. 从输入的文件名 *filename* 中加载模型文件。 2. 设定模型的输入大小,即宽度 *width* 和高度 *height*。 3. 设定后端引擎和计算设备。 可用的后端引擎如下: | 后端引擎 | 说明 | | ------------------------------ | ------------------------------------------------------ | | *DNN_BACKEND_DEFAULT* | 默认后端引擎,根据编译时的配置和可用的后端引擎自行选择 | | *DNN_BACKEND_HALIDE* | 使用 Halide 后端引擎 | | *DNN_BACKEND_INFERENCE_ENGINE* | 使用 Intel OpenVINO Inference Engine 后端引擎 | | *DNN_BACKEND_OPENCV* | 使用 OpenCV 自带的 DNN 后端引擎 | | *DNN_BACKEND_CUDA* | 使用 NVIDIA CUDA 后端引擎 | 可用的计算设备如下: | 目标设备 | 说明 | | ------------------------ | -------------------------------------------------------- | | *DNN_TARGET_CPU* | 在 CPU 上执行推理操作 | | *DNN_TARGET_OPENCL* | 在支持 OpenCL 的设备上执行推理操作,如 GPU | | *DNN_TARGET_OPENCL_FP16* | 在支持 OpenCL 的设备上以 FP16 精度执行推理操作 | | *DNN_TARGET_MYRIAD* | Intel Movidius Neural Compute Stick(NCS)等 Myriad 设备 | | *DNN_TARGET_FPGA* | 在 FPGA 上执行推理操作 | | *DNN_TARGET_CUDA* | 在 NVIDIA CUDA GPU 上执行推理操作 | | *DNN_TARGET_CUDA_FP16* | 在 NVIDIA CUDA GPU 上以 FP16 精度执行推理操作 | | *DNN_TARGET_CPU_FP16* | 在 CPU 上以 FP16 精度执行推理操作 | > 具体内容请查看 [官方文档](https://docs.opencv.org/4.x/d6/d0f/group__dnn.html#gga186f7d9bfacac8b0ff2e26e2eab02625a51129aae9bc5df62a3ba95f98008717e) 和具体的 OpenCV 版本。 ### 4.4.3 图像预处理 对于待检测图像而言,其尺寸大小是变化的,但是对于网络模型的输入而言,其大小是固定的,因此,需要对图像做出预处理操作以调整图像大小,同时对图像中的像素进行标准化和归一化操作。预处理主要包含如下步骤: 1. **归一化**。原始的像素值位于 0~255 之间,归一化将其转换到 0~1 之间。 2. **标准化**。对图像进行均值和标准差处理。 3. **裁剪图像**。由于模型输入固定,而原始图像的大小不固定,因此对原始图像做出裁剪、填充、缩放等操作以适应模型的输入大小。 预处理部分的代码主要如下,其中 `scalefactor` 表示缩放因子,即对图像进行归一化,`size` 表示输入图像的长和宽,`mean` 表示均值,`swapRB` 表示是否交换 R 通道和 B 通道(RGB 通道分别表示 Red 通道、Green 通道、Blue 通道,在 OpenCV 中通道的分布方式是 BGR,与我们常说的 RGB 不同),`ddepth` 表示数据精度,精度越高,内存消耗越大,`datalayout` 表示数据在内存中的分布方式,可选的方式有 *DNN_LAYOUT_NCHW* 和 *DNN_LAYOUT_NHWC*,其中的 *N* 表示批处理大小(Batch Size)),即同时处理的样本数量,也就是输入图像的数量,*C* 表示通道(Channel),即图像的通道数,*H* 和 *W* 则分别表示输入图像的高度和宽度,`paddingmode` 表示图像的填充模式: ```cpp std::vector BaseYolo::Detect(const cv::Mat& src) { // 预处理 cv::dnn::Image2BlobParams params; params.scalefactor = cv::Scalar::all(1.0 / 255.0); params.size = cv::Size(input_width_, input_height_); params.mean = mean_; params.swapRB = true; params.ddepth = CV_32F; params.datalayout = data_layout_; params.paddingmode = padding_mode_; cv::Mat input_blob = cv::dnn::blobFromImageWithParams(src, params); // 除以标准差 if (std_[0] != 0.0 && std_[1] != 0.0 && std_[2] != 0.0) { cv::divide(input_blob, std_, input_blob); } // ... } ``` 常见的填充模式有如下三种,其中 yolo 使用的是最后一种模式: - *DNN_PMODE_NULL*:调整所需的输入大小而无需额外处理。 - *DNN_PMODE_CROP_CENTER*:先调整图像大小,然后裁剪。 - *DNN_PMODE_LETTERBOX*:在保留原始图像长宽比的同时,将图像大小调整到所需的尺寸。 ### 4.4.4 前向推理与后处理 前向推理与后处理的代码如下,其中,前向推理过程即神经网络模型计算的过程,在图像经过预处理操作后,得到一个输入矩阵 `input_blob`,将该矩阵输入到网络模型中,进行网络节点运算,最终得到一个输出矩阵 `output_blobs`。代码中的 `forward` 函数即为 DNN 模块提供的前向推理接口。 ```cpp std::vector BaseYolo::Detect(const cv::Mat& src) { // 预处理 // ... // 前向推理 model_->setInput(input_blob); std::vector output_blobs; model_->forward(output_blobs, model_->getUnconnectedOutLayersNames()); // 后处理 return PostProcessed(params, output_blobs, src.size()); } ``` 前向推理完成后,对于所得到的 `output_blobs`,我们需要根据不同的任务类型对其数据进行解析,将数据中的数据转换为标签类型、包围盒坐标等信息,这一过程便是后处理过程。 所有任务类型的后处理过程可以总结为以下几个部分: ![](./images/dnn4.png) 其中,非极大值抑制(Non Maximum Suppression)的原理见 [NMS原理讲解](https://blog.csdn.net/m0_74055982/article/details/138647169),“还原包围盒坐标” 处理过程是因为我们通过网络得到的输出矩阵中的包围盒信息是相对于 640x640 的图像而言的,而为了得到其在原始图像上的对应位置,需要对其进行一个逆向的转化操作,将包围盒坐标逆向转换到原始的图像坐标系上。 其大致的代码如下,各个任务类型之间的不同只在 “将输出数据转化为得分信息、包围盒坐标信息” 部分有所差异,其它部分的内容大致相同。 ```cpp std::vector YoloXXX::PostProcessed(const cv::dnn::Image2BlobParams& params, const std::vector& outputs, const cv::Size& size) { std::vector boxes; std::vector class_ids; std::vector confidences; for (auto preds : outputs) { preds = preds.reshape(1, preds.size[1]); cv::transpose(preds, preds); // 将输出数据转换为得分信息、包围盒坐标信息 for (int i = 0; i < preds.rows; ++i) { // 获取最高的目标得分 // ... // 置信度筛选 if (score < conf_threshold_) continue; // 获取包围盒坐标 // ... } } // 非极大值抑制 std::vector target_idxs; cv::dnn::NMSBoxes(boxes, confidences, conf_threshold_, nms_threshold_, target_idxs); std::vector res; for (int idx : target_idxs) { // 还原盒子坐标及其尺寸 cv::Rect box(cvFloor(boxes[idx].x), cvFloor(boxes[idx].y), cvFloor(boxes[idx].width - boxes[idx].x), cvFloor(boxes[idx].height - boxes[idx].y)); box = BlobRectToImageRect(box, params, size); // 去除异常坐标的盒子 if (box.x < 0 || box.y < 0 || box.width < 0 || box.height < 0 || box.x + box.width > size.width || box.y + box.height > size.height) continue; std::vector target_points; target_points.emplace_back(box.x, box.y); target_points.emplace_back(box.x + box.width, box.y + box.height); res.emplace_back(class_ids[idx], confidences[idx], target_points); } return res; } ``` 对于输出矩阵 `outputs` 中的每一个 `preds` 而言,其大小为 $1\times a\times b$ 的形式,通过如下代码将其转换为 $b\times a$ 的矩阵形式: ```cpp preds = preds.reshape(1, preds.size[1]); cv::transpose(preds, preds); ``` 假设原始的网络模型输出为 $1\times7\times8400$,经过上述转换后所得到的形式为 $8400\times7$,也就是矩阵的行数为 8400,列数为 7。其中,8400 行表示 8400 个目标,也就是 8400 个目标框,每一个目标框通过 7 个数据构成,具体这 7 个数据分别代表什么含义见下文解释。 #### (1) Detect:目标检测任务类型 对于目标检测任务类型而言,其每一行的数据结构如下图所示: ![](./images/dnn5.png) 其中每一列数据的具体含义如下: - *cx*: 即 center_x,也就是目标框的中心点横坐标。 - *cy*: 即 center_y,也就是目标框的中心点纵坐标。 - *w*: 即 width,也就是目标框的宽度。 - *h*: 即 height,也就是目标框的高度。 - *score1*: 当前目标框中的物体是第 1 个标签的得分。 - *score2*: 当前目标框中的物体是第 2 个标签的得分。 - ... - *scoren*: 当前目标框中的物体是第 n 个标签的得分。 也就是说 “列数目 = 4 + 标签数量”。我们需要从这些得分中选择最高的得分,其索引所对应的标签类型即为当前目标框中物体的预测类型,然后通过置信度阈值筛选其真实性,并将 cx、cy、w、h 转化为 OpenCV 矩形框的表示形式。 #### (2) Obb: 旋转目标检测任务类型 对于旋转目标检测任务类型而言,其每一行的数据结构如下图所示: ![](./images/dnn6.png) 其中每一列数据的具体含义如下: - *cx*: 即 center_x,也就是目标框的中心点横坐标。 - *cy*: 即 center_y,也就是目标框的中心点纵坐标。 - *w*: 即 width,也就是目标框的宽度。 - *h*: 即 height,也就是目标框的高度。 - *score1*: 当前目标框中的物体是第 1 个标签的得分。 - *score2*: 当前目标框中的物体是第 2 个标签的得分。 - ... - *scoren*: 当前目标框中的物体是第 n 个标签的得分。 - angle:目标框的角度。 与目标检测结果不同的是多了一个 angle 角度信息。其处理过程与目标检测过程类似,从这些得分中选择最高的得分,其索引所对应的标签类型即为当前目标框中物体的预测类型,然后通过置信度阈值筛选其真实性,并将 cx、cy、w、h、angle 转化为 OpenCV 旋转矩形框的表示形式。 #### (3) Pose:位姿检测任务类型 对于位姿检测任务类型而言,其每一行的数据结构如下图所示: ![](./images/dnn7.png) 其中每一列数据的具体含义如下: - *cx*: 即 center_x,也就是目标框的中心点横坐标。 - *cy*: 即 center_y,也就是目标框的中心点纵坐标。 - *w*: 即 width,也就是目标框的宽度。 - *h*: 即 height,也就是目标框的高度。 - *conf*: 当前目标框中的物体是目标类型得分(位姿检测任务首先要有待检测目标,然后在目标上生成多个位姿点)。 - *px1*: 第一个位姿点的横坐标。 - *py1*: 第一个位姿点的纵坐标。 - *px2*: 第二个位姿点的横坐标。 - *py2*: 第二个位姿点的纵坐标。 - ... - *pxn*: 第n个位姿点的横坐标。 - *pyn*: 第n个位姿点的纵坐标。 其处理过程与目标检测过程类似,从这些得分中选择最高的得分,其索引所对应的标签类型即为当前目标框中物体的预测类型,然后通过置信度阈值筛选其真实性,并将 cx、cy、w、h 转化为 OpenCV 矩形框的表示形式,将 px、py 转换为 OpenCV 点的表示形式。 #### (4) Segment: 语义分割任务类型 > 待补充。 #### (5) Classify: 图像分类任务类型 > 待补充。 ### 4.4.5 如何自定义 如果 YOLO 提供了新的任务类型,只需要新建一个 `YoloXXX` 类,让其继承于 `YoloBase` 类,对其中的 `PostProcessed` 方法进行实现即可。 具体在该方法中完成输出矩阵的数据解析工作以及 NMS 工作,具体如何解析数据需要看 yolo 官方中对于数据是如何定义的。