首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

YOLO11 实例分割 | 导出ONNX模型 | ONNX模型推理

  • 25-02-18 14:02
  • 2221
  • 6605
blog.csdn.net

本文分享YOLO11中,从xxx.pt权重文件转为.onnx文件,然后使用.onnx文件,进行实例分割任务的模型推理。

用ONNX模型推理,便于算法到开发板或芯片的部署。

备注:本文是使用Python,编写ONNX模型推理代码的

目录

1、导出ONNX模型

2、实例分割——ONNX模型推理

2.1、整体ONNX推理流程代码

2.1、 图像预处理函数

2.3、 后处理函数

2.4、分割效果可视化函数

2.5、YOLO11实例分割——ONNX模型推理完整代码 


1、导出ONNX模型

首先我们训练好的模型,生成xxx.pt权重文件;

然后用下面代码,导出ONNX模型(简洁版)

  1. from ultralytics import YOLO
  2. # 加载一个模型,路径为 YOLO 模型的 .pt 文件
  3. model = YOLO("runs/segment/train6/weights/best.pt")
  4. # 导出模型,格式为 ONNX
  5. model.export(format="onnx")

运行代码后,会在上面路径中生成best.onnx文件的

  • 比如,填写的路径是:"runs/segment/train6/weights/best.pt"
  • 那么在runs/segment/train6/weights/目录中,会生成与best.pt同名的onnx文件,即best.onnx

上面代码示例是简单版,如果需要更专业设置ONNX,用下面版本的

YOLO11导出ONNX模型(专业版)

  1. from ultralytics import YOLO
  2. # 加载一个模型,路径为 YOLO 模型的 .pt 文件
  3. model = YOLO("runs/segment/train6/weights/best.pt")
  4. # 导出模型,设置多种参数
  5. model.export(
  6. format="onnx", # 导出格式为 ONNX
  7. imgsz=(640, 640), # 设置输入图像的尺寸
  8. keras=False, # 不导出为 Keras 格式
  9. optimize=False, # 不进行优化
  10. half=False, # 不启用 FP16 量化
  11. int8=False, # 不启用 INT8 量化
  12. dynamic=False, # 不启用动态输入尺寸
  13. simplify=True, # 简化 ONNX 模型
  14. opset=None, # 使用最新的 opset 版本
  15. workspace=4.0, # 为 TensorRT 优化设置最大工作区大小(GiB)
  16. nms=False, # 不添加 NMS(非极大值抑制)
  17. batch=1 # 指定批处理大小
  18. )

对于model.export( )函数中,各种参数说明:

  1. format="onnx":指定导出模型的格式为 ONNX。
  2. imgsz=(640, 640):输入图像的尺寸设为 640x640。如果需要其他尺寸可以修改这个值。
  3. keras=False:不导出为 Keras 格式的模型。
  4. optimize=False:不应用 TorchScript 移动设备优化。
  5. half=False:不启用 FP16(半精度)量化。
  6. int8=False:不启用 INT8 量化。
  7. dynamic=False:不启用动态输入尺寸。
  8. simplify=True:简化模型以提升 ONNX 模型的性能。
  9. opset=None:使用默认的 ONNX opset 版本,如果需要可以手动指定。
  10. workspace=4.0:为 TensorRT 优化指定最大工作空间大小为 4 GiB。
  11. nms=False:不为 CoreML 导出添加非极大值抑制(NMS)。
  12. batch=1:设置批处理大小为 1。

参考官网文档:https://docs.ultralytics.com/modes/export/#arguments

当然了,YOLO11中不但支持ONNX模型,还支持下面表格中格式

支持的导出格式format参数值生成的模型示例model.export( )函数的参数
PyTorch-yolo11n.pt-
TorchScripttorchscriptyolo11n.torchscriptimgsz, optimize, batch
ONNXonnxyolo11n.onnximgsz, half, dynamic, simplify, opset, batch
OpenVINOopenvinoyolo11n_openvino_model/imgsz, half, int8, batch
TensorRTengineyolo11n.engineimgsz, half, dynamic, simplify, workspace, int8, batch
CoreMLcoremlyolo11n.mlpackageimgsz, half, int8, nms, batch
TF SavedModelsaved_modelyolo11n_saved_model/imgsz, keras, int8, batch
TF GraphDefpbyolo11n.pbimgsz, batch
TF Litetfliteyolo11n.tfliteimgsz, half, int8, batch
TF Edge TPUedgetpuyolo11n_edgetpu.tfliteimgsz
TF.jstfjsyolo11n_web_model/imgsz, half, int8, batch
PaddlePaddlepaddleyolo11n_paddle_model/imgsz, batch
NCNNncnnyolo11n_ncnn_model/imgsz, half, batch

