小目标检测实战

小白学视觉

共 14901字,需浏览 30分钟

 · 2024-04-30

点击上方小白学视觉”,选择加"星标"或“置顶

重磅干货,第一时间送达


利用切片辅助的超级推断

目标检测是计算机视觉中的基本任务之一。在高层次上,它涉及预测图像中物体的位置和类别。像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 foimport fiftyone.zoo as fozimport fiftyone.utils.huggingface as fouhfrom 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 AutoDetectionModelfrom 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: 17Detections 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 8432weighted 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 8432weighted 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 8432weighted 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_viewfor 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 boxessmall_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 reportsprint("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 BoxBase 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 5885weighted avg 0.82 0.08 0.15 5885
--------------------------------------------------Small BoxLarge 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 5885weighted avg 0.80 0.23 0.36 5885
--------------------------------------------------Small BoxSmall 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 5885weighted avg 0.77 0.32 0.45 5885


后续工作


在本教程中,我们介绍了如何将 SAHI 预测添加到您的数据中,然后严格评估了切片对预测质量的影响。我们看到了切片辅助的超级推断(SAHI)如何在不需要在较大图像上训练模型的情况下,改善检测的召回率和 F1 分数,特别是对于小物体。为了最大程度地提高 SAHI 的效果,您可能希望尝试以下内容:

  • 切片超参数,如切片高度和宽度以及重叠

  • 基本目标检测模型,因为 SAHI 兼容许多模型,包括 YOLOv5 和 Hugging Face Transformers 模型

  • 置信度阈值,可能是逐类别的,以减少误检的数量

  • 后处理技术,如非极大值抑制(NMS),以减少重叠检测的数量

无论您想调整哪些参数,重要的是要超越单一数字指标。在处理小物体检测任务时,图像中的小物体越多,就越可能缺少“ground truth”标签。SAHI 可以帮助您发现潜在的错误,您可以通过人工干预工作流程来纠正。


        
下载1:OpenCV-Contrib扩展模块中文版教程
在「小白学视觉」公众号后台回复:扩展模块中文教程即可下载全网第一份OpenCV扩展模块教程中文版,涵盖扩展模块安装、SFM算法、立体视觉、目标跟踪、生物视觉、超分辨率处理等二十多章内容。

下载2:Python视觉实战项目52讲
小白学视觉公众号后台回复:Python视觉实战项目即可下载包括图像分割、口罩检测、车道线检测、车辆计数、添加眼线、车牌识别、字符识别、情绪检测、文本内容提取、面部识别等31个视觉实战项目,助力快速学校计算机视觉。

下载3:OpenCV实战项目20讲
小白学视觉公众号后台回复:OpenCV实战项目20讲即可下载含有20个基于OpenCV实现20个实战项目,实现OpenCV学习进阶。

交流群


欢迎加入公众号读者群一起和同行交流,目前有SLAM、三维视觉、传感器自动驾驶、计算摄影、检测、分割、识别、医学影像、GAN算法竞赛等微信群(以后会逐渐细分),请扫描下面微信号加群,备注:”昵称+学校/公司+研究方向“,例如:”张三 + 上海交大 + 视觉SLAM“。请按照格式备注,否则不予通过。添加成功后会根据研究方向邀请进入相关微信群。请勿在群内发送广告,否则会请出群,谢谢理解~


浏览 7
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报