File size: 7,829 Bytes
1edc459
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# YOLO11 PT 转 ONNX 教程

## 一、使用命令行导出

```bash
yolo export model=path/to/model.pt format=onnx imgsz=640 half=False simplify=True
```

常用参数:
- `format=onnx`: 导出为 ONNX 格式
- `imgsz=640`: 输入图像尺寸,需与训练时一致
- `half=False`: FP32 精度;设为 True 为 FP16(需设备支持)
- `simplify=True`: 简化 ONNX 图结构,减小模型体积
- `dynamic=True`: 支持动态 batch 和输入尺寸
- `nms=True`: 将 NMS 内置到模型中
- `device=0`: 指定 GPU 设备号

## 二、使用 Python API 导出

```python
from ultralytics import YOLO

model = YOLO("path/to/model.pt")
model.export(
    format="onnx",
    imgsz=640,
    half=False,
    simplify=True,
    device=0,
    verbose=True
)
```

导出后的 ONNX 文件默认保存在与 PT 文件同级目录下,文件名为 `model.onnx`。

## 三、使用 ONNXRuntime 加载推理

```python
import cv2
import numpy as np
import onnxruntime as ort

sess = ort.InferenceSession("model.onnx", providers=["CUDAExecutionProvider"])
input_name = sess.get_inputs()[0].name
output_name = sess.get_outputs()[0].name

# 预处理
img = cv2.imread("image.jpg")
img = cv2.resize(img, (640, 640))
img = img.astype(np.float32) / 255.0
img = np.transpose(img, (2, 0, 1))
input_tensor = np.expand_dims(img, axis=0)

# 推理
outputs = sess.run([output_name], {input_name: input_tensor})
```


# FQA
## Q1: export_to_onnx 中 imgsz 参数设置为640,意味着推理的时候,输入的图片必须要是640x640大小的吗?

答案:不是必须的。
ONNX模型导出时,imgsz=640 默认会固定输入形状为 1, 3, 640, 640。推理时需要 resize + pad 到 640x640。
但如果导出时加 dynamic=True,可以支持动态尺寸输入。
我需要解释清楚这两点,简洁回答。
不是。分两种情况:
1. 默认导出(静态 shape):imgsz=640
- ONNX 输入被固定为 [1, 3, 640, 640]
- 推理时 必须 把图片处理成 640×640
- 但原始图片可以是任意尺寸,通过 resize + 等比填充 到 640×640(我代码里已经实现了)
2. 动态 shape 导出:加 dynamic=True
model.export(format="onnx", imgsz=640, dynamic=True)
- ONNX 输入为 [1, 3, ?, ?]
- 推理时可以用任意尺寸输入(如 320×320、800×800)
- 灵活性更高,但推理速度可能略慢
总结:你导出时写的 imgsz=640 只是指定训练/导出的参考尺寸,原始图片不需要是 640×640,推理前会 resize 处理。


## Q2: 默认参数设置

以下是 yolo export 各参数的默认值(来自 default.yaml):

|参数 |	默认值	| 说明 |
|--- | --- | ---|
|save name	| 自动	| {原文件名}.{格式后缀},如 yolo11n.pt → yolo11n.onnx|
|nms	| False	| 不把 NMS 内置到模型,需自己后处理|
|simplify	| True	| 默认启用 ONNX 图简化|
|dynamic	| False	| 默认固定输入 shape|
|half	| False	| 默认 FP32 精度|
|batch	| 1	| 导出时固定为 batch=1(与训练默认的16不同)|
|device	| None	| 自动选择(优先 GPU)
|verbose	| False	| 导出时静默输出(与训练默认的True不同)|
|int8	| False	| 不启用 INT8 量化|


所以你这行命令:
yolo export model=page-nano-best-epo50.pt format=onnx imgsz=640 half=False simplify=True
等价于:
- 保存为 page-nano-best-epo50.onnx
- FP32,固定 shape [1,3,640,640]
- 不带 NMS(需要自己后处理)
- 简化 ONNX 图
- 静默输出


## Q3: 建议使用dynamic, nms 参数吗?

dynamic=True:
- 优点:推理时可以用任意尺寸,灵活性高
- 缺点:某些部署环境不支持,推理可能稍慢,某些优化器无法优化
nms=True:
- 优点:模型输出直接就是过滤后的结果,推理代码简单
- 缺点:NMS 参数固化在模型中,不方便调整;某些后处理流程可能更复杂
需要根据用户是用于 ONNXRuntime 推理的场景来分析。
分场景:
dynamic=True:
- ✅ 建议开:你需要同一模型处理不同尺寸的图片
- ❌ 不建议:
  - 固定尺寸推理(如全 640×640)
  - 部署到某些不支持动态 shape 的环境(TensorRT、部分嵌入式)
nms=True:
- ✅ 建议开:
  - 想让推理代码更简单(模型直接输出最终结果)
  - 固定 conf/iou 阈值即可
- ❌ 不建议:
  - 需要频繁调 conf/iou(改了要重新 export)
  - 需要自定义后处理逻辑(如按类别不同阈值)
你当前场景(ONNXRuntime + 自己写后处理代码):
dynamic=False  # 固定 640x640 性能更好
nms=False      # 你已经自己实现了后处理+NMS
如果未来想简化推理代码,可以重新 export:
yolo export model=xxx.pt format=onnx nms=True conf=0.25 iou=0.45
输出直接是 N x 7 的 [x1,y1,x2,y2,conf,cls_id],省去后处理。

## Q4: 如何知道目前模型的iou, conf?

这两个参数不在模型里,是你在推理时自己设定的。模型权重里只存网络参数,不存 conf/iou。
但你可以查模型训练时用的默认验证参数:
from ultralytics import YOLO
model = YOLO("models/page-nano-best-epo50.pt")
print(model.model.args)
看 conf 和 iou(或 nms.iou_thres)字段,这是训练验证时用的参考值。
实际建议:
- 用 yolo val 看不同阈值下的 mAP,选平衡点
- 检测任务常用:conf=0.25, iou=0.45(Ultralytics 默认)
- 如果你 export 时用了 nms=True,需要显式指定

## Q5: mAP50-95 偏低是什么情况?

## mAP50-95 < 0.5 的效果判断

**不能一概而论**,需要结合任务难度和模型规模来看。以下是通用参考:

---

## 不同场景下的参考标准

### 通用目标检测(COCO 80类,复杂场景)

| mAP50-95 | 水平 | 代表模型 |
|----------|------|---------|
| > 0.55 | 优秀 | YOLOv8x、YOLOv10x |
| 0.45 ~ 0.55 | 良好 | YOLOv8m、YOLOv8l |
| 0.35 ~ 0.45 | 中等 | YOLOv8s、YOLOv8n |
| < 0.35 | 较差 | 小模型/训练不足 |

> 在 COCO 这种**80类、复杂场景**的基准上,0.5 已经是不错的水平了,YOLOv8s 也才 0.44 左右。

### 简单/少类别任务(工业质检、特定场景)

| mAP50-95 | 水平 |
|----------|------|
| > 0.8 | 优秀,可上线 |
| 0.6 ~ 0.8 | 良好,基本可用 |
| 0.4 ~ 0.6 | 中等,需要优化 |
| < 0.4 | 较差,不建议上线 |

> 类别少、场景简单时,0.5 只能算**及格线**,通常还有较大提升空间。

---

## 0.5 以下意味着什么

1. **定位精度不足**:mAP50-95 对框的位置很敏感,低于 0.5 说明很多框"检测到了但框得不准"
2. **可能存在的问题**:
   - 标注框质量差(标注本身就不准)
   - 小目标多(小目标 IoU 天然低)
   - 训练不充分(epoch 不够、数据量少)
   - 模型太小(容量不足)
   - 锚框不匹配

---

## 怎么判断能不能用

看你的**业务需求**:

- **只需要"有没有"**(如安防告警、粗略计数):看 mAP50,如果 mAP50 高(>0.8),即使 mAP50-95 低也能用
- **需要精确位置**(如医疗影像、工业测量、自动驾驶):mAP50-95 < 0.5 **不够用**,需要继续优化
- **需要区分密集目标**:mAP50-95 低可能导致框偏移,重叠目标分不清

---

## 提升 mAP50-95 的方向

1. **提高标注质量**:检查标注框是否紧密贴合目标
2. **增加训练轮次**:通常 300 epoch 比 100 epoch 定位更准
3. **用更大的模型**:m/l/x 版本定位精度更高
4. **数据增强**:Mosaic、MixUp 有助于定位
5. **调整锚框**:用 `autoanchor=True` 自动计算
6. **多尺度训练**:`imgsz` 设大一些(如 1280)

**总结**:如果是 COCO 级别的复杂任务,0.5 以下属正常中等水平;如果是简单少类别任务,0.5 以下说明还有明显优化空间。关键看你的 mAP50 是多少——如果 mAP50 高但 mAP50-95 低,问题主要在**定位精度**。