2、实例分割——ONNX模型推理

我们需要编写代码实现了一个使用 ONNXRuntime 执行 YOLOv11 分割模型推理的完整流程,包含图像预处理、推理、后处理和可视化 。

需要编写的代码功能包括:

  • 加载 YOLOv11 模型:代码使用 ONNXRuntime 加载和执行 YOLOv11 分割模型。
  • 支持不同尺寸图像的输入:代码对输入的图像进行预处理,保持原始宽高比,通过 letterbox 填充图像,使其符合模型的输入尺寸要求。
  • 模型推理:通过 ONNXRuntime 进行推理,生成预测的边界框、分割区域和掩膜。
  • 后处理:代码处理推理结果,应用置信度过滤和 NMS(非极大值抑制)来提取有效的检测结果,并对掩膜进行处理和转换为分割区域。
  • 可视化分割结果:代码支持可视化模型的输出结果,在图像上绘制检测到的分割区域、边界框和类别信息。支持将可视化结果保存到文件中。
  • 主函数:用户可以通过命令行参数指定 ONNX 模型路径、输入图像路径、置信度阈值和 IoU 阈值等参数,从而灵活配置模型推理。

2.1、整体ONNX推理流程代码

首先编写一个用于运行YOLO11分割模型的推理类:YOLO11Seg

ONNX推理流程:预处理 -> 推理 -> 后处理

  1. class YOLO11Seg:
  2. def __init__(self, onnx_model):
  3. # 创建 Ort 推理会话,选择 CPU 或 GPU 提供者
  4. self.session = ort.InferenceSession(
  5. onnx_model,
  6. providers=["CUDAExecutionProvider", "CPUExecutionProvider"]
  7. if ort.get_device() == "GPU"
  8. else ["CPUExecutionProvider"],
  9. )
  10. # 根据 ONNX 模型类型选择 Numpy 数据类型(支持 FP32 和 FP16)
  11. self.ndtype = np.half if self.session.get_inputs()[0].type == "tensor(float16)" else np.single
  12. # 获取模型的输入宽度和高度(YOLO11-seg 只有一个输入)
  13. self.model_height, self.model_width = [x.shape for x in self.session.get_inputs()][0][-2:]
  14. # 打印模型的输入尺寸
  15. print("YOLO11 ? 实例分割 ONNXRuntime")
  16. print("模型名称:", onnx_model)
  17. print(f"模型输入尺寸:宽度 = {self.model_width}, 高度 = {self.model_height}")
  18. # 加载类别名称
  19. self.classes = CLASS_NAMES
  20. # 加载类别对应的颜色
  21. self.class_colors = CLASS_COLORS
  22. def __call__(self, im0, conf_threshold=0.4, iou_threshold=0.45, nm=32):
  23. """
  24. 完整的推理流程:预处理 -> 推理 -> 后处理
  25. Args:
  26. im0 (Numpy.ndarray): 原始输入图像
  27. conf_threshold (float): 置信度阈值
  28. iou_threshold (float): NMS 中的 IoU 阈值
  29. nm (int): 掩膜数量
  30. Returns:
  31. boxes (List): 边界框列表
  32. segments (List): 分割区域列表
  33. masks (np.ndarray): [N, H, W] 输出掩膜
  34. """
  35. # 图像预处理
  36. im, ratio, (pad_w, pad_h) = self.preprocess(im0)
  37. # ONNX 推理
  38. preds = self.session.run(None, {self.session.get_inputs()[0].name: im})
  39. # 后处理
  40. boxes, segments, masks = self.postprocess(
  41. preds,
  42. im0=im0,
  43. ratio=ratio,
  44. pad_w=pad_w,
  45. pad_h=pad_h,
  46. conf_threshold=conf_threshold,
  47. iou_threshold=iou_threshold,
  48. nm=nm,
  49. )
  50. return boxes, segments, masks

2.1、 图像预处理函数

