本文分享自华为云社区《昇腾 CANN YOLOV8 和 YOLOV9 适配》,作者:jackwangcumt。

1 概述

华为昇腾 CANN YOLOV8 推理示例 C++样例 , 是基于Ascend CANN Samples官方示例中的sampleYOLOV7进行的YOLOV8适配。一般来说,YOLOV7模型输出的数据大小为[1,25200,85],而YOLOV8模型输出的数据大小为[1,84,8400],因此,需要对sampleYOLOV7中的后处理部分进行修改,从而做到YOLOV8/YOLOV9模型的适配。因项目研发需要,公司购置了一台 Atlas 500 Pro 智能边缘服务器, 安装的操作系统为Ubuntu 20.04 LTS Server,并按照官方说明文档,安装的Ascend-cann-toolkit_7.0.RC1_linux-aarch64.run等软件。具体可以参考另外一篇博文【Atlas 500 Pro 智能边缘服务器推理环境搭建】,这里不再赘述。

2 YOLOV8模型准备

在进行YOLOV8模型适配工作之前,首先需要获取YOLOV8的模型文件,这里以官方的 YOLOV8n.pt模型为例,在Windows操作系统上可以安装YOLOV8环境,并执行如下python脚本(pth2onnx.py)将.pt模型转化成.onnx模型:

import argparse
from ultralytics import YOLO def main():
parser = argparse.ArgumentParser()
parser.add_argument('--pt', default="yolov8n", help='.pt file')
args = parser.parse_args()
model = YOLO(args.pt)
onnx_model = model.export(format="onnx", dynamic=False, simplify=True, opset=11) if __name__ == '__main__':
main()

具体的YOLOV8环境搭建步骤,可以参考 https://github.com/ultralytics/ultralytics 网站。当成功执行后,会生成yolov8n.onnx模型。输出内容示例如下所示:

(base) I:\yolov8\Yolov8_for_PyTorch>python pth2onnx.py --pt=yolov8n.pt
Ultralytics YOLOv8.0.229 Python-3.11.5 torch-2.1.2 CPU (Intel Core(TM) i7-10700K 3.80GHz)
YOLOv8n summary (fused): 168 layers, 3151904 parameters, 0 gradients, 8.7 GFLOPs PyTorch: starting from 'yolov8n.pt' with input shape (1, 3, 640, 640) BCHW and output shape(s) (1, 84, 8400) (6.2 MB) ONNX: starting export with onnx 1.15.0 opset 11...
ONNX: simplifying with onnxsim 0.4.36...
ONNX: export success 1.0s, saved as 'yolov8n.onnx' (12.2 MB) Export complete (3.2s)
Results saved to I:\yolov8\Yolov8_for_PyTorch
Predict: yolo predict task=detect model=yolov8n.onnx imgsz=640
Validate: yolo val task=detect model=yolov8n.onnx imgsz=640 data=coco.yaml
Visualize: https://netron.app

从输出信息中可以看出, yolov8n.pt原始模型的输出尺寸为 (1, 3, 640, 640),格式为 BCHW ,输出尺寸为 (1, 84, 8400) 。这个模型的更多信息,可以用 netron 工具进行可视化查看,在安装了netron后,可以执行如下命令打开yolov8n.onnx模型进行Web网络结构的查看:

(base) I:\yolov8\Yolov8_for_PyTorch>netron yolov8n.onnx
Serving 'yolov8x.onnx' at http://localhost:8080

可以看到,转化后的yolov8n.onnx模型输入的节点名称为images,输入张量的大小为[1,3,640,640] 。在将yolov8n.onnx模型上传到Atlas 500 Pro服务器上,执行如下命令进行模型转换:

atc --model=yolov8n.onnx --framework=5
--output=yolov8n
--input_shape="images:1,3,640,640"
--soc_version=Ascend310P3
--insert_op_conf=aipp.cfg

其中的:

--soc_version=Ascend310P3可以通过npu-smi info命令进行查看,我这里打印的是 310P3 则,--soc_version 为 Ascend前缀加上310P3,即Ascend310P3。

--input_shape="images:1,3,640,640" 表示NCHW,即批处理为1,通道为3,图片大小为640x640,这与onnx模型的输入节点一致 。

