yolov8在rknn(rv1109/1126)模型转换、量化移植过程

news/2024/11/16 9:42:50/

续:rv1109/1126 rknn 模型量化过程_CodingInCV的博客-CSDN博客

Yolov8简介

yolov8是比较新的目标检测模型,根据论文和开源项目的报告,相对使用比较广泛的yolov5提升还比较明显。
image.png
yolov8与yolov5相比,结构上的主要区别是将C3结构换成了C2f,检测头换成了anchor free检测头(详细见:YOLOv8 深度详解!一文看懂,快速上手 - 知乎 (zhihu.com))。
image.png
image.png

模型转换和量化

模型导出的修改

当我们直接将模型导出为onnx,然后进行量化,我们会发现整个过程是可以进行了,也就是从算子角度来说,rknn是支持yolov8的。但是如果拿量化好的模型进行仿真推理,会发现无法检测到目标(根据我的尝试,若有不同结论,欢迎分享)。
根据分析pytorch的yolov8代码以及观察yolov8的模型结构,会发现,有一部分后处理操作被导出到了onnx模型中:
image.png
这一部分在训练中是忽略的,只会在推理中存在:
image.png

但若我们导出的onnx中包含了这一部分,会参与量化的过程,可能对结果造成了比较大的影响(也是猜测,博主也尝试了混合量化,仍旧没有解决)。既然这部分并没有包含参数信息,我们可以不导出这一部分,而将这一部分作为后处理的一部分自行实现。修改ultralytics/nn/modules/head.py中的条件:
image.png
这样我们导出的模型就不包含后处理这部分了,模型输出变成了3个:
image.png

分别是三个特征层的输出(每个都是box层和class层拼接后的结果)。通道数144=16*4(框的4个坐标)+80(coco类别数)。

后处理的实现

改变了模型的输出,就需要我们自己实现这部分后处理了,这部分我们可以参考ultralytics/nn/modules/head.py中的实现,主要的过程:
1.将三个特征层输出拼接得到1x144x8400的特征图,8400=80*80+40*40+20*20
2.将特征图切分为1x64x8400(对于框)和1x80x8400(对于类别)的2个特征图
3.对于框的特征图进行softmax操作后,与1x16x1x1的矩阵进行一个卷积,然后转换为原图上的xywh坐标(1x4x8400),对于类别特征图进行sigmoid操作
4.将2个特征图拼接得到1x84x8400的特征图(与未修改导出前的模型输出一致了)
5.阈值过滤以及nms得到最终的输出。
对于1~4,python(numpy)实现如下:

