1引用

[1] 同济子豪兄的github项目
[2] 小破站关键点检测视频

onnx部署的优点就是快,比原来模型要快3倍。这里大佬的部署可谓是无敌,读完之后膜拜了。在此向大佬的开源精神致敬,这一系列代码让我少走路很多弯路。

2代码以及解析

2.1代码解析

导入一下常用的库,因为我的老师会在部署的时候,将torch这个比较大的框架去掉,但这边作者是保留的。实际项目中可以考虑去掉torch框架,能够减少软件的体量。

import cv2
import numpy as np
from PIL import Image

import onnxruntime

import torch
# 有 GPU 就用 GPU,没有就用 CPU
device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')

import matplotlib.pyplot as plt
%matplotlib inline

这里需要指定关键点的shape,因为是3角板,所以是3个点,每个点有两个坐标和一个置信度信息,所以shape是[3, 3]
在这里插入图片描述

kpts_shape = [3, 3] # 关键点 shape

创建onnx的推理引擎

ort_session = onnxruntime.InferenceSession('checkpoint/Triangle_215_yolov8l_pretrain.onnx', providers=['CUDAExecutionProvider', 'CPUExecutionProvider'])

获取输入层的信息,这是模型的本身信息。
在这里插入图片描述
获取模型的输出层信息。对于onnx部署,我们需要提前了解的就是输入输出层的信息。
在这里插入图片描述
用opencv读取图像
在这里插入图片描述
图像的预处理工作。从之前推理引擎中可以看到输入图片的类型必须是[1,3,640,640]。
在这里插入图片描述
求图像的缩放比例,通常按照缩放大的比例作为标准
在这里插入图片描述
对于图像需要进行归一化,然后变成tensor
在这里插入图片描述
将图像输入到推理引擎。之后将输出的转成tensor,因为yolov8处理结果的时候,预测结果的类型就是tensor的类型。yolov有很多成熟的解析结果的函数,比如非极大值抑制等等都是基于tensor的预测结果的。

# ONNX Runtime 推理预测
ort_output = ort_session.run(output_name, {input_name[0]: input_tensor.cpu().numpy()})[0]

# 转 Tensor
preds = torch.Tensor(ort_output)

这里使用了yolov8自带的非极大值抑制的函数。pred = preds[0]输出结果为torch.Size([3, 15]),表示了三个图片,每个图片上有15个参数。

from ultralytics.utils import ops
preds = ops.non_max_suppression(preds, conf_thres=0.25, iou_thres=0.7, nc=1)
pred = preds[0]

len(pred_det)表示长度,这里等于pred的行数。因为一行记录了一个预测框的信息。

pred_det = pred[:, :6].cpu().numpy()
num_bbox = len(pred_det)
print('预测出 {} 个框'.format(num_bbox))

这里可以看到pred的每个部分的参数代表的含义。第一次觉得这么具体的。
在这里插入图片描述
我们知道的输入图像尺寸要求的就是640*640,因此对于预测结果来说,预测框坐标值被缩放了,因此我们需要将xy重新映射会原图像上。此外画图需要保证坐标点为整数uint32。

# 目标检测框 XYXY 坐标
# 还原为缩放之前原图上的坐标
pred_det[:, 0] = pred_det[:, 0] * x_ratio
pred_det[:, 1] = pred_det[:, 1] * y_ratio
pred_det[:, 2] = pred_det[:, 2] * x_ratio
pred_det[:, 3] = pred_det[:, 3] * y_ratio
bboxes_xyxy = pred_det[:, :4].astype('uint32')

我们再来解析关键点的数据。关键点数据为pred的第6列到最后,一共3个框*每个框9个值=27个值。
在这里插入图片描述
我们将关键点坐标映射回原图

# 还原为缩放之前原图上的坐标
bboxes_keypoints[:,:,0] = bboxes_keypoints[:,:,0] * x_ratio
bboxes_keypoints[:,:,1] = bboxes_keypoints[:,:,1] * y_ratio
bboxes_keypoints = bboxes_keypoints.astype('uint32')

下面这部分和前两天的一个模版。

2.2opencv可视化模版

# 框(rectangle)可视化配置
bbox_color = (150, 0, 0)             # 框的 BGR 颜色
bbox_thickness = 6                   # 框的线宽

# 框类别文字
bbox_labelstr = {
    'font_size':4,         # 字体大小
    'font_thickness':10,   # 字体粗细
    'offset_x':0,          # X 方向,文字偏移距离,向右为正
    'offset_y':-80,        # Y 方向,文字偏移距离,向下为正
}

# 关键点 BGR 配色
kpt_color_map = {
    0:{'name':'angle_30', 'color':[255, 0, 0], 'radius':40},      # 30度角点
    1:{'name':'angle_60', 'color':[0, 255, 0], 'radius':40},      # 60度角点
    2:{'name':'angle_90', 'color':[0, 0, 255], 'radius':40},      # 90度角点
}

