本文首发于公众号【DeepDriving】,欢迎关注。
RT-DETR
是百度开源的一个基于DETR
架构的实时端到端目标检测算法,在速度和精度上均超过了YOLOv5
、YOLOv8
等YOLO
系列检测算法,目前在YOLOv8
的官方代码仓库ultralytics
中也已支持RT-DETR
算法。
在上一篇文章《AI模型部署 | onnxruntime部署YOLOv8分割模型详细教程》中我介绍了如何使用onnxruntime框架来部署YOLOv8
分割模型,本文将介绍如何使用onnxruntime
框架来部署RT-DETR
模型,代码还是采用Python
实现。
安装onnxruntime
onnxruntime
分为GPU
版本和CPU
版本,均可以通过pip
直接安装:
pip install onnxruntime-gpu #安装GPU版本
pip install onnxruntime #安装CPU版本
注意: GPU
版本和CPU
版本建议只选其中一个安装,否则默认会使用CPU
版本。
下载RT-DETR
模型
Ultralytics
官方提供了用COCO
数据集训练的RT-DETR
模型权重,我们可以直接从GitHub
网站https://github.com/ultralytics/assets/releases
下载使用,本文使用的模型为rtdetr-l.pt
。
转换为onnx
模型
调用下面的命令可以把rtdetr-l.pt
模型转换为onnx
格式的模型:
yolo export model=rtdetr-l.pt format=onnx opset=17 simplify=True
转换成功后得到的模型为rtdetr-l.onnx
。这里设置opset=17
是为了能够直接导出LayerNormalization
算子,因为onnx
从版本17
开始才支持该算子。如果opset
小于17
,那么导出模型的时候会把LayerNormalization
算子拆分成一系列子算子进行导出。下面两幅图分别是设置opset=16
和opset=17
导出的onnx
模型中的结构:
opset=16,红色框内是把LayerNormalization进行拆分的结果
opset=17:
另外,这里设置simplify=True
是为了调用onnx-simplifier
工具对模型进行简化,否则生成的模型有一大堆零碎的算子,看起来就很麻烦。
首先导入onnxruntime
包,然后调用其API
加载模型即可:
import onnxruntime as ort
session = ort.InferenceSession("rtdetr-l.onnx", providers=["CUDAExecutionProvider"])
因为我使用的是GPU
版本的onnxruntime
,所以providers
参数设置的是"CUDAExecutionProvider"
;如果是CPU
版本,则需设置为"CPUExecutionProvider"
。
模型加载成功后,我们可以查看一下模型的输入、输出层的属性:
for input in session.get_inputs():
print("input name: ", input.name)
print("input shape: ", input.shape)
print("input type: ", input.type)
for output in session.get_outputs():
print("output name: ", output.name)
print("output shape: ", output.shape)
print("output type: ", output.type)
结果如下:
input name: images
input shape: [1, 3, 640, 640]
input type: tensor(float)
output name: output0
output shape: [1, 300, 84]
output type: tensor(float)
从上面的打印信息可以知道,模型有一个尺寸为[1, 3, 640, 640]
的输入层和一个尺寸分别为[1, 300, 84]
的输出层。
数据预处理采用OpenCV
和Numpy
实现,首先导入这两个包
import cv2
import numpy as np
用OpenCV
读取图片后,把数据按照与YOLOv8
一样的要求做预处理
image = cv2.imread("soccer.jpg")
image_height, image_width, _ = image.shape
input_tensor = prepare_input(image, model_width, model_height)
print("input_tensor shape: ", input_tensor.shape)
其中预处理函数prepare_input
的实现如下:
def prepare_input(bgr_image, width, height):
image = cv2.cvtColor(bgr_image, cv2.COLOR_BGR2RGB)
image = cv2.resize(image, (width, height)).astype(np.float32)
image = image / 255.0
image = np.transpose(image, (2, 0, 1))
input_tensor = np.expand_dims(image, axis=0)
return input_tensor
处理流程如下:
1. 把OpenCV读取的BGR格式的图片转换为RGB格式;
2. 把图片resize到模型输入尺寸640x640;
3. 对像素值除以255做归一化操作;
4. 把图像数据的通道顺序由HWC调整为CHW;
5. 扩展数据维度,将数据的维度调整为NCHW。
经过预处理后,输入数据input_tensor
的维度变为[1, 3, 640, 640]
,与模型的输入尺寸一致。
输入数据准备好以后,就可以送入模型进行推理:
outputs = session.run(None, {session.get_inputs()[0].name: input_tensor})
前面我们打印了模型的输入输出属性,可以知道模型只有一个输出分支,所以只要取outputs[0]
的数据进行处理:
output = np.squeeze(outputs[0])
print("output shape: ", output.shape)
output shape: (300, 84)
从前文知道模型输出的维度为300x84
,其中300
表示模型在一张图片上最多能检测的目标数量,84
表示每个目标的参数包含4
个坐标属性(cx,cy,w,h
)和80
个类别置信度。每个目标的坐标信息都是做了归一化的,只要乘以原始图像的尺寸就可以把坐标恢复到在原始图像中的大小。RT-DETR
的检测结果不需要再做NMS
这些额外的后处理操作。后处理的代码如下:
for out in output:
confidence = out[4:].max()
if confidence < 0.5:
continue
class_id = out[4:].argmax()
cx, cy, bw, bh = out[:4]
xmin = (cx - 0.5 * bw) * image_width
xmax = (cx + 0.5 * bw) * image_width
ymin = (cy - 0.5 * bh) * image_height
ymax = (cy + 0.5 * bh) * image_height
可以看到,RT-DETR
的后处理极其简单!
检测效果也是很不错的: