本帖最后由 waterman 于 2025-1-28 23:53 编辑
本节将基于RDK Python 语言的hobot_dnn模型推理库,完成静态图片推理学习,包括模型加载、数据预处理、模型推理、算法结果后处理等操作。hobot_dnn推理库主要使用的类和接口如下:
-
Model : 算法模型类,执行加载算法模型、推理计算。
-
pyDNNTensor:算法数据输入、输出数据 tensor 类。
-
TensorProperties :模型输入 tensor 的属性类。
-
load:加载算法模型。
模型推理流程
开发板Ubuntu系统预装了Python版本的pyeasy_dnn模型推理模块,通过加载模型并创建Model对象,完成模型推理、数据解析等功能。
模型加载
from hobot_dnn import pyeasy_dnn as dnn
#create model object
models = dnn.load('./model.bin')
图像推理
#do inference with image
outputs = models[0].forward(image)
数据解析
for item in outputs:
output_array.append(item.buffer)
post_process(output_array)
其中在图像推理部分,我们需要输入待处理的图像,但这个图像的大小我们应该如何查看呢,可以使用Model.inputs进行查看。以yolov5s_672x672_nv12.bin为例,在app/pydev_demo目录下运行如下代码,查看模型的tensor输入信息。
from hobot_dnn import pyeasy_dnn as dnn
def print_properties(pro):
print("tensor type:", pro.tensor_type)
print("data type:", pro.dtype)
print("layout:", pro.layout)
print("shape:", pro.shape)
models = dnn.load('./models/yolov5s_672x672_nv12.bin')
input = models[0].inputs[0]
print_properties(input.properties)
输出结果如下:
其中NV12指的是图像格式,属于 YUV 颜色空间中的 YUV420SP 格式,每四个 Y 分量共用一组 U 分量和 V 分量,Y 连续存放,U 与 V 交叉存放。数据类型为uint8,数据格式为NCHW,既Batch*Channel*Height*Width,输入的形状为1*3*672*672。
此外,我们还能使用Model.outputs来查看模型的输出数据属性,具体代码如下:
from hobot_dnn import pyeasy_dnn as dnn
def print_properties(pro):
print("tensor type:", pro.tensor_type)
print("data type:", pro.dtype)
print("layout:", pro.layout)
print("shape:", pro.shape)
models = dnn.load('./models/yolov5s_672x672_nv12.bin')
output = models[0].outputs[0]
print_properties(output.properties)
输出结果如下图所示:
其中与输入有所不同,数据类型为float32,数据格式为NHWC,既Batch*Height*Width*Channel,输出的形状为1*84*84*255。
下面我们就以yolov5s_672x672_nv12模型为例,进行静态图片推理的实现。
静态图片推理
-
首先导入相关的库
import numpy as np
import cv2
from hobot_dnn import pyeasy_dnn as dnn
import time
import ctypes
import json
其中,time用于测量代码执行时间,ctypes提供了Python与C语言动态链接库交互的桥梁,json用于解析JSON格式的数据。
-
加载并配置后处理库
libpostprocess = ctypes.CDLL('/usr/lib/libpostprocess.so')
get_Postprocess_result = libpostprocess.Yolov5PostProcess
get_Postprocess_result.argtypes = [ctypes.POINTER(Yolov5PostProcessInfo_t)]
get_Postprocess_result.restype = ctypes.c_char_p
其中libpostprocess.so是官方提供的一个后处理动态库,其中包含了对Yolov5模型输出结果的后处理,需要使用ctypes调用。
-
读取模型并打印输入输出张量属性
models = dnn.load('../models/yolov5s_672x672_nv12.bin')
# 打印输入 tensor 的属性
print_properties(models[0].inputs[0].properties)
# 打印输出 tensor 的属性
print(len(models[0].outputs))
for output in models[0].outputs:
print_properties(output.properties)
-
图像读取与预处理
img_file = cv2.imread('./kite.jpg')
h, w = get_hw(models[0].inputs[0].properties)
des_dim = (w, h)
resized_data = cv2.resize(img_file, des_dim, interpolation=cv2.INTER_AREA)
nv12_data = bgr2nv12_opencv(resized_data)
这里使用kite.jpg图片进行推理,但是由于图片与模型的输出可能不同,因此需要调用cv2.resize对图像进行放缩,并且需要将图片格式转换为我们在上面看到的nv12的格式。
-
模型推理
t0 = time.time()
outputs = models[0].forward(nv12_data)
t1 = time.time()
-
数据后处理
yolov5_postprocess_info = Yolov5PostProcessInfo_t()
yolov5_postprocess_info.height = h
yolov5_postprocess_info.width = w
org_height, org_width = img_file.shape[0:2]
yolov5_postprocess_info.ori_height = org_height
yolov5_postprocess_info.ori_width = org_width
yolov5_postprocess_info.score_threshold = 0.4
yolov5_postprocess_info.nms_threshold = 0.45
yolov5_postprocess_info.nms_top_k = 20
yolov5_postprocess_info.is_pad_resize = 0
output_tensors = (hbDNNTensor_t * len(models[0].outputs))()
for i in range(len(models[0].outputs)):
output_tensors[i].properties.tensorLayout = get_TensorLayout(outputs[i].properties.layout)
if (len(outputs[i].properties.scale_data) == 0):
output_tensors[i].properties.quantiType = 0
output_tensors[i].sysMem[0].virAddr = ctypes.cast(outputs[i].buffer.ctypes.data_as(ctypes.POINTER(ctypes.c_float)), ctypes.c_void_p)
else:
output_tensors[i].properties.quantiType = 2
output_tensors[i].properties.scale.scaleData = outputs[i].properties.scale_data.ctypes.data_as(ctypes.POINTER(ctypes.c_float))
output_tensors[i].sysMem[0].virAddr = ctypes.cast(outputs[i].buffer.ctypes.data_as(ctypes.POINTER(ctypes.c_int32)), ctypes.c_void_p)
for j in range(len(outputs[i].properties.shape)):
output_tensors[i].properties.validShape.dimensionSize[j] = outputs[i].properties.shape[j]
libpostprocess.Yolov5doProcess(output_tensors[i], ctypes.pointer(yolov5_postprocess_info), i)
result_str = get_Postprocess_result(ctypes.pointer(yolov5_postprocess_info))
result_str = result_str.decode('utf-8')
这里进行YOLOv5模型的后处理信息,并调用外部C库进行后处理操作。首先创建一个Yolov5PostProcessInfo_t实例,该结构体包含后处理所需的各种参数。之后对每个输出张量调用Yolov5doProcess函数进行处理,得到字符串格式的输出。
-
结果解析并绘制边界框
data = json.loads(result_str[16:])
for result in data:
bbox = result['bbox'] # 矩形框位置信息
score = result['score'] # 得分
id = result['id'] # id
name = result['name'] # 类别名称
cv2.rectangle(img_file, (int(bbox[0]), int(bbox[1])), (int(bbox[2]), int(bbox[3])), (0, 255, 0), 2)
font = cv2.FONT_HERSHEY_SIMPLEX
cv2.putText(img_file, f'{name} {score:.2f}', (int(bbox[0]), int(bbox[1]) - 10), font, 0.5, (0, 255, 0), 1)
cv2.imwrite('output_image.jpg', img_file)
最后对字符串进行解析,获取图像中的目标检测结果并绘制到图片并输出。得到结果如下:
|