然后编写输入图像预处理函数:preprocess

  • 对不同尺寸的输入图像,使其适配YOLO模型的输入要求,同时保证图像的宽高比不变
  • 主要包含图像尺寸调整、填充、通道转换和归一化等操作
  1. def preprocess(self, img):
  2. """
  3. 图像预处理
  4. Args:
  5. img (Numpy.ndarray): 输入图像
  6. Returns:
  7. img_process (Numpy.ndarray): 处理后的图像
  8. ratio (tuple): 宽高比例
  9. pad_w (float): 宽度的填充
  10. pad_h (float): 高度的填充
  11. """
  12. # 调整输入图像大小并使用 letterbox 填充
  13. shape = img.shape[:2] # 原始图像大小
  14. new_shape = (self.model_height, self.model_width)
  15. r = min(new_shape[0] / shape[0], new_shape[1] / shape[1])
  16. ratio = r, r
  17. new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r))
  18. pad_w, pad_h = (new_shape[1] - new_unpad[0]) / 2, (new_shape[0] - new_unpad[1]) / 2 # 填充宽高
  19. if shape[::-1] != new_unpad: # 调整图像大小
  20. img = cv2.resize(img, new_unpad, interpolation=cv2.INTER_LINEAR)
  21. top, bottom = int(round(pad_h - 0.1)), int(round(pad_h + 0.1))
  22. left, right = int(round(pad_w - 0.1)), int(round(pad_w + 0.1))
  23. img = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_CONSTANT, value=(114, 114, 114))
  24. # 转换:HWC -> CHW -> BGR 转 RGB -> 除以 255 -> contiguous -> 添加维度
  25. img = np.ascontiguousarray(np.einsum("HWC->CHW", img)[::-1], dtype=self.ndtype) / 255.0
  26. img_process = img[None] if len(img.shape) == 3 else img
  27. return img_process, ratio, (pad_w, pad_h)

2.3、 后处理函数

再编写YOLO11分割模型推理结果的后处理函数:postprocess

主要包括边界框和分割掩膜的处理

1、后处理框架 (postprocess):

  • 获取预测输出的边界框和掩膜原型。
  • 过滤低置信度结果,结合边界框、置信度、类别和掩膜信息。
  • 应用非极大值抑制(NMS)去除重复框。
  • 将边界框缩放到原始图像的尺寸。
  • 处理并生成二值化的分割掩膜,并将其转换为分割区域。

2、掩膜处理:

  • 利用预测的掩膜原型和边界框生成最终的掩膜。
  • 缩放掩膜尺寸到原始图像大小,并进行裁剪以匹配边界框。

3、分割区域转换 (masks2segments):

  • 将掩膜转换为分割区域的轮廓,用于进一步的分析或可视化。

4、掩膜缩放与裁剪:

  • 通过 scale_mask 和 crop_mask,将掩膜调整到与原始图像匹配的大小,并裁剪到边界框内部,生成最终的分割掩膜。
  1. def postprocess(self, preds, im0, ratio, pad_w, pad_h, conf_threshold, iou_threshold, nm=32):
  2. """
  3. 推理后的结果后处理
  4. Args:
  5. preds (Numpy.ndarray): 来自 ONNX 的推理结果
  6. im0 (Numpy.ndarray): [h, w, c] 原始输入图像
  7. ratio (tuple): 宽高比例
  8. pad_w (float): 宽度的填充
  9. pad_h (float): 高度的填充
  10. conf_threshold (float): 置信度阈值
  11. iou_threshold (float): IoU 阈值
  12. nm (int): 掩膜数量
  13. Returns:
  14. boxes (List): 边界框列表
  15. segments (List): 分割区域列表
  16. masks (np.ndarray): 掩膜数组
  17. """
  18. x, protos = preds[0], preds[1] # 获取模型的两个输出:预测和原型
  19. # 转换维度
  20. x = np.einsum("bcn->bnc", x)
  21. # 置信度过滤
  22. x = x[np.amax(x[..., 4:-nm], axis=-1) > conf_threshold]
  23. # 合并边界框、置信度、类别和掩膜
  24. x = np.c_[x[..., :4], np.amax(x[..., 4:-nm], axis=-1), np.argmax(x[..., 4:-nm], axis=-1), x[..., -nm:]]
  25. # NMS 过滤
  26. x = x[cv2.dnn.NMSBoxes(x[:, :4], x[:, 4], conf_threshold, iou_threshold)]
  27. # 解析并返回结果
  28. if len(x) > 0:
  29. # 边界框格式转换:从 cxcywh -> xyxy
  30. x[..., [0, 1]] -= x[..., [2, 3]] / 2
  31. x[..., [2, 3]] += x[..., [0, 1]]
  32. # 缩放边界框,使其与原始图像尺寸匹配
  33. x[..., :4] -= [pad_w, pad_h, pad_w, pad_h]
  34. x[..., :4] /= min(ratio)
  35. # 限制边界框在图像边界内
  36. x[..., [0, 2]] = x[:, [0, 2]].clip(0, im0.shape[1])
  37. x[..., [1, 3]] = x[:, [1, 3]].clip(0, im0.shape[0])
  38. # 处理掩膜
  39. masks = self.process_mask(protos[0], x[:, 6:], x[:, :4], im0.shape)
  40. # 将掩膜转换为分割区域
  41. segments = self.masks2segments(masks)
  42. return x[..., :6], segments, masks # 返回边界框、分割区域和掩膜
  43. else:
  44. return [], [], []
  45. @staticmethod
  46. def masks2segments(masks):
  47. """
  48. 将掩膜转换为分割区域
  49. Args:
  50. masks (numpy.ndarray): 模型输出的掩膜,形状为 (n, h, w)
  51. Returns:
  52. segments (List): 分割区域的列表
  53. """
  54. segments = []
  55. for x in masks.astype("uint8"):
  56. c = cv2.findContours(x, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)[0] # 找到轮廓
  57. if c:
  58. c = np.array(c[np.array([len(x) for x in c]).argmax()]).reshape(-1, 2)
  59. else:
  60. c = np.zeros((0, 2)) # 如果没有找到分割区域,返回空数组
  61. segments.append(c.astype("float32"))
  62. return segments
  63. @staticmethod
  64. def crop_mask(masks, boxes):
  65. """
  66. 裁剪掩膜,使其与边界框对齐
  67. Args:
  68. masks (Numpy.ndarray): [n, h, w] 掩膜数组
  69. boxes (Numpy.ndarray): [n, 4] 边界框
  70. Returns:
  71. (Numpy.ndarray): 裁剪后的掩膜
  72. """
  73. n, h, w = masks.shape
  74. x1, y1, x2, y2 = np.split(boxes[:, :, None], 4, 1)
  75. r = np.arange(w, dtype=x1.dtype)[None, None, :]
  76. c = np.arange(h, dtype=x1.dtype)[None, :, None]
  77. return masks * ((r >= x1) * (r < x2) * (c >= y1) * (c < y2))
  78. def process_mask(self, protos, masks_in, bboxes, im0_shape):
  79. """
  80. 处理模型输出的掩膜
  81. Args:
  82. protos (numpy.ndarray): [mask_dim, mask_h, mask_w] 掩膜原型
  83. masks_in (numpy.ndarray): [n, mask_dim] 掩膜数量
  84. bboxes (numpy.ndarray): 缩放到原始图像尺寸的边界框
  85. im0_shape (tuple): 原始输入图像的尺寸 (h,w,c)
  86. Returns:
  87. (numpy.ndarray): 处理后的掩膜
  88. """
  89. c, mh, mw = protos.shape
  90. masks = np.matmul(masks_in, protos.reshape((c, -1))).reshape((-1, mh, mw)).transpose(1, 2, 0) # HWN
  91. masks = np.ascontiguousarray(masks)
  92. masks = self.scale_mask(masks, im0_shape) # 将掩膜从 P3 尺寸缩放到原始输入图像大小
  93. masks = np.einsum("HWN -> NHW", masks) # HWN -> NHW
  94. masks = self.crop_mask(masks, bboxes) # 裁剪掩膜
  95. return np.greater(masks, 0.5) # 返回二值化后的掩膜
  96. @staticmethod
  97. def scale_mask(masks, im0_shape, ratio_pad=None):
  98. """
  99. 将掩膜缩放至原始图像大小
  100. Args:
  101. masks (np.ndarray): 缩放和填充后的掩膜
  102. im0_shape (tuple): 原始图像大小
  103. ratio_pad (tuple): 填充与原始图像的比例
  104. Returns:
  105. masks (np.ndarray): 缩放后的掩膜
  106. """
  107. im1_shape = masks.shape[:2]
  108. if ratio_pad is None: # 计算比例
  109. gain = min(im1_shape[0] / im0_shape[0], im1_shape[1] / im0_shape[1]) # 比例
  110. pad = (im1_shape[1] - im0_shape[1] * gain) / 2, (im1_shape[0] - im0_shape[0] * gain) / 2 # 填充
  111. else:
  112. pad = ratio_pad[1]
  113. # 计算掩膜的边界
  114. top, left = int(round(pad[1] - 0.1)), int(round(pad[0] - 0.1)) # y, x
  115. bottom, right = int(round(im1_shape[0] - pad[1] + 0.1)), int(round(im1_shape[1] - pad[0] + 0.1))
  116. if len(masks.shape) < 2:
  117. raise ValueError(f'"len of masks shape" 应该是 2 或 3,但得到 {len(masks.shape)}')
  118. masks = masks[top:bottom, left:right]
  119. masks = cv2.resize(
  120. masks, (im0_shape[1], im0_shape[0]), interpolation=cv2.INTER_LINEAR
  121. ) # 使用 INTER_LINEAR 插值调整大小
  122. if len(masks.shape) == 2:
  123. masks = masks[:, :, None]
  124. return masks

2.4、分割效果可视化函数

该代码的核心功能是绘制和可视化检测和分割结果

  1. def draw_and_visualize(self, im, bboxes, segments, vis=False, save=True):
  2. """
  3. 绘制和可视化结果
  4. Args:
  5. im (np.ndarray): 原始图像,形状为 [h, w, c]
  6. bboxes (numpy.ndarray): [n, 4],n 是边界框数量
  7. segments (List): 分割区域的列表
  8. vis (bool): 是否使用 OpenCV 显示图像
  9. save (bool): 是否保存带注释的图像
  10. Returns:
  11. None
  12. """
  13. # 创建图像副本
  14. im_canvas = im.copy()
  15. for (*box, conf, cls_), segment in zip(bboxes, segments):
  16. # 获取类别对应的颜色
  17. color = self.get_color_for_class(int(cls_))
  18. # 绘制轮廓和填充掩膜
  19. # cv2.polylines(im, np.int32([segment]), True, (255, 255, 255), 2) # 绘制白色边框
  20. cv2.fillPoly(im_canvas, np.int32([segment]), color) # 使用类别对应的颜色填充多边形
  21. # 绘制边界框
  22. cv2.rectangle(im, (int(box[0]), int(box[1])), (int(box[2]), int(box[3])), color, 1, cv2.LINE_AA)
  23. # 在图像上绘制类别名称和置信度
  24. cv2.putText(im, f"{self.classes[cls_]}: {conf:.3f}", (int(box[0]), int(box[1] - 9)), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 1, cv2.LINE_AA)
  25. # 将图像和绘制的多边形混合
  26. im = cv2.addWeighted(im_canvas, 0.3, im, 0.7, 0)
  27. # 显示图像
  28. if vis:
  29. cv2.imshow("seg_result_picture", im)
  30. cv2.waitKey(0)
  31. cv2.destroyAllWindows()
  32. # 保存图像
  33. if save:
  34. cv2.imwrite("seg_result_picture.jpg", im)

2.5、YOLO11实例分割——ONNX模型推理完整代码 