def xywh2xyxy(x: np.ndarray):"""Convert bounding box coordinates from (x, y, width, height) format to (x1, y1, x2, y2) format where (x1, y1) is thetop-left corner and (x2, y2) is the bottom-right corner.Args:x (np.ndarray) or (torch.Tensor): The input bounding box coordinates in (x, y, width, height) format.Returns:y (np.ndarray) or (torch.Tensor): The bounding box coordinates in (x1, y1, x2, y2) format."""y = np.copy(x)y[..., 0] = x[..., 0] - x[..., 2] / 2  # top left xy[..., 1] = x[..., 1] - x[..., 3] / 2  # top left yy[..., 2] = x[..., 0] + x[..., 2] / 2  # bottom right xy[..., 3] = x[..., 1] + x[..., 3] / 2  # bottom right yreturn ydef make_anchors(feats: np.ndarray, strides, grid_cell_offset=0.5):"""Generate anchors from features."""anchor_points, stride_tensor = [], []assert feats is not Nonedtype = feats[0].dtypefor i, stride in enumerate(strides):_, _, h, w = feats[i].shapesx = np.arange(stop=w, dtype=dtype) + grid_cell_offset  # shift xsy = np.arange(stop=h, dtype=dtype) + grid_cell_offset  # shift ysx, sy = np.meshgrid(sx, sy)anchor_points.append(np.stack((sx, sy), -1).reshape(-1, 2))stride_tensor.append(np.full((h * w, 1), stride, dtype=dtype))return np.concatenate(anchor_points), np.concatenate(stride_tensor)def dist2bbox(distance: np.ndarray, anchor_points, xywh=True, dim=-1):"""Transform distance(ltrb) to box(xywh or xyxy)."""lt, rb = np.array_split(distance, 2, dim)x1y1 = anchor_points - ltx2y2 = anchor_points + rbif xywh:c_xy = (x1y1 + x2y2) / 2wh = x2y2 - x1y1return np.concatenate((c_xy, wh), dim)  # xywh bboxreturn np.concatenate((x1y1, x2y2), dim)  # xyxy bboxdef softmax(x, axis=-1):# 计算指数exp_x = np.exp(x - np.max(x, axis=axis, keepdims=True))# 计算分母sum_exp_x = np.sum(exp_x, axis=axis, keepdims=True)# 计算 softmaxsoftmax_x = exp_x / sum_exp_xreturn softmax_xdef sigmoid(x):return 1 / (1 + np.exp(-x))def dfl(x: np.ndarray):c1 = 16b, c, a = x.shapeconv = np.arange(0, c1, dtype=np.float32)conv = conv.reshape(1, 16,1,1)softmax_x = softmax(x.reshape(b, 4, c1, a).transpose(0,2, 1,3), 1)return np.sum(softmax_x * conv, 1,keepdims=True).reshape(b, 4, a)def yolov8_head(x, anchors, nc):  # prediction headstrides = [8, 16, 32]  # P3, P4, P5 stridesshape = x[0].shapereg_max = 16no = nc + reg_max*4  # number of outputs per anchoranchors, strides = (x.transpose(1, 0) for x in make_anchors(x, strides, 0.5))x_cat = np.concatenate([xi.reshape(shape[0], no, -1) for xi in x], 2)box, cls = np.split(x_cat,(reg_max*4,), 1)dbox = dist2bbox(dfl(box), anchors[np.newaxis,:], xywh=True, dim=1) * stridesy = np.concatenate((dbox, sigmoid(cls)), 1)return y

对于5:

def yolov8_postprocess(prediction: np.ndarray,conf_thres=0.25,iou_thres=0.45,classes=None,agnostic=False,multi_label=False,labels=(),max_det=300,nc=0,  # number of classes (optional)max_time_img=0.05,max_nms=30000,max_wh=7680,):assert 0 <= conf_thres <= 1, f'Invalid Confidence threshold {conf_thres}, valid values are between 0.0 and 1.0'assert 0 <= iou_thres <= 1, f'Invalid IoU {iou_thres}, valid values are between 0.0 and 1.0'if isinstance(prediction, (list, tuple)):  # YOLOv8 model in validation model, output = (inference_out, loss_out)prediction = prediction[0]  # select only inference outputbs = prediction.shape[0]  # batch sizenc = nc or (prediction.shape[1] - 4)  # number of classesnm = prediction.shape[1] - nc - 4mi = 4 + nc  # mask start indexxc = np.amax(prediction[:, 4:mi], 1) > conf_thres # scores per image# Settings# min_wh = 2  # (pixels) minimum box width and heighttime_limit = 0.5 + max_time_img * bs  # seconds to quit afterredundant = True  # require redundant detectionsmulti_label &= nc > 1  # multiple labels per box (adds 0.5ms/img)merge = False  # use merge-NMSt = time.time()output = [np.zeros((0, 6 + nm))] * bsfor xi, x in enumerate(prediction):  # image index, image inference# Apply constraints# x[((x[:, 2:4] < min_wh) | (x[:, 2:4] > max_wh)).any(1), 4] = 0  # width-heightx = x.transpose(1, 0)[xc[xi]]  # confidence# If none remain process next imageif not x.shape[0]:continue# Detections matrix nx6 (xyxy, conf, cls)box, cls, mask = np.split(x,(4, nc+4,), 1)box = xywh2xyxy(box)  # center_x, center_y, width, height) to (x1, y1, x2, y2)if multi_label:i, j = (cls > conf_thres).nonzero(as_tuple=False).Tx = np.concatenate((box[i], x[i, 4 + j, None], j[:, None].float(), mask[i]), 1)else:  # best class onlyconf = cls.max(1, keepdims=True)j = np.argmax(cls, 1, keepdims=True)x = np.concatenate((box, conf, j.astype(float), mask), 1)[np.squeeze(conf > conf_thres)]# Apply finite constraint# if not torch.isfinite(x).all():#     x = x[torch.isfinite(x).all(1)]# Check shapen = x.shape[0]  # number of boxesif not n:  # no boxescontinuex = x[x[:, 4].argsort()[::-1][:max_nms]]  # sort by confidence and remove excess boxes# Batched NMSc = x[:, 5:6] * (0 if agnostic else max_wh)  # classes,如果是agnostic,那么就是0,否则就是max_wh,为了对每种类别的框进行NMSboxes, scores = x[:, :4] + c, x[:, 4]  # boxes (offset by class), scores# i = torchvision.ops.nms(boxes, scores, iou_thres)  # NMSi = cv2.dnn.NMSBoxes(boxes.tolist(), scores.tolist(), conf_thres,iou_thres).flatten()i = i[:max_det]  # limit detectionsoutput[xi] = x[i]if (time.time() - t) > time_limit:break  # time limit exceededreturn output

onnx模型推理验证

为了验证我们实现的后处理的正确性,先使用onnx模型推理测试。

import os
import cv2
import onnxruntime
import numpy as np
model = onnxruntime.InferenceSession("weights/yolov8n.onnx",providers=['CUDAExecutionProvider'])
input_name = model.get_inputs()[0].name
image_name="0.jpg"
image = cv2.imread(image_name)
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
image = cv2.resize(image, (640,640))
image = image.transpose(2,0,1)
image = image.astype('float32')
image = image/255.0
image = image[np.newaxis,:]outputs = model.run(None, {input_name: image})
outputs = yolov8_head(outputs, anchors=None, nc=80)
outputs = yolov8_postprocess(outputs, conf_thres=0.25, iou_thres=0.45, classes=None, agnostic=False, multi_label=False, labels=(), max_det=300, nc=0, max_time_img=0.05, max_nms=30000, max_wh=7680)
result = outputs[0]
image = cv2.imread(image_path)
for box in result:box = box.tolist()cls = int(box[5])box[0:4] = [int(i) for i in box[0:4]]x1 = box[0]y1 = box[1]x2 = box[2]y2 = box[3]score = box[4]cv2.rectangle(image, (x1,y1), (x2,y2), (0,0,255), 2)cv2.putText(image, "{}_{}".format(cls,str(score)), (x1,y1), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,0,255), 2)
# cv2.imwrite(os.path.join(save_dir, str(not_zeor_cls[0]), image_name), image)
cv2.imshow("image", image)
cv2.waitKey(0)    

image.png
结果与pytorch执行结果完全一致。

模型量化

步骤参考 rv1109/1126 rknn 模型量化过程_CodingInCV的博客-CSDN博客 采用默认量化方式。

rknn推理

if __name__ == "__main__":# Create RKNN objectrknn = RKNN()# Direct load rknn modelprint('--> Loading RKNN model')ret = rknn.load_rknn('/mnt/pai-storage-8/jieshen/code/yolov8/weights/yolov8n_nohead.rknn')rknn.eval_perf()if ret != 0:print('Load  failed!')exit(ret)rknn.init_runtime()image = cv2.imread("../coco_resize/0.jpg")image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)outputs = rknn.inference(inputs=[image])outputs = yolov8_head(outputs, None, 80)# print(outputs.shape)outputs = yolov8_postprocess(outputs, conf_thres=0.25, iou_thres=0.45, max_det=300)result = outputs[0]image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)for box in result:box = box.tolist()cls = int(box[5])box[0:4] = [int(i) for i in box[0:4]]x1 = box[0]y1 = box[1]x2 = box[2]y2 = box[3]score = box[4]cv2.rectangle(image, (x1,y1), (x2,y2), (0,0,255), 2)cv2.putText(image, "{}_{}".format(cls,str(score)), (x1,y1), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,0,255), 2)# cv2.imwrite(os.path.join(save_dir, str(not_zeor_cls[0]), image_name), image)cv2.imshow("image", image)cv2.waitKey(0)   rknn.release()

