EasyMetrics 0.3.0__py3-none-any.whl → 0.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- easyMetrics/tasks/detection/map.py +221 -53
- easymetrics-0.4.0.dist-info/METADATA +561 -0
- {easymetrics-0.3.0.dist-info → easymetrics-0.4.0.dist-info}/RECORD +6 -6
- easymetrics-0.3.0.dist-info/METADATA +0 -136
- {easymetrics-0.3.0.dist-info → easymetrics-0.4.0.dist-info}/WHEEL +0 -0
- {easymetrics-0.3.0.dist-info → easymetrics-0.4.0.dist-info}/licenses/LICENSE +0 -0
- {easymetrics-0.3.0.dist-info → easymetrics-0.4.0.dist-info}/top_level.txt +0 -0
|
@@ -139,9 +139,78 @@ class MeanAveragePrecision(Metric):
|
|
|
139
139
|
# 1. 识别所有唯一类别
|
|
140
140
|
unique_classes = set()
|
|
141
141
|
for t in self.targets:
|
|
142
|
-
|
|
142
|
+
labels = t['labels']
|
|
143
|
+
# 处理不同类型的 labels 输入
|
|
144
|
+
try:
|
|
145
|
+
if isinstance(labels, list):
|
|
146
|
+
# 处理列表情况
|
|
147
|
+
for label in labels:
|
|
148
|
+
if isinstance(label, list):
|
|
149
|
+
# 处理嵌套列表情况
|
|
150
|
+
for l in label:
|
|
151
|
+
unique_classes.add(l)
|
|
152
|
+
else:
|
|
153
|
+
unique_classes.add(label)
|
|
154
|
+
else:
|
|
155
|
+
try:
|
|
156
|
+
# 假设是 numpy 数组
|
|
157
|
+
label_list = labels.tolist()
|
|
158
|
+
if isinstance(label_list, list):
|
|
159
|
+
for label in label_list:
|
|
160
|
+
if isinstance(label, list):
|
|
161
|
+
# 处理嵌套列表情况
|
|
162
|
+
for l in label:
|
|
163
|
+
unique_classes.add(l)
|
|
164
|
+
else:
|
|
165
|
+
unique_classes.add(label)
|
|
166
|
+
else:
|
|
167
|
+
# 处理标量情况
|
|
168
|
+
unique_classes.add(label_list)
|
|
169
|
+
except AttributeError:
|
|
170
|
+
# 处理标量情况
|
|
171
|
+
unique_classes.add(labels)
|
|
172
|
+
except Exception as e:
|
|
173
|
+
# 捕获所有异常,确保程序不会崩溃
|
|
174
|
+
print(f"处理 labels 时出错: {e}")
|
|
175
|
+
print(f"labels 类型: {type(labels)}")
|
|
176
|
+
print(f"labels 值: {labels}")
|
|
177
|
+
|
|
143
178
|
for p in self.preds:
|
|
144
|
-
|
|
179
|
+
labels = p['labels']
|
|
180
|
+
# 处理不同类型的 labels 输入
|
|
181
|
+
try:
|
|
182
|
+
if isinstance(labels, list):
|
|
183
|
+
# 处理列表情况
|
|
184
|
+
for label in labels:
|
|
185
|
+
if isinstance(label, list):
|
|
186
|
+
# 处理嵌套列表情况
|
|
187
|
+
for l in label:
|
|
188
|
+
unique_classes.add(l)
|
|
189
|
+
else:
|
|
190
|
+
unique_classes.add(label)
|
|
191
|
+
else:
|
|
192
|
+
try:
|
|
193
|
+
# 假设是 numpy 数组
|
|
194
|
+
label_list = labels.tolist()
|
|
195
|
+
if isinstance(label_list, list):
|
|
196
|
+
for label in label_list:
|
|
197
|
+
if isinstance(label, list):
|
|
198
|
+
# 处理嵌套列表情况
|
|
199
|
+
for l in label:
|
|
200
|
+
unique_classes.add(l)
|
|
201
|
+
else:
|
|
202
|
+
unique_classes.add(label)
|
|
203
|
+
else:
|
|
204
|
+
# 处理标量情况
|
|
205
|
+
unique_classes.add(label_list)
|
|
206
|
+
except AttributeError:
|
|
207
|
+
# 处理标量情况
|
|
208
|
+
unique_classes.add(labels)
|
|
209
|
+
except Exception as e:
|
|
210
|
+
# 捕获所有异常,确保程序不会崩溃
|
|
211
|
+
print(f"处理 labels 时出错: {e}")
|
|
212
|
+
print(f"labels 类型: {type(labels)}")
|
|
213
|
+
print(f"labels 值: {labels}")
|
|
145
214
|
|
|
146
215
|
sorted_classes = sorted(list(unique_classes))
|
|
147
216
|
|
|
@@ -291,71 +360,170 @@ class MeanAveragePrecision(Metric):
|
|
|
291
360
|
|
|
292
361
|
# 收集 GT
|
|
293
362
|
for img_idx, target in enumerate(self.targets):
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
363
|
+
try:
|
|
364
|
+
labels = target['labels']
|
|
365
|
+
boxes = target['boxes']
|
|
366
|
+
|
|
367
|
+
# 处理 labels 是标量的情况
|
|
368
|
+
if np.isscalar(labels):
|
|
369
|
+
if labels == cls_id:
|
|
370
|
+
# 如果是标量且匹配,使用所有 boxes
|
|
371
|
+
mask = True
|
|
372
|
+
else:
|
|
373
|
+
# 如果是标量且不匹配,使用空 mask
|
|
374
|
+
mask = False
|
|
375
|
+
else:
|
|
376
|
+
# 处理 labels 是二维数组的情况
|
|
377
|
+
if labels.ndim == 2:
|
|
378
|
+
# 展平二维数组为一维
|
|
379
|
+
labels = labels.flatten()
|
|
380
|
+
# 正常情况,labels 是一维数组
|
|
381
|
+
mask = labels == cls_id
|
|
382
|
+
|
|
383
|
+
# 应用 mask
|
|
384
|
+
if isinstance(mask, bool):
|
|
385
|
+
if mask:
|
|
386
|
+
# 如果是 True,使用所有 boxes
|
|
387
|
+
filtered_boxes = boxes
|
|
388
|
+
else:
|
|
389
|
+
# 如果是 False,使用空 boxes
|
|
390
|
+
filtered_boxes = np.array([])
|
|
391
|
+
else:
|
|
392
|
+
# 正常情况,mask 是数组
|
|
393
|
+
filtered_boxes = boxes[mask]
|
|
394
|
+
|
|
395
|
+
# 检查 filtered_boxes 是否为空
|
|
396
|
+
if len(filtered_boxes) == 0:
|
|
397
|
+
n_pos += 0
|
|
398
|
+
class_gt[img_idx] = {
|
|
399
|
+
'boxes': np.array([]),
|
|
400
|
+
'used': np.array([], dtype=bool)
|
|
401
|
+
}
|
|
402
|
+
continue
|
|
403
|
+
|
|
404
|
+
# 确保 filtered_boxes 是二维数组
|
|
405
|
+
if filtered_boxes.ndim == 1:
|
|
406
|
+
if len(filtered_boxes) == 4:
|
|
407
|
+
# 如果是一维数组且长度为 4,说明是单个边界框
|
|
408
|
+
filtered_boxes = filtered_boxes.reshape(1, 4)
|
|
409
|
+
else:
|
|
410
|
+
# 如果是一维数组且长度不为 4,说明是空的或者格式不对,跳过
|
|
411
|
+
n_pos += 0
|
|
412
|
+
class_gt[img_idx] = {
|
|
413
|
+
'boxes': np.array([]),
|
|
414
|
+
'used': np.array([], dtype=bool)
|
|
415
|
+
}
|
|
416
|
+
continue
|
|
417
|
+
|
|
418
|
+
areas = (filtered_boxes[:, 2] - filtered_boxes[:, 0]) * (filtered_boxes[:, 3] - filtered_boxes[:, 1])
|
|
419
|
+
valid_area_mask = (areas >= min_area) & (areas < max_area)
|
|
420
|
+
valid_boxes = filtered_boxes[valid_area_mask]
|
|
421
|
+
|
|
422
|
+
n_pos += len(valid_boxes)
|
|
300
423
|
class_gt[img_idx] = {
|
|
301
|
-
'boxes':
|
|
302
|
-
'used': np.
|
|
424
|
+
'boxes': valid_boxes,
|
|
425
|
+
'used': np.zeros(len(valid_boxes), dtype=bool)
|
|
303
426
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
427
|
+
except Exception as e:
|
|
428
|
+
# 捕获所有异常,确保程序不会崩溃
|
|
429
|
+
print(f"处理 GT 数据时出错: {e}")
|
|
430
|
+
print(f"img_idx: {img_idx}")
|
|
431
|
+
print(f"labels 类型: {type(labels)}")
|
|
432
|
+
print(f"labels 值: {labels}")
|
|
433
|
+
print(f"boxes 类型: {type(boxes)}")
|
|
434
|
+
print(f"boxes 值: {boxes}")
|
|
435
|
+
# 使用空数据继续
|
|
309
436
|
n_pos += 0
|
|
310
437
|
class_gt[img_idx] = {
|
|
311
438
|
'boxes': np.array([]),
|
|
312
439
|
'used': np.array([], dtype=bool)
|
|
313
440
|
}
|
|
314
441
|
continue
|
|
315
|
-
|
|
316
|
-
areas = (boxes[:, 2] - boxes[:, 0]) * (boxes[:, 3] - boxes[:, 1])
|
|
317
|
-
valid_area_mask = (areas >= min_area) & (areas < max_area)
|
|
318
|
-
valid_boxes = boxes[valid_area_mask]
|
|
319
|
-
|
|
320
|
-
n_pos += len(valid_boxes)
|
|
321
|
-
class_gt[img_idx] = {
|
|
322
|
-
'boxes': valid_boxes,
|
|
323
|
-
'used': np.zeros(len(valid_boxes), dtype=bool)
|
|
324
|
-
}
|
|
325
442
|
|
|
326
443
|
# 收集预测值
|
|
327
444
|
limit_dets = max(self.max_detection_thresholds) if self.max_detection_thresholds else 100
|
|
328
445
|
|
|
329
446
|
for img_idx, pred in enumerate(self.preds):
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
447
|
+
try:
|
|
448
|
+
labels = pred['labels']
|
|
449
|
+
scores = pred['scores']
|
|
450
|
+
boxes = pred['boxes']
|
|
451
|
+
|
|
452
|
+
# 处理 labels 是标量的情况
|
|
453
|
+
if np.isscalar(labels):
|
|
454
|
+
if labels == cls_id:
|
|
455
|
+
# 如果是标量且匹配,使用所有 scores 和 boxes
|
|
456
|
+
mask = True
|
|
457
|
+
else:
|
|
458
|
+
# 如果是标量且不匹配,使用空 mask
|
|
459
|
+
mask = False
|
|
460
|
+
else:
|
|
461
|
+
# 处理 labels 是二维数组的情况
|
|
462
|
+
if labels.ndim == 2:
|
|
463
|
+
# 展平二维数组为一维
|
|
464
|
+
labels = labels.flatten()
|
|
465
|
+
# 正常情况,labels 是一维数组
|
|
466
|
+
mask = labels == cls_id
|
|
467
|
+
|
|
468
|
+
# 应用 mask
|
|
469
|
+
if isinstance(mask, bool):
|
|
470
|
+
if mask:
|
|
471
|
+
# 如果是 True,使用所有 scores 和 boxes
|
|
472
|
+
filtered_scores = scores
|
|
473
|
+
filtered_boxes = boxes
|
|
474
|
+
else:
|
|
475
|
+
# 如果是 False,使用空 scores 和 boxes
|
|
476
|
+
filtered_scores = np.array([])
|
|
477
|
+
filtered_boxes = np.array([])
|
|
478
|
+
else:
|
|
479
|
+
# 正常情况,mask 是数组
|
|
480
|
+
filtered_scores = scores[mask]
|
|
481
|
+
filtered_boxes = boxes[mask]
|
|
482
|
+
|
|
483
|
+
# 检查 filtered_boxes 是否为空
|
|
484
|
+
if len(filtered_boxes) == 0:
|
|
485
|
+
continue
|
|
486
|
+
|
|
487
|
+
# 确保 filtered_boxes 是二维数组
|
|
488
|
+
if filtered_boxes.ndim == 1:
|
|
489
|
+
if len(filtered_boxes) == 4:
|
|
490
|
+
# 如果是一维数组且长度为 4,说明是单个边界框
|
|
491
|
+
filtered_boxes = filtered_boxes.reshape(1, 4)
|
|
492
|
+
# 确保 filtered_scores 也是一维数组
|
|
493
|
+
if np.isscalar(filtered_scores):
|
|
494
|
+
filtered_scores = np.array([filtered_scores])
|
|
495
|
+
else:
|
|
496
|
+
# 如果是一维数组且长度不为 4,说明是空的或者格式不对,跳过
|
|
497
|
+
continue
|
|
498
|
+
|
|
499
|
+
areas = (filtered_boxes[:, 2] - filtered_boxes[:, 0]) * (filtered_boxes[:, 3] - filtered_boxes[:, 1])
|
|
500
|
+
valid_area_mask = (areas >= min_area) & (areas < max_area)
|
|
501
|
+
|
|
502
|
+
filtered_scores = filtered_scores[valid_area_mask]
|
|
503
|
+
filtered_boxes = filtered_boxes[valid_area_mask]
|
|
504
|
+
|
|
505
|
+
if len(filtered_scores) > 0:
|
|
506
|
+
order = np.argsort(-filtered_scores)
|
|
507
|
+
sorted_scores = filtered_scores[order][:limit_dets]
|
|
508
|
+
sorted_boxes = filtered_boxes[order][:limit_dets]
|
|
509
|
+
ranks = np.arange(len(sorted_scores))
|
|
510
|
+
else:
|
|
511
|
+
sorted_scores, sorted_boxes, ranks = [], [], []
|
|
512
|
+
|
|
513
|
+
for s, b, r in zip(sorted_scores, sorted_boxes, ranks):
|
|
514
|
+
class_preds.append((s, img_idx, b, r))
|
|
515
|
+
except Exception as e:
|
|
516
|
+
# 捕获所有异常,确保程序不会崩溃
|
|
517
|
+
print(f"处理预测值时出错: {e}")
|
|
518
|
+
print(f"img_idx: {img_idx}")
|
|
519
|
+
print(f"labels 类型: {type(labels)}")
|
|
520
|
+
print(f"labels 值: {labels}")
|
|
521
|
+
print(f"scores 类型: {type(scores)}")
|
|
522
|
+
print(f"scores 值: {scores}")
|
|
523
|
+
print(f"boxes 类型: {type(boxes)}")
|
|
524
|
+
print(f"boxes 值: {boxes}")
|
|
525
|
+
# 跳过这个预测,继续处理下一个
|
|
341
526
|
continue
|
|
342
|
-
|
|
343
|
-
areas = (boxes[:, 2] - boxes[:, 0]) * (boxes[:, 3] - boxes[:, 1])
|
|
344
|
-
valid_area_mask = (areas >= min_area) & (areas < max_area)
|
|
345
|
-
|
|
346
|
-
scores = scores[valid_area_mask]
|
|
347
|
-
boxes = boxes[valid_area_mask]
|
|
348
|
-
|
|
349
|
-
if len(scores) > 0:
|
|
350
|
-
order = np.argsort(-scores)
|
|
351
|
-
scores = scores[order][:limit_dets]
|
|
352
|
-
boxes = boxes[order][:limit_dets]
|
|
353
|
-
ranks = np.arange(len(scores))
|
|
354
|
-
else:
|
|
355
|
-
scores, boxes, ranks = [], [], []
|
|
356
|
-
|
|
357
|
-
for s, b, r in zip(scores, boxes, ranks):
|
|
358
|
-
class_preds.append((s, img_idx, b, r))
|
|
359
527
|
|
|
360
528
|
# 全局按分数排序
|
|
361
529
|
class_preds.sort(key=lambda x: x[0], reverse=True)
|
|
@@ -0,0 +1,561 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: EasyMetrics
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: 轻量级、零依赖的机器学习指标评估平台
|
|
5
|
+
Author-email: EasyMetrics Team <team@easymetrics.example>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 EasyMetrics Team
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: homepage, https://github.com/easymetrics/easymetrics
|
|
29
|
+
Project-URL: documentation, https://easymetrics.readthedocs.io
|
|
30
|
+
Project-URL: repository, https://github.com/easymetrics/easymetrics
|
|
31
|
+
Project-URL: issues, https://github.com/easymetrics/easymetrics/issues
|
|
32
|
+
Classifier: Programming Language :: Python :: 3
|
|
33
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
34
|
+
Classifier: Operating System :: OS Independent
|
|
35
|
+
Classifier: Intended Audience :: Developers
|
|
36
|
+
Classifier: Intended Audience :: Science/Research
|
|
37
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
38
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
39
|
+
Requires-Python: >=3.7
|
|
40
|
+
Description-Content-Type: text/markdown
|
|
41
|
+
License-File: LICENSE
|
|
42
|
+
Requires-Dist: numpy
|
|
43
|
+
Provides-Extra: progress
|
|
44
|
+
Requires-Dist: tqdm; extra == "progress"
|
|
45
|
+
Dynamic: license-file
|
|
46
|
+
|
|
47
|
+
# EasyMetrics
|
|
48
|
+
|
|
49
|
+
一个轻量级、零依赖的机器学习指标评估平台,专注于提供简单易用且准确的模型评估工具。
|
|
50
|
+
|
|
51
|
+
## ✨ 核心特性
|
|
52
|
+
|
|
53
|
+
### 🔧 设计理念
|
|
54
|
+
- **零依赖**: 仅依赖 Python 和 numpy,无需安装大型深度学习框架
|
|
55
|
+
- **模块化架构**: 清晰的代码结构,易于扩展和定制
|
|
56
|
+
- **用户友好**: 简洁的 API 设计,一行代码完成评估
|
|
57
|
+
- **性能优化**: 支持并行计算,大幅提升评估速度
|
|
58
|
+
|
|
59
|
+
### 📊 功能特性
|
|
60
|
+
|
|
61
|
+
#### 目标检测评估
|
|
62
|
+
- **完整的 COCO 指标支持**:
|
|
63
|
+
- mAP (IoU 0.5:0.95) - 综合评估指标
|
|
64
|
+
- mAP_50 (IoU 0.5) - 宽松条件下的性能
|
|
65
|
+
- mAP_75 (IoU 0.75) - 严格条件下的性能
|
|
66
|
+
- mAP_s/m/l - 不同尺度目标的性能
|
|
67
|
+
- AR_1, AR_10, AR_100 - 不同最大检测数下的召回率
|
|
68
|
+
- **每类别独立评估**: 详细的类别级指标
|
|
69
|
+
- **独家功能**: 自动计算满足特定精度要求的最佳置信度阈值
|
|
70
|
+
|
|
71
|
+
#### 数据格式支持
|
|
72
|
+
- **多种输入格式**: 支持 COCO、VOC、YOLO 等常见格式
|
|
73
|
+
- **自动类型转换**: 支持标量、列表、numpy 数组等多种输入类型
|
|
74
|
+
- **灵活的格式指定**: 可分别指定预测值和真值的格式
|
|
75
|
+
- **智能格式检测**: 自动识别常见的数据格式
|
|
76
|
+
|
|
77
|
+
#### 用户体验
|
|
78
|
+
- **进度条功能**: 直观展示评估进度,可选择禁用
|
|
79
|
+
- **并行计算**: 支持多核并行加速,可指定线程数
|
|
80
|
+
- **详细的指标解释**: 提供清晰的指标含义说明
|
|
81
|
+
- **丰富的示例代码**: 覆盖各种使用场景
|
|
82
|
+
- **错误处理**: 完善的边界情况处理和错误提示
|
|
83
|
+
|
|
84
|
+
### 🎯 技术优势
|
|
85
|
+
- **高精度计算**: 采用 COCO 标准的 101 点插值法,计算更准确
|
|
86
|
+
- **多尺度评估**: 支持对不同大小目标的独立评估
|
|
87
|
+
- **内存优化**: 高效的内存管理,支持大规模数据集
|
|
88
|
+
- **扩展性强**: 模块化设计,易于添加新的评估任务
|
|
89
|
+
|
|
90
|
+
## 📋 参数详解
|
|
91
|
+
|
|
92
|
+
### evaluate_detection 函数参数说明
|
|
93
|
+
|
|
94
|
+
#### 核心参数
|
|
95
|
+
|
|
96
|
+
| 参数名 | 类型 | 默认值 | 说明 |
|
|
97
|
+
| :--- | :--- | :--- | :--- |
|
|
98
|
+
| **preds** | List[Any] | 必填 | 每张图片的预测结果列表。支持多种格式,具体取决于 `format` 或 `pred_format` 参数。 |
|
|
99
|
+
| **targets** | List[Any] | 必填 | 每张图片的真实标签列表。支持多种格式,具体取决于 `format` 或 `target_format` 参数。 |
|
|
100
|
+
| **metrics** | Optional[List[str]] | None | 需要返回的特定指标列表。如果为 None,则返回所有计算的指标。 |
|
|
101
|
+
| **n_jobs** | int | 1 | 并行计算线程数。1 表示串行计算,-1 表示使用所有可用 CPU 核心。 |
|
|
102
|
+
| **score_criteria** | Optional[List[Tuple[float, float]]] | None | 计算指定 IoU 和精度下的最佳置信度阈值。格式为 `[(iou_thresh, min_precision), ...]`,例如 `[(0.5, 0.9)]` 表示寻找 IoU=0.5 时精度至少为 0.9 的最低置信度。 |
|
|
103
|
+
| **progress** | bool | True | 是否显示进度条。默认为 True,在计算过程中显示评估进度。 |
|
|
104
|
+
|
|
105
|
+
#### 格式参数
|
|
106
|
+
|
|
107
|
+
| 参数名 | 类型 | 默认值 | 说明 |
|
|
108
|
+
| :--- | :--- | :--- | :--- |
|
|
109
|
+
| **format** | str | "coco" | 输入数据的默认格式,当 `pred_format` 和 `target_format` 未指定时使用。支持 "coco", "voc", "yolo", "custom"。 |
|
|
110
|
+
| **pred_format** | Optional[str] | None | 预测结果的格式,优先级高于 `format`。 |
|
|
111
|
+
| **target_format** | Optional[str] | None | 真实标签的格式,优先级高于 `format`。 |
|
|
112
|
+
|
|
113
|
+
#### 额外参数(**kwargs)
|
|
114
|
+
|
|
115
|
+
| 参数名 | 类型 | 默认值 | 说明 |
|
|
116
|
+
| :--- | :--- | :--- | :--- |
|
|
117
|
+
| **image_size** | Tuple[int, int] | (640, 640) | YOLO 格式需要的图像尺寸 (width, height),用于将归一化坐标转换为绝对坐标。 |
|
|
118
|
+
| **custom_converter** | Callable | None | 自定义格式的转换函数,当 `format` 为 "custom" 时使用。 |
|
|
119
|
+
|
|
120
|
+
## 📦 安装方法
|
|
121
|
+
|
|
122
|
+
### 从 PyPI 安装 (推荐)
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
pip install EasyMetrics
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### 从 GitHub 安装
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
pip install git+https://github.com/Lucidyn/EasyMetrics.git
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### 依赖项
|
|
135
|
+
- **核心依赖**: numpy >= 1.17.0
|
|
136
|
+
- **可选依赖**: tqdm (用于进度条)
|
|
137
|
+
|
|
138
|
+
## 🚀 快速上手
|
|
139
|
+
|
|
140
|
+
### 基本用法
|
|
141
|
+
|
|
142
|
+
一行代码完成目标检测评估:
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
import numpy as np
|
|
146
|
+
from easyMetrics.tasks.detection import evaluate_detection
|
|
147
|
+
|
|
148
|
+
# 准备数据 - 每张图片一个字典
|
|
149
|
+
preds = [{
|
|
150
|
+
'boxes': np.array([[10, 10, 50, 50]]), # [x1, y1, x2, y2] 格式
|
|
151
|
+
'scores': np.array([0.95]), # 置信度分数
|
|
152
|
+
'labels': np.array([0]) # 类别索引
|
|
153
|
+
}]
|
|
154
|
+
|
|
155
|
+
targets = [{
|
|
156
|
+
'boxes': np.array([[10, 10, 50, 50]]), # 真实边界框
|
|
157
|
+
'labels': np.array([0]) # 真实类别
|
|
158
|
+
}]
|
|
159
|
+
|
|
160
|
+
# 执行评估
|
|
161
|
+
results = evaluate_detection(preds, targets)
|
|
162
|
+
|
|
163
|
+
# 查看结果
|
|
164
|
+
print(f"mAP (IoU 0.5:0.95): {results['mAP']:.4f}")
|
|
165
|
+
print(f"mAP_50 (IoU 0.5): {results['mAP_50']:.4f}")
|
|
166
|
+
print(f"mAP_75 (IoU 0.75): {results['mAP_75']:.4f}")
|
|
167
|
+
print(f"AR_100 (MaxDets=100): {results['AR_100']:.4f}")
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### 完整示例
|
|
171
|
+
|
|
172
|
+
下面是一个更完整的示例,包含多个图片的评估:
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
import numpy as np
|
|
176
|
+
from easyMetrics.tasks.detection import evaluate_detection
|
|
177
|
+
|
|
178
|
+
# 准备数据 - 假设有 2 张图片
|
|
179
|
+
|
|
180
|
+
# 图片 1: 预测完全正确
|
|
181
|
+
preds_1 = {
|
|
182
|
+
'boxes': np.array([[10, 10, 50, 50]]),
|
|
183
|
+
'scores': np.array([0.95]),
|
|
184
|
+
'labels': np.array([0])
|
|
185
|
+
}
|
|
186
|
+
targets_1 = {
|
|
187
|
+
'boxes': np.array([[10, 10, 50, 50]]),
|
|
188
|
+
'labels': np.array([0])
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
# 图片 2: 有一个误检 (FP) 和一个漏检 (FN)
|
|
192
|
+
preds_2 = {
|
|
193
|
+
'boxes': np.array([[100, 100, 150, 150], [0, 0, 20, 20]]),
|
|
194
|
+
'scores': np.array([0.9, 0.6]),
|
|
195
|
+
'labels': np.array([0, 0])
|
|
196
|
+
}
|
|
197
|
+
targets_2 = {
|
|
198
|
+
'boxes': np.array([[100, 100, 150, 150], [200, 200, 250, 250]]),
|
|
199
|
+
'labels': np.array([0, 0])
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
preds = [preds_1, preds_2]
|
|
203
|
+
targets = [targets_1, targets_2]
|
|
204
|
+
|
|
205
|
+
# 执行评估
|
|
206
|
+
results = evaluate_detection(preds, targets)
|
|
207
|
+
|
|
208
|
+
# 查看结果
|
|
209
|
+
print(f"mAP (IoU 0.5:0.95): {results['mAP']:.4f}")
|
|
210
|
+
print(f"mAP_50 (IoU 0.5): {results['mAP_50']:.4f}")
|
|
211
|
+
print(f"mAP_75 (IoU 0.75): {results['mAP_75']:.4f}")
|
|
212
|
+
print(f"AR_100 (MaxDets=100): {results['AR_100']:.4f}")
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### 并行计算
|
|
216
|
+
|
|
217
|
+
对于大规模数据集,启用并行计算加速:
|
|
218
|
+
|
|
219
|
+
```python
|
|
220
|
+
# 使用 4 个核心
|
|
221
|
+
results = evaluate_detection(preds, targets, n_jobs=4)
|
|
222
|
+
|
|
223
|
+
# 使用所有可用核心
|
|
224
|
+
results = evaluate_detection(preds, targets, n_jobs=-1)
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### 寻找最佳阈值
|
|
228
|
+
|
|
229
|
+
自动计算满足特定精度要求的最佳置信度阈值:
|
|
230
|
+
|
|
231
|
+
```python
|
|
232
|
+
# 场景: IoU=0.5 时精度至少达到 90%
|
|
233
|
+
results = evaluate_detection(
|
|
234
|
+
preds, targets,
|
|
235
|
+
score_criteria=[(0.5, 0.9)] # 格式: [(iou阈值, 最低精度要求)]
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
# 获取类别 0 的最佳阈值
|
|
239
|
+
best_thresh = results.get('BestScore_IoU0.50_P0.90_0')
|
|
240
|
+
print(f"类别 0 的推荐阈值: {best_thresh}")
|
|
241
|
+
|
|
242
|
+
# 场景: 多个精度要求
|
|
243
|
+
results = evaluate_detection(
|
|
244
|
+
preds, targets,
|
|
245
|
+
score_criteria=[(0.5, 0.9), (0.75, 0.8)]
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
# 获取不同要求下的阈值
|
|
249
|
+
best_thresh_50 = results.get('BestScore_IoU0.50_P0.90_0')
|
|
250
|
+
best_thresh_75 = results.get('BestScore_IoU0.75_P0.80_0')
|
|
251
|
+
print(f"IoU=0.5, P>=0.9 阈值: {best_thresh_50}")
|
|
252
|
+
print(f"IoU=0.75, P>=0.8 阈值: {best_thresh_75}")
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### 自定义指标筛选
|
|
256
|
+
|
|
257
|
+
只计算你关心的指标,减少输出干扰,提高计算效率:
|
|
258
|
+
|
|
259
|
+
```python
|
|
260
|
+
# 只计算 mAP 和 mAP_50
|
|
261
|
+
results = evaluate_detection(preds, targets, metrics=['mAP', 'mAP_50'])
|
|
262
|
+
print(results)
|
|
263
|
+
|
|
264
|
+
# 只计算召回率相关指标
|
|
265
|
+
results = evaluate_detection(preds, targets, metrics=['AR_100'])
|
|
266
|
+
print(results)
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### 多种数据格式
|
|
270
|
+
|
|
271
|
+
支持多种输入格式,包括 COCO、VOC、YOLO 等:
|
|
272
|
+
|
|
273
|
+
```python
|
|
274
|
+
# VOC 格式示例
|
|
275
|
+
preds_voc = [[10, 10, 50, 50, 0, 0.95]] # [x1, y1, x2, y2, class_id, confidence]
|
|
276
|
+
targets_voc = [[10, 10, 50, 50, 0]] # [x1, y1, x2, y2, class_id]
|
|
277
|
+
|
|
278
|
+
results = evaluate_detection(
|
|
279
|
+
[preds_voc], [targets_voc],
|
|
280
|
+
pred_format="voc",
|
|
281
|
+
target_format="voc"
|
|
282
|
+
)
|
|
283
|
+
print(f"VOC 格式评估结果: {results['mAP']:.4f}")
|
|
284
|
+
|
|
285
|
+
# YOLO 格式示例
|
|
286
|
+
# YOLO 格式: [class_id, x_center, y_center, width, height, confidence]
|
|
287
|
+
# 注意: 坐标是归一化的 (0-1)
|
|
288
|
+
preds_yolo = [[0, 0.5, 0.5, 0.2, 0.2, 0.95]]
|
|
289
|
+
targets_yolo = [[0, 0.5, 0.5, 0.2, 0.2]]
|
|
290
|
+
|
|
291
|
+
results = evaluate_detection(
|
|
292
|
+
[preds_yolo], [targets_yolo],
|
|
293
|
+
pred_format="yolo",
|
|
294
|
+
target_format="yolo",
|
|
295
|
+
image_size=(640, 640) # YOLO 格式需要的图像尺寸
|
|
296
|
+
)
|
|
297
|
+
print(f"YOLO 格式评估结果: {results['mAP']:.4f}")
|
|
298
|
+
|
|
299
|
+
# 混合格式示例
|
|
300
|
+
# 预测值使用 YOLO 格式,真值使用 VOC 格式
|
|
301
|
+
results = evaluate_detection(
|
|
302
|
+
[preds_yolo], [targets_voc],
|
|
303
|
+
pred_format="yolo",
|
|
304
|
+
target_format="voc",
|
|
305
|
+
image_size=(640, 640)
|
|
306
|
+
)
|
|
307
|
+
print(f"混合格式评估结果: {results['mAP']:.4f}")
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### 灵活的输入格式
|
|
311
|
+
|
|
312
|
+
支持标量、列表等多种输入类型:
|
|
313
|
+
|
|
314
|
+
```python
|
|
315
|
+
# 标量输入示例 (单目标情况)
|
|
316
|
+
preds_scalar = {'boxes': [10, 10, 50, 50], 'scores': 0.95, 'labels': 0}
|
|
317
|
+
targets_scalar = {'boxes': [10, 10, 50, 50], 'labels': [0]}
|
|
318
|
+
|
|
319
|
+
results = evaluate_detection([preds_scalar], [targets_scalar])
|
|
320
|
+
print(f"标量输入评估结果: {results['mAP']:.4f}")
|
|
321
|
+
|
|
322
|
+
# 列表输入示例
|
|
323
|
+
preds_list = {
|
|
324
|
+
'boxes': [[10, 10, 50, 50], [100, 100, 150, 150]],
|
|
325
|
+
'scores': [0.95, 0.9],
|
|
326
|
+
'labels': [0, 0]
|
|
327
|
+
}
|
|
328
|
+
targets_list = {
|
|
329
|
+
'boxes': [[10, 10, 50, 50], [100, 100, 150, 150]],
|
|
330
|
+
'labels': [0, 0]
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
results = evaluate_detection([preds_list], [targets_list])
|
|
334
|
+
print(f"列表输入评估结果: {results['mAP']:.4f}")
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### 进度条控制
|
|
338
|
+
|
|
339
|
+
控制是否显示评估进度条:
|
|
340
|
+
|
|
341
|
+
```python
|
|
342
|
+
# 启用进度条 (默认)
|
|
343
|
+
results = evaluate_detection(preds, targets, progress=True)
|
|
344
|
+
|
|
345
|
+
# 禁用进度条
|
|
346
|
+
results = evaluate_detection(preds, targets, progress=False)
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### 多类别评估
|
|
350
|
+
|
|
351
|
+
评估包含多个类别的检测结果:
|
|
352
|
+
|
|
353
|
+
```python
|
|
354
|
+
# 多类别示例
|
|
355
|
+
preds_multi = [{
|
|
356
|
+
'boxes': np.array([[10, 10, 50, 50], [100, 100, 150, 150]]),
|
|
357
|
+
'scores': np.array([0.95, 0.9]),
|
|
358
|
+
'labels': np.array([0, 1]) # 两个不同的类别
|
|
359
|
+
}]
|
|
360
|
+
targets_multi = [{
|
|
361
|
+
'boxes': np.array([[10, 10, 50, 50], [100, 100, 150, 150]]),
|
|
362
|
+
'labels': np.array([0, 1]) # 两个不同的类别
|
|
363
|
+
}]
|
|
364
|
+
|
|
365
|
+
results = evaluate_detection(preds_multi, targets_multi)
|
|
366
|
+
print(f"多类别评估 mAP: {results['mAP']:.4f}")
|
|
367
|
+
print(f"类别 0 的 AP: {results['AP_0']:.4f}")
|
|
368
|
+
print(f"类别 1 的 AP: {results['AP_1']:.4f}")
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
## 📋 目录结构
|
|
372
|
+
|
|
373
|
+
```
|
|
374
|
+
easyMetrics/
|
|
375
|
+
├── easyMetrics/ # 核心代码
|
|
376
|
+
│ ├── core/ # 抽象基类
|
|
377
|
+
│ │ └── base.py
|
|
378
|
+
│ └── tasks/ # 任务实现
|
|
379
|
+
│ └── detection/ # 目标检测
|
|
380
|
+
│ ├── interface.py # 对外接口
|
|
381
|
+
│ ├── map.py # mAP 核心逻辑
|
|
382
|
+
│ ├── matcher.py # 匹配策略
|
|
383
|
+
│ ├── utils.py # 辅助函数
|
|
384
|
+
│ └── format_converter.py # 格式转换器
|
|
385
|
+
├── docs/ # 文档
|
|
386
|
+
│ ├── 使用指南.md # 详细使用指南
|
|
387
|
+
│ └── 指标详解.md # 指标计算原理
|
|
388
|
+
├── demo.py # 完整使用示例
|
|
389
|
+
├── README.md # 项目概述
|
|
390
|
+
├── pyproject.toml # 项目配置
|
|
391
|
+
└── LICENSE # 许可证
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
## 📚 详细文档
|
|
395
|
+
|
|
396
|
+
- **使用指南.md**: 详细的安装、配置和使用说明,包含各种功能的完整示例
|
|
397
|
+
- **指标详解.md**: 详细介绍各种评估指标的计算原理和含义
|
|
398
|
+
- **demo.py**: 完整的代码示例,覆盖所有主要功能
|
|
399
|
+
|
|
400
|
+
## � 指标详解
|
|
401
|
+
|
|
402
|
+
### 平均精度 (Average Precision, AP)
|
|
403
|
+
|
|
404
|
+
AP 是目标检测中最重要的指标,它综合了**精度 (Precision)** 和 **召回率 (Recall)**。
|
|
405
|
+
|
|
406
|
+
- **Precision (查准率)**: 预测为正样本中,真正正确的比例。 $P = \frac{TP}{TP + FP}$
|
|
407
|
+
- **Recall (查全率)**: 所有真实正样本中,被正确预测出的比例。 $R = \frac{TP}{TP + FN}$
|
|
408
|
+
|
|
409
|
+
我们使用 **IoU (Intersection over Union)** 来判定预测框是否正确。如果 `IoU(Pred, GT) >= 阈值`,则视为 TP (True Positive)。
|
|
410
|
+
|
|
411
|
+
### 主要指标
|
|
412
|
+
|
|
413
|
+
| 指标键名 | 含义 | 详细解释 |
|
|
414
|
+
| :--- | :--- | :--- |
|
|
415
|
+
| **mAP** | **平均 AP** | **最重要的综合指标**。它是在 IoU 阈值从 0.50 到 0.95(步长 0.05)共 10 个阈值下计算出的 AP 的平均值,并且对所有类别取平均。它反映了模型在不同严格程度下的综合表现。在 COCO 竞赛中简称为 AP。 |
|
|
416
|
+
| **mAP_50** | IoU=0.50 的 mAP | PASCAL VOC 竞赛的标准指标。只要求 IoU > 0.5 就算正确,相对宽松。 |
|
|
417
|
+
| **mAP_75** | IoU=0.75 的 mAP | 严格模式下的指标。要求检测框与真值重叠度很高才算正确。 |
|
|
418
|
+
| **AP_<class_id>** | 特定类别的 AP | 该类别的 Average Precision (IoU 0.5:0.95)。 |
|
|
419
|
+
| **AP_50_<class_id>** | 特定类别的 AP@50 | 该类别在 IoU=0.50 下的 AP。 |
|
|
420
|
+
| **AP_75_<class_id>** | 特定类别的 AP@75 | 该类别在 IoU=0.75 下的 AP。 |
|
|
421
|
+
|
|
422
|
+
### 不同尺度的 AP
|
|
423
|
+
|
|
424
|
+
为了分析模型对不同大小物体的检测能力,我们将物体按面积(像素数)分为三类:
|
|
425
|
+
|
|
426
|
+
| 指标键名 | 含义 | 定义 (面积 = 宽 × 高) |
|
|
427
|
+
| :--- | :--- | :--- |
|
|
428
|
+
| **mAP_s** | 小目标 mAP | 面积 < $32^2$ (小于 1024 像素) |
|
|
429
|
+
| **mAP_m** | 中目标 mAP | $32^2 \le$ 面积 < $96^2$ (1024 ~ 9216 像素) |
|
|
430
|
+
| **mAP_l** | 大目标 mAP | 面积 $\ge 96^2$ (大于 9216 像素) |
|
|
431
|
+
|
|
432
|
+
通常小目标检测 (mAP_s) 是最难的。
|
|
433
|
+
|
|
434
|
+
### 平均召回率 (Average Recall, AR)
|
|
435
|
+
|
|
436
|
+
AR 反映了模型发现目标的能力,不考虑分数的排序,主要关注有多少真值被覆盖了。
|
|
437
|
+
|
|
438
|
+
EasyMetrics 按照每张图片允许的最大检测数 (Max Dets) 来计算 AR:
|
|
439
|
+
|
|
440
|
+
| 指标键名 | 含义 | 详细解释 |
|
|
441
|
+
| :--- | :--- | :--- |
|
|
442
|
+
| **AR_1** | MaxDets=1 的 AR | 每张图只取置信度最高的 1 个框来计算召回率。 |
|
|
443
|
+
| **AR_10** | MaxDets=10 的 AR | 每张图取前 10 个框计算召回率。 |
|
|
444
|
+
| **AR_100** | MaxDets=100 的 AR | 每张图取前 100 个框计算召回率。通常接近模型能达到的召回率上限。 |
|
|
445
|
+
|
|
446
|
+
### 计算细节
|
|
447
|
+
|
|
448
|
+
1. **101点插值法**:
|
|
449
|
+
我们在计算 AP 时,采用 COCO 标准的 101 点插值法。即在 recall = [0.00, 0.01, ..., 1.00] 这 101 个点上取对应的最大 precision 并求平均。这比传统的 11 点插值法更精确。
|
|
450
|
+
|
|
451
|
+
2. **多尺度评估逻辑**:
|
|
452
|
+
- 计算 `mAP_s` 时,我们只保留真实面积在 `[0, 32^2)` 范围内的 GT,并且只考虑面积在该范围内的预测框。
|
|
453
|
+
- 如果某张图片没有该尺度的目标,则不参与该尺度的计算。
|
|
454
|
+
|
|
455
|
+
### 最佳阈值推荐 (Best Score Suggestion)
|
|
456
|
+
|
|
457
|
+
EasyMetrics 支持根据指定的精度 (Precision) 要求,自动寻找最佳的置信度阈值。这对于实际部署模型时设定阈值非常有帮助。
|
|
458
|
+
|
|
459
|
+
| 指标键名 | 含义 | 详细解释 |
|
|
460
|
+
| :--- | :--- | :--- |
|
|
461
|
+
| **BestScore_IoU0.50_P0.90_<class_id>** | 满足精度要求的最低阈值 | 在 IoU=0.50 的评估标准下,为了让该类别的预测精度达到 90%,建议设置的最低置信度阈值。 |
|
|
462
|
+
|
|
463
|
+
### 如何选择评估指标
|
|
464
|
+
|
|
465
|
+
- **如果只看一个数**: 关注 **mAP**。
|
|
466
|
+
- **如果定位精度很重要**: 关注 **mAP_75**。
|
|
467
|
+
- **如果漏检代价很大** (如自动驾驶): 关注 **AR_100**。
|
|
468
|
+
- **如果发现小物体检测很差**: 关注 **mAP_s**,可能需要调整数据增强或模型结构。
|
|
469
|
+
|
|
470
|
+
## � 扩展与定制
|
|
471
|
+
|
|
472
|
+
### 添加新任务
|
|
473
|
+
|
|
474
|
+
要添加新的评估任务(如分类、分割等):
|
|
475
|
+
|
|
476
|
+
1. 在 `easyMetrics/tasks/` 下创建新目录
|
|
477
|
+
2. 创建评估接口文件
|
|
478
|
+
3. 继承 `easyMetrics.core.Metric` 基类
|
|
479
|
+
4. 实现 `reset()`, `update()` 和 `compute()` 方法
|
|
480
|
+
|
|
481
|
+
### 自定义匹配策略
|
|
482
|
+
|
|
483
|
+
EasyMetrics 支持自定义匹配策略,详情请参考源码中的 `matcher.py` 文件。
|
|
484
|
+
|
|
485
|
+
## 🎯 版本历史
|
|
486
|
+
|
|
487
|
+
### v0.4.0 (2026-02-05)
|
|
488
|
+
- **增强批量处理能力**: 支持批量 YOLO 格式数据的处理
|
|
489
|
+
- **修复标签处理**: 完善对二维标签数组的处理
|
|
490
|
+
- **提升鲁棒性**: 增强对各种边缘情况的处理能力
|
|
491
|
+
- **优化性能**: 改进并行计算效率
|
|
492
|
+
- **完善文档**: 添加批量 YOLO 格式数据示例
|
|
493
|
+
|
|
494
|
+
### v0.3.0 (2026-02-05)
|
|
495
|
+
- **增强类型兼容性**: 支持标量、列表、numpy 数组等多种输入类型
|
|
496
|
+
- **修复边界情况**: 优化空数据、只有预测/目标等特殊情况的处理
|
|
497
|
+
- **提升稳定性**: 修复索引错误和类型转换错误
|
|
498
|
+
- **增强格式支持**: 完善对不同输入格式的处理
|
|
499
|
+
- **改进用户体验**: 添加进度条功能,可选择禁用
|
|
500
|
+
- **完善文档**: 更新使用指南和指标详解
|
|
501
|
+
|
|
502
|
+
### v0.2.0 (2026-02-05)
|
|
503
|
+
- **修复类型转换**: 解决标量输入的处理问题
|
|
504
|
+
- **提升稳定性**: 优化类型转换逻辑
|
|
505
|
+
- **添加格式支持**: 支持 COCO、VOC、YOLO 等多种输入格式
|
|
506
|
+
- **添加并行计算**: 支持多核并行加速
|
|
507
|
+
|
|
508
|
+
### v0.1.0 (2026-02-05)
|
|
509
|
+
- **初始版本**: 发布核心功能
|
|
510
|
+
- **目标检测评估**: 支持完整的 COCO 指标
|
|
511
|
+
- **最佳阈值计算**: 支持自动计算最佳置信度阈值
|
|
512
|
+
- **基础格式支持**: 支持 COCO 格式输入
|
|
513
|
+
|
|
514
|
+
## 💡 实用技巧
|
|
515
|
+
|
|
516
|
+
1. **数据预处理**:确保输入的边界框坐标格式正确(`[x1, y1, x2, y2]`),且为浮点数类型。
|
|
517
|
+
|
|
518
|
+
2. **类别索引**:确保预测结果和真实标签使用相同的类别索引体系。
|
|
519
|
+
|
|
520
|
+
3. **性能优化**:
|
|
521
|
+
- 对于大规模数据集,使用 `n_jobs=-1` 启用并行计算
|
|
522
|
+
- 使用 `metrics` 参数只计算需要的指标
|
|
523
|
+
|
|
524
|
+
4. **结果分析**:
|
|
525
|
+
- 关注 `mAP_50` 指标评估模型在宽松条件下的性能
|
|
526
|
+
- 关注 `mAP_75` 指标评估模型在严格条件下的性能
|
|
527
|
+
- 使用 `AR_100` 评估模型的召回能力
|
|
528
|
+
- 使用 `mAP_s`/`mAP_m`/`mAP_l` 分析模型对不同尺度目标的表现
|
|
529
|
+
|
|
530
|
+
5. **格式选择**:
|
|
531
|
+
- 对于标准场景,使用默认的 COCO 格式
|
|
532
|
+
- 对于与其他框架集成,可选择 VOC 或 YOLO 格式
|
|
533
|
+
- 对于特殊需求,可使用自定义格式转换函数
|
|
534
|
+
|
|
535
|
+
## 🤝 贡献指南
|
|
536
|
+
|
|
537
|
+
欢迎贡献代码和提出建议!
|
|
538
|
+
|
|
539
|
+
1. **Fork 项目**
|
|
540
|
+
2. **创建特性分支**
|
|
541
|
+
3. **提交更改**
|
|
542
|
+
4. **发起 Pull Request**
|
|
543
|
+
|
|
544
|
+
### 开发规范
|
|
545
|
+
|
|
546
|
+
- **代码风格**:遵循 PEP 8 规范
|
|
547
|
+
- **测试**:为新功能添加测试用例
|
|
548
|
+
- **文档**:更新相关文档
|
|
549
|
+
- **提交信息**:清晰、简洁的提交信息
|
|
550
|
+
|
|
551
|
+
## 📄 许可证
|
|
552
|
+
|
|
553
|
+
EasyMetrics 使用 MIT 许可证,详见 LICENSE 文件。
|
|
554
|
+
|
|
555
|
+
## 📞 联系我们
|
|
556
|
+
|
|
557
|
+
如有问题或建议,欢迎通过 GitHub Issues 提出。
|
|
558
|
+
|
|
559
|
+
---
|
|
560
|
+
|
|
561
|
+
*Created with ❤️ by EasyMetrics Team*
|
|
@@ -5,11 +5,11 @@ easyMetrics/tasks/__init__.py,sha256=Cwc3WM7ZgblWq1jfIjrHt_bBrJRobx4q_WbHn3LaN68
|
|
|
5
5
|
easyMetrics/tasks/detection/__init__.py,sha256=74SsA-fJbamX7WGIWJJmeTXjjHnogYU1jwYse65d45U,80
|
|
6
6
|
easyMetrics/tasks/detection/format_converter.py,sha256=80vM8B3exuH6EHlmYwvMrpslTDm-FMWzuccQKka42dc,12632
|
|
7
7
|
easyMetrics/tasks/detection/interface.py,sha256=ZbR9aJVXsRiSNGrhcVZAEGkfH2SFX0kJi0XY_rUEbMA,2568
|
|
8
|
-
easyMetrics/tasks/detection/map.py,sha256=
|
|
8
|
+
easyMetrics/tasks/detection/map.py,sha256=9n5m-XzKKHLNY8JIwrXK4qoDnKiANu9Wfx5wbzwfeKM,28346
|
|
9
9
|
easyMetrics/tasks/detection/matcher.py,sha256=C-4hEVBJseQKgcHupBLlaDNFGhKxOxN-YdPNnpuit9I,2443
|
|
10
10
|
easyMetrics/tasks/detection/utils.py,sha256=jNDPHxWzGo7478QDI1f8uVGK6HUlwvHlPbfwzEk-H0A,2770
|
|
11
|
-
easymetrics-0.
|
|
12
|
-
easymetrics-0.
|
|
13
|
-
easymetrics-0.
|
|
14
|
-
easymetrics-0.
|
|
15
|
-
easymetrics-0.
|
|
11
|
+
easymetrics-0.4.0.dist-info/licenses/LICENSE,sha256=Z-thSEoGCfxHrpC8rpgrqY-LSoJuyHbgPj5Gt8BuG_0,1073
|
|
12
|
+
easymetrics-0.4.0.dist-info/METADATA,sha256=1itPd_H84kueDwaJY_dx3_1ByHbRc8ycOKVlAGPFL8k,21146
|
|
13
|
+
easymetrics-0.4.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
14
|
+
easymetrics-0.4.0.dist-info/top_level.txt,sha256=2ytZxZ9LMTdKpeZunCE1rpvkpDAhWeQLiiXTrFy9niw,12
|
|
15
|
+
easymetrics-0.4.0.dist-info/RECORD,,
|
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: EasyMetrics
|
|
3
|
-
Version: 0.3.0
|
|
4
|
-
Summary: 轻量级、零依赖的机器学习指标评估平台
|
|
5
|
-
Author-email: EasyMetrics Team <team@easymetrics.example>
|
|
6
|
-
License: MIT License
|
|
7
|
-
|
|
8
|
-
Copyright (c) 2026 EasyMetrics Team
|
|
9
|
-
|
|
10
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
-
in the Software without restriction, including without limitation the rights
|
|
13
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
-
furnished to do so, subject to the following conditions:
|
|
16
|
-
|
|
17
|
-
The above copyright notice and this permission notice shall be included in all
|
|
18
|
-
copies or substantial portions of the Software.
|
|
19
|
-
|
|
20
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
-
SOFTWARE.
|
|
27
|
-
|
|
28
|
-
Project-URL: homepage, https://github.com/easymetrics/easymetrics
|
|
29
|
-
Project-URL: documentation, https://easymetrics.readthedocs.io
|
|
30
|
-
Project-URL: repository, https://github.com/easymetrics/easymetrics
|
|
31
|
-
Project-URL: issues, https://github.com/easymetrics/easymetrics/issues
|
|
32
|
-
Classifier: Programming Language :: Python :: 3
|
|
33
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
34
|
-
Classifier: Operating System :: OS Independent
|
|
35
|
-
Classifier: Intended Audience :: Developers
|
|
36
|
-
Classifier: Intended Audience :: Science/Research
|
|
37
|
-
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
38
|
-
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
39
|
-
Requires-Python: >=3.7
|
|
40
|
-
Description-Content-Type: text/markdown
|
|
41
|
-
License-File: LICENSE
|
|
42
|
-
Requires-Dist: numpy
|
|
43
|
-
Provides-Extra: progress
|
|
44
|
-
Requires-Dist: tqdm; extra == "progress"
|
|
45
|
-
Dynamic: license-file
|
|
46
|
-
|
|
47
|
-
# EasyMetrics
|
|
48
|
-
|
|
49
|
-
一个轻量级、零依赖的机器学习指标评估平台,基于 `numpy` 从零构建,专注于提供简单易用且准确的模型评估工具。
|
|
50
|
-
|
|
51
|
-
## ✨ 核心特性
|
|
52
|
-
- **零依赖**: 仅需 Python 和 Numpy,无需安装大型深度学习框架
|
|
53
|
-
- **易于扩展**: 模块化设计,通过继承 `Metric` 基类即可添加新任务
|
|
54
|
-
- **功能强大**: 完美支持目标检测任务的全方位评估
|
|
55
|
-
- 标准 COCO 指标: mAP、mAP_50、mAP_75、mAP_s/m/l
|
|
56
|
-
- 平均召回率 (AR) 指标
|
|
57
|
-
- 每类别独立评估
|
|
58
|
-
- **独家功能**: 自动计算满足特定精度要求的最佳置信度阈值
|
|
59
|
-
|
|
60
|
-
## � 目录结构
|
|
61
|
-
```
|
|
62
|
-
easyMetrics/
|
|
63
|
-
├── easyMetrics/ # 核心代码
|
|
64
|
-
│ ├── core/ # 抽象基类
|
|
65
|
-
│ │ └── base.py
|
|
66
|
-
│ └── tasks/ # 任务实现
|
|
67
|
-
│ └── detection/ # 目标检测
|
|
68
|
-
│ ├── interface.py # 对外接口
|
|
69
|
-
│ ├── map.py # mAP 核心逻辑
|
|
70
|
-
│ ├── matcher.py # 匹配策略
|
|
71
|
-
│ ├── utils.py # 辅助函数
|
|
72
|
-
│ └── format_converter.py # 格式转换器
|
|
73
|
-
├── docs/ # 文档
|
|
74
|
-
│ ├── 使用指南.md
|
|
75
|
-
│ └── 指标详解.md
|
|
76
|
-
├── demo.py # 使用示例
|
|
77
|
-
└── README.md
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
## 🚀 快速上手
|
|
81
|
-
|
|
82
|
-
### 目标检测评估
|
|
83
|
-
|
|
84
|
-
使用 `evaluate_detection` 函数,一行代码完成评估:
|
|
85
|
-
|
|
86
|
-
```python
|
|
87
|
-
import numpy as np
|
|
88
|
-
from easyMetrics.tasks.detection import evaluate_detection
|
|
89
|
-
|
|
90
|
-
# 准备数据 - 每张图片一个字典
|
|
91
|
-
preds = [{
|
|
92
|
-
'boxes': np.array([[10, 10, 50, 50]]), # [x1, y1, x2, y2] 格式
|
|
93
|
-
'scores': np.array([0.9]), # 置信度分数
|
|
94
|
-
'labels': np.array([0]) # 类别索引
|
|
95
|
-
}]
|
|
96
|
-
targets = [{
|
|
97
|
-
'boxes': np.array([[10, 10, 50, 50]]), # 真实边界框
|
|
98
|
-
'labels': np.array([0]) # 真实类别
|
|
99
|
-
}]
|
|
100
|
-
|
|
101
|
-
# 1. 计算标准 COCO 指标
|
|
102
|
-
results = evaluate_detection(preds, targets)
|
|
103
|
-
print(f"mAP: {results['mAP']:.4f}")
|
|
104
|
-
print(f"mAP_50: {results['mAP_50']:.4f}")
|
|
105
|
-
|
|
106
|
-
# 2. 寻找最佳置信度阈值
|
|
107
|
-
# 场景: IoU=0.5 时精度至少达到 90%
|
|
108
|
-
results = evaluate_detection(
|
|
109
|
-
preds, targets,
|
|
110
|
-
score_criteria=[(0.5, 0.9)]
|
|
111
|
-
)
|
|
112
|
-
print(f"推荐阈值: {results.get('BestScore_IoU0.50_P0.90_0')}")
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
### 并行加速
|
|
116
|
-
|
|
117
|
-
对于大规模数据集,启用多核并行计算:
|
|
118
|
-
|
|
119
|
-
```python
|
|
120
|
-
# 使用 4 个核心
|
|
121
|
-
results = evaluate_detection(preds, targets, n_jobs=4)
|
|
122
|
-
|
|
123
|
-
# 使用所有可用核心
|
|
124
|
-
results = evaluate_detection(preds, targets, n_jobs=-1)
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
## 🔧 扩展新任务
|
|
128
|
-
|
|
129
|
-
添加新指标(例如分类任务的准确率):
|
|
130
|
-
|
|
131
|
-
1. 在 `easyMetrics/tasks/` 下创建新目录(如 `classification`)
|
|
132
|
-
2. 继承 `easyMetrics.core.Metric` 基类
|
|
133
|
-
3. 实现 `reset()`, `update()` 和 `compute()` 方法
|
|
134
|
-
|
|
135
|
-
---
|
|
136
|
-
*Created with ❤️ by EasyMetrics Team*
|
|
File without changes
|
|
File without changes
|
|
File without changes
|