完整代码,如下所示:

  1. # Ultralytics YOLO ?, AGPL-3.0 license
  2. """
  3. YOLO11 分割模型 ONNXRuntime
  4. 功能1: 支持不用尺寸图像的输入
  5. 功能2: 支持可视化分割结果
  6. """
  7. import argparse
  8. import cv2
  9. import numpy as np
  10. import onnxruntime as ort
  11. # 类外定义类别映射关系,使用字典格式
  12. CLASS_NAMES = {
  13. 0: 'class_name1', # 类别 0 名称
  14. 1: 'class_name2' # 类别 1 名称
  15. # 可以添加更多类别...
  16. }
  17. # 定义类别对应的颜色,格式为 (R, G, B)
  18. CLASS_COLORS = {
  19. 0: (255, 255, 0), # 类别 0 的颜色为青黄色
  20. 1: (255, 0, 0) # 类别 1 的颜色为红色
  21. # 可以为其他类别指定颜色...
  22. }
  23. class YOLO11Seg:
  24. def __init__(self, onnx_model):
  25. # 创建 Ort 推理会话,选择 CPU 或 GPU 提供者
  26. self.session = ort.InferenceSession(
  27. onnx_model,
  28. providers=["CUDAExecutionProvider", "CPUExecutionProvider"]
  29. if ort.get_device() == "GPU"
  30. else ["CPUExecutionProvider"],
  31. )
  32. # 根据 ONNX 模型类型选择 Numpy 数据类型(支持 FP32 和 FP16)
  33. self.ndtype = np.half if self.session.get_inputs()[0].type == "tensor(float16)" else np.single
  34. # 获取模型的输入宽度和高度(YOLO11-seg 只有一个输入)
  35. self.model_height, self.model_width = [x.shape for x in self.session.get_inputs()][0][-2:]
  36. # 打印模型的输入尺寸
  37. print("YOLO11 ? 实例分割 ONNXRuntime")
  38. print("模型名称:", onnx_model)
  39. print(f"模型输入尺寸:宽度 = {self.model_width}, 高度 = {self.model_height}")
  40. # 加载类别名称
  41. self.classes = CLASS_NAMES
  42. # 加载类别对应的颜色
  43. self.class_colors = CLASS_COLORS
  44. def get_color_for_class(self, class_id):
  45. return self.class_colors.get(class_id, (255, 255, 255)) # 如果没有找到类别颜色,返回白色
  46. def __call__(self, im0, conf_threshold=0.4, iou_threshold=0.45, nm=32):
  47. """
  48. 完整的推理流程:预处理 -> 推理 -> 后处理
  49. Args:
  50. im0 (Numpy.ndarray): 原始输入图像
  51. conf_threshold (float): 置信度阈值
  52. iou_threshold (float): NMS 中的 IoU 阈值
  53. nm (int): 掩膜数量
  54. Returns:
  55. boxes (List): 边界框列表
  56. segments (List): 分割区域列表
  57. masks (np.ndarray): [N, H, W] 输出掩膜
  58. """
  59. # 图像预处理
  60. im, ratio, (pad_w, pad_h) = self.preprocess(im0)
  61. # ONNX 推理
  62. preds = self.session.run(None, {self.session.get_inputs()[0].name: im})
  63. # 后处理
  64. boxes, segments, masks = self.postprocess(
  65. preds,
  66. im0=im0,
  67. ratio=ratio,
  68. pad_w=pad_w,
  69. pad_h=pad_h,
  70. conf_threshold=conf_threshold,
  71. iou_threshold=iou_threshold,
  72. nm=nm,
  73. )
  74. return boxes, segments, masks
  75. def preprocess(self, img):
  76. """
  77. 图像预处理
  78. Args:
  79. img (Numpy.ndarray): 输入图像
  80. Returns:
  81. img_process (Numpy.ndarray): 处理后的图像
  82. ratio (tuple): 宽高比例
  83. pad_w (float): 宽度的填充
  84. pad_h (float): 高度的填充
  85. """
  86. # 调整输入图像大小并使用 letterbox 填充
  87. shape = img.shape[:2] # 原始图像大小
  88. new_shape = (self.model_height, self.model_width)
  89. r = min(new_shape[0] / shape[0], new_shape[1] / shape[1])
  90. ratio = r, r
  91. new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r))
  92. pad_w, pad_h = (new_shape[1] - new_unpad[0]) / 2, (new_shape[0] - new_unpad[1]) / 2 # 填充宽高
  93. if shape[::-1] != new_unpad: # 调整图像大小
  94. img = cv2.resize(img, new_unpad, interpolation=cv2.INTER_LINEAR)
  95. top, bottom = int(round(pad_h - 0.1)), int(round(pad_h + 0.1))
  96. left, right = int(round(pad_w - 0.1)), int(round(pad_w + 0.1))
  97. img = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_CONSTANT, value=(114, 114, 114))
  98. # 转换:HWC -> CHW -> BGR 转 RGB -> 除以 255 -> contiguous -> 添加维度
  99. img = np.ascontiguousarray(np.einsum("HWC->CHW", img)[::-1], dtype=self.ndtype) / 255.0
  100. img_process = img[None] if len(img.shape) == 3 else img
  101. return img_process, ratio, (pad_w, pad_h)
  102. def postprocess(self, preds, im0, ratio, pad_w, pad_h, conf_threshold, iou_threshold, nm=32):
  103. """
  104. 推理后的结果后处理
  105. Args:
  106. preds (Numpy.ndarray): 来自 ONNX 的推理结果
  107. im0 (Numpy.ndarray): [h, w, c] 原始输入图像
  108. ratio (tuple): 宽高比例
  109. pad_w (float): 宽度的填充
  110. pad_h (float): 高度的填充
  111. conf_threshold (float): 置信度阈值
  112. iou_threshold (float): IoU 阈值
  113. nm (int): 掩膜数量
  114. Returns:
  115. boxes (List): 边界框列表
  116. segments (List): 分割区域列表
  117. masks (np.ndarray): 掩膜数组
  118. """
  119. x, protos = preds[0], preds[1] # 获取模型的两个输出:预测和原型
  120. # 转换维度
  121. x = np.einsum("bcn->bnc", x)
  122. # 置信度过滤
  123. x = x[np.amax(x[..., 4:-nm], axis=-1) > conf_threshold]
  124. # 合并边界框、置信度、类别和掩膜
  125. x = np.c_[x[..., :4], np.amax(x[..., 4:-nm], axis=-1), np.argmax(x[..., 4:-nm], axis=-1), x[..., -nm:]]
  126. # NMS 过滤
  127. x = x[cv2.dnn.NMSBoxes(x[:, :4], x[:, 4], conf_threshold, iou_threshold)]
  128. # 解析并返回结果
  129. if len(x) > 0:
  130. # 边界框格式转换:从 cxcywh -> xyxy
  131. x[..., [0, 1]] -= x[..., [2, 3]] / 2
  132. x[..., [2, 3]] += x[..., [0, 1]]
  133. # 缩放边界框,使其与原始图像尺寸匹配
  134. x[..., :4] -= [pad_w, pad_h, pad_w, pad_h]
  135. x[..., :4] /= min(ratio)
  136. # 限制边界框在图像边界内
  137. x[..., [0, 2]] = x[:, [0, 2]].clip(0, im0.shape[1])
  138. x[..., [1, 3]] = x[:, [1, 3]].clip(0, im0.shape[0])
  139. # 处理掩膜
  140. masks = self.process_mask(protos[0], x[:, 6:], x[:, :4], im0.shape)
  141. # 将掩膜转换为分割区域
  142. segments = self.masks2segments(masks)
  143. return x[..., :6], segments, masks # 返回边界框、分割区域和掩膜
  144. else:
  145. return [], [], []
  146. @staticmethod
  147. def masks2segments(masks):
  148. """
  149. 将掩膜转换为分割区域
  150. Args:
  151. masks (numpy.ndarray): 模型输出的掩膜,形状为 (n, h, w)
  152. Returns:
  153. segments (List): 分割区域的列表
  154. """
  155. segments = []
  156. for x in masks.astype("uint8"):
  157. c = cv2.findContours(x, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)[0] # 找到轮廓
  158. if c:
  159. c = np.array(c[np.array([len(x) for x in c]).argmax()]).reshape(-1, 2)
  160. else:
  161. c = np.zeros((0, 2)) # 如果没有找到分割区域,返回空数组
  162. segments.append(c.astype("float32"))
  163. return segments
  164. @staticmethod
  165. def crop_mask(masks, boxes):
  166. """
  167. 裁剪掩膜,使其与边界框对齐
  168. Args:
  169. masks (Numpy.ndarray): [n, h, w] 掩膜数组
  170. boxes (Numpy.ndarray): [n, 4] 边界框
  171. Returns:
  172. (Numpy.ndarray): 裁剪后的掩膜
  173. """
  174. n, h, w = masks.shape
  175. x1, y1, x2, y2 = np.split(boxes[:, :, None], 4, 1)
  176. r = np.arange(w, dtype=x1.dtype)[None, None, :]
  177. c = np.arange(h, dtype=x1.dtype)[None, :, None]
  178. return masks * ((r >= x1) * (r < x2) * (c >= y1) * (c < y2))
  179. def process_mask(self, protos, masks_in, bboxes, im0_shape):
  180. """
  181. 处理模型输出的掩膜
  182. Args:
  183. protos (numpy.ndarray): [mask_dim, mask_h, mask_w] 掩膜原型
  184. masks_in (numpy.ndarray): [n, mask_dim] 掩膜数量
  185. bboxes (numpy.ndarray): 缩放到原始图像尺寸的边界框
  186. im0_shape (tuple): 原始输入图像的尺寸 (h,w,c)
  187. Returns:
  188. (numpy.ndarray): 处理后的掩膜
  189. """
  190. c, mh, mw = protos.shape
  191. masks = np.matmul(masks_in, protos.reshape((c, -1))).reshape((-1, mh, mw)).transpose(1, 2, 0) # HWN
  192. masks = np.ascontiguousarray(masks)
  193. masks = self.scale_mask(masks, im0_shape) # 将掩膜从 P3 尺寸缩放到原始输入图像大小
  194. masks = np.einsum("HWN -> NHW", masks) # HWN -> NHW
  195. masks = self.crop_mask(masks, bboxes) # 裁剪掩膜
  196. return np.greater(masks, 0.5) # 返回二值化后的掩膜
  197. @staticmethod
  198. def scale_mask(masks, im0_shape, ratio_pad=None):
  199. """
  200. 将掩膜缩放至原始图像大小
  201. Args:
  202. masks (np.ndarray): 缩放和填充后的掩膜
  203. im0_shape (tuple): 原始图像大小
  204. ratio_pad (tuple): 填充与原始图像的比例
  205. Returns:
  206. masks (np.ndarray): 缩放后的掩膜
  207. """
  208. im1_shape = masks.shape[:2]
  209. if ratio_pad is None: # 计算比例
  210. gain = min(im1_shape[0] / im0_shape[0], im1_shape[1] / im0_shape[1]) # 比例
  211. pad = (im1_shape[1] - im0_shape[1] * gain) / 2, (im1_shape[0] - im0_shape[0] * gain) / 2 # 填充
  212. else:
  213. pad = ratio_pad[1]
  214. # 计算掩膜的边界
  215. top, left = int(round(pad[1] - 0.1)), int(round(pad[0] - 0.1)) # y, x
  216. bottom, right = int(round(im1_shape[0] - pad[1] + 0.1)), int(round(im1_shape[1] - pad[0] + 0.1))
  217. if len(masks.shape) < 2:
  218. raise ValueError(f'"len of masks shape" 应该是 2 或 3,但得到 {len(masks.shape)}')
  219. masks = masks[top:bottom, left:right]
  220. masks = cv2.resize(
  221. masks, (im0_shape[1], im0_shape[0]), interpolation=cv2.INTER_LINEAR
  222. ) # 使用 INTER_LINEAR 插值调整大小
  223. if len(masks.shape) == 2:
  224. masks = masks[:, :, None]
  225. return masks
  226. def draw_and_visualize(self, im, bboxes, segments, vis=False, save=True):
  227. """
  228. 绘制和可视化结果
  229. Args:
  230. im (np.ndarray): 原始图像,形状为 [h, w, c]
  231. bboxes (numpy.ndarray): [n, 4],n 是边界框数量
  232. segments (List): 分割区域的列表
  233. vis (bool): 是否使用 OpenCV 显示图像
  234. save (bool): 是否保存带注释的图像
  235. Returns:
  236. None
  237. """
  238. # 创建图像副本
  239. im_canvas = im.copy()
  240. for (*box, conf, cls_), segment in zip(bboxes, segments):
  241. # 获取类别对应的颜色
  242. color = self.get_color_for_class(int(cls_))
  243. # 绘制轮廓和填充掩膜
  244. # cv2.polylines(im, np.int32([segment]), True, (255, 255, 255), 2) # 绘制白色边框
  245. cv2.fillPoly(im_canvas, np.int32([segment]), color) # 使用类别对应的颜色填充多边形
  246. # 绘制边界框
  247. cv2.rectangle(im, (int(box[0]), int(box[1])), (int(box[2]), int(box[3])), color, 1, cv2.LINE_AA)
  248. # 在图像上绘制类别名称和置信度
  249. cv2.putText(im, f"{self.classes[cls_]}: {conf:.3f}", (int(box[0]), int(box[1] - 9)), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 1, cv2.LINE_AA)
  250. # 将图像和绘制的多边形混合
  251. im = cv2.addWeighted(im_canvas, 0.3, im, 0.7, 0)
  252. # 显示图像
  253. if vis:
  254. cv2.imshow("seg_result_picture", im)
  255. cv2.waitKey(0)
  256. cv2.destroyAllWindows()
  257. # 保存图像
  258. if save:
  259. cv2.imwrite("seg_result_picture.jpg", im)
  260. if __name__ == "__main__":
  261. # 创建命令行参数解析器
  262. parser = argparse.ArgumentParser()
  263. parser.add_argument("--model", type=str, default=r"weights/seg_best_exp6.onnx" , help="ONNX 模型路径")
  264. parser.add_argument("--source", type=str, default=r"datasets/Mix_seg_point_offer_num53/images/point_offer_20240930_14_Image.png", help="输入图像路径")
  265. parser.add_argument("--conf", type=float, default=0.6, help="置信度阈值")
  266. parser.add_argument("--iou", type=float, default=0.45, help="NMS 的 IoU 阈值")
  267. args = parser.parse_args()
  268. # 加载模型
  269. model = YOLO11Seg(args.model)
  270. # 使用 OpenCV 读取图像
  271. img = cv2.imread(args.source)
  272. # 模型推理
  273. boxes, segments, _ = model(img, conf_threshold=args.conf, iou_threshold=args.iou)
  274. # 如果检测到目标,绘制边界框和分割区域
  275. if len(boxes) > 0:
  276. model.draw_and_visualize(img, boxes, segments, vis=False, save=True)

