首页 最新 热门 推荐

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

YOLO11 旋转目标检测 | OBB定向检测 | ONNX模型推理 | 旋转NMS

  • 25-02-18 14:02
  • 2529
  • 7406
blog.csdn.net

本文分享YOLO11中,从xxx.pt权重文件转为.onnx文件,然后使用.onnx文件,进行旋转目标检测任务的模型推理。

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

本文提供源代码,支持不同尺寸图片输入、支持旋转NMS过滤重复框、支持旋转IOU计算。

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

目录

1、导出ONNX模型

2、所需依赖库

3、整体框架思路

4、支持不同尺寸图像输入 函数

5、旋转边界框IoU计算 函数

6、旋转NMS过滤重复框 函数

7、解析ONNX模型输出 函数

8、完整代码


1、导出ONNX模型

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

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

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

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

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

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

YOLO11导出ONNX模型(专业版)

  1. from ultralytics import YOLO
  2. # 加载一个模型,路径为 YOLO 模型的 .pt 文件
  3. model = YOLO(r"runs/obb/train1/weights/best.pt")
  4. # 导出模型,设置多种参数
  5. model.export(
  6. format="onnx", # 导出格式为 ONNX
  7. imgsz=(640, 640), # 设置输入图像的尺寸
  8. keras=False, # 不导出为 Keras 格式
  9. optimize=False, # 不进行优化 False, 移动设备优化的参数,用于在导出为TorchScript 格式时进行模型优化
  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. device="cpu" # 指定导出设备为CPU或GPU,对应参数为"cpu" , "0"
  19. )

对于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。
  13. device="cpu", 指定导出设备为CPU或GPU,对应参数为"cpu" , "0"

参考官网文档: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、所需依赖库

本文的代码中,主要依赖opencv、onnxruntime、numpy这三个库;

不需要安装torch、ultralytics等库的。

import os

import cv2

import numpy as np

import onnxruntime as ort

import logging

3、整体框架思路

我们编写代码,实现了一个基于 YOLO11 旋转目标检测(OBB)的推理和检测可视化系统。

以下是代码的整体思路分析:

3.1、基本功能与目标

  • YOLO11模型推理:使用ONNX格式的YOLO11模型,对图像中的旋转目标进行检测。
  • 输出解析:解析模型输出,获取检测的旋转边界框坐标、类别和置信度。
  • 旋转边界框处理:支持旋转NMS(非极大值抑制)和 ProbIoU(概率交并比)来处理重复检测框。
  • 可视化检测结果:在图像上绘制旋转边界框,标注检测结果。

3.2、图像预处理 (letterbox 函数)

  • 将输入图像调整为指定的 imgsz 大小(默认为 640x640),保持长宽比并添加填充。
  • 返回图像缩放的 ratio 和填充偏移 dw, dh,以便后续解析输出时恢复原图坐标。

3.3、加载模型 (load_model 函数)

  • 加载 ONNX 格式的 YOLO11 模型,使用 onnxruntime 进行推理。

3.4、旋转边界框的协方差矩阵计算 (_get_covariance_matrix 函数)

  • 基于边界框的宽、高和角度,计算协方差矩阵的元素 a, b, c,这是 ProbIoU 计算的前提。

3.5、旋转边界框的ProbIoU计算 (batch_probiou 函数)

  • 基于两个旋转边界框集合,使用协方差矩阵计算 ProbIoU 值。ProbIoU 计算边界框之间的相似性,考虑了旋转角度的影响。
  • 返回一个矩阵,表示每对边界框的 ProbIoU 值。

3.6、旋转NMS过滤重复框 (rotated_nms_with_probiou 函数)

  • 使用 ProbIoU 执行旋转边界框的 NMS 操作,去除重叠度过高的检测框。
  • 根据置信度分数 scores 降序排列,逐步计算当前检测框与其余框的 ProbIoU,如果 ProbIoU 小于设定的阈值 iou_threshold,则保留当前框。

3.7、推理处理 (run_inference 函数)

  • 从输入字节流中解码图像数据。
  • 将图像经过 letterbox 预处理后转换为模型输入格式。
  • 使用 ONNX 模型进行推理,返回推理结果 result 及缩放 ratio 和填充 dwdh。

3.8、解析模型输出 (parse_onnx_output 函数)

  • 提取每个检测框的中心坐标、宽高、类别置信度及旋转角度。
  • 根据设定的 conf_threshold 过滤置信度低的检测框。
  • 多类别模型中,提取所有类别的置信度,并选择置信度最高的类别。
  • 使用 rotated_nms_with_probiou 函数执行旋转NMS去除重复框。
  • 将检测框坐标按比例缩放并去除填充,返回处理后的检测结果,包括位置坐标、类别和置信度。

3.9、旋转边界框四角点计算 (calculate_obb_corners 函数)

  • 根据旋转角度和宽高,计算旋转边界框的四个角点坐标,以便在图像上进行绘制。

3.10、绘制检测结果 (save_detections 函数)

  • 在原始图像上绘制旋转边界框和类别标签,标注置信度。
  • 将绘制完成的图像保存到指定的输出路径。

3.11、批量处理文件夹图像 (process_images_in_folder 函数)

  • 从指定文件夹中逐张读取图像文件,对每张图像执行推理、解析输出、绘制结果,并将绘制后的图像保存到输出文件夹。

3.12、主函数执行参数

  • 定义输入图像文件夹、模型路径、输出文件夹、置信度阈值、IoU阈值及模型输入大小 imgsz 等参数。
  • 执行 process_images_in_folder 对文件夹内的图像批量处理并保存结果。

4、支持不同尺寸图像输入 函数

letterbox 函数的主要任务是将输入图像调整为特定尺寸,使用模型所需的输入大小(如 640x640),同时保持图像的长宽比,并为目标尺寸添加适量的填充。

  • 它通过保持原始宽高比来避免图像的变形,通过对称填充使得图像保持中心化
  • 最终得到标准尺寸的图像,适应深度学习模型的输入需求。
  • 参数 auto、scale_fill 和 scale_up 提供了不同的选项,以适应不同的应用场景:比如是否需要按指定步幅填充,是否强制拉伸以填满,或者是否允许图像放大。
  1. def letterbox(img, new_shape=(640, 640), color=(0, 0, 0), auto=False, scale_fill=False, scale_up=False, stride=32):
  2. """
  3. 将图像调整为指定尺寸,同时保持长宽比,添加填充以适应目标输入形状。
  4. """
  5. # 获取图像的当前高度和宽度
  6. shape = img.shape[:2]
  7. # 如果 new_shape 是整数,则将其转换为 (new_shape, new_shape) 的格式
  8. if isinstance(new_shape, int):
  9. new_shape = (new_shape, new_shape)
  10. # 计算缩放比例 r,以便图像适配到 new_shape,保持长宽比
  11. r = min(new_shape[0] / shape[0], new_shape[1] / shape[1])
  12. # 若不允许放大(scale_up=False),限制 r 最大值为 1.0
  13. if not scale_up:
  14. r = min(r, 1.0)
  15. # 保存缩放比例 ratio,计算未填充的新尺寸 new_unpad
  16. ratio = r, r
  17. new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r))
  18. # 计算目标尺寸与缩放后尺寸的宽度、高度差值 dw, dh
  19. dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1]
  20. # 若 auto=True,则将 dw 和 dh 调整为 stride 的倍数
  21. if auto:
  22. dw, dh = np.mod(dw, stride), np.mod(dh, stride)
  23. # 若 scale_fill=True,则忽略比例,强制缩放到目标尺寸
  24. elif scale_fill:
  25. dw, dh = 0.0, 0.0
  26. new_unpad = (new_shape[1], new_shape[0])
  27. ratio = new_shape[1] / shape[1], new_shape[0] / shape[0]
  28. # 将填充值平分到图像四周
  29. dw /= 2
  30. dh /= 2
  31. # 如果当前图像尺寸与目标尺寸不一致,则将图像缩放到 new_unpad
  32. if shape[::-1] != new_unpad:
  33. img = cv2.resize(img, new_unpad, interpolation=cv2.INTER_LINEAR)
  34. # 应用上下左右的填充以使图像符合目标尺寸
  35. top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1))
  36. left, right = int(round(dw - 0.1)), int(round(dw + 0.1))
  37. img = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color)
  38. # 返回填充后的图像、缩放比例和填充量
  39. return img, ratio, (dw, dh)

5、旋转边界框IoU计算 函数

这里编写batch_probiou 函数,用于旋转边界框IoU计算,其中使用到ProbIoU方法。

