[最新]Yolov12自定义segment分割模型转ONNX、RKNN并推理,实现在PC端进行模拟、连接板端NPU调用


写在前面
本篇文章主要是记录自己在这个问题上踩的一些坑,加深个人对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分割模型结构输出结构
!我们这里](https://i-blog.csdnimg.cn/direct/1b9bd722d2ef4d5cb80f7d3c1f34a5c4.jpeg)
只需要关注结构里面的输入和输出格式

  • 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++进行推理加快检测速度。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值