# 点类别文字
kpt_labelstr = {
    'font_size':4,             # 字体大小
    'font_thickness':10,       # 字体粗细
    'offset_x':30,             # X 方向,文字偏移距离,向右为正
    'offset_y':120,            # Y 方向,文字偏移距离,向下为正
}

# 骨架连接 BGR 配色
skeleton_map = [
    {'srt_kpt_id':0, 'dst_kpt_id':1, 'color':[196, 75, 255], 'thickness':3},        # 30度角点-60度角点
    {'srt_kpt_id':0, 'dst_kpt_id':2, 'color':[180, 187, 28], 'thickness':3},        # 30度角点-90度角点
    {'srt_kpt_id':1, 'dst_kpt_id':2, 'color':[47,255, 173], 'thickness':3},         # 60度角点-90度角点
]

for idx in range(num_bbox): # 遍历每个框
    
    # 获取该框坐标
    bbox_xyxy = bboxes_xyxy[idx] 
    
    # 获取框的预测类别(对于关键点检测,只有一个类别)
    bbox_label = 'sjb_rect'
    
    # 画框
    img_bgr = cv2.rectangle(img_bgr, (bbox_xyxy[0], bbox_xyxy[1]), (bbox_xyxy[2], bbox_xyxy[3]), bbox_color, bbox_thickness)
    
    # 写框类别文字:图片,文字字符串,文字左上角坐标,字体,字体大小,颜色,字体粗细
    img_bgr = cv2.putText(img_bgr, bbox_label, (bbox_xyxy[0]+bbox_labelstr['offset_x'], bbox_xyxy[1]+bbox_labelstr['offset_y']), cv2.FONT_HERSHEY_SIMPLEX, bbox_labelstr['font_size'], bbox_color, bbox_labelstr['font_thickness'])
    
    bbox_keypoints = bboxes_keypoints[idx] # 该框所有关键点坐标和置信度
    
    # 画该框的骨架连接
    for skeleton in skeleton_map:
        
        # 获取起始点坐标
        srt_kpt_id = skeleton['srt_kpt_id']
        srt_kpt_x = bbox_keypoints[srt_kpt_id][0]
        srt_kpt_y = bbox_keypoints[srt_kpt_id][1]
        
        # 获取终止点坐标
        dst_kpt_id = skeleton['dst_kpt_id']
        dst_kpt_x = bbox_keypoints[dst_kpt_id][0]
        dst_kpt_y = bbox_keypoints[dst_kpt_id][1]
        
        # 获取骨架连接颜色
        skeleton_color = skeleton['color']
        
        # 获取骨架连接线宽
        skeleton_thickness = skeleton['thickness']
        
        # 画骨架连接
        img_bgr = cv2.line(img_bgr, (srt_kpt_x, srt_kpt_y),(dst_kpt_x, dst_kpt_y),color=skeleton_color,thickness=skeleton_thickness)
        
    # 画该框的关键点
    for kpt_id in kpt_color_map:
        
        # 获取该关键点的颜色、半径、XY坐标
        kpt_color = kpt_color_map[kpt_id]['color']
        kpt_radius = kpt_color_map[kpt_id]['radius']
        kpt_x = bbox_keypoints[kpt_id][0]
        kpt_y = bbox_keypoints[kpt_id][1]
        
        # 画圆:图片、XY坐标、半径、颜色、线宽(-1为填充)
        img_bgr = cv2.circle(img_bgr, (kpt_x, kpt_y), kpt_radius, kpt_color, -1)
        
        # 写关键点类别文字:图片,文字字符串,文字左上角坐标,字体,字体大小,颜色,字体粗细
        # kpt_label = str(kpt_id) # 写关键点类别 ID
        kpt_label = str(kpt_color_map[kpt_id]['name']) # 写关键点类别名称
        img_bgr = cv2.putText(img_bgr, kpt_label, (kpt_x+kpt_labelstr['offset_x'], kpt_y+kpt_labelstr['offset_y']), cv2.FONT_HERSHEY_SIMPLEX, kpt_labelstr['font_size'], kpt_color, kpt_labelstr['font_thickness'])

最后得出的图像是这样子的

plt.imshow(img_bgr[:,:,::-1])
plt.show()

在这里插入图片描述

3总结

其实我们可以看到应用onnx来预测确实是准确性有所降低,这里也是用的最小的模型yolov8n-pose的模型,可能用大的模型会更好。但重点是一些套路,比如onnx推理引擎的使用,opencv可视化的模版,这些套路对以后开发是很有帮助的。

4相关文章

[1]三天从YOLOV8关键点检测入门到实战(第一天)——初识YOLOV8
[2] 三天从YOLOV8关键点检测入门到实战(第二天)——用python调用YOLOV8预测图片并解析结果
[3]三天从YOLOV8关键点检测入门到实战(第二天)——用python调用YOLOV8预测视频并解析结果
[4]三天从YOLOV8关键点检测入门到实战(第三天)——用onnx部署yolov8

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