这是一个基于协方差矩阵的方法,用于评估旋转边界框之间的相似性。

  1. def batch_probiou(obb1, obb2, eps=1e-7):
  2. """
  3. 计算旋转边界框之间的 ProbIoU。
  4. :param obb1: 第一个旋转边界框集合
  5. :param obb2: 第二个旋转边界框集合
  6. :param eps: 防止除零的极小值
  7. :return: 两个旋转边界框之间的 ProbIoU
  8. """
  9. # 提取两个旋转边界框的中心坐标 (x, y)
  10. x1, y1 = obb1[..., 0], obb1[..., 1]
  11. x2, y2 = obb2[..., 0], obb2[..., 1]
  12. # 计算两个旋转边界框的协方差矩阵元素 a, b, c
  13. a1, b1, c1 = _get_covariance_matrix(obb1)
  14. a2, b2, c2 = _get_covariance_matrix(obb2)
  15. # 计算 ProbIoU 的三个部分 t1, t2, t3
  16. # t1 表示中心点位置差异的贡献
  17. t1 = ((a1[:, None] + a2) * (y1[:, None] - y2) ** 2 + (b1[:, None] + b2) * (x1[:, None] - x2) ** 2) / (
  18. (a1[:, None] + a2) * (b1[:, None] + b2) - (c1[:, None] + c2) ** 2 + eps) * 0.25
  19. # t2 表示旋转角度的耦合贡献
  20. t2 = ((c1[:, None] + c2) * (x2 - x1[:, None]) * (y1[:, None] - y2)) / (
  21. (a1[:, None] + a2) * (b1[:, None] + b2) - (c1[:, None] + c2) ** 2 + eps) * 0.5
  22. # t3 表示面积和形状之间的差异贡献
  23. t3 = np.log(((a1[:, None] + a2) * (b1[:, None] + b2) - (c1[:, None] + c2) ** 2) /
  24. (4 * np.sqrt((a1 * b1 - c1 ** 2)[:, None] * (a2 * b2 - c2 ** 2)) + eps) + eps) * 0.5
  25. # 计算 Bhattacharyya 距离 bd
  26. bd = np.clip(t1 + t2 + t3, eps, 100.0) # 将 bd 限制在 [eps, 100.0] 范围内
  27. # 计算 ProbIoU 值 hd
  28. hd = np.sqrt(1.0 - np.exp(-bd) + eps) # 使用 Bhattacharyya 距离计算 hd
  29. return 1 - hd # 返回 1 - hd,hd 越小表示相似度越高,1 - hd 即为 ProbIoU

ProbIoU的论文地址:https://arxiv.org/pdf/2106.06072v1.pdf

batch_probiou 函数需要用到协方差矩阵,这里也编写一个函数进行封装

  1. def _get_covariance_matrix(obb):
  2. """
  3. 计算旋转边界框的协方差矩阵。
  4. :param obb: 旋转边界框 (Oriented Bounding Box),包含中心坐标、宽、高和旋转角度
  5. :return: 协方差矩阵的三个元素 a, b, c
  6. """
  7. widths = obb[..., 2] / 2 # 获取宽度的一半
  8. heights = obb[..., 3] / 2 # 获取高度的一半
  9. angles = obb[..., 4] # 获取旋转角度
  10. cos_angle = np.cos(angles) # 计算旋转角度的余弦值
  11. sin_angle = np.sin(angles) # 计算旋转角度的正弦值
  12. # 计算协方差矩阵的三个元素 a, b, c
  13. a = (widths * cos_angle) ** 2 + (heights * sin_angle) ** 2
  14. b = (widths * sin_angle) ** 2 + (heights * cos_angle) ** 2
  15. c = widths * cos_angle * heights * sin_angle
  16. return a, b, c

总结:

  • 该代码用于计算两个旋转边界框之间的 ProbIoU,以量化它们的相似程度。
  • 核心思想是通过协方差矩阵来描述每个旋转边界框的形状和旋转角度,结合 Bhattacharyya 距离来评估边界框之间的相似性。
  • ProbIoU 的计算考虑了旋转角度、边界框的中心位置以及大小差异,是比传统 IoU 更加复杂和准确的度量旋转边界框相似性的方法,特别适用于场景中有方向性的对象。

6、旋转NMS过滤重复框 函数

这里编写rotated_nms_with_probiou函数,实现了旋转边界框的非极大值抑制NMS。

  • NMS 的目标是去除冗余的边界框,只保留最有代表性的那个,以减少重叠检测。
  • 在这个实现中,通过 ProbIoU 来计算旋转边界框之间的相似度,用于确定哪些框需要保留。
  • NMS 的核心在于选取当前得分最高的边界框,将它加入保留列表中,
  • 然后与剩余边界框进行相似性计算,通过ProbIoU计算旋转边界框之间的相似度,最终剔除那些相似度高于阈值的边界框。
  1. def rotated_nms_with_probiou(boxes, scores, iou_threshold=0.5):
  2. """
  3. 使用 ProbIoU 执行旋转边界框的非极大值抑制(NMS)。
  4. :param boxes: 旋转边界框的集合
  5. :param scores: 每个边界框的置信度得分
  6. :param iou_threshold: IoU 阈值,用于确定是否抑制框
  7. :return: 保留的边界框索引列表
  8. """
  9. order = scores.argsort()[::-1] # 根据置信度得分降序排序
  10. keep = [] # 用于存储保留的边界框索引
  11. while len(order) > 0:
  12. i = order[0] # 选择当前得分最高的边界框
  13. keep.append(i) # 将该边界框的索引加入到保留列表中
  14. if len(order) == 1: # 如果只剩下一个边界框,跳出循环
  15. break
  16. remaining_boxes = boxes[order[1:]] # 获取剩余的边界框
  17. iou_values = batch_probiou(boxes[i:i + 1], remaining_boxes).squeeze(0) # 计算当前框与剩余框之间的 ProbIoU
  18. mask = iou_values < iou_threshold # 找出与当前框 IoU 小于阈值的边界框
  19. order = order[1:][mask] # 更新剩余的边界框索引,只保留 IoU 小于阈值的部分
  20. return keep # 返回保留的边界框索引列表