image.png
可以看到检测结果和原模型还是差异挺大的(15是猫,66是键盘),不过结果还是正确的。总体的量化精度有待评估。

结语

通过对导出的模型进行一定的修改,1109上可以实现yolov8的运行并得到检测框,不过最终的运行速度和精度还有待验证。后处理的方式目前也是完全按照pytorch中的实现,过多的concat和split,可能对于C++并不太友好,后续尝试用更好的实现方式。
Todo: 量化精度的测试以及C++部署


http://www.ppmy.cn/news/1010179.html

相关文章

谈谈网络安全

目录 1.概念 2.发展现状 3.主要问题 1.概念 网络安全是指保护计算机网络和其中的数据免受未经授权访问、损坏、窃取或破坏的过程和技术。网络安全涉及预防和检测潜在的威胁和漏洞&#xff0c;并采取措施保护网络的机密性、完整性和可用性。 网络安全的概念包括以下几个方面&am…

每日一题——两数之和

题目 给出一个整型数组 numbers 和一个目标值 target&#xff0c;请在数组中找出两个加起来等于目标值的数的下标&#xff0c;返回的下标按升序排列。 &#xff08;注&#xff1a;返回的数组下标从1开始算起&#xff0c;保证target一定可以由数组里面2个数字相加得到&#xff0…

C++路线(全网20篇高赞文章总结)

为节省时间&#xff0c;可直接跳转到 --> &#x1f33c;干货 目录 &#x1f33c;前言 &#x1f33c;来源 &#x1f416;现状 &#x1f33c;干货 入门阶段 入门项目 学习顺序 &#x1f409;大二打算 &#x1f33c;前言 来源的20篇博客&#xff0c;视频中&#x…

【C++】二叉搜索树的模拟实现

&#x1f307;个人主页&#xff1a;平凡的小苏 &#x1f4da;学习格言&#xff1a;命运给你一个低的起点&#xff0c;是想看你精彩的翻盘&#xff0c;而不是让你自甘堕落&#xff0c;脚下的路虽然难走&#xff0c;但我还能走&#xff0c;比起向阳而生&#xff0c;我更想尝试逆风…

neo4j查询语言Cypher详解(二)--Pattern和类型

Patterns 图形模式匹配是Cypher的核心。它是一种用于通过应用声明性模式从图中导航、描述和提取数据的机制。在MATCH子句中&#xff0c;可以使用图模式定义要搜索的数据和要返回的数据。图模式匹配也可以在不使用MATCH子句的情况下在EXISTS、COUNT和COLLECT子查询中使用。 图…

Sencha Ext.NET Crack 快速应用程序的正确工具集

Sencha Ext.NET Crack 快速应用程序的正确工具集 Sencha Ext.NET是一个高级的ASP.NET核心组件框架&#xff0c;它包含了强大的跨浏览器Sencha Ext JS库。通过140多个预构建和专业测试的UI组件实现企业级性能和生产效率。Sencha Ext.NET使用尖端的Web技术创建功能强大的Web应用程…

[JavaScript游戏开发] Q版地图上让英雄、地图都动起来

系列文章目录 第一章 2D二维地图绘制、人物移动、障碍检测 第二章 跟随人物二维动态地图绘制、自动寻径、小地图显示(人物红点显示) 第三章 绘制冰宫宝藏地图、人物鼠标点击移动、障碍检测 第四章 绘制Q版地图、键盘上下左右地图场景切换 第五章 Q版地图上让英雄、地图都动起来…

linux下载软件包

linux下载软件包 linux下只有两种软件包 源码包(tar 压缩包&#xff0c;如有.tar.gz 和.tar.bz2) 二进制包(rpm) centos下 (除了rpm还有srpm&#xff0c;srpm 包为未编译过的 rpm 包&#xff0c;需要以 rpm 管理的方式编译&#xff0c;然后以 rpm 的安装方式安装) RPM包操作 rp…