赞
踩
点击下方卡片,关注“小白玩转Python”公众号
利用切片辅助的超级推断
目标检测是计算机视觉中的基本任务之一。在高层次上,它涉及预测图像中物体的位置和类别。像You-Only-Look-Once(YOLO)系列中的最先进(SOTA)深度学习模型已经达到了令人瞩目的准确度。然而,目标检测中一个众所周知的挑战是小物体。在本文中,您将学习如何使用切片辅助的超级推断(SAHI)来检测数据集中的小物体。我们将涵盖以下内容:
为什么检测小物体很困难
SAHI 的工作原理
如何将 SAHI 应用于您的数据集
如何评估这些预测的质量
为什么检测小物体很困难?
它们很小
首先,检测小物体很困难,因为小物体很小。物体越小,检测模型可用的信息就越少。如果汽车远处,它可能只占据图像中的几个像素。与人类难以辨认远处物体的情况类似,我们的模型在没有视觉可辨识特征(如车轮和车牌)的情况下更难识别汽车!
训练数据
模型只有它们所训练的数据好。大多数标准的目标检测数据集和基准都专注于中大型物体,这意味着大多数现成的目标检测模型未经优化用于小物体检测。
固定的输入尺寸
目标检测模型通常采用固定尺寸的输入。例如,YOLOv8 是在最大边长为 640 像素的图像上训练的。这意味着当我们将一张大小为 1920x1080 的图像输入时,模型会将图像缩小到 640x360 然后进行预测,降低了小物体的分辨率并丢弃了重要信息。
SAHI 的工作原理
理论上,您可以在较大的图像上训练模型以提高小物体的检测能力。然而,在实际操作中,这将需要更多的内存、更多的计算能力和更加费时费力的数据集创建。与之相反的方法是利用现有的目标检测,将模型应用于图像中固定大小的块或切片,然后将结果拼接在一起。
SAHI 将图像分成完全覆盖它的切片,并对每个切片使用指定的检测模型进行推断。然后,将所有这些切片的预测结果合并在一起,生成整个图像上的一个检测列表。SAHI 中的“超级”来自于 SAHI 的输出不是模型推断的结果,而是涉及多个模型推断的计算结果。SAHI 切片允许重叠(如上面的 GIF 所示),这可以确保至少有一个切片中包含足够多的对象以进行检测。
设置
为了说明如何使用 SAHI 检测小物体,我们将使用中国天津大学机器学习与数据挖掘实验室的 AISKYEYE 团队的 VisDrone 检测数据集。该数据集包含 8,629 张图像,边长从 360 像素到 2,000 像素不等,使其成为 SAHI 的理想测试平台。Ultralytics 的 YOLOv8l 将作为我们的基本目标检测模型。我们将使用以下库:
fiftyone 用于数据集管理和可视化
huggingface_hub 用于从 Hugging Face Hub 加载 VisDrone 数据集
ultralytics 用于使用 YOLOv8 进行推断
sahi 用于在图像切片上运行推断
如果尚未安装,请安装这些库的最新版本。您需要 fiftyone>=0.23.8 来从 Hugging Face Hub 加载 VisDrone:
pip install -U fiftyone sahi ultralytics huggingface_hub --quiet
现在在 Python 进程中,让我们导入我们将用于查询和管理数据的 FiftyOne 模块:
- import fiftyone as fo
- import fiftyone.zoo as foz
- import fiftyone.utils.huggingface as fouh
- from fiftyone import ViewField as F
我们将使用 FiftyOne 的 Hugging Face 工具中的 load_from_hub() 函数直接从 Hugging Face Hub 通过 repo_id 加载 VisDrone 数据集的部分。为了演示并保持代码执行尽可能快,我们将只取数据集的前 100 张图像。我们还将给我们正在创建的新数据集命名为 ”sahi-test“:
使用YOLOv8进行推理
在接下来的部分,我们将使用SAHI对我们的数据进行超级推理。在引入SAHI之前,让我们使用Ultralytics的大型YOLOv8模型进行标准目标检测推理。首先,我们创建一个ultralytics.YOLO模型实例,如果需要的话会下载模型检查点。然后,我们将这个模型应用到我们的数据集上,并将结果存储在我们的样本的“base_model”字段中:
- from ultralytics import YOLO
-
-
- ckpt_path = "yolov8l.pt"
- model = YOLO(ckpt_path)
-
-
- dataset.apply_model(model, label_field="base_model")
- session.view = dataset.view()
通过观察模型的预测和ground truth标签,我们可以看到一些情况。首先,我们的YOLOv8l模型检测到的类与VisDrone数据集中的ground truth类不同。我们的YOLO模型是在COCO数据集上训练的,该数据集有80个类,而VisDrone数据集有12个类,包括一个 ignore_regions 类。为了简化比较,我们将专注于数据集中最常见的几个类,并将VisDrone类映射到COCO类,如下所示:
- mapping = {"pedestrians": "person", "people": "person", "van": "car"}
- mapped_view = dataset.map_labels("ground_truth", mapping)
然后只筛选我们感兴趣的类别:
- def get_label_fields(sample_collection):
- """Get the (detection) label fields of a Dataset or DatasetView."""
- 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 Python包中实现的。SAHI是一个与许多目标检测模型兼容的框架,包括YOLOv8。我们可以选择要使用的检测模型,并创建任何子类为 sahi.models.DetectionModel 的类的实例,包括 YOLOv8、YOLOv5,甚至是 Hugging Face Transformers 模型。我们将使用 SAHI 的 AutoDetectionModel 类来创建我们的模型对象,指定模型类型和检查点文件的路径:
- from sahi import AutoDetectionModel
- from sahi.predict import get_prediction, get_sliced_prediction
-
-
- detection_model = AutoDetectionModel.from_pretrained(
- model_type='yolov8',
- model_path=ckpt_path,
- confidence_threshold=0.25, ## same as the default value for our base model
- image_size=640,
- device="cpu", # or 'cuda' if you have access to GPU
- )
在生成切片预测之前,让我们使用 SAHI 的 get_prediction() 函数检查模型在一张试验图像上的预测结果:
- result = get_prediction(dataset.first().filepath, detection_model)
- print(result)
<sahi.prediction.PredictionResult object at 0x2b0e9c250>
幸运的是,SAHI 的结果对象有一个 to_fiftyone_detections() 方法,将结果转换为 FiftyOne 检测对象的列表:
print(result.to_fiftyone_detections())
- [<Detection: {
- 'id': '661858c20ae3edf77139db7a',
- 'attributes': {},
- 'tags': [],
- 'label': 'car',
- 'bounding_box': [
- 0.6646394729614258,
- 0.7850866247106482,
- 0.06464214324951172,
- 0.09088355170355902,
- ],
- 'mask': None,
- 'confidence': 0.8933132290840149,
- 'index': None,
- }>, <Detection: {
- 'id': '661858c20ae3edf77139db7b',
- 'attributes': {},
- 'tags': [],
- 'label': 'car',
- 'bounding_box': [
- 0.6196376800537109,
- 0.7399617513020833,
- 0.06670347849527995,
- 0.09494832356770834,
- ],
- 'mask': None,
- 'confidence': 0.8731599450111389,
- 'index': None,
- }>, <Detection: {
- ....
- ....
- ....
这样我们就可以专注于数据,而不是琐碎的格式转换细节。SAHI 的 get_sliced_prediction() 函数的工作方式与 get_prediction() 相同,但增加了一些额外的超参数,让我们可以配置图像如何被切片。特别是,我们可以指定切片的高度和宽度,以及切片之间的重叠。下面是一个示例:
- sliced_result = get_sliced_prediction(
- dataset.skip(40).first().filepath,
- 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"Detections predicted without slicing: {num_orig_dets}")
- print(f"Detections predicted with slicing: {num_sliced_dets}")
-
-
- Detections predicted without slicing: 17
- Detections predicted with slicing: 73
我们可以看到,预测数量大大增加了!我们还没有确定额外的预测是有效的还是只有更多的误报。我们稍后将使用 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
- )
- 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):
- predict_with_slicing(sample, label_field="small_slices", slice_height=320, slice_width=320, **kwargs)
- predict_with_slicing(sample, label_field="large_slices", slice_height=480, slice_width=480, **kwargs)
请注意,这些推理时间比原始推理时间要长得多。这是因为我们在每个图像上运行模型的多个切片,这增加了模型需要进行的前向传递次数。我们在进行权衡,以改善对小物体的检测。现在让我们再次筛选我们的标签,只包括我们感兴趣的类别,并在 FiftyOne App 中可视化结果:
- filtered_view = filter_all_labels(mapped_view)
- session = fo.launch_app(filtered_view, auto=False)
从几个视觉示例来看,切片似乎提高了ground truth检测的覆盖率,特别是较小的切片似乎更多地捕获了人物检测。但是我们如何确切地知道呢?让我们运行一个评估程序,将检测标记为真阳性、假阳性或假阴性,以将切片预测与ground truth进行比较。我们将使用我们筛选的视图的 evaluate_detections() 方法。
评估SAHI预测
继续使用我们数据集的筛选视图,让我们运行一个评估程序,将每个预测标签字段的预测与ground truth标签进行比较。在这里,我们使用默认的 IoU 阈值为0.5,但您可以根据需要进行调整:
- base_results = filtered_view.evaluate_detections("base_model", gt_field="ground_truth", eval_key="eval_base_model")
- large_slice_results = filtered_view.evaluate_detections("large_slices", gt_field="ground_truth", eval_key="eval_large_slices")
- small_slice_results = filtered_view.evaluate_detections("small_slices", gt_field="ground_truth", eval_key="eval_small_slices")
让我们输出每个report:
- print("Base model results:")
- base_results.print_report()
-
-
- print("-" * 50)
- print("Large slice results:")
- large_slice_results.print_report()
-
-
- print("-" * 50)
- print("Small slice results:")
- small_slice_results.print_report()
- Base model results:
- precision recall f1-score support
-
-
- car 0.81 0.55 0.66 692
- person 0.94 0.16 0.28 7475
- truck 0.66 0.34 0.45 265
-
-
- micro avg 0.89 0.20 0.33 8432
- macro avg 0.80 0.35 0.46 8432
- weighted avg 0.92 0.20 0.31 8432
-
-
- --------------------------------------------------
- Large slice results:
- precision recall f1-score support
-
-
- car 0.67 0.71 0.69 692
- person 0.89 0.34 0.49 7475
- truck 0.55 0.45 0.49 265
-
-
- micro avg 0.83 0.37 0.51 8432
- macro avg 0.70 0.50 0.56 8432
- weighted avg 0.86 0.37 0.51 8432
-
-
- --------------------------------------------------
- Small slice results:
- precision recall f1-score support
-
-
- car 0.66 0.75 0.70 692
- person 0.84 0.42 0.56 7475
- truck 0.49 0.46 0.47 265
-
-
- micro avg 0.80 0.45 0.57 8432
- macro avg 0.67 0.54 0.58 8432
- weighted avg 0.82 0.45 0.57 8432
我们可以看到,随着引入更多的切片,假阳性的数量增加,而假阴性的数量减少。这是预期的,因为模型能够使用更多的切片检测到更多的物体,但也会犯更多的错误!您可以对置信度进行更激进的阈值处理,以应对假阳性的增加,但即使不这样做,F1 分数也有了显著的提升。
让我们深入一点研究这些结果。我们之前指出模型在小物体上有困难,所以让我们看看这三种方法在小于 32x32 像素的物体上的表现如何。我们可以使用 FiftyOne 的 ViewField 来执行这种过滤:
## Filtering for only small boxes 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) session.view = small_boxes_view.view()
通过结果我们可以发现在使用 SAHI 时,小物体的召回率大大提高,而精确度没有明显下降,导致 F1 分数提升。这在人物检测中尤其明显,F1 分数增加了三倍!
## Evaluating on only small boxes 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") ## Printing reports print("Small Box — Base model results:") small_boxes_base_results.print_report() print("-" * 50) print("Small Box — Large slice results:") small_boxes_large_slice_results.print_report() print("-" * 50) print("Small Box — Small slice results:") small_boxes_small_slice_results.print_report()
- Small Box — Base model results:
- precision recall f1-score support
-
-
- car 0.71 0.25 0.37 147
- person 0.83 0.08 0.15 5710
- truck 0.00 0.00 0.00 28
-
-
- micro avg 0.82 0.08 0.15 5885
- macro avg 0.51 0.11 0.17 5885
- weighted avg 0.82 0.08 0.15 5885
-
-
- --------------------------------------------------
- Small Box — Large slice results:
- precision recall f1-score support
-
-
- car 0.46 0.48 0.47 147
- person 0.82 0.23 0.35 5710
- truck 0.20 0.07 0.11 28
-
-
- micro avg 0.78 0.23 0.36 5885
- macro avg 0.49 0.26 0.31 5885
- weighted avg 0.80 0.23 0.36 5885
-
-
- --------------------------------------------------
- Small Box — Small slice results:
- precision recall f1-score support
-
-
- car 0.42 0.53 0.47 147
- person 0.79 0.31 0.45 5710
- truck 0.21 0.18 0.19 28
-
-
- micro avg 0.75 0.32 0.45 5885
- macro avg 0.47 0.34 0.37 5885
- weighted avg 0.77 0.32 0.45 5885
后续工作
在本教程中,我们介绍了如何将 SAHI 预测添加到您的数据中,然后严格评估了切片对预测质量的影响。我们看到了切片辅助的超级推断(SAHI)如何在不需要在较大图像上训练模型的情况下,改善检测的召回率和 F1 分数,特别是对于小物体。为了最大程度地提高 SAHI 的效果,您可能希望尝试以下内容:
切片超参数,如切片高度和宽度以及重叠
基本目标检测模型,因为 SAHI 兼容许多模型,包括 YOLOv5 和 Hugging Face Transformers 模型
置信度阈值,可能是逐类别的,以减少误检的数量
后处理技术,如非极大值抑制(NMS),以减少重叠检测的数量
无论您想调整哪些参数,重要的是要超越单一数字指标。在处理小物体检测任务时,图像中的小物体越多,就越可能缺少“ground truth”标签。SAHI 可以帮助您发现潜在的错误,您可以通过人工干预工作流程来纠正。
· END ·
HAPPY LIFE
本文仅供学习交流使用,如有侵权请联系作者删除
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。