7、解析ONNX模型输出 函数

这里编写parse_onnx_output函数,实现了对 ONNX 模型的输出进行解析

  • 提取旋转边界框的坐标、旋转角度、置信度和类别信息,
  • 应用旋转边界框的非极大值抑制(NMS),并计算旋转边界框的四个角点

这里我们需要知道:

ONNX输出格式: x_center, y_center, width, height, class1_confidence, ..., classN_confidence, angle 

  1. def parse_onnx_output(output, ratio, dwdh, conf_threshold=0.5, iou_threshold=0.5):
  2. """
  3. 解析ONNX模型的输出,提取旋转边界框坐标、置信度和类别信息,并应用旋转NMS。
  4. :param output: ONNX模型的输出,包含预测的边界框信息
  5. :param ratio: 缩放比例,用于将坐标还原到原始尺度
  6. :param dwdh: 填充的宽高,用于调整边界框的中心点坐标
  7. :param conf_threshold: 置信度阈值,过滤低于该阈值的检测框
  8. :param iou_threshold: IoU 阈值,用于旋转边界框的非极大值抑制(NMS)
  9. :return: 符合条件的旋转边界框的检测结果
  10. """
  11. boxes, scores, classes, detections = [], [], [], [] # 初始化存储检测结果的列表
  12. num_detections = output.shape[2] # 获取检测的边界框数量
  13. num_classes = output.shape[1] - 6 # 计算类别数量
  14. # 逐个解析每个检测结果
  15. for i in range(num_detections):
  16. detection = output[0, :, i] # 获取第 i 个检测结果
  17. x_center, y_center, width, height = detection[0], detection[1], detection[2], detection[3] # 提取边界框的中心坐标和宽高
  18. angle = detection[-1] # 提取旋转角度
  19. # 处理类别信息和置信度
  20. if num_classes > 0:
  21. class_confidences = detection[4:4 + num_classes] # 获取类别置信度
  22. if class_confidences.size == 0:
  23. continue
  24. class_id = np.argmax(class_confidences) # 获取置信度最高的类别索引
  25. confidence = class_confidences[class_id] # 获取对应的置信度
  26. else:
  27. confidence = detection[4] # 如果没有类别信息,直接使用置信度值
  28. class_id = 0 # 默认类别为 0
  29. # 过滤低置信度的检测结果
  30. if confidence > conf_threshold:
  31. x_center = (x_center - dwdh[0]) / ratio[0] # 还原中心点 x 坐标
  32. y_center = (y_center - dwdh[1]) / ratio[1] # 还原中心点 y 坐标
  33. width /= ratio[0] # 还原宽度
  34. height /= ratio[1] # 还原高度
  35. boxes.append([x_center, y_center, width, height, angle]) # 将边界框信息加入列表
  36. scores.append(confidence) # 将置信度加入列表
  37. classes.append(class_id) # 将类别加入列表
  38. if not boxes: # 如果没有符合条件的边界框,返回空列表
  39. return []
  40. # 转换为 NumPy 数组
  41. boxes = np.array(boxes)
  42. scores = np.array(scores)
  43. classes = np.array(classes)
  44. # 应用旋转 NMS
  45. keep_indices = rotated_nms_with_probiou(boxes, scores, iou_threshold=iou_threshold)
  46. # 构建最终检测结果
  47. for idx in keep_indices:
  48. x_center, y_center, width, height, angle = boxes[idx] # 获取保留的边界框信息
  49. confidence = scores[idx] # 获取对应的置信度
  50. class_id = classes[idx] # 获取类别
  51. obb_corners = calculate_obb_corners(x_center, y_center, width, height, angle) # 计算旋转边界框的四个角点
  52. # 将检测结果添加到列表中
  53. detections.append({
  54. "position": obb_corners, # 旋转边界框的角点坐标
  55. "confidence": float(confidence), # 置信度
  56. "class_id": int(class_id), # 类别 ID
  57. "angle": float(angle) # 旋转角度
  58. })
  59. return detections # 返回检测结果