--insert_op_conf=aipp.cfg 中的aipp.cfg来自官网sampleYOLOV7示例。由于原始输入图片的大小可能不符合要求,需要缩放到640x640的尺寸。aipp.cfg内容如下:

 aipp_op{
aipp_mode:static
input_format : YUV420SP_U8
src_image_size_w : 640
src_image_size_h : 640 csc_switch : true
rbuv_swap_switch : false
matrix_r0c0 : 256
matrix_r0c1 : 0
matrix_r0c2 : 359
matrix_r1c0 : 256
matrix_r1c1 : -88
matrix_r1c2 : -183
matrix_r2c0 : 256
matrix_r2c1 : 454
matrix_r2c2 : 0
input_bias_0 : 0
input_bias_1 : 128
input_bias_2 : 128 crop: true
load_start_pos_h : 0
load_start_pos_w : 0
crop_size_w : 640
crop_size_h : 640 min_chn_0 : 0
min_chn_1 : 0
min_chn_2 : 0
var_reci_chn_0: 0.0039215686274509803921568627451
var_reci_chn_1: 0.0039215686274509803921568627451
var_reci_chn_2: 0.0039215686274509803921568627451
}

生执行成功后,会生成 yolov8n.om 离线模型

3 适配代码

根据官网sampleYOLOV7示例适配的YOLOV8示例,代码已经开源,地址为:https://gitee.com/cumt/ascend-yolov8-sample。核心代码sampleYOLOV8.cpp中的后处理方法GetResult为:

Result SampleYOLOV8::GetResult(std::vector<InferenceOutput> &inferOutputs,
string imagePath, size_t imageIndex, bool release)
{
uint32_t outputDataBufId = 0;
float *classBuff = static_cast<float *>(inferOutputs[outputDataBufId].data.get());
// confidence threshold
float confidenceThreshold = 0.35; // class number
size_t classNum = 80; //// number of (x, y, width, hight)
size_t offset = 4; // total number of boxs yolov8 [1,84,8400]
size_t modelOutputBoxNum = 8400; // read source image from file
cv::Mat srcImage = cv::imread(imagePath);
int srcWidth = srcImage.cols;
int srcHeight = srcImage.rows; // filter boxes by confidence threshold
vector<BoundBox> boxes;
size_t yIndex = 1;
size_t widthIndex = 2;
size_t heightIndex = 3; // size_t all_num = 1 * 84 * 8400 ; // 705,600 for (size_t i = 0; i < modelOutputBoxNum; ++i)
{ float maxValue = 0;
size_t maxIndex = 0;
for (size_t j = 0; j < classNum; ++j)
{ float value = classBuff[(offset + j) * modelOutputBoxNum + i];
if (value > maxValue)
{
// index of class
maxIndex = j;
maxValue = value;
}
} if (maxValue > confidenceThreshold)
{
BoundBox box;
box.x = classBuff[i] * srcWidth / modelWidth_;
box.y = classBuff[yIndex * modelOutputBoxNum + i] * srcHeight / modelHeight_;
box.width = classBuff[widthIndex * modelOutputBoxNum + i] * srcWidth / modelWidth_;
box.height = classBuff[heightIndex * modelOutputBoxNum + i] * srcHeight / modelHeight_;
box.score = maxValue;
box.classIndex = maxIndex;
box.index = i;
if (maxIndex < classNum)
{
boxes.push_back(box);
}
}
} ACLLITE_LOG_INFO("filter boxes by confidence threshold > %f success, boxes size is %ld", confidenceThreshold,boxes.size()); // filter boxes by NMS
vector<BoundBox> result;
result.clear();
float NMSThreshold = 0.45;
int32_t maxLength = modelWidth_ > modelHeight_ ? modelWidth_ : modelHeight_;
std::sort(boxes.begin(), boxes.end(), sortScore);
BoundBox boxMax;
BoundBox boxCompare;
while (boxes.size() != 0)
{
size_t index = 1;
result.push_back(boxes[0]);
while (boxes.size() > index)
{
boxMax.score = boxes[0].score;
boxMax.classIndex = boxes[0].classIndex;
boxMax.index = boxes[0].index; // translate point by maxLength * boxes[0].classIndex to
// avoid bumping into two boxes of different classes
boxMax.x = boxes[0].x + maxLength * boxes[0].classIndex;
boxMax.y = boxes[0].y + maxLength * boxes[0].classIndex;
boxMax.width = boxes[0].width;
boxMax.height = boxes[0].height; boxCompare.score = boxes[index].score;
boxCompare.classIndex = boxes[index].classIndex;
boxCompare.index = boxes[index].index; // translate point by maxLength * boxes[0].classIndex to
// avoid bumping into two boxes of different classes
boxCompare.x = boxes[index].x + boxes[index].classIndex * maxLength;
boxCompare.y = boxes[index].y + boxes[index].classIndex * maxLength;
boxCompare.width = boxes[index].width;
boxCompare.height = boxes[index].height; // the overlapping part of the two boxes
float xLeft = max(boxMax.x, boxCompare.x);
float yTop = max(boxMax.y, boxCompare.y);
float xRight = min(boxMax.x + boxMax.width, boxCompare.x + boxCompare.width);
float yBottom = min(boxMax.y + boxMax.height, boxCompare.y + boxCompare.height);
float width = max(0.0f, xRight - xLeft);
float hight = max(0.0f, yBottom - yTop);
float area = width * hight;
float iou = area / (boxMax.width * boxMax.height + boxCompare.width * boxCompare.height - area); // filter boxes by NMS threshold
if (iou > NMSThreshold)
{
boxes.erase(boxes.begin() + index);
continue;
}
++index;
}
boxes.erase(boxes.begin());
} ACLLITE_LOG_INFO("filter boxes by NMS threshold > %f success, result size is %ld", NMSThreshold,result.size()); // opencv draw label params
const double fountScale = 0.5;
const uint32_t lineSolid = 2;
const uint32_t labelOffset = 11;
const cv::Scalar fountColor(0, 0, 255); // BGR
const vector<cv::Scalar> colors{
cv::Scalar(255, 0, 0), cv::Scalar(0, 255, 0),
cv::Scalar(0, 0, 255)}; int half = 2;
for (size_t i = 0; i < result.size(); ++i)
{
cv::Point leftUpPoint, rightBottomPoint;
leftUpPoint.x = result[i].x - result[i].width / half;
leftUpPoint.y = result[i].y - result[i].height / half;
rightBottomPoint.x = result[i].x + result[i].width / half;
rightBottomPoint.y = result[i].y + result[i].height / half;
cv::rectangle(srcImage, leftUpPoint, rightBottomPoint, colors[i % colors.size()], lineSolid);
string className = label[result[i].classIndex];
string markString = to_string(result[i].score) + ":" + className; ACLLITE_LOG_INFO("object detect [%s] success", markString.c_str()); cv::putText(srcImage, markString, cv::Point(leftUpPoint.x, leftUpPoint.y + labelOffset),
cv::FONT_HERSHEY_COMPLEX, fountScale, fountColor);
}
string savePath = "out_" + to_string(imageIndex) + ".jpg";
cv::imwrite(savePath, srcImage);
if (release)
{
free(classBuff);
classBuff = nullptr;
}
return SUCCESS;
}

