YOLO11 旋转目标检测 | OBB定向检测 | ONNX模型推理 | 旋转NMS
本文分享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模型(简洁版)
from ultralytics import YOLO# 加载一个模型,路径为 YOLO 模型的 .pt 文件
model = YOLO("runs/obb/train1/weights/best.pt")# 导出模型,格式为 ONNX
model.export(format="onnx")
运行代码后,会在上面路径中生成best.onnx文件的
- 比如,填写的路径是:"runs/obb/train3/weights/best.pt"
- 那么在runs/obb/train3/weights/目录中,会生成与best.pt同名的onnx文件,即best.onnx
上面代码示例是简单版,如果需要更专业设置ONNX,用下面版本的
YOLO11导出ONNX模型(专业版)
from ultralytics import YOLO# 加载一个模型,路径为 YOLO 模型的 .pt 文件
model = YOLO(r"runs/obb/train1/weights/best.pt")# 导出模型,设置多种参数
model.export(format="onnx", # 导出格式为 ONNXimgsz=(640, 640), # 设置输入图像的尺寸keras=False, # 不导出为 Keras 格式optimize=False, # 不进行优化 False, 移动设备优化的参数,用于在导出为TorchScript 格式时进行模型优化half=False, # 不启用 FP16 量化int8=False, # 不启用 INT8 量化dynamic=False, # 不启用动态输入尺寸simplify=True, # 简化 ONNX 模型opset=None, # 使用最新的 opset 版本workspace=4.0, # 为 TensorRT 优化设置最大工作区大小(GiB)nms=False, # 不添加 NMS(非极大值抑制)batch=1, # 指定批处理大小device="cpu" # 指定导出设备为CPU或GPU,对应参数为"cpu" , "0"
)
对于model.export( )函数中,各种参数说明:
format="onnx"
:指定导出模型的格式为 onnx。imgsz=(640, 640)
:输入图像的尺寸设为 640x640。如果需要其他尺寸可以修改这个值。keras=False
:不导出为 Keras 格式的模型。optimize=False
:不应用 TorchScript 移动设备优化。half=False
:不启用 FP16(半精度)量化。int8=False
:不启用 INT8 量化。dynamic=False
:不启用动态输入尺寸。simplify=True
:简化模型以提升 ONNX 模型的性能。opset=None
:使用默认的 ONNX opset 版本,如果需要可以手动指定。workspace=4.0
:为 TensorRT 优化指定最大工作空间大小为 4 GiB。nms=False
:不为 CoreML 导出添加非极大值抑制(NMS)。batch=1
:设置批处理大小为 1。- device="cpu", 指定导出设备为CPU或GPU,对应参数为"cpu" , "0"
参考官网文档:https://docs.ultralytics.com/modes/export/#arguments
当然了,YOLO11中不仅支持ONNX模型,还支持下面表格中格式
支持的导出格式 | format 参数值 | 生成的模型示例 | model.export( )函数的参数 |
---|---|---|---|
PyTorch | - | yolo11n.pt | - |
TorchScript | torchscript | yolo11n.torchscript | imgsz , optimize , batch |
ONNX | onnx | yolo11n.onnx | imgsz , half , dynamic , simplify , opset , batch |
OpenVINO | openvino | yolo11n_openvino_model/ | imgsz , half , int8 , batch |
TensorRT | engine | yolo11n.engine | imgsz , half , dynamic , simplify , workspace , int8 , batch |
CoreML | coreml | yolo11n.mlpackage | imgsz , half , int8 , nms , batch |
TF SavedModel | saved_model | yolo11n_saved_model/ | imgsz , keras , int8 , batch |
TF GraphDef | pb | yolo11n.pb | imgsz , batch |
TF Lite | tflite | yolo11n.tflite | imgsz , half , int8 , batch |
TF Edge TPU | edgetpu | yolo11n_edgetpu.tflite | imgsz |
TF.js | tfjs | yolo11n_web_model/ | imgsz , half , int8 , batch |
PaddlePaddle | paddle | yolo11n_paddle_model/ | imgsz , batch |
NCNN | ncnn | yolo11n_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
提供了不同的选项,以适应不同的应用场景:比如是否需要按指定步幅填充,是否强制拉伸以填满,或者是否允许图像放大。
def letterbox(img, new_shape=(640, 640), color=(0, 0, 0), auto=False, scale_fill=False, scale_up=False, stride=32):"""将图像调整为指定尺寸,同时保持长宽比,添加填充以适应目标输入形状。"""# 获取图像的当前高度和宽度shape = img.shape[:2]# 如果 new_shape 是整数,则将其转换为 (new_shape, new_shape) 的格式if isinstance(new_shape, int):new_shape = (new_shape, new_shape)# 计算缩放比例 r,以便图像适配到 new_shape,保持长宽比r = min(new_shape[0] / shape[0], new_shape[1] / shape[1])# 若不允许放大(scale_up=False),限制 r 最大值为 1.0if not scale_up:r = min(r, 1.0)# 保存缩放比例 ratio,计算未填充的新尺寸 new_unpadratio = r, rnew_unpad = int(round(shape[1] * r)), int(round(shape[0] * r))# 计算目标尺寸与缩放后尺寸的宽度、高度差值 dw, dhdw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1]# 若 auto=True,则将 dw 和 dh 调整为 stride 的倍数if auto:dw, dh = np.mod(dw, stride), np.mod(dh, stride)# 若 scale_fill=True,则忽略比例,强制缩放到目标尺寸elif scale_fill:dw, dh = 0.0, 0.0new_unpad = (new_shape[1], new_shape[0])ratio = new_shape[1] / shape[1], new_shape[0] / shape[0]# 将填充值平分到图像四周dw /= 2 dh /= 2# 如果当前图像尺寸与目标尺寸不一致,则将图像缩放到 new_unpadif shape[::-1] != new_unpad:img = cv2.resize(img, new_unpad, interpolation=cv2.INTER_LINEAR)# 应用上下左右的填充以使图像符合目标尺寸top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1))left, right = int(round(dw - 0.1)), int(round(dw + 0.1))img = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color)# 返回填充后的图像、缩放比例和填充量return img, ratio, (dw, dh)
5、旋转边界框IoU计算 函数
这里编写batch_probiou 函数,用于旋转边界框IoU计算,其中使用到ProbIoU方法。
这是一个基于协方差矩阵的方法,用于评估旋转边界框之间的相似性。
def batch_probiou(obb1, obb2, eps=1e-7):"""计算旋转边界框之间的 ProbIoU。:param obb1: 第一个旋转边界框集合:param obb2: 第二个旋转边界框集合:param eps: 防止除零的极小值:return: 两个旋转边界框之间的 ProbIoU"""# 提取两个旋转边界框的中心坐标 (x, y)x1, y1 = obb1[..., 0], obb1[..., 1]x2, y2 = obb2[..., 0], obb2[..., 1]# 计算两个旋转边界框的协方差矩阵元素 a, b, ca1, b1, c1 = _get_covariance_matrix(obb1)a2, b2, c2 = _get_covariance_matrix(obb2)# 计算 ProbIoU 的三个部分 t1, t2, t3# t1 表示中心点位置差异的贡献t1 = ((a1[:, None] + a2) * (y1[:, None] - y2) ** 2 + (b1[:, None] + b2) * (x1[:, None] - x2) ** 2) / ((a1[:, None] + a2) * (b1[:, None] + b2) - (c1[:, None] + c2) ** 2 + eps) * 0.25# t2 表示旋转角度的耦合贡献t2 = ((c1[:, None] + c2) * (x2 - x1[:, None]) * (y1[:, None] - y2)) / ((a1[:, None] + a2) * (b1[:, None] + b2) - (c1[:, None] + c2) ** 2 + eps) * 0.5# t3 表示面积和形状之间的差异贡献t3 = np.log(((a1[:, None] + a2) * (b1[:, None] + b2) - (c1[:, None] + c2) ** 2) /(4 * np.sqrt((a1 * b1 - c1 ** 2)[:, None] * (a2 * b2 - c2 ** 2)) + eps) + eps) * 0.5# 计算 Bhattacharyya 距离 bdbd = np.clip(t1 + t2 + t3, eps, 100.0) # 将 bd 限制在 [eps, 100.0] 范围内# 计算 ProbIoU 值 hdhd = np.sqrt(1.0 - np.exp(-bd) + eps) # 使用 Bhattacharyya 距离计算 hdreturn 1 - hd # 返回 1 - hd,hd 越小表示相似度越高,1 - hd 即为 ProbIoU
ProbIoU的论文地址:https://arxiv.org/pdf/2106.06072v1.pdf
batch_probiou 函数需要用到协方差矩阵,这里也编写一个函数进行封装
def _get_covariance_matrix(obb):"""计算旋转边界框的协方差矩阵。:param obb: 旋转边界框 (Oriented Bounding Box),包含中心坐标、宽、高和旋转角度:return: 协方差矩阵的三个元素 a, b, c"""widths = obb[..., 2] / 2 # 获取宽度的一半heights = obb[..., 3] / 2 # 获取高度的一半angles = obb[..., 4] # 获取旋转角度cos_angle = np.cos(angles) # 计算旋转角度的余弦值sin_angle = np.sin(angles) # 计算旋转角度的正弦值# 计算协方差矩阵的三个元素 a, b, ca = (widths * cos_angle) ** 2 + (heights * sin_angle) ** 2b = (widths * sin_angle) ** 2 + (heights * cos_angle) ** 2c = widths * cos_angle * heights * sin_anglereturn a, b, c
总结:
- 该代码用于计算两个旋转边界框之间的 ProbIoU,以量化它们的相似程度。
- 核心思想是通过协方差矩阵来描述每个旋转边界框的形状和旋转角度,结合 Bhattacharyya 距离来评估边界框之间的相似性。
- ProbIoU 的计算考虑了旋转角度、边界框的中心位置以及大小差异,是比传统 IoU 更加复杂和准确的度量旋转边界框相似性的方法,特别适用于场景中有方向性的对象。
6、旋转NMS过滤重复框 函数
这里编写rotated_nms_with_probiou函数,实现了旋转边界框的非极大值抑制NMS。
- NMS 的目标是去除冗余的边界框,只保留最有代表性的那个,以减少重叠检测。
- 在这个实现中,通过 ProbIoU 来计算旋转边界框之间的相似度,用于确定哪些框需要保留。
- NMS 的核心在于选取当前得分最高的边界框,将它加入保留列表中,
- 然后与剩余边界框进行相似性计算,通过ProbIoU计算旋转边界框之间的相似度,最终剔除那些相似度高于阈值的边界框。
def rotated_nms_with_probiou(boxes, scores, iou_threshold=0.5):"""使用 ProbIoU 执行旋转边界框的非极大值抑制(NMS)。:param boxes: 旋转边界框的集合:param scores: 每个边界框的置信度得分:param iou_threshold: IoU 阈值,用于确定是否抑制框:return: 保留的边界框索引列表"""order = scores.argsort()[::-1] # 根据置信度得分降序排序keep = [] # 用于存储保留的边界框索引while len(order) > 0:i = order[0] # 选择当前得分最高的边界框keep.append(i) # 将该边界框的索引加入到保留列表中if len(order) == 1: # 如果只剩下一个边界框,跳出循环breakremaining_boxes = boxes[order[1:]] # 获取剩余的边界框iou_values = batch_probiou(boxes[i:i + 1], remaining_boxes).squeeze(0) # 计算当前框与剩余框之间的 ProbIoUmask = iou_values < iou_threshold # 找出与当前框 IoU 小于阈值的边界框order = order[1:][mask] # 更新剩余的边界框索引,只保留 IoU 小于阈值的部分return keep # 返回保留的边界框索引列表
7、解析ONNX模型输出 函数
这里编写parse_onnx_output函数,实现了对 ONNX 模型的输出进行解析
- 提取旋转边界框的坐标、旋转角度、置信度和类别信息,
- 应用旋转边界框的非极大值抑制(NMS),并计算旋转边界框的四个角点
这里我们需要知道:
ONNX输出格式: x_center, y_center, width, height, class1_confidence, ..., classN_confidence, angle
def parse_onnx_output(output, ratio, dwdh, conf_threshold=0.5, iou_threshold=0.5):"""解析ONNX模型的输出,提取旋转边界框坐标、置信度和类别信息,并应用旋转NMS。:param output: ONNX模型的输出,包含预测的边界框信息:param ratio: 缩放比例,用于将坐标还原到原始尺度:param dwdh: 填充的宽高,用于调整边界框的中心点坐标:param conf_threshold: 置信度阈值,过滤低于该阈值的检测框:param iou_threshold: IoU 阈值,用于旋转边界框的非极大值抑制(NMS):return: 符合条件的旋转边界框的检测结果"""boxes, scores, classes, detections = [], [], [], [] # 初始化存储检测结果的列表num_detections = output.shape[2] # 获取检测的边界框数量num_classes = output.shape[1] - 6 # 计算类别数量# 逐个解析每个检测结果for i in range(num_detections):detection = output[0, :, i] # 获取第 i 个检测结果x_center, y_center, width, height = detection[0], detection[1], detection[2], detection[3] # 提取边界框的中心坐标和宽高angle = detection[-1] # 提取旋转角度# 处理类别信息和置信度if num_classes > 0:class_confidences = detection[4:4 + num_classes] # 获取类别置信度if class_confidences.size == 0:continueclass_id = np.argmax(class_confidences) # 获取置信度最高的类别索引confidence = class_confidences[class_id] # 获取对应的置信度else:confidence = detection[4] # 如果没有类别信息,直接使用置信度值class_id = 0 # 默认类别为 0# 过滤低置信度的检测结果if confidence > conf_threshold:x_center = (x_center - dwdh[0]) / ratio[0] # 还原中心点 x 坐标y_center = (y_center - dwdh[1]) / ratio[1] # 还原中心点 y 坐标width /= ratio[0] # 还原宽度height /= ratio[1] # 还原高度boxes.append([x_center, y_center, width, height, angle]) # 将边界框信息加入列表scores.append(confidence) # 将置信度加入列表classes.append(class_id) # 将类别加入列表if not boxes: # 如果没有符合条件的边界框,返回空列表return []# 转换为 NumPy 数组boxes = np.array(boxes)scores = np.array(scores)classes = np.array(classes)# 应用旋转 NMSkeep_indices = rotated_nms_with_probiou(boxes, scores, iou_threshold=iou_threshold)# 构建最终检测结果for idx in keep_indices:x_center, y_center, width, height, angle = boxes[idx] # 获取保留的边界框信息confidence = scores[idx] # 获取对应的置信度class_id = classes[idx] # 获取类别obb_corners = calculate_obb_corners(x_center, y_center, width, height, angle) # 计算旋转边界框的四个角点# 将检测结果添加到列表中detections.append({"position": obb_corners, # 旋转边界框的角点坐标"confidence": float(confidence), # 置信度"class_id": int(class_id), # 类别 ID"angle": float(angle) # 旋转角度})return detections # 返回检测结果
parse_onnx_output函数需要用到calculate_obb_corners函数,根据旋转角度计算旋转边界框的四个角点
def calculate_obb_corners(x_center, y_center, width, height, angle):"""根据旋转角度计算旋转边界框的四个角点。:param x_center: 边界框中心的 x 坐标:param y_center: 边界框中心的 y 坐标:param width: 边界框的宽度:param height: 边界框的高度:param angle: 旋转角度:return: 旋转边界框的四个角点坐标"""cos_angle = np.cos(angle) # 计算旋转角度的余弦值sin_angle = np.sin(angle) # 计算旋转角度的正弦值dx = width / 2 # 计算宽度的一半dy = height / 2 # 计算高度的一半# 计算旋转边界框的四个角点坐标corners = [(int(x_center + cos_angle * dx - sin_angle * dy), int(y_center + sin_angle * dx + cos_angle * dy)),(int(x_center - cos_angle * dx - sin_angle * dy), int(y_center - sin_angle * dx + cos_angle * dy)),(int(x_center - cos_angle * dx + sin_angle * dy), int(y_center - sin_angle * dx - cos_angle * dy)),(int(x_center + cos_angle * dx + sin_angle * dy), int(y_center + sin_angle * dx - cos_angle * dy)),]return corners # 返回角点坐标
8、完整代码
完整代码,如下所示:
import os
import cv2
import numpy as np
import onnxruntime as ort
import logging"""
YOLO11 旋转目标检测OBB
1、ONNX模型推理、可视化
2、ONNX输出格式: x_center, y_center, width, height, class1_confidence, ..., classN_confidence, angle
3、支持不同尺寸图片输入、支持旋转NMS过滤重复框、支持ProbIoU旋转IOU计算
"""def letterbox(img, new_shape=(640, 640), color=(0, 0, 0), auto=False, scale_fill=False, scale_up=False, stride=32):"""将图像调整为指定尺寸,同时保持长宽比,添加填充以适应目标输入形状。:param img: 输入图像:param new_shape: 目标尺寸:param color: 填充颜色:param auto: 是否自动调整填充为步幅的整数倍:param scale_fill: 是否强制缩放以完全填充目标尺寸:param scale_up: 是否允许放大图像:param stride: 步幅,用于自动调整填充:return: 调整后的图像、缩放比例、填充尺寸(dw, dh)"""shape = img.shape[:2]if isinstance(new_shape, int):new_shape = (new_shape, new_shape)r = min(new_shape[0] / shape[0], new_shape[1] / shape[1]) # 计算缩放比例if not scale_up:r = min(r, 1.0)ratio = r, rnew_unpad = int(round(shape[1] * r)), int(round(shape[0] * r))dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1]if auto:dw, dh = np.mod(dw, stride), np.mod(dh, stride)elif scale_fill:dw, dh = 0.0, 0.0new_unpad = (new_shape[1], new_shape[0])ratio = new_shape[1] / shape[1], new_shape[0] / shape[0]dw /= 2 # 填充均分dh /= 2if shape[::-1] != new_unpad:img = cv2.resize(img, new_unpad, interpolation=cv2.INTER_LINEAR)top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1))left, right = int(round(dw - 0.1)), int(round(dw + 0.1))img = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color)return img, ratio, (dw, dh)def load_model(weights):"""加载ONNX模型并返回会话对象。:param weights: 模型权重文件路径:return: ONNX运行会话对象"""session = ort.InferenceSession(weights, providers=['CPUExecutionProvider'])logging.info(f"模型加载成功: {weights}")return sessiondef _get_covariance_matrix(obb):"""计算旋转边界框的协方差矩阵。:param obb: 旋转边界框 (Oriented Bounding Box),包含中心坐标、宽、高和旋转角度:return: 协方差矩阵的三个元素 a, b, c"""widths = obb[..., 2] / 2heights = obb[..., 3] / 2angles = obb[..., 4]cos_angle = np.cos(angles)sin_angle = np.sin(angles)a = (widths * cos_angle)**2 + (heights * sin_angle)**2b = (widths * sin_angle)**2 + (heights * cos_angle)**2c = widths * cos_angle * heights * sin_anglereturn a, b, cdef batch_probiou(obb1, obb2, eps=1e-7):"""计算旋转边界框之间的 ProbIoU。:param obb1: 第一个旋转边界框集合:param obb2: 第二个旋转边界框集合:param eps: 防止除零的极小值:return: 两个旋转边界框之间的 ProbIoU"""x1, y1 = obb1[..., 0], obb1[..., 1]x2, y2 = obb2[..., 0], obb2[..., 1]a1, b1, c1 = _get_covariance_matrix(obb1)a2, b2, c2 = _get_covariance_matrix(obb2)t1 = ((a1[:, None] + a2) * (y1[:, None] - y2)**2 + (b1[:, None] + b2) * (x1[:, None] - x2)**2) / ((a1[:, None] + a2) * (b1[:, None] + b2) - (c1[:, None] + c2)**2 + eps) * 0.25t2 = ((c1[:, None] + c2) * (x2 - x1[:, None]) * (y1[:, None] - y2)) / ((a1[:, None] + a2) * (b1[:, None] + b2) - (c1[:, None] + c2)**2 + eps) * 0.5t3 = np.log(((a1[:, None] + a2) * (b1[:, None] + b2) - (c1[:, None] + c2)**2) /(4 * np.sqrt((a1 * b1 - c1**2)[:, None] * (a2 * b2 - c2**2)) + eps) + eps) * 0.5bd = np.clip(t1 + t2 + t3, eps, 100.0)hd = np.sqrt(1.0 - np.exp(-bd) + eps)return 1 - hddef rotated_nms_with_probiou(boxes, scores, iou_threshold=0.5):"""使用 ProbIoU 执行旋转边界框的非极大值抑制(NMS)。:param boxes: 旋转边界框的集合:param scores: 每个边界框的置信度得分:param iou_threshold: IoU 阈值,用于确定是否抑制框:return: 保留的边界框索引列表"""order = scores.argsort()[::-1] # 根据置信度得分降序排序keep = []while len(order) > 0:i = order[0]keep.append(i)if len(order) == 1:breakremaining_boxes = boxes[order[1:]]iou_values = batch_probiou(boxes[i:i+1], remaining_boxes).squeeze(0)mask = iou_values < iou_threshold # 保留 IoU 小于阈值的框order = order[1:][mask]return keepdef run_inference(session, image_bytes, imgsz=(640, 640)):"""对输入图像进行预处理,然后使用ONNX模型执行推理。:param session: ONNX运行会话对象:param image_bytes: 输入图像的字节数据:param imgsz: 模型输入的尺寸:return: 推理结果、缩放比例、填充尺寸"""im0 = cv2.imdecode(np.frombuffer(image_bytes, np.uint8), cv2.IMREAD_COLOR) # 解码图像字节数据if im0 is None:raise ValueError("无法从image_bytes解码图像")img, ratio, (dw, dh) = letterbox(im0, new_shape=imgsz) # 调整图像尺寸img = img.transpose((2, 0, 1))[::-1] # 调整通道顺序img = np.ascontiguousarray(img)img = img[np.newaxis, ...].astype(np.float32) / 255.0 # 归一化处理input_name = session.get_inputs()[0].nameresult = session.run(None, {input_name: img}) # 执行模型推理return result[0], ratio, (dw, dh)def parse_onnx_output(output, ratio, dwdh, conf_threshold=0.5, iou_threshold=0.5):"""解析ONNX模型的输出,提取旋转边界框坐标、置信度和类别信息,并应用旋转NMS。:param output: ONNX模型的输出,包含预测的边界框信息:param ratio: 缩放比例,用于将坐标还原到原始尺度:param dwdh: 填充的宽高,用于调整边界框的中心点坐标:param conf_threshold: 置信度阈值,过滤低于该阈值的检测框:param iou_threshold: IoU 阈值,用于旋转边界框的非极大值抑制(NMS):return: 符合条件的旋转边界框的检测结果"""boxes, scores, classes, detections = [], [], [], []num_detections = output.shape[2] # 获取检测的边界框数量num_classes = output.shape[1] - 6 # 计算类别数量# 逐个解析每个检测结果for i in range(num_detections):detection = output[0, :, i]x_center, y_center, width, height = detection[0], detection[1], detection[2], detection[3] # 提取边界框的中心坐标和宽高angle = detection[-1] # 提取旋转角度if num_classes > 0:class_confidences = detection[4:4 + num_classes] # 获取类别置信度if class_confidences.size == 0:continueclass_id = np.argmax(class_confidences) # 获取置信度最高的类别索引confidence = class_confidences[class_id] # 获取对应的置信度else:confidence = detection[4] # 如果没有类别信息,直接使用置信度值class_id = 0 # 默认类别为 0if confidence > conf_threshold: # 过滤掉低置信度的检测结果x_center = (x_center - dwdh[0]) / ratio[0] # 还原中心点 x 坐标y_center = (y_center - dwdh[1]) / ratio[1] # 还原中心点 y 坐标width /= ratio[0] # 还原宽度height /= ratio[1] # 还原高度boxes.append([x_center, y_center, width, height, angle]) # 将边界框信息加入列表scores.append(confidence) # 将置信度加入列表classes.append(class_id) # 将类别加入列表if not boxes:return []# 转换为 NumPy 数组boxes = np.array(boxes)scores = np.array(scores)classes = np.array(classes)# 应用旋转 NMSkeep_indices = rotated_nms_with_probiou(boxes, scores, iou_threshold=iou_threshold)# 构建最终检测结果for idx in keep_indices:x_center, y_center, width, height, angle = boxes[idx] # 获取保留的边界框信息confidence = scores[idx] # 获取对应的置信度class_id = classes[idx] # 获取类别obb_corners = calculate_obb_corners(x_center, y_center, width, height, angle) # 计算旋转边界框的四个角点detections.append({"position": obb_corners, # 旋转边界框的角点坐标"confidence": float(confidence), # 置信度"class_id": int(class_id), # 类别 ID"angle": float(angle) # 旋转角度})return detectionsdef calculate_obb_corners(x_center, y_center, width, height, angle):"""根据旋转角度计算旋转边界框的四个角点。:param x_center: 边界框中心的 x 坐标:param y_center: 边界框中心的 y 坐标:param width: 边界框的宽度:param height: 边界框的高度:param angle: 旋转角度:return: 旋转边界框的四个角点坐标"""cos_angle = np.cos(angle) # 计算旋转角度的余弦值sin_angle = np.sin(angle) # 计算旋转角度的正弦值dx = width / 2 # 计算宽度的一半dy = height / 2 # 计算高度的一半# 计算旋转边界框的四个角点坐标corners = [(int(x_center + cos_angle * dx - sin_angle * dy), int(y_center + sin_angle * dx + cos_angle * dy)),(int(x_center - cos_angle * dx - sin_angle * dy), int(y_center - sin_angle * dx + cos_angle * dy)),(int(x_center - cos_angle * dx + sin_angle * dy), int(y_center - sin_angle * dx - cos_angle * dy)),(int(x_center + cos_angle * dx + sin_angle * dy), int(y_center + sin_angle * dx - cos_angle * dy)),]return corners # 返回角点坐标def save_detections(image, detections, output_path):"""在图像上绘制旋转边界框检测结果并保存。:param image: 原始图像:param detections: 检测结果列表:param output_path: 保存路径"""for det in detections:corners = det['position'] # 获取旋转边界框的四个角点confidence = det['confidence'] # 获取置信度class_id = det['class_id'] # 获取类别ID# 绘制边界框的四条边for j in range(4):pt1 = corners[j]pt2 = corners[(j + 1) % 4]cv2.line(image, pt1, pt2, (0, 0, 255), 2)# 在边界框上方显示类别和置信度cv2.putText(image, f'Class: {class_id}, Conf: {confidence:.2f}', (corners[0][0], corners[0][1] - 10), cv2.FONT_HERSHEY_SIMPLEX, 1.0, (255, 255, 255), 3)cv2.imwrite(output_path, image) # 保存绘制后的图像def process_images_in_folder(folder_path, model_weights, output_folder, conf_threshold, iou_threshold, imgsz):"""批量处理文件夹中的图像,执行推理、解析和可视化,保存结果。:param folder_path: 输入图像文件夹路径:param model_weights: ONNX模型权重文件路径:param output_folder: 输出结果文件夹路径:param conf_threshold: 置信度阈值:param iou_threshold: IoU 阈值,用于旋转NMS:param imgsz: 模型输入大小"""session = load_model(weights=model_weights) # 加载ONNX模型if not os.path.exists(output_folder):os.makedirs(output_folder) # 如果输出文件夹不存在,则创建for filename in os.listdir(folder_path):if filename.endswith(('.jpg', '.png', '.jpeg')): # 处理图片文件image_path = os.path.join(folder_path, filename)with open(image_path, 'rb') as f:image_bytes = f.read()print("image_path:", image_path)raw_output, ratio, dwdh = run_inference(session=session, image_bytes=image_bytes, imgsz=imgsz) # 执行推理detections = parse_onnx_output(raw_output, ratio, dwdh, conf_threshold=conf_threshold, iou_threshold=iou_threshold) # 解析输出im0 = cv2.imdecode(np.frombuffer(image_bytes, np.uint8), cv2.IMREAD_COLOR) # 解码图像output_path = os.path.join(output_folder, filename)save_detections(im0, detections, output_path) # 保存检测结果# 主函数:加载参数
if __name__ == "__main__":folder_path = r"point_offer_20240930_rgb" # 输入图像文件夹路径model_weights = r"YOLO11_obb_exp39_cpu.onnx" # ONNX模型路径output_folder = "results" # 输出结果文件夹conf_threshold = 0.5 # 置信度阈值iou_threshold = 0.5 # IoU阈值,用于旋转NMSimgsz = (640, 640) # 模型输入大小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博客
分享完成,欢迎大家多多点赞和收藏,谢谢~
相关文章:
YOLO11 旋转目标检测 | OBB定向检测 | ONNX模型推理 | 旋转NMS
本文分享YOLO11中,从xxx.pt权重文件转为.onnx文件,然后使用.onnx文件,进行旋转目标检测任务的模型推理。 用ONNX模型推理,便于算法到开发板或芯片的部署。 本文提供源代码,支持不同尺寸图片输入、支持旋转NMS过滤重复…...
PCL 点云拟合 拟合空间直线
目录 一、概述 1.1原理 1.2实现步骤 1.3应用场景 二、代码实现 2.1关键函数 2.1.1 设置RANSAC算法参数 2.1.2拟合直线模型 2.1.3 提取拟合直线内点 2.2完整代码 三、实现效果 PCL点云算法汇总及实战案例汇总的目录地址链接: PCL点云算法与项目实战案例汇总(长期更…...
我的创作纪念日-20241112-感谢困难
我的创作纪念日-20241112-感谢困难 一、机缘二、收获1、积累2、感谢困难 三、日常四、成就五、憧憬 一、机缘 我之前有一个自己的私人博客,但是后来发现CSDN的功能更强大,更专业,所以我就把自己博客内容转到CSDN上面来了。 二、收获 1、积累…...

苍穹外卖05-Redis相关知识点
目录 什么是Redis? redis中的一些常用指令 value的5种常用数据类型 各种数据类型的特点 Redis中数据操作的常用命令 字符串类型常用命令: 哈希类型常用命令 列表操作命令 集合操作命令 有序集合操作命令 通用命令 在java中操作Redis 环境…...

unity 玩家和炸弹切线计算方式
脚本挂在炸弹上! using System.Collections; using System.Collections.Generic; using UnityEngine;public class TargetDetaction : MonoBehaviour {private Transform PlayerTF;private Transform bomb;private float radius;private string Player "Play…...
【MySQL】MySQL中的函数之REGEXP_LIKE
在 MySQL 中,REGEXP_LIKE() 函数用于检查一个字符串是否与正则表达式匹配。不过需要注意的是,REGEXP_LIKE() 并不是所有版本的 MySQL 都支持的函数。这个函数是在 MySQL 8.0 版本中引入的。 基本语法 REGEXP_LIKE(str, pat [, match_type ])str: 要测试…...

跟着尚硅谷学vue2—进阶版4.0—Vuex1.0
5. Vuex 1. 理解 Vuex 1. 多组件共享数据-全局事件总线实现 红线是读,绿线是写 2. 多组件共享数据-vuex实现 vuex 不属于任何组件 3. 求和案例-纯vue版 核心代码 1.Count.vue <template><div><h1>当前求和为:{{ sum }}</h1&…...

深度学习服务器租赁AutoDL
省钱绝招 #AutoDL #GPU #租显卡...

excel常用技能
1.基础技能 1.1 下拉框设置 a. 选中需要设置的列或单元格,数据 ---》 数据验证 b.验证条件 ---> 序列(多个值逗号隔开) 1.2 进度条百分比显示设置 开始 ---> 条件格式 --->新建规则--->编辑规则 1.3 相对引用和绝对引用…...
Mac电脑中隐藏文件(即以 . 开头的文件/文件夹)的显示和隐藏的两种方法
方法一:使用电脑快捷键,步骤如下: 1、点击一下桌面,用来激活 Finder ; 2、同时按下 Command Shift 点,即 【Command Shift . 】; 3、 打开可能包含此类文件的文件夹,比如磁盘…...

【Linux】:进程信号(信号概念 信号处理 信号产生)
✨ 眼里有诗,自向远方 🌏 📃个人主页:island1314 🔥个人专栏:Linux—登神长阶 ⛺️ 欢迎关注:👍点赞 👂&#…...

Flink运行时架构以及核心概念
1.运行构架 1.提交作业后启动一个客户端进程,客户端解析参数(-d -t 等等),后进行封装由Actor通信系统提交,取消,更新任务给JobManager。 2.JobManager(进程)通信系统一个组件叫分发…...

用 Python 从零开始创建神经网络(五):损失函数(Loss Functions)计算网络误差
用损失函数(Loss Functions)计算网络误差 引言1. 分类交叉熵损失(Categorical Cross-Entropy Loss)2. 分类交叉熵损失类(The Categorical Cross-Entropy Loss Class)展示到目前为止的所有代码3. 准确率计算…...

[CKS] K8S RuntimeClass SetUp
最近准备花一周的时间准备CKS考试,在准备考试中发现有一个题目关于RuntimeClass创建和挂载的题目。 专栏其他文章: [CKS] Create/Read/Mount a Secret in K8S-CSDN博客[CKS] Audit Log Policy-CSDN博客 -[CKS] 利用falco进行容器日志捕捉和安全监控-CSDN博客[CKS…...

【Python爬虫实战】轻量级爬虫利器:DrissionPage之SessionPage与WebPage模块详解
🌈个人主页:易辰君-CSDN博客 🔥 系列专栏:https://blog.csdn.net/2401_86688088/category_12797772.html 目录 前言 一、SessionPage (一)SessionPage 模块的基本功能 (二)基本使…...

计算机网络-2.1物理层
文章目录 通信的基础概念信源、信宿、信号、信道码元、速率、波特带宽(Hz) 奈奎斯特采样定律和香农采样定律编码&解码,调制&解调常用的编码方法常用的调制方法 传输介质1. 导向型传输介质2. 非导向型传输介质物理层接口的特性 物理层…...

纯血鸿蒙系统 HarmonyOS NEXT自动化测试实践
1、测试框架选择 hdc:类似 android 系统的 adb 命令,提供设备信息查询,包管理,调试相关的命令ohos.UiTest:鸿蒙 sdk 的一部分,类似 android sdk 里的uiautomator,基于 Accessibility 服务&…...
C 语言标准库 - <errno.h>
目录 1.errno 变量 2.宏 1.errno 变量 errno.h 声明了一个 int 类型的 errno 变量,用来存储错误码(正整数)。 如果这个变量有非零值,表示已经执行的程序发生了错误。 #include <errno.h> #include <stdio.h> #in…...
Golang自带的测试库testing的使用
testing是golang自带的测试库。 testting规则: 在待测试功能所在文件的同级目录中创建一个以_test.go结尾的文件。 测试函数名必须是TestXxxx这个形式,而且Xxxx必须以大写字母开头,另外函数带有一个*testing.T类型的参数。 // 单元测试&am…...

29.电影院售票系统(基于springboot和vue的Java项目)
目录 1.系统的受众说明 2 论文背景 2.1 国内研究现状: 2.2 国外研究现状: 2.3 所用技术 3 系统需求分析 3.1 需求分析 3.2 可行性分析 3.2.1技术可行性分析 3.2.2市场可行性分析 3.2.3经济可…...
Vue解决开发环境 Ajax 跨域问题
一、前言 在使用 Vue 进行前后端分离开发时,前端通常运行在本地开发服务器(如 http://localhost:8080),而后端接口可能部署在其他域名或端口下(如 http://api.example.com:3000)。这时就可能出现 跨域&…...
【判断既约分数】2022-4-3
缘由既约分数,除了辗转相除法-编程语言-CSDN问答 void 判断既约分数() {int a 1, b 2020, aa b, y 2, gs 0;while (aa){while (a < b){while (y < a && y < aa)if (a%y 0 && aa%y 0)a, y 2;elsey;if (a < b)gs; else;a, y 2;…...
元器件基础学习笔记——结型场效应晶体管 (JFET)
场效应晶体管(Field Effect Transistor,FET)简称场效应管,是一种三端子半导体器件,它根据施加到其其中一个端子的电场来控制电流的流动。与双极结型晶体管 (BJT) 不同,场效应晶体管 …...

Spring Boot SSE流式输出+AI消息持久化升级实践:从粗暴到优雅的跃迁
在 AI 应用落地过程中,我们常常需要将用户和 AI 的对话以“完整上下文”的形式持久化到数据库中。但当 AI 回复非常长,甚至接近上万字时,传统的单条消息保存机制就会出问题。 在本篇文章中,我将深入讲解一次实际项目中对 对话持久…...
Windows 下搭建 Zephyr 开发环境
1. 系统要求 操作系统:Windows 10/11(64位)磁盘空间:至少 8GB 可用空间(Zephyr 及其工具链较大)权限:管理员权限(部分工具需要) 2. 安装必要工具 winget安装依赖工具&am…...

GIC700组件
GIC700包含了几个重要的组件,它们使用一个内部的GIC互联,用于在不同的组件之间使用AXI5-Stream接口进行路由。 1. Distributor(GICD) gicd是GIC700中所有组件之间的主要通信节点。它作为SPI的管理者以及维护LPI的cache,并且与其它chip上的GIC700组件进行通信。当支持GIC…...
GitHub 趋势日报 (2025年06月05日)
📊 由 TrendForge 系统生成 | 🌐 https://trendforge.devlive.org/ 🌐 本日报中的项目描述已自动翻译为中文 📈 今日获星趋势图 今日获星趋势图 1472 onlook 991 HowToCook 752 ChinaTextbook 649 quarkdown 451 scrapy 324 age…...
Kafka 快速上手:安装部署与 HelloWorld 实践(一)
一、Kafka 是什么?为什么要学? ** 在大数据和分布式系统的领域中,Kafka 是一个如雷贯耳的名字。Kafka 是一种分布式的、基于发布 / 订阅的消息系统,由 LinkedIn 公司开发,后成为 Apache 基金会的顶级开源项目 。它以…...

Ubuntu20.04基础配置安装——系统安装(一)
引言: 工作需要,Ubuntu的各类环境配置,从23年开始使用Ubuntu20.04之后,尽管能力在不断提升,但是依旧会遇到Ubuntu系统崩掉的情况,为了方便后续系统出现问题及时替换,减少从网上搜索资源进行基础…...

基于JWT+SpringSecurity整合一个单点认证授权机制
基于 JWT Spring Security 的授权认证机制,在整体架构设计上体现了高度的安全性与灵活性。其在整合框架中的应用,充分展示了模块化、可扩展性和高效鉴权的设计理念,为开发者提供了一种值得借鉴的安全架构模式。 1.SpringSecurity概念理解 …...