parse_onnx_output函数需要用到calculate_obb_corners函数,根据旋转角度计算旋转边界框的四个角点

  1. def calculate_obb_corners(x_center, y_center, width, height, angle):
  2. """
  3. 根据旋转角度计算旋转边界框的四个角点。
  4. :param x_center: 边界框中心的 x 坐标
  5. :param y_center: 边界框中心的 y 坐标
  6. :param width: 边界框的宽度
  7. :param height: 边界框的高度
  8. :param angle: 旋转角度
  9. :return: 旋转边界框的四个角点坐标
  10. """
  11. cos_angle = np.cos(angle) # 计算旋转角度的余弦值
  12. sin_angle = np.sin(angle) # 计算旋转角度的正弦值
  13. dx = width / 2 # 计算宽度的一半
  14. dy = height / 2 # 计算高度的一半
  15. # 计算旋转边界框的四个角点坐标
  16. corners = [
  17. (int(x_center + cos_angle * dx - sin_angle * dy), int(y_center + sin_angle * dx + cos_angle * dy)),
  18. (int(x_center - cos_angle * dx - sin_angle * dy), int(y_center - sin_angle * dx + cos_angle * dy)),
  19. (int(x_center - cos_angle * dx + sin_angle * dy), int(y_center - sin_angle * dx - cos_angle * dy)),
  20. (int(x_center + cos_angle * dx + sin_angle * dy), int(y_center + sin_angle * dx - cos_angle * dy)),
  21. ]
  22. return corners # 返回角点坐标

8、完整代码

完整代码,如下所示:

  1. import os
  2. import cv2
  3. import numpy as np
  4. import onnxruntime as ort
  5. import logging
  6. """
  7. YOLO11 旋转目标检测OBB
  8. 1、ONNX模型推理、可视化
  9. 2、ONNX输出格式: x_center, y_center, width, height, class1_confidence, ..., classN_confidence, angle
  10. 3、支持不同尺寸图片输入、支持旋转NMS过滤重复框、支持ProbIoU旋转IOU计算
  11. """
  12. def letterbox(img, new_shape=(640, 640), color=(0, 0, 0), auto=False, scale_fill=False, scale_up=False, stride=32):
  13. """
  14. 将图像调整为指定尺寸,同时保持长宽比,添加填充以适应目标输入形状。
  15. :param img: 输入图像
  16. :param new_shape: 目标尺寸
  17. :param color: 填充颜色
  18. :param auto: 是否自动调整填充为步幅的整数倍
  19. :param scale_fill: 是否强制缩放以完全填充目标尺寸
  20. :param scale_up: 是否允许放大图像
  21. :param stride: 步幅,用于自动调整填充
  22. :return: 调整后的图像、缩放比例、填充尺寸(dw, dh)
  23. """
  24. shape = img.shape[:2]
  25. if isinstance(new_shape, int):
  26. new_shape = (new_shape, new_shape)
  27. r = min(new_shape[0] / shape[0], new_shape[1] / shape[1]) # 计算缩放比例
  28. if not scale_up:
  29. r = min(r, 1.0)
  30. ratio = r, r
  31. new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r))
  32. dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1]
  33. if auto:
  34. dw, dh = np.mod(dw, stride), np.mod(dh, stride)
  35. elif scale_fill:
  36. dw, dh = 0.0, 0.0
  37. new_unpad = (new_shape[1], new_shape[0])
  38. ratio = new_shape[1] / shape[1], new_shape[0] / shape[0]
  39. dw /= 2 # 填充均分
  40. dh /= 2
  41. if shape[::-1] != new_unpad:
  42. img = cv2.resize(img, new_unpad, interpolation=cv2.INTER_LINEAR)
  43. top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1))
  44. left, right = int(round(dw - 0.1)), int(round(dw + 0.1))
  45. img = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color)
  46. return img, ratio, (dw, dh)
  47. def load_model(weights):
  48. """
  49. 加载ONNX模型并返回会话对象。
  50. :param weights: 模型权重文件路径
  51. :return: ONNX运行会话对象
  52. """
  53. session = ort.InferenceSession(weights, providers=['CPUExecutionProvider'])
  54. logging.info(f"模型加载成功: {weights}")
  55. return session
  56. def _get_covariance_matrix(obb):
  57. """
  58. 计算旋转边界框的协方差矩阵。
  59. :param obb: 旋转边界框 (Oriented Bounding Box),包含中心坐标、宽、高和旋转角度
  60. :return: 协方差矩阵的三个元素 a, b, c
  61. """
  62. widths = obb[..., 2] / 2
  63. heights = obb[..., 3] / 2
  64. angles = obb[..., 4]
  65. cos_angle = np.cos(angles)
  66. sin_angle = np.sin(angles)
  67. a = (widths * cos_angle)**2 + (heights * sin_angle)**2
  68. b = (widths * sin_angle)**2 + (heights * cos_angle)**2
  69. c = widths * cos_angle * heights * sin_angle
  70. return a, b, c
  71. def batch_probiou(obb1, obb2, eps=1e-7):
  72. """
  73. 计算旋转边界框之间的 ProbIoU。
  74. :param obb1: 第一个旋转边界框集合
  75. :param obb2: 第二个旋转边界框集合
  76. :param eps: 防止除零的极小值
  77. :return: 两个旋转边界框之间的 ProbIoU
  78. """
  79. x1, y1 = obb1[..., 0], obb1[..., 1]
  80. x2, y2 = obb2[..., 0], obb2[..., 1]
  81. a1, b1, c1 = _get_covariance_matrix(obb1)
  82. a2, b2, c2 = _get_covariance_matrix(obb2)
  83. t1 = ((a1[:, None] + a2) * (y1[:, None] - y2)**2 + (b1[:, None] + b2) * (x1[:, None] - x2)**2) / (
  84. (a1[:, None] + a2) * (b1[:, None] + b2) - (c1[:, None] + c2)**2 + eps) * 0.25
  85. t2 = ((c1[:, None] + c2) * (x2 - x1[:, None]) * (y1[:, None] - y2)) / (
  86. (a1[:, None] + a2) * (b1[:, None] + b2) - (c1[:, None] + c2)**2 + eps) * 0.5
  87. t3 = np.log(((a1[:, None] + a2) * (b1[:, None] + b2) - (c1[:, None] + c2)**2) /
  88. (4 * np.sqrt((a1 * b1 - c1**2)[:, None] * (a2 * b2 - c2**2)) + eps) + eps) * 0.5
  89. bd = np.clip(t1 + t2 + t3, eps, 100.0)
  90. hd = np.sqrt(1.0 - np.exp(-bd) + eps)
  91. return 1 - hd
  92. def rotated_nms_with_probiou(boxes, scores, iou_threshold=0.5):
  93. """
  94. 使用 ProbIoU 执行旋转边界框的非极大值抑制(NMS)。
  95. :param boxes: 旋转边界框的集合
  96. :param scores: 每个边界框的置信度得分
  97. :param iou_threshold: IoU 阈值,用于确定是否抑制框
  98. :return: 保留的边界框索引列表
  99. """
  100. order = scores.argsort()[::-1] # 根据置信度得分降序排序
  101. keep = []
  102. while len(order) > 0:
  103. i = order[0]
  104. keep.append(i)
  105. if len(order) == 1:
  106. break
  107. remaining_boxes = boxes[order[1:]]
  108. iou_values = batch_probiou(boxes[i:i+1], remaining_boxes).squeeze(0)
  109. mask = iou_values < iou_threshold # 保留 IoU 小于阈值的框
  110. order = order[1:][mask]
  111. return keep
  112. def run_inference(session, image_bytes, imgsz=(640, 640)):
  113. """
  114. 对输入图像进行预处理,然后使用ONNX模型执行推理。
  115. :param session: ONNX运行会话对象
  116. :param image_bytes: 输入图像的字节数据
  117. :param imgsz: 模型输入的尺寸
  118. :return: 推理结果、缩放比例、填充尺寸
  119. """
  120. im0 = cv2.imdecode(np.frombuffer(image_bytes, np.uint8), cv2.IMREAD_COLOR) # 解码图像字节数据
  121. if im0 is None:
  122. raise ValueError("无法从image_bytes解码图像")
  123. img, ratio, (dw, dh) = letterbox(im0, new_shape=imgsz) # 调整图像尺寸
  124. img = img.transpose((2, 0, 1))[::-1] # 调整通道顺序
  125. img = np.ascontiguousarray(img)
  126. img = img[np.newaxis, ...].astype(np.float32) / 255.0 # 归一化处理
  127. input_name = session.get_inputs()[0].name
  128. result = session.run(None, {input_name: img}) # 执行模型推理
  129. return result[0], ratio, (dw, dh)
  130. def parse_onnx_output(output, ratio, dwdh, conf_threshold=0.5, iou_threshold=0.5):
  131. """
  132. 解析ONNX模型的输出,提取旋转边界框坐标、置信度和类别信息,并应用旋转NMS。
  133. :param output: ONNX模型的输出,包含预测的边界框信息
  134. :param ratio: 缩放比例,用于将坐标还原到原始尺度
  135. :param dwdh: 填充的宽高,用于调整边界框的中心点坐标
  136. :param conf_threshold: 置信度阈值,过滤低于该阈值的检测框
  137. :param iou_threshold: IoU 阈值,用于旋转边界框的非极大值抑制(NMS)
  138. :return: 符合条件的旋转边界框的检测结果
  139. """
  140. boxes, scores, classes, detections = [], [], [], []
  141. num_detections = output.shape[2] # 获取检测的边界框数量
  142. num_classes = output.shape[1] - 6 # 计算类别数量
  143. # 逐个解析每个检测结果
  144. for i in range(num_detections):
  145. detection = output[0, :, i]
  146. x_center, y_center, width, height = detection[0], detection[1], detection[2], detection[3] # 提取边界框的中心坐标和宽高
  147. angle = detection[-1] # 提取旋转角度
  148. if num_classes > 0:
  149. class_confidences = detection[4:4 + num_classes] # 获取类别置信度
  150. if class_confidences.size == 0:
  151. continue
  152. class_id = np.argmax(class_confidences) # 获取置信度最高的类别索引
  153. confidence = class_confidences[class_id] # 获取对应的置信度
  154. else:
  155. confidence = detection[4] # 如果没有类别信息,直接使用置信度值
  156. class_id = 0 # 默认类别为 0
  157. if confidence > conf_threshold: # 过滤掉低置信度的检测结果
  158. x_center = (x_center - dwdh[0]) / ratio[0] # 还原中心点 x 坐标
  159. y_center = (y_center - dwdh[1]) / ratio[1] # 还原中心点 y 坐标
  160. width /= ratio[0] # 还原宽度
  161. height /= ratio[1] # 还原高度
  162. boxes.append([x_center, y_center, width, height, angle]) # 将边界框信息加入列表
  163. scores.append(confidence) # 将置信度加入列表
  164. classes.append(class_id) # 将类别加入列表
  165. if not boxes:
  166. return []
  167. # 转换为 NumPy 数组
  168. boxes = np.array(boxes)
  169. scores = np.array(scores)
  170. classes = np.array(classes)
  171. # 应用旋转 NMS
  172. keep_indices = rotated_nms_with_probiou(boxes, scores, iou_threshold=iou_threshold)
  173. # 构建最终检测结果
  174. for idx in keep_indices:
  175. x_center, y_center, width, height, angle = boxes[idx] # 获取保留的边界框信息
  176. confidence = scores[idx] # 获取对应的置信度
  177. class_id = classes[idx] # 获取类别
  178. obb_corners = calculate_obb_corners(x_center, y_center, width, height, angle) # 计算旋转边界框的四个角点
  179. detections.append({
  180. "position": obb_corners, # 旋转边界框的角点坐标
  181. "confidence": float(confidence), # 置信度
  182. "class_id": int(class_id), # 类别 ID
  183. "angle": float(angle) # 旋转角度
  184. })
  185. return detections
  186. def calculate_obb_corners(x_center, y_center, width, height, angle):
  187. """
  188. 根据旋转角度计算旋转边界框的四个角点。
  189. :param x_center: 边界框中心的 x 坐标
  190. :param y_center: 边界框中心的 y 坐标
  191. :param width: 边界框的宽度
  192. :param height: 边界框的高度
  193. :param angle: 旋转角度
  194. :return: 旋转边界框的四个角点坐标
  195. """
  196. cos_angle = np.cos(angle) # 计算旋转角度的余弦值
  197. sin_angle = np.sin(angle) # 计算旋转角度的正弦值
  198. dx = width / 2 # 计算宽度的一半
  199. dy = height / 2 # 计算高度的一半
  200. # 计算旋转边界框的四个角点坐标
  201. corners = [
  202. (int(x_center + cos_angle * dx - sin_angle * dy), int(y_center + sin_angle * dx + cos_angle * dy)),
  203. (int(x_center - cos_angle * dx - sin_angle * dy), int(y_center - sin_angle * dx + cos_angle * dy)),
  204. (int(x_center - cos_angle * dx + sin_angle * dy), int(y_center - sin_angle * dx - cos_angle * dy)),
  205. (int(x_center + cos_angle * dx + sin_angle * dy), int(y_center + sin_angle * dx - cos_angle * dy)),
  206. ]
  207. return corners # 返回角点坐标
  208. def save_detections(image, detections, output_path):
  209. """
  210. 在图像上绘制旋转边界框检测结果并保存。
  211. :param image: 原始图像
  212. :param detections: 检测结果列表
  213. :param output_path: 保存路径
  214. """
  215. for det in detections:
  216. corners = det['position'] # 获取旋转边界框的四个角点
  217. confidence = det['confidence'] # 获取置信度
  218. class_id = det['class_id'] # 获取类别ID
  219. # 绘制边界框的四条边
  220. for j in range(4):
  221. pt1 = corners[j]
  222. pt2 = corners[(j + 1) % 4]
  223. cv2.line(image, pt1, pt2, (0, 0, 255), 2)
  224. # 在边界框上方显示类别和置信度
  225. cv2.putText(image, f'Class: {class_id}, Conf: {confidence:.2f}',
  226. (corners[0][0], corners[0][1] - 10), cv2.FONT_HERSHEY_SIMPLEX, 1.0, (255, 255, 255), 3)
  227. cv2.imwrite(output_path, image) # 保存绘制后的图像
  228. def process_images_in_folder(folder_path, model_weights, output_folder, conf_threshold, iou_threshold, imgsz):
  229. """
  230. 批量处理文件夹中的图像,执行推理、解析和可视化,保存结果。
  231. :param folder_path: 输入图像文件夹路径
  232. :param model_weights: ONNX模型权重文件路径
  233. :param output_folder: 输出结果文件夹路径
  234. :param conf_threshold: 置信度阈值
  235. :param iou_threshold: IoU 阈值,用于旋转NMS
  236. :param imgsz: 模型输入大小
  237. """
  238. session = load_model(weights=model_weights) # 加载ONNX模型
  239. if not os.path.exists(output_folder):
  240. os.makedirs(output_folder) # 如果输出文件夹不存在,则创建
  241. for filename in os.listdir(folder_path):
  242. if filename.endswith(('.jpg', '.png', '.jpeg')): # 处理图片文件
  243. image_path = os.path.join(folder_path, filename)
  244. with open(image_path, 'rb') as f:
  245. image_bytes = f.read()
  246. print("image_path:", image_path)
  247. raw_output, ratio, dwdh = run_inference(session=session, image_bytes=image_bytes, imgsz=imgsz) # 执行推理
  248. detections = parse_onnx_output(raw_output, ratio, dwdh, conf_threshold=conf_threshold, iou_threshold=iou_threshold) # 解析输出
  249. im0 = cv2.imdecode(np.frombuffer(image_bytes, np.uint8), cv2.IMREAD_COLOR) # 解码图像
  250. output_path = os.path.join(output_folder, filename)
  251. save_detections(im0, detections, output_path) # 保存检测结果
  252. # 主函数:加载参数
  253. if __name__ == "__main__":
  254. folder_path = r"point_offer_20240930_rgb" # 输入图像文件夹路径
  255. model_weights = r"YOLO11_obb_exp39_cpu.onnx" # ONNX模型路径
  256. output_folder = "results" # 输出结果文件夹
  257. conf_threshold = 0.5 # 置信度阈值
  258. iou_threshold = 0.5 # IoU阈值,用于旋转NMS
  259. imgsz = (640, 640) # 模型输入大小
  260. process_images_in_folder(folder_path, model_weights, output_folder, conf_threshold, iou_threshold, imgsz) # 执行批量处理

使用这个代码时,需要修改配置参数:

  • folder_path: 输入图像文件夹路径,指向包含待检测图像的目录。

  • model_weights: ONNX 模型文件路径,指向训练好的模型文件。

  • output_folder: 检测结果保存的文件夹路径,输出检测后的图片。

  • conf_threshold: 置信度阈值,用于过滤低置信度的检测框。建议调整以平衡检测精度,默认值为 0.5。

  • iou_threshold: IoU 阈值,用于旋转边界框的非极大值抑制(NMS),默认值为 0.5。较低值减少重叠,较高值保留更多边界框。

  • imgsz: 输入图像的尺寸,例如 (640, 640)。应与模型训练时的输入尺寸一致。

 YOLO11相关文章推荐:

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

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

一篇文章快速认识YOLO11 | 旋转目标检测 | 原理分析 | 模型训练 | 模型推理_yolov11 obb-CSDN博客

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

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

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

YOLO11 实例分割 | 导出ONNX模型 | ONNX模型推理-CSDN博客

YOLO11 目标检测 | 导出ONNX模型 | ONNX模型推理-CSDN博客

YOLO11 目标检测 | 自动标注 | 预标注 | 标签格式转换 | 手动校正标签_yolo11 标注平台-CSDN博客

YOLO11 图像缩放 | 图像填充 | 自适应不同尺寸的图片_yolov11 中图像预处理-CSDN博客

YOLO11 旋转目标检测 | 数据标注 | 自定义数据集 | 模型训练 | 模型推理-CSDN博客

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

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

/ 登录

评论记录:

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

分类栏目

后端 (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