YOLOV8的输出尺寸为 (1, 84, 8400),其中的8400代表模型原始预测的对象检测框信息,即代码中用 size_t modelOutputBoxNum = 8400 ; 进行表示。而 84 代表 4个位的边界框预测值(x,y,w,h)位置信息和80个检测类别数,即 84 = 4 + 80 。由于模型检测结果是用内存连续的一维数组进行表示的,因此,需要根据yolov8输出尺寸的实际含义,来访问需要的数组内存地址来获取需要的值。根据资料显示,yolov8模型不另外对置信度进行预测, 而是采用类别里面最大的概率作为置信度的值,8400是yolov8模型各尺度输出特征图叠加之后的结果,一般推理不需要处理。下面给出模型尺寸和内存数组的映射示意图 :

即除首行外,将其他83行的每一行依次变换到首行的末尾构成一维数组,一维数组的大小位 8400 x 84 。遍历数组时,首先将8400个预测信息中的置信度获取到,即偏移offset=4个后,获取80个类别位中最大的值以及索引转化为置信度和类别ID。前4个代表x,y,w,预测框信息。对于个性化定制的模型,则需要修改 size_t classNum = 80; 即可,参考onnx输出尺寸[1,84,8400]中的84-4 = 80 , 比如自定义的模型输出为[1,26,8400],则 size_t classNum = 22 (26-4).

4 编译运行

下载开源代码,上传服务器,并解压,然后执行如下命令进行代码编译:

unzip ascend-yolov8-sample-master.zip -d ./kztech
cd ascend-yolov8-sample-master/src
# src目录下
cmake .
make
#如果正确执行,则会在../out目录中生成 main 可执行文件,在src目录中运行示例
../out/main
#如果报如下错误:
../out/main: error while loading shared libraries: libswresample.so.3:
cannot open shared object file: No such file or directory
则尝试设置如下环境变量后重试:
export LD_LIBRARY_PATH=/usr/local/Ascend/thirdpart/aarch64/lib:$LD_LIBRARY_PATH
#正确执行后,会在当前目录中生成out_0.jpg文件

执行成功,控制台打印如下信息:

root@atlas500ai:/home/kztech/ascend-yolov8-sample-master/src# ../out/main
[INFO] Acl init ok
[INFO] Open device 0 ok
[INFO] Use default context currently
[INFO] dvpp init resource ok
[INFO] Load model ../model/yolov8n.om success
[INFO] Create model description success
[INFO] Create model(../model/yolov8n.om) output success
[INFO] Init model ../model/yolov8n.om success
[INFO] filter boxes by confidence threshold > 0.350000 success, boxes size is 10
[INFO] filter boxes by NMS threshold > 0.450000 success, result size is 1
[INFO] object detect [0.878906:dog] success
[INFO] Inference elapsed time : 0.038817 s , fps is 25.761685
[INFO] Unload model ../model/yolov8n.om success
[INFO] destroy context ok
[INFO] Reset device 0 ok
[INFO] Finalize acl ok

5 总结

YOLO各系列的适配过程,大部分都是处理输入格式和输出格式的变换上,参考YOLOV7,可以进行YOLOV8模型的适配,同理,YOLOV9的模型适配也是一样的。目前YOLOV9和YOLOV8模型输出格式一致,因此,只需要进行yolov9xx.om模型的生成工作即可。yolov9-c-converted.pt模型(https://github.com/WongKinYiu/yolov9/releases/download/v0.1/yolov9-c-converted.pt)转换如下: 在windows操作系统上可以安装YOLOV9环境,并执行如下python脚本将.pt模型转化成.onnx模型:

#从base环境创建新的环境yolov9
conda create -n yolov9 --clone base
#激活虚拟环境yolov9
conda activate yolov9
#克隆yolov9代码
git clone https://github.com/WongKinYiu/yolov9
# 安装yolov9项目的依赖
(yolov9) I:\yolov9-main>pip install -r requirements.txt
# 模型转换导出onnx
(yolov9) I:\yolov9-main>python export.py --weights yolov9-c-converted.pt --include onnx
(yolov9) I:\yolov9-main>python export.py --weights yolov9-c-converted.pt --include onnx
export: data=I:\yolov9-main\data\coco.yaml, weights=['yolov9-c-converted.pt'], imgsz=[640, 640], batch_size=1, device=cpu, half=False, inplace=False,
keras=False, optimize=False, int8=False, dynamic=False, simplify=False, opset=12, verbose=False, workspace=4, nms=False, agnostic_nms=False, topk_per_class=100, topk_all=100, iou_thres=0.45, conf_thres=0.25, include=['onnx']
YOLO 2024-3-13 Python-3.11.5 torch-2.1.2 CPU Fusing layers...
gelan-c summary: 387 layers, 25288768 parameters, 64944 gradients, 102.1 GFLOPs PyTorch: starting from yolov9-c-converted.pt with output shape (1, 84, 8400) (49.1 MB) ONNX: starting export with onnx 1.15.0...
ONNX: export success 2.9s, saved as yolov9-c-converted.onnx (96.8 MB) Export complete (4.4s)
Results saved to I:\yolov9-main
Detect: python detect.py --weights yolov9-c-converted.onnx
Validate: python val.py --weights yolov9-c-converted.onnx
PyTorch Hub: model = torch.hub.load('ultralytics/yolov5', 'custom', 'yolov9-c-converted.onnx')
Visualize: https://netron.app
atc --model=yolov9-c-converted.onnx --framework=5
--output=yolov9-c-converted
--input_shape="images:1,3,640,640"
--soc_version=Ascend310P3
--insert_op_conf=aipp.cfg

点击关注,第一时间了解华为云新鲜技术~

实例讲解昇腾 CANN YOLOV8 和 YOLOV9 适配的更多相关文章

  1. 开发实践丨昇腾CANN的推理应用开发体验

    摘要:这是关于一次 Ascend 在线实验的记录,主要内容是通过网络模型加载.推理.结果输出的部署全流程展示,从而快速熟悉并掌握 ACL(Ascend Computing Language)基本开发流 ...

  2. 一键抠除路人甲,昇腾CANN带你识破神秘的“AI消除术”

    摘要:都说人工智能改变了生活,你感觉到了么?AI的魔力就在你抠去路人甲的一瞬间来到了你身边.今天就跟大家聊聊--神秘的"AI消除术". 引语 旅途归来,重温美好却被秀丽河山前的路人 ...

  3. 昇腾CANN论文上榜CVPR,全景图像生成算法交互性再增强!

    摘要:近日,CVPR 2022放榜,基于CANN的AI论文<Interactive Image Synthesis with Panoptic Layout Generation>强势上榜 ...

  4. float实例讲解

    float实例讲解 float是个强大的属性,在实际前端开发过程中,人们经常拿它来进行布局,但有时,使用的不好,也麻烦多多啊. 比如,现在我们要实现一个两列布局,左边的列,宽度固定:右边的列,宽度自动 ...

  5. S3C2440上RTC时钟驱动开发实例讲解(转载)

    嵌入式Linux之我行,主要讲述和总结了本人在学习嵌入式linux中的每个步骤.一为总结经验,二希望能给想入门嵌入式Linux的朋友提供方便.如有错误之处,谢请指正. 共享资源,欢迎转载:http:/ ...

  6. 实例讲解Oracle数据库设置默认表空间问题

    实例讲解Oracle数据库设置默认表空间问题   实例讲解Oracle数据库设置默认表空间问题,阅读实例讲解Oracle数据库设置默认表空间问题,DBA们经常会遇到一个这样令人头疼的问题:不知道谁在O ...

  7. 基于tcpdump实例讲解TCP/IP协议

    前言 虽然网络编程的socket大家很多都会操作,但是很多还是不熟悉socket编程中,底层TCP/IP协议的交互过程,本文会一个简单的客户端程序和服务端程序的交互过程,使用tcpdump抓包,实例讲 ...

  8. makefile基础实例讲解 分类: C/C++ 2015-03-16 10:11 66人阅读 评论(0) 收藏

    一.makefile简介 定义:makefile定义了软件开发过程中,项目工程编译链.接接的方法和规则. 产生:由IDE自动生成或者开发者手动书写. 作用:Unix(MAC OS.Solars)和Li ...

  9. 实例讲解Linux系统中硬链接与软链接的创建

    导读 Linux链接分两种,一种被称为硬链接(Hard Link),另一种被称为符号链接(Symbolic Link).默认情况下,ln命令产生硬链接.硬链接与软链接的区别从根本上要从Inode节点说 ...

  10. spring事务传播机制实例讲解

    http://kingj.iteye.com/blog/1680350   spring事务传播机制实例讲解 博客分类:   spring java历险     天温习spring的事务处理机制,总结 ...

随机推荐

  1. JVM内存模式

    Java内存模型即Java Memory Model,简称JMM. JMM定义了Java 虚拟机(JVM)在计算机内存(RAM)中的工作方式.JVM 是整个计算机虚拟模型,所以 JMM 是隶属于 JV ...

  2. Advanced .Net Debugging 3:基本调试任务(上)

    一.简介 这是我的<Advanced .Net Debugging>这个系列的第三篇文章.这个系列的每篇文章写的周期都要很长,因为每篇文章都是原书的一章内容(太长的就会分开写).再者说,原 ...

  3. xml中xsd、xsi、xmlns的含义

    XML是可扩展标记语言,它定义了按格式编码文件的一系列规则[3],编码的文件是机器可读和人可读的.XML文件对于机器可读是基于XSD(XML Schema Definition)[1]的.XSD是受W ...

  4. js收藏网页功能,纠正网上乱转没求证的案例

    网站一般流行以下收藏代码 function AddFavorite(title, url){ try{ //ie收藏 window.external.addFavorite(url, title); ...

  5. 3、dubbo核心用法

    https://dubbo.apache.org/zh/docs/v2.7/user/examples/preflight-check/ 1.启动时检查 在启动时检查依赖的服务是否可用 Dubbo 缺 ...

  6. IIS web.config 跨域设置 不包含 options的设置 thinkphp tp3 跨域

    web.config <?xml version="1.0" encoding="UTF-8"?> <configuration> &l ...

  7. 基于可穿戴的GPS定位存储模块方案特色解析

    前记   GPS作为一个位置定位手段,在日常生活中扮演着非常重要的角色.在研发动物可穿戴产品的同时.团队一直在做产品和模块标准化的事情,尽量把研发出来的东西标准化.按照任老板的说法,在追求理想主义的路 ...

  8. 《TencentNCNN系列》 之工作原理简要解析(以LeNet-5为例)

    PS:要转载请注明出处,本人版权所有. PS: 这个只是基于<我自己>的理解, 如果和你的原则及想法相冲突,请谅解,勿喷. 前置说明   本文作为本人csdn blog的主站的备份.(Bl ...

  9. offer收割机--js的隐式类型转换规则整理

    类型转换 文中的值类型等价于所说的基础类型,其范围是(boolean,string,number) 转换为基础类型 布尔值 undefined, null, false, NaN,'', 0 --&g ...

  10. 三维模型OBJ格式轻量化压缩处理效率提高的技术方法探讨

    三维模型OBJ格式轻量化压缩处理效率提高的技术方法探讨 要提高三维模型OBJ格式轻量化压缩处理的效率,可以采取以下方法: 1.优化算法选择:选择合适的优化算法对模型进行轻量化处理.不同的优化算法有不同 ...