需要修改类别映射关系,以及类别对应的颜色

比如,定义两个类别(car、person), 示例代码:

  1. # 类外定义类别映射关系,使用字典格式
  2. CLASS_NAMES = {
  3. 0: 'car', # 类别 0 名称
  4. 1: 'person' # 类别 1 名称
  5. # 可以添加更多类别...
  6. }

类别对应的两种颜色, 示例代码:

  1. # 定义类别对应的颜色,格式为 (R, G, B)
  2. CLASS_COLORS = {
  3. 0: (255, 255, 0), # 类别 0 的颜色为青黄色
  4. 1: (255, 0, 0) # 类别 1 的颜色为红色
  5. # 可以为其他类别指定颜色...
  6. }

运行代码,打印信息:

YOLO11 ? 实例分割 ONNXRuntime
模型名称: weights/seg_best_exp6.onnx
模型输入尺寸:宽度 = 640, 高度 = 640

可视化看一下分割效果,保存名称是:seg_result_picture.jpg

 YOLO11相关文章推荐:

一篇文章快速认识YOLO11 | 关键改进点 | 安装使用 | 模型训练和推理-CSDN博客

一篇文章快速认识 YOLO11 | 实例分割 | 模型训练 | 自定义数据集-CSDN博客

YOLO11模型推理 | 目标检测与跟踪 | 实例分割 | 关键点估计 | OBB旋转目标检测-CSDN博客

YOLO11模型训练 | 目标检测与跟踪 | 实例分割 | 关键点姿态估计-CSDN博客

YOLO11 实例分割 | 自动标注 | 预标注 | 标签格式转换 | 手动校正标签-CSDN博客

分享完成,欢迎大家多多点赞和收藏,谢谢~

注:本文转载自blog.csdn.net的一颗小树x的文章"https://blog.csdn.net/qq_41204464/article/details/142922223"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

101
推荐
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top