写在前面
本篇文章主要是记录自己在这个问题上踩的一些坑,加深个人对RKNN推理以及模型输入和输出这个方面知识的了解,如果能帮助到一些需要将Yolov12转为Onnx并推理,再将Onnx转为RKNN进行分割模型的推理的同学,那我会感到非常荣幸。网上现在有许多关于转RKNN推理的文章,但是基本都是基于yolo默认的检测模型,推理实例分割模型的文章比较少,所以做个记录进行分享。如果哪里说的有问题,欢迎各位批评指正。
一.工作环境介绍
PC端:linux(以下环境都是默认在此环境,windows无法进行RKNN模拟推理)
python: 3.12
rknn toolkit2: 2.2.0
yolo模型:yolov12n-seg.pt
二.自定义pt模型转为onnx
1.目前使用的是yolov12n-seg.pt作为预训练模型,然后使用自己的分割数据集得到的best.pt,然后直接进行导出。(如果使用其他方式导出的onnx比如说其他文章里面的修改最后的输出头,可能会出现模型的输出格式不同,只影响最后推理的后处理部分,到时候只需要根据不同的输出格式修改后处理部分就行)
from ultralytics import YOLO
yolo_model_path = r'/home/files/weights/best.pt'
model = YOLO(yolo_model_path)
model.export(format='onnx',opset=11)
2.当前onnx分割模型结构输出结构
只需要关注结构里面的输入和输出格式
- inputs为单张图片,格式为[1,3,640,640],分别代表批次,通道数,高和宽。
- outputs存在两个部分,output0为检测框数据,格式为[1,37,8400],分别代表批次数,特征数以及最大检测框数目。
- output1为分割的特征数据,格式为[1,32,160,160],分别代表批次数,通道数,高,宽。
三.onnx编译构建rknn模型
以下代码是转换逻辑,分为PC端模拟和PC端连板测试
1.PC端模拟推理:
只在PC端模拟推理RKNN模型,不与开发板连接,那也必然没使用开发板NPU进行推理。
代码如下:
# 初始化 RKNN 对象
rknn = RKNN()
rknn.config(mean_values=[[0, 0, 0]],
std_values=[[1, 1, 1]],
target_platform='rk3568',
quantized_algorithm='normal',
quantized_method='channel')
#加载原始 ONNX转为rknn
rknn.load_onnx(model=model_path)
# 编译模型
ret = rknn.build(do_quantization=False)#
# 导出 RKNN 模型
print("Exporting RKNN model...")
ret = self.rknn.export_rknn(output_path)
if ret != 0:
print("Failed to export RKNN model!")
exit(ret)
print("RKNN导出成功!")
ret = rknn.init_runtime(target=None, perf_debug=True)
#推理
outputs = rknn.inference(inputs=[im], data_format='nchw') #'nchw'
注意:
- 如果进行PC端模拟时(不连接板端),必须通过加载ONNX模型然后编译构建RKNN,然后进行后续的推理,同时,指定的target参数需要设置为none,如下面代码。
#直接加载rknn,但是不能在PC模拟下使用
ret = rknn.load_rknn(path=model_path)
#运行初始化
ret = self.rknn.init_runtime(target=None, perf_debug=True)
- 模型配置里面的均值和方差使用不同的值会有很大的影响,我这使用的是默认的值,因为训练时我没有修改yolo模型的参数配置,如果此处修改的话需要和你训练模型使用的归一化参数一致。
mean_values=[[0, 0, 0]]
std_values=[[1, 1, 1]]
2.PC端连板调用NPU推理测试
- 连接板端:通过 adb devices 命令可以列出所有连接的设备及其 device_id,出现设备号ID即成功。
adb devices
- 启动端口:为了在PC调用板子上的端口,需要启动rknn server,启动方法如下:
新的板子连接设置rknn server方法见:板端初始启动rknn server
代码如下(示例):
adb shell
chmod +x /usr/bin/rknn_server
chmod +x /usr/bin/start_rknn.sh
chmod +x /usr/bin/restart_rknn.sh
restart_rknn.sh
- NPU推理:需要修改指定板端的target类型和device_id
代码如下:
# 初始化 RKNN 对象
rknn = RKNN()
#配置
rknn.config(mean_values=[[0, 0, 0]],
std_values=[[1, 1, 1]],
quantized_algorithm='normal',
quantized_method='channel',
target_platform='rk3568')
#直接加载rknn模型
ret = rknn.load_rknn(path=model_path)
#初始化,此处需指定target类型,如果PC端连接多个开发板,
#则需要指定某个开发板的device_id
ret = rknn.init_runtime(target='rk3568',
device_id=None, perf_debug=True)
#推理
outputs = rknn.inference(inputs=[im], data_format='nchw') #'nchw'
- init_runtime 方法的参数,可以自行设置修改NPU核心数
target:目标硬件平台。支持的平台包括 "rk3562"、"rk3566"、"rk3568"、"rk3588"、"rv1103"、"rv1106" 等。默认值为 None,表示在 PC 上使用模拟器运行。
device_id:设备编号。如果连接了多台设备,需要指定该参数。默认值为 None。
perf_debug:进行性能评估时是否开启 debug 模式。在 debug 模式下,可以获取到每一层的运行时间,否则只能获取模型运行的总时间。默认值为 False。
eval_mem:是否进入内存评估模式。进入内存评估模式后,可以调用 eval_memory 接口获取模型运行时的内存使用情况。默认值为 False。
async_mode:是否使用异步模式。默认值为 False。在异步模式下,每次返回的推理结果都是上一帧的。
core_mask:设置运行时的 NPU 核心。支持的平台为 RK3588 / RK3576,支持的配置包括:
RKNN.NPU_CORE_AUTO:自动调度模型,运行在当前空闲的 NPU 核上。
RKNN.NPU_CORE_0:运行在 NPU0 核心上。
RKNN.NPU_CORE_1:运行在 NPU1 核心上。
RKNN.NPU_CORE_2:运行在 NPU2 核心上。
RKNN.NPU_CORE_0_1:同时运行在 NPU0 和 NPU1 核心上。
RKNN.NPU_CORE_0_1_2:同时运行在 NPU0、NPU1 和 NPU2 核心上。
RKNN.NPU_CORE_ALL:根据平台自动配置 NPU 核心数量。
默认值为 RKNN.NPU_CORE_AUTO。
fallback_prior_device:设置当 OP 超出 NPU 规格时 fallback 的优先级,当前支持 "cpu" 或 "gpu"。默认值是 "cpu"。
四.推理结果后处理
进行上诉得到推理结果后,需要将数据进行解析后处理,得到最后输出的检测框以及分割区域。
由于目前onnx和rknn的输出格式的结构是一致的,所以后处理函数可以公用,使用onnx进行推理和后处理大家也可以尝试一下,这里就不叙述了。
详细后处理函数代码如下:
def postprocess(self, preds, im0, ratio, pad_w, pad_h, conf_threshold, iou_threshold, nm=32):
"""
推理后的结果后处理
Args:
preds (Numpy.ndarray): 来自 RKNN 的推理结果
im0 (Numpy.ndarray): [h, w, c] 原始输入图像
ratio (tuple): 宽高比例
pad_w (float): 宽度的填充
pad_h (float): 高度的填充
conf_threshold (float): 置信度阈值
iou_threshold (float): IoU 阈值
nm (int): 掩膜数量
Returns:
boxes (List): 边界框列表
segments (List): 分割区域列表
masks (np.ndarray): 掩膜数组
"""
x, protos = preds[0], preds[1] # 获取模型的两个输出:预测和原型
# 转换维度
x = np.einsum("bcn->bnc", x) #从 (1, 37, 8400) 转换为 (1, 8400, 37)
# 置信度过滤
x = x[np.amax(x[..., 4:-nm], axis=-1) > conf_threshold]
# 合并边界框、置信度、类别和掩膜 ,合并后x存在38列
x = np.c_[x[..., :4], np.amax(x[..., 4:-nm], axis=-1), np.argmax(x[..., 4:-nm], axis=-1), x[..., -nm:]]
# NMS 过滤
x = x[cv2.dnn.NMSBoxes(x[:, :4], x[:, 4], conf_threshold, iou_threshold)]
# 解析并返回结果
if len(x) > 0:
# 边界框格式转换:从 cxcywh -> xyxy
x[..., [0, 1]] -= x[..., [2, 3]] / 2
x[..., [2, 3]] += x[..., [0, 1]]
# 缩放边界框,使其与原始图像尺寸匹配
x[..., :4] -= [pad_w, pad_h, pad_w, pad_h]
x[..., :4] /= min(ratio)
# 限制边界框在图像边界内
x[..., [0, 2]] = x[:, [0, 2]].clip(0, im0.shape[1])
x[..., [1, 3]] = x[:, [1, 3]].clip(0, im0.shape[0])
# 处理掩膜
masks = self.process_mask(protos[0], x[:, 6:], x[:, :4], im0.shape)
segments = self.masks2segments(masks)
return x[..., :6], segments, masks # 返回边界框、分割区域和掩膜
else:
return [], [], []
五.完整代码流程
import cv2
import numpy as np
import os
import time
from rknn.api import RKNN
# 类别名称和颜色映射
CLASS_NAMES = {
0: 'your_calss', # 类别 0 名称
1: 'class_name2' # 类别 1 名称
# 可以添加更多类别...
}
CLASS_COLORS = {
0: (255, 255, 0), # 类别 0 的颜色为青黄色
1: (255, 0, 0) # 类别 1 的颜色为红色
# 可以为其他类别指定颜色...
}
class YOLO12SegRKNN:
def __init__(self, model_path, output_path, target="rk3568", dataset=None):
# 初始化 RKNN 对象
self.rknn = RKNN()
# self.rknn.config(mean_values=[[0.406 * 255, 0.456 * 255, 0.485 * 255]],
# std_values=[[0.225 * 255, 0.224 * 255, 0.229 * 255]],
# target_platform='rk3568', quantized_algorithm='normal', quantized_method='channel')
self.rknn.config(mean_values=[[0, 0, 0]],
std_values=[[1, 1, 1]],
target_platform='rk3568', optimization_level=2, quantized_algorithm='normal', quantized_method='channel')
# self.rknn.config(mean_values=[[0.406 * 255, 0.456 * 255, 0.485 * 255]],
# std_values=[[0.225 * 255, 0.224 * 255, 0.229 * 255]],
# target_platform='rk3568', optimization_level=2)
# 1.加载原始 ONNX转为rknn
self.rknn.load_onnx(model=model_path)
# 编译模型
if dataset:
self.rknn.build(target=target, dataset=dataset, pre_compile=True)
else:
ret = self.rknn.build(do_quantization=False)
# #2.直接加载rknn,但是不能在模拟环境下使用
# ret = self.rknn.load_rknn(path=model_path)
# 导出 RKNN 模型
print("Exporting RKNN model...")
ret = self.rknn.export_rknn(output_path)
if ret != 0:
print("Failed to export RKNN model!")
exit(ret)
print("RKNN导出成功!")
# 初始化运行时
ret = self.rknn.init_runtime(target=None, perf_debug=True)
if ret != 0:
print('初始化运行环境失败')
exit(ret)
# 获取输入输出信息 ,此处进行修改模型的输入尺寸
self.model_height, self.model_width = (640, 640)
# 打印模型的输入尺寸
print("YOLO11 🚀 实例分割 RKNN")
print("模型名称:", onnx_model_path)
print(f"模型输入尺寸:宽度 = {self.model_width}, 高度 = {self.model_height}")
# 加载类别名称
self.classes = CLASS_NAMES
# 加载类别对应的颜色
self.class_colors = CLASS_COLORS
def get_color_for_class(self, class_id):
return self.class_colors.get(class_id, (255, 255, 255)) # 如果没有找到类别颜色,返回白色
def __call__(self, im0, conf_threshold=0.3, iou_threshold=0.3, nm=32):
"""
完整的推理流程:预处理 -> 推理 -> 后处理
Args:
im0 (Numpy.ndarray): 原始输入图像
conf_threshold (float): 置信度阈值
iou_threshold (float): NMS 中的 IoU 阈值
nm (int): 掩膜数量
Returns:
boxes (List): 边界框列表
segments (List): 分割区域列表
masks (np.ndarray): [N, H, W] 输出掩膜
"""
# 图像预处理
im, ratio, (pad_w, pad_h) = self.preprocess(im0)
print("im0.shape", im0.shape)
print("im.shape", im.shape)
# RKNN 推理
start_time = time.time()
outputs = self.rknn.inference(inputs=[im], data_format='nchw') #'nchw'
end_time = time.time()
print(f"RKNN 模型推理时间: {(end_time - start_time) * 1000:.2f} 毫秒")
# 后处理
boxes, segments, masks = self.postprocess(
outputs,
im0=im0,
ratio=ratio,
pad_w=pad_w,
pad_h=pad_h,
conf_threshold=conf_threshold,
iou_threshold=iou_threshold,
nm=nm,
)
print("检测框:", boxes)
print("分割:", segments)
return boxes, segments, masks
def preprocess(self, img):
"""
图像预处理
Args:
img (Numpy.ndarray): 输入图像
Returns:
img_process (Numpy.ndarray): 处理后的图像
ratio (tuple): 宽高比例
pad_w (float): 宽度的填充
pad_h (float): 高度的填充
"""
# 调整输入图像大小并使用 letterbox 填充
shape = img.shape[:2] # 原始图像大小
new_shape = (self.model_height, self.model_width)
r = min(new_shape[0] / shape[0], new_shape[1] / shape[1])
ratio = r, r
new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r))
pad_w, pad_h = (new_shape[1] - new_unpad[0]) / 2, (new_shape[0] - new_unpad[1]) / 2 # 填充宽高
if shape[::-1] != new_unpad: # 调整图像大小
img = cv2.resize(img, new_unpad, interpolation=cv2.INTER_LINEAR)
top, bottom = int(round(pad_h - 0.1)), int(round(pad_h + 0.1))
left, right = int(round(pad_w - 0.1)), int(round(pad_w + 0.1))
img = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_CONSTANT, value=(114, 114, 114))
# # 转换:HWC -> CHW -> BGR 转 RGB -> 除以 255 -> contiguous -> 添加维度
img = np.ascontiguousarray(np.einsum("HWC->CHW", img)[::-1], dtype=np.float32) / 255.0
img_process = img[None] if len(img.shape) == 3 else img
# # 1. 转换为 CHW 格式并从 BGR 转换为 RGB
# img = np.einsum("HWC->CHW", img)[::-1]
# # 2. 将像素值归一化到 [0, 1] 范围
# img = img.astype(np.float32) / 255.0
# # 3. 应用均值和标准差归一化
# mean_values = np.array([0.485 * 255, 0.456 * 255, 0.406 * 255]) / 255.0 # 归一化后的均值
# std_values = np.array([0.229 * 255, 0.224 * 255, 0.225 * 255]) / 255.0 # 归一化后的标准差
# # 确保均值和标准差是 float32 类型
# mean_values = mean_values.astype(np.float32)
# std_values = std_values.astype(np.float32)
# img = (img - mean_values[:, None, None]) / std_values[:, None, None]
# # 4. 确保数组是连续的
# img = np.ascontiguousarray(img)
# img_process = img[None] if len(img.shape) == 3 else img #添加一个维度
# 转换:HWC -> NHWC
#img_process = np.expand_dims(img, axis=0).astype(np.float32) / 255.0
print("img_process.shape", img_process.shape)
return img_process, ratio, (pad_w, pad_h)
def postprocess(self, preds, im0, ratio, pad_w, pad_h, conf_threshold, iou_threshold, nm=32):
"""
推理后的结果后处理
Args:
preds (Numpy.ndarray): 来自 RKNN 的推理结果
im0 (Numpy.ndarray): [h, w, c] 原始输入图像
ratio (tuple): 宽高比例
pad_w (float): 宽度的填充
pad_h (float): 高度的填充
conf_threshold (float): 置信度阈值
iou_threshold (float): IoU 阈值
nm (int): 掩膜数量
Returns:
boxes (List): 边界框列表
segments (List): 分割区域列表
masks (np.ndarray): 掩膜数组
"""
x, protos = preds[0], preds[1] # 获取模型的两个输出:预测和原型
# 转换维度
x = np.einsum("bcn->bnc", x) #从 (1, 37, 8400) 转换为 (1, 8400, 37)
# 置信度过滤
x = x[np.amax(x[..., 4:-nm], axis=-1) > conf_threshold]
# 合并边界框、置信度、类别和掩膜 ,合并后x存在38列
x = np.c_[x[..., :4], np.amax(x[..., 4:-nm], axis=-1), np.argmax(x[..., 4:-nm], axis=-1), x[..., -nm:]]
# NMS 过滤
x = x[cv2.dnn.NMSBoxes(x[:, :4], x[:, 4], conf_threshold, iou_threshold)]
# 解析并返回结果
if len(x) > 0:
# 边界框格式转换:从 cxcywh -> xyxy
x[..., [0, 1]] -= x[..., [2, 3]] / 2
x[..., [2, 3]] += x[..., [0, 1]]
# 缩放边界框,使其与原始图像尺寸匹配
x[..., :4] -= [pad_w, pad_h, pad_w, pad_h]
x[..., :4] /= min(ratio)
# 限制边界框在图像边界内
x[..., [0, 2]] = x[:, [0, 2]].clip(0, im0.shape[1])
x[..., [1, 3]] = x[:, [1, 3]].clip(0, im0.shape[0])
# 处理掩膜
masks = self.process_mask(protos[0], x[:, 6:], x[:, :4], im0.shape)
segments = self.masks2segments(masks)
return x[..., :6], segments, masks # 返回边界框、分割区域和掩膜
else:
return [], [], []
@staticmethod
def masks2segments(masks):
"""
将掩膜转换为分割区域
Args:
masks (numpy.ndarray): 模型输出的掩膜,形状为 (n, h, w)
Returns:
segments (List): 分割区域的列表
"""
segments = []
for x in masks.astype("uint8"):
c = cv2.findContours(x, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)[0] # 找到轮廓
if c:
c = np.array(c[np.array([len(x) for x in c]).argmax()]).reshape(-1, 2)
else:
c = np.zeros((0, 2)) # 如果没有找到分割区域,返回空数组
segments.append(c.astype("float32"))
return segments
def crop_mask(self, masks, boxes):
"""
裁剪掩膜,使其与边界框对齐
Args:
masks (Numpy.ndarray): [n, h, w] 掩膜数组
boxes (Numpy.ndarray): [n, 4] 边界框
Returns:
(Numpy.ndarray): 裁剪后的掩膜
"""
n, h, w = masks.shape
x1, y1, x2, y2 = np.split(boxes, 4, axis=1) # 分割边界框坐标
x1, y1, x2, y2 = x1.flatten(), y1.flatten(), x2.flatten(), y2.flatten() # 展平坐标数组
# 确保边界框坐标在掩膜尺寸范围内
x1 = np.clip(x1, 0, w - 1).astype(np.int32)
y1 = np.clip(y1, 0, h - 1).astype(np.int32)
x2 = np.clip(x2, 0, w - 1).astype(np.int32)
y2 = np.clip(y2, 0, h - 1).astype(np.int32)
# 创建网格
r = np.arange(w, dtype=np.int32)[None, None, :]
c = np.arange(h, dtype=np.int32)[None, :, None]
# 裁剪掩膜
result = np.zeros_like(masks, dtype=np.bool_)
for i in range(n):
result[i] = masks[i] * ((r >= x1[i]) & (r < x2[i]) & (c >= y1[i]) & (c < y2[i]))
return result
def process_mask(self, protos, masks_in, bboxes, im0_shape):
"""
处理模型输出的掩膜
Args:
protos (numpy.ndarray): [mask_dim, mask_h, mask_w] 掩膜原型
masks_in (numpy.ndarray): [n, mask_dim] 掩膜数量
bboxes (numpy.ndarray): 缩放到原始图像尺寸的边界框
im0_shape (tuple): 原始输入图像的尺寸 (h,w,c)
Returns:
(numpy.ndarray): 处理后的掩膜
"""
c, mh, mw = protos.shape
masks = np.matmul(masks_in, protos.reshape((c, -1))).reshape((-1, mh, mw)).transpose(1, 2, 0) # HWN
masks = np.ascontiguousarray(masks)
masks = self.scale_mask(masks, im0_shape) # 将掩膜从 P3 尺寸缩放到原始输入图像大小
masks = np.einsum("HWN -> NHW", masks) # HWN -> NHW
masks = self.crop_mask(masks,bboxes) # 裁剪掩膜
return np.greater(masks, 0.5) # 返回二值化后的掩膜
@staticmethod
def scale_mask(masks, im0_shape, ratio_pad=None):
"""
将掩膜缩放至原始图像大小
Args:
masks (np.ndarray): 缩放和填充后的掩膜
im0_shape (tuple): 原始图像大小
ratio_pad (tuple): 填充与原始图像的比例
Returns:
masks (np.ndarray): 缩放后的掩膜
"""
im1_shape = masks.shape[:2]
if ratio_pad is None: # 计算比例
gain = min(im1_shape[0] / im0_shape[0], im1_shape[1] / im0_shape[1]) # 比例
pad = (im1_shape[1] - im0_shape[1] * gain) / 2, (im1_shape[0] - im0_shape[0] * gain) / 2 # 填充
else:
pad = ratio_pad[1]
# 计算掩膜的边界
top, left = int(round(pad[1] - 0.1)), int(round(pad[0] - 0.1)) # y, x
bottom, right = int(round(im1_shape[0] - pad[1] + 0.1)), int(round(im1_shape[1] - pad[0] + 0.1))
if len(masks.shape) < 2:
raise ValueError(f'"len of masks shape" 应该是 2 或 3,但得到 {len(masks.shape)}')
masks = masks[top:bottom, left:right]
masks = cv2.resize(
masks, (im0_shape[1], im0_shape[0]), interpolation=cv2.INTER_LINEAR
) # 使用 INTER_LINEAR 插值调整大小
if len(masks.shape) == 2:
masks = masks[:, :, None]
print("masks.shape",masks.shape)
return masks
def draw_and_visualize(self, im, bboxes, segments, save_path,vis=False, save=True):
"""
绘制和可视化结果
Args:
im (np.ndarray): 原始图像,形状为 [h, w, c]
bboxes (numpy.ndarray): [n, 4],n 是边界框数量
segments (List): 分割区域的列表
vis (bool): 是否使用 OpenCV 显示图像
save (bool): 是否保存带注释的图像
Returns:
None
"""
# 创建图像副本
im_canvas = im.copy()
for (*box, conf, cls_), segment in zip(bboxes, segments):
# 获取类别对应的颜色
color = self.get_color_for_class(int(cls_))
# 检查轮廓数据是否为空
if len(segment) > 0:
# 转换为 int32 类型
segment = np.int32([segment])
# 绘制轮廓
cv2.polylines(im, segment, True, (255, 0, 0), 3) # 绘制白色边框
# 填充多边形
cv2.fillPoly(im_canvas, segment, color) # 使用类别对应的颜色填充多边形
# 绘制边界框
cv2.rectangle(im, (int(box[0]), int(box[1])), (int(box[2]), int(box[3])), color, 3, cv2.LINE_AA)
# 在图像上绘制类别名称和置信度
cv2.putText(im, f"{self.classes[cls_]}: {conf:.3f}", (int(box[0]), int(box[1] - 9)),
cv2.FONT_HERSHEY_SIMPLEX, 1, color, 3, cv2.LINE_AA)
# 将图像和绘制的多边形混合
im = cv2.addWeighted(im_canvas, 0.3, im, 0.7, 0)
# 显示图像
if vis:
cv2.imshow("seg_result_picture", im)
cv2.waitKey(0)
cv2.destroyAllWindows()
# 保存图像
if save:
cv2.imwrite(save_path, im)
def release(self):
"""
释放 RKNN 资源
"""
self.rknn.release()
if __name__ =="__main__":
# 初始化 RKNN 模型
out_rknn_path = "/home/code/rknn_test/models/best_rknn.rknn"
onnx_model_path = "/home/code/rknn_test/models/best.onnx"
yolo_seg = YOLO12SegRKNN(onnx_model_path, out_rknn_path, target=None)#加载onnx以此编译rknn进行推理
# 读取图像
image_path = "/home/code/rknn_test/frame_00000.jpg"
save_path = "/home/code/rknn_test/out_imgs/seg_result_picture.jpg"
image = cv2.imread(image_path)
# 进行推理
boxes, segments, masks = yolo_seg(image, conf_threshold=0.5, iou_threshold=0.3, nm=32)
# 绘制和可视化结果
result_image = yolo_seg.draw_and_visualize(image, boxes, segments, save_path, vis=False, save=True)
# 释放资源
yolo_seg.release()
总结与后续
以上就是个人在分割任务中使用RKNN进行推理工作的分享,本文的重心主要是在进行RKNN推理输出上,并未论述和附出结果来验证pt模型转为rknn模型后精度的变化以及使用NPU后推理速度的变化,目前我在本地测试pt和onnx,rknn三个模型的检测精度差别不大都能有较好的检测分割效果,如果需要加快推理速度后续有时间再进行研究和分享或者使用c++进行推理加快检测速度。