目标检测是计算机视觉中的一个基本任务。它主要是预测图像中对象的位置和类别。最前沿的这类模型,如You-Only-Look-Once (YOLO)已经达到了惊人的准确度。然而,检测小物体是目标检测中一个特别具有挑战性的领域。
在这篇帖子中,你将学习如何使用Slicing Aided Hyper Inference (SAHI,切片辅助超推理) 在你的数据集中检测小物体。我们将讨论以下几个方面:
- 为什么检测小物体很难
- SAHI的工作原理
- 如何将SAHI应用到您的数据集中
- 如何评估这些预测的质量和准确性
为什么检测小东西这么难?
它们真小首先,检测小物体很难,因为小物体嘛,确实很小。物体越小,模型能用到的信息就越少。如果一辆车在远处,它在图像中可能只占几个像素。就像人眼难以分辨远处的物体一样,我们的模型在没有视觉上明显的特征(比如轮胎和车牌)时,也很难识别出汽车。
训练集:模型的好坏取决于它们训练所用的数据。大多数标准的物体重检测数据集和基准主要关注中到大型物体,这意味着,市面上常用的物体检测模型并不适合用来检测小型物体。
固定的输入大小对象检测模型通常接受固定大小的输入。例如,YOLOv8 是在图像边长最大为 640 像素的情况下训练的。这意味着当我们为它提供一张 1920x1080 的图像时,模型会将图像缩小到 640x360,从而降低分辨率并丢失对小物体重要的信息。
SAHI是怎么工作的插图说明:切片辅助超推理示意图。图片来自SAHI 的 GitHub 仓库。
理论上来说,你可以用更大的图像来训练模型,以更准确地检测小物体。然而实际上,这实际上需要更多的内存和计算资源,以及更耗时去准备的大型数据集。
另一种方法是利用现有的对象检测模型,将模型应用于我们图像上的固定大小的补丁或切片,然后将这些结果拼接在一起。这就是Slicing-Aided Hyper Inference的基本思想!
SAHI通过将图像分割成完全覆盖该图像的多个切片,并在每个切片上运行指定检测模型的推理来工作。然后将这些切片的预测合并在一起,生成整个图像的检测结果列表。SAHI中的“hyper”之所以这么叫,即SAHI的输出并非单纯的模型推理结果,而是基于多个模型推理计算的结果。
💡SAHI切片可以重叠(如上所示的GIF动画),这有助于确保对象至少在一个切片中被检测到。
使用SAHI的关键优势在于它不依赖特定模型。SAHI不仅可以利用当今最先进的物体检测模型,还可以利用未来的任何最先进的模型。
当然,毕竟没有免费的午餐。为了“超推理”,你需要运行更多的前向传递,次数是原来的多倍。此外还得处理一下把结果拼接起来的过程。
设置环境为了展示如何使用SAHI检测小对象,我们将使用由天津大学机器学习与数据挖掘实验室的AISKYEYE团队提供的VisDrone检测数据集。该数据集包含8,629张图像,边长从360像素到2000像素不等,这使得它成为测试SAHI的理想平台。Ultralytics的YOLOv8l将作为我们的基础检测模型。
我们将用到以下库:
fiftyone
用于数据集的管理和可视化huggingface_hub
用于从 Hugging Face Hub 加载 VisDrone 数据集ultralytics
用于使用 YOLOv8 进行推理工作,sahi
用于对图像切片进行推理处理
如果没有安装过,安装这些库的最新版本(如果还没有的话)。您需要fiftyone>=0.23.8
来从Hugging Face Hub加载VisDrone。
安装必要的Python库
pip install -U fiftyone sahi ultralytics huggingface_hub --quiet
现在,让我们在 Python 中导入我们将用于查询和管理我们数据的 FiftyOne 模块:
# 下面是一些关于 fiftyone 模块的导入语句
# 导入 fiftyone 模块并命名为 fo
import fiftyone as fo
# 导入 fiftyone 的动物园模块并命名为 foz
import fiftyone.zoo as foz
# 导入 fiftyone 的 huggingface 工具模块并命名为 fouh
import fiftyone.utils.huggingface as fouh
# 从 fiftyone 导入 ViewField 并命名为 F
from fiftyone import ViewField as F
就这样,我们准备好加载数据了!我们将使用FiftyOne的Hugging Face工具中的load_from_hub()
方法直接从Hugging Face Hub加载VisDrone数据集的一部分。
为了演示并尽可能快地运行代码,我们将仅从数据集中取前100张图片。我们将新数据集命名为"sahi-test"
:
# 加载数据集的代码
dataset = fouh.load_from_hub(
"Voxel51/VisDrone2019-DET", # 数据集名称
name="sahi-test", # 设置数据集的名称
max_samples=100 # 设置最大样本数量为100
)
在添加任何预测之前,我们先看看我们的数据集,FiftyOne App:
session = fo.launch_app(dataset)
💡查看FiftyOne与Hugging Face的集成以了解更多详情。
基于YOLOv8的标准推断在下一节中,我们将使用SAHI对我们数据进行超推理(Hyper-Inference)处理。在引入SAHI之前,让我们使用Ultralytics的YOLOv8大型版本模型对我们数据进行标准对象检测。
首先,我们创建一个 ultralytics.YOLO
模型实例,并如必要,则下载模型检查点。然后,我们将该模型应用到我们的数据集上,并将结果存储在样本的 “base_model”
字段中。
从 ultralytics 导入 YOLO 模型.
ckpt_path = "yolov8l.pt"
model = YOLO(ckpt_path) # 加载YOLO模型,路径为ckpt_path.
dataset.apply_model(model, label_field="base_model")
session.view = dataset 的视图()
💡查看FiftyOne的Ultralytics集成功能以获取更多了解。
通过比较模型预测与实际标签,我们可以注意到一些关键点。首先,最重要的是,因此我们的 YOLOv8l 模型检测到的类别与 VisDrone 数据集中的实际类别是不同的。我们的 YOLO 模型是在 COCO 数据集 上训练的,该数据集有 80 个类别,而 VisDrone 数据集则有 12 个类别,其中包括一个名为 ignore_regions
的类别。
为了简化比较,我们只需重点关注数据集中最常见的几个类别,并将VisDrone类别映射到COCO类别,如下:
mapping = {"pedestrians": "person", "people": "person", "van": "car"} # 将"pedestrians"、"people"和"van"映射到新的标签
mapped_view = dataset.map_labels("ground_truth", mapping) # 将数据集中的ground_truth标签映射到新的标签
然后只保留我们感兴趣的类别的标签。
labels = [label for label in labels if label in classes_of_interest]
def get_label_fields(sample_collection):
"""获取数据集或数据集视图的检测标签字段."""
label_fields = list(
sample_collection.get_field_schema(embedded_doc_type=fo.Detections).keys()
)
return label_fields
def filter_all_labels(sample_collection):
"""过滤所有标签."""
label_fields = get_label_fields(sample_collection)
filtered_view = sample_collection
for lf in label_fields:
filtered_view = filtered_view.filter_labels(
lf, F("label").is_in(["person", "car", "truck"]), only_matches=False
)
return filtered_view
filtered_view = filter_all_labels(mapped_view)
session.view = filtered_view.view()
现在我们有了基础模型的预测,让我们使用SAHI来切割和分析我们的图像,动手吧 💪。
使用SAHI进行超推断注:SAHI是指用于超推断的技术或工具,如果读者不熟悉该术语,此处可适当增加解释以提高理解。
SAHI技术实现在我们之前安装的sahi
Python包里。SAHI是一个兼容许多目标检测模型的框架,比如YOLOv8。我们可以选择想要使用的检测模型,并创建继承自sahi.models.DetectionModel
类的实例,包括YOLOv8、YOLOv5,甚至还可以使用Hugging Face Transformers模型。
我们将使用SAHI的AutoDetectionModel
类来创建模型对象,并指定模型类型以及检查点文件的路径。
从sahi导入AutoDetectionModel类
从sahi.predict导入get_prediction和get_sliced_prediction
detection_model = AutoDetectionModel.from_pretrained(
model_type='yolov8',
model_path=ckpt_path,
confidence_threshold=0.25, # 与我们基础模型的默认值相同
image_size=640,
device="cpu", # 如果你有访问GPU的权限
)
在我们生成切片预测结果之前,让我们使用SAHI的get_prediction()
方法检查模型的预测。
result = get_prediction(dataset.first().filepath, detection_model)
print(result)
<sahi.prediction.预测结果 对象实例>
幸运的是,SAHI结果具备一个to_fiftyone_detections()
方法,可以将结果转换为FiftyOne的Detection
对象列表。
打印结果中的fiftyone检测结果
[<检测结果: {
'id': '661858c20ae3edf77139db7a',
'attributes': {},
'tags': [],
'标签': '汽车',
'边界框': [
0.6646394729614258,
0.7850866247106482,
0.06464214324951172,
0.09088355170355902,
],
'掩模': None,
'置信度': 0.8933132290840149,
'索引': None,
}>, <检测结果: {
'id': '661858c20ae3edf77139db7b',
'attributes': {},
'tags': [],
'标签': '汽车',
'边界框': [
0.6196376800537109,
0.7399617513020833,
0.06670347849527995,
0.09494832356770834,
],
'掩模': None,
'置信度': 0.8731599450111389,
'索引': None,
}>, <检测结果: {
...
...
...
}>
这让我们的生活变得更简单,可以专注于数据,而不是繁琐的格式转换细节。SAHI的get_sliced_prediction()
函数与get_prediction()
的工作方式类似,但增加了几个超参数,让我们配置图像如何被切片。特别是,我们可以指定切片的高度和宽度,以及它们之间的重叠。这里是一个例子:,
sliced_result = 获取切片预测结果(
dataset.skip(40).first().filepath, // 跳过前40个数据集条目并获取第一个文件路径
detection_model, // 检测模型
slice_height = 320, // 切片高度
slice_width = 320, // 切片宽度
overlap_height_ratio = 0.2, // 重叠高度比例
overlap_width_ratio = 0.2, // 重叠宽度比例
)
作为初步检查,我们可以比较切分预测结果的数量与原始的预测结果的数量,
num_sliced_dets = len(sliced_result.to_fiftyone_detections())
num_orig_dets = len(result.to_fiftyone_detections())
print(f"未切片预测的检测数:{num_orig_dets}")
print(f"切片预测的检测数:{num_sliced_dets}")
我们注意到预测的数量显著增加了!我们还需要确定这些新增的预测是否有效,还是仅仅增加了更多的假阳性结果。我们会用FiftyOne的评估API来解决这个问题。我们还想为我们的分割找到一组合适的超参数。为了完成这些任务,我们需要将SAHI应用于整个数据集。我们现在就开始吧!
为了简化这个过程,我们将定义一个函数,该函数将把预测添加到样本的指定标签字段中,然后我们将遍历数据集,对每个样本应用该函数。此函数会将样本的文件路径和切片超参数传递给get_sliced_prediction()
,然后将预测添加到样本的指定标签字段。
def predict_with_slicing(sample, label_field, **kwargs):
# 获取切片预测结果
result = get_sliced_prediction(
sample.filepath, detection_model, verbose=0, **kwargs
)
# 将预测结果转换为FiftyOne格式的检测结果
sample[label_field] = fo.Detections(detections=result.to_fiftyone_detections())
我们将重叠部分固定为0.2,看看切片的高度和宽度会如何影响预测质量。
kwargs = {"overlap_height_ratio": 0.2, "overlap_width_ratio": 0.2}
# 遍历数据集中的样本,并设置进度条和自动保存选项。
for sample in dataset.iter_samples(progress=True, autosave=True):
# 使用切片预测,标签字段为"small_slices",切片高度和宽度分别为320。
predict_with_slicing(sample, label_field="small_slices", slice_height=320, slice_width=320, **kwargs)
# 使用切片预测,标签字段为"large_slices",切片高度和宽度分别为480。
predict_with_slicing(sample, label_field="large_slices", slice_height=480, slice_width=480, **kwargs)
请注意,这些推断时间比最初的推断时间长得多。这是因为我们在每张图像上运行模型的多个部分,增加了模型需要进行的前向传递次数。我们采取了权衡以更好地检测小物体。
我们现在再筛选标签,只保留我们感兴趣的类别,并在FiftyOne应用中可视化结果。
filtered_view = 对映射视图进行所有标签过滤 session = fo启动应用程序(filtered_view, auto=False),创建会话
结果确实看起来很有希望!从几个视觉示例来看,切片似乎改善了地面实况检测的覆盖率,尤其是较小的切片似乎捕获了更多的 person
检测,比如更多的检测结果。但我们怎么肯定这一点呢?让我们运行一个评估例程,将检测标记为真正例、假正例或假负例,以比较切片预测与地面实况。我们将使用过滤视图中的 evaluate_detections()
方法。
继续使用我们过滤后的数据集视角,让我们运行一个评估流程,将每个预测标签字段的预测与真实标签进行比较。这里,我们使用默认的 IoU 阈值 0.5,但您可以根据需要调整该阈值。
# 基础模型的检测评估
base_results = filtered_view.evaluate_detections("基础模型", gt_field="ground_truth", eval_key="eval_base_model")
# 大切片的检测评估
large_slice_results = filtered_view.evaluate_detections("大切片", gt_field="ground_truth", eval_key="eval_large_slices")
# 小切片的检测评估
small_slice_results = filtered_view.evaluate_detections("小切片", gt_field="ground_truth", eval_key="eval_small_slices")
这是用于评估不同检测模型结果的代码片段:基础模型、大切片和小切片。
让我们为每一个都打印一份报告。
print("基础模型的结果:")
base_results.print_report()
print("(打印50个破折号)")
print("大切片的结果:")
large_slice_results.print_report()
print("(打印50个破折号)")
print("小切片的结果:")
small_slice_results.print_report()
基础模型结果:
准确率 查准率 F1值 支持
car 0.81 0.55 0.66 692
person 0.94 0.16 0.28 7475
truck 0.66 0.34 0.45 265
微平均值 0.89 0.20 0.33 8432
宏平均值 0.80 0.35 0.46 8432
加权平均值 0.92 0.20 0.31 8432
--------------------------------------------------
大样本结果:
准确率 查准率 F1值 支持
car 0.67 0.71 0.69 692
person 0.89 0.34 0.49 7475
truck 0.55 0.45 0.49 265
微平均值 0.83 0.37 0.51 8432
宏平均值 0.70 0.50 0.56 8432
加权平均值 0.86 0.37 0.51 8432
--------------------------------------------------
小样本结果:
准确率 查准率 F1值 支持
car 0.66 0.75 0.70 692
person 0.84 0.42 0.56 7475
truck 0.49 0.46 0.47 265
微平均值 0.80 0.45 0.57 8432
宏平均值 0.67 0.54 0.58 8432
加权平均值 0.82 0.45 0.57 8432
我们可以看到,当我们增加切片时,误报数量增加,而漏报数量减少。这是可以预料到的,因为模型能够检测到更多的对象,但同时也犯了更多的错误。你可以通过采用更激进的置信阈值来对抗误报的增加,但即使不这样做,F1分数也已经显著提升了。
让我们更深入地探讨这些结果。我们之前注意到该模型在识别小物体方面遇到挑战,所以让我们看看这三种方法在处理这些小物体时的表现如何。我们可以使用FiftyOne的ViewField功能来筛选这些对象。
## 只筛选那些小框框
box_width, box_height = F("bounding_box")[2], F("bounding_box")[3]
rel_bbox_area = box_width * box_height # 这样就能算出相对框面积了
im_width, im_height = F("$metadata.width"), F("$metadata.height")
abs_area = rel_bbox_area * im_width * im_height # 这样就能算出绝对面积了
small_boxes_view = filtered_view # 先把过滤过的视图保存下来
for lf in get_label_fields(filtered_view): # 从过滤过的视图中获取标签字段
small_boxes_view = small_boxes_view.filter_labels(lf, abs_area < 32**2, only_matches=False) # 按照绝对面积小于32²的条件过滤标签
session.view = small_boxes_view.view() # 最后将处理后的视图应用到会话中
如果我们像以前一样,在这些视图上评估我们的模型并打印报告的话,我们可以清楚地看到SAHI所提供的价值!使用SAHI时,小物体的召回率显著提高,而精度没有明显下降,这使F1分数得到提升。这一点在person
检测中尤为明显,F1分数翻了三倍!
## 只在小盒子上进行评估
small_boxes_base_results = small_boxes_view.evaluate_detections("base_model", gt_field="ground_truth", eval_key="eval_small_boxes_base_model")
small_boxes_large_slice_results = small_boxes_view.evaluate_detections("large_slices", gt_field="ground_truth", eval_key="eval_small_boxes_large_slices")
small_boxes_small_slice_results = small_boxes_view.evaluate_detections("small_slices", gt_field="ground_truth", eval_key="eval_small_boxes_small_slices")
## 打印评估报告
print("小盒子 — 基础模型的评估结果如下:")
small_boxes_base_results.print_report()
print("-" * 50)
print("小盒子 — 大切片的评估结果如下:")
small_boxes_large_slice_results.print_report()
print("-" * 50)
print("小盒子 — 小切片的评估结果如下:")
small_boxes_small_slice_results.print_report()
小盒子 — 基础模型结果:
精确度, 查全率, F1值, 支持数,
汽车 0.71, 0.25, 0.37, 147,
人 0.83, 0.08, 0.15, 5710,
货车 0.00, 0.00, 0.00, 28,
微平均值 0.82, 0.08, 0.15, 5885,
宏平均值 0.51, 0.11, 0.17, 5885,
加权平均值 0.82, 0.08, 0.15, 5885,
--------------------------------------------------
小盒子的大样本结果:
精确度, 查全率, F1值, 支持数,
汽车 0.46, 0.48, 0.47, 147,
人 0.82, 0.23, 0.35, 5710,
货车 0.20, 0.07, 0.11, 28,
微平均值 0.78, 0.23, 0.36, 5885,
宏平均值 0.49, 0.26, 0.31, 5885,
加权平均值 0.80, 0.23, 0.36, 5885,
--------------------------------------------------
小盒子的小样本结果:
精确度, 查全率, F1值, 支持数,
汽车 0.42, 0.53, 0.47, 147,
人 0.79, 0.31, 0.45, 5710,
货车 0.21, 0.18, 0.19, 28,
微平均值 0.75, 0.32, 0.45, 5885,
宏平均值 0.47, 0.34, 0.37, 5885,
加权平均值 0.77, 0.32, 0.45, 5885
接下来会是什么
在这次指南中,我们讲解了如何将SAHI预测添加到您的数据中,并且严格评估了切片增强超推理(SAHI)对预测质量的影响。我们看到了切片增强超推理如何在无需在大图像上训练模型的情况下,提升检测的召回率和F1值,特别是对于小物体的检测。
为了更好地发挥SAHI的作用,你可以试试下面的几点:
- 切片参数,例如切片高度和宽度以及重叠
- 基础对象检测模型,例如 SAHI 兼容许多模型,包括 YOLOv5 和 Hugging Face Transformers 模型
- 设置类别的置信度阈值,以减少误报的数量
- 后处理方法,如 非极大值抑制 (NMS),以减少重叠检测的数量
无论你想调整哪些设置,重要的是要超越单一指标的视角。在处理小目标检测任务时,图像中的小目标越多,标记缺失的可能性也越大。SAHI 可以帮你发现潜在的错误,你可以通过人机协作(HITL)工作流程来纠正这些错误。
这里还有一些你可能会觉得有用的资源。