切片辅助超推理 (SAHI) 提高检测的召回率和 F1 分数的图像智能识别训练模型

切片辅助超推理 (SAHI) 提高检测的召回率和 F1 分数的图像智能识别训练模型部署指南

使用切片辅助超推理

img

物体检测是计算机视觉领域的基本任务之一。从高层次上讲,它涉及预测图像中物体的位置和类别。最先进的 (SOTA) 深度学习模型(例如You-Only-Look-Once (YOLO)系列中的模型)已经达到了惊人的准确度。然而,物体检测领域一个众所周知的挑战性前沿是小物体。

在本文中,您将学习如何使用切片辅助超推理(SAHI) 检测数据集中的小物体。我们将介绍以下内容:

  • 为什么难以检测小物体
  • SAHI 的工作原理
  • 如何将 SAHI 应用于你的数据集,以及
  • 如何评估这些预测的质量

为何检测小物体如此困难?

他们很小

首先,检测小物体很难,因为小物体本身就很小。物体越小,检测模型需要处理的信息就越少。如果汽车距离很远,它可能只占据我们图像中的几个像素。就像人类很难辨别远处的物体一样,如果没有车轮和车牌等视觉上可辨别的特征,我们的模型就很难识别汽车!

训练数据

模型的好坏取决于训练模型所用的数据。大多数标准物体检测数据集和基准都侧重于中大型物体,这意味着大多数现成的物体检测模型并未针对小物体检测进行优化。

固定输入大小

物体检测模型通常采用固定大小的输入。例如,YOLOv8 在最大边长为 640 像素的图像上进行训练。这意味着当我们输入 1920x1080 大小的图像时,该模型会在进行预测之前将图像下采样为 640x360,从而降低分辨率并丢弃小物体的重要信息。

SAHI 的工作原理

img

切片辅助超推理的图示。图片由SAHI GitHub Repo提供。

理论上,你可以在更大的图像上训练模型,以改进对小物体的检测。但实际上,这需要更多的内存、更多的计算能力,以及需要更多人力才能创建的数据集。

另一种方法是利用现有的物体检测,将模型应用于图像中固定大小的块或切片,然后将结果拼接在一起。这就是切片辅助超推理背后的想法!

SAHI 的工作原理是将图像分成完全覆盖图像的切片,并使用指定的检测模型对每个切片进行推理。然后将所有这些切片的预测合并在一起,以生成整个图像的检测列表。SAHI 中的“超”来自于这样一个事实:SAHI 的输出不是模型推理的结果,而是涉及多个模型推理的计算的结果。

💡SAHI 切片可以重叠(如上图 GIF 所示),这有助于确保至少一个切片中有足够的物体可供检测。

使用 SAHI 的主要优势在于它与模型无关。SAHI 可以利用当今的 SOTA 对象检测模型以及未来的 SOTA 模型!

当然,天下没有免费的午餐。作为“超推理”的交换,除了将结果拼接在一起所需的处理之外,你还需要运行检测模型的多次前向传递。

设置

为了说明如何应用 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 安装 -U fiftyone sahi ultralytics huggingface_hub --quiet

现在在 Python 进程中,让我们导入用于查询和管理数据的 FiftyOne 模块:

导入fiftyone作为fo
导入fiftyone.zoo作为foz
导入fiftyone.utils.huggingface作为fouh
从fiftyone导入ViewField作为F

就这样,我们就可以加载数据了!我们将使用FiftyOne 的 Hugging Face 实用程序load_from_hub()中的函数,通过其直接从 Hugging Face Hub加载 VisDrone 数据集的一部分。repo_id

为了演示并尽可能快地执行代码,我们将仅从数据集中获取前 100 张图像。我们还将为这个正在创建的新数据集命名”sahi-test”

数据集 = fouh.load_from_hub(
    “Voxel51/VisDrone2019-DET”,
    名称= “sahi-test”,
    max_samples= 100
)

在添加任何预测之前,让我们先看看 FiftyOne 应用程序中的数据集:

会话 = fo.launch_app(数据集)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

💡查看FiftyOne 的 Hugging Face Integration了解更多信息。

使用 YOLOv8 进行标准推理

在下一节中,我们将使用 SAHI 对数据进行超推理。在引入 SAHI 之前,让我们使用 Ultralytics 的 YOLOv8 模型的大型变体对我们的数据进行标准对象检测推理。

首先,我们创建一个ultralytics.YOLO模型实例,并在必要时下载模型检查点。然后,我们将此模型应用于我们的数据集,并将结果存储在”base_model”样本的字段中:

从ultralytics导入YOLO 

ckpt_path = “yolov8l.pt”
模型 = YOLO(ckpt_path)

数据集.apply_model(模型,label_field= “base_model” ) 
session.view = dataset.view()

img

💡查看FiftyOne 的 Ultralytics 集成以了解更多信息。

通过查看模型的预测和真实值标签,我们可以看到一些事情。首先,我们的 YOLOv8l 模型检测到的类别与 VisDrone 数据集中的真实值类别不同。我们的 YOLO 模型是在COCO 数据集上训练的,该数据集有 80 个类别,而 VisDrone 数据集有 12 个类别,包括一个ignore_regions类别。

为了简化比较,我们将仅关注数据集中最常见的几个类,并将 VisDrone 类映射到 COCO 类,如下所示:

映射 = { “行人”:“人”,“人”:“人”,“货车”:“汽车” } 
mapped_view = dataset.map_labels(“ground_truth”,映射)

然后过滤标签,仅包含我们感兴趣的类别:

def  get_label_fields ( sample_collection ): 
    """获取 Dataset 或 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
         ) 
    returnfiltered_viewfiltered_view = filter_all_labels( mapped_view 

) 
session.view =filtered_view.view()

img

现在我们有了基本模型预测,让我们使用 SAHI 来对图像进行切片和切块💪。

使用 SAHI 进行超推理

SAHI 技术在我们之前安装的 Python 包中实现sahi。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" , # 或 'cuda' 如果您可以访问 GPU
 )

在生成切片预测之前,让我们使用 SAHI 函数检查模型在试验图像上的预测get_prediction()

result = get_prediction(dataset.first().filepath,detection_model)
打印(结果)
<sahi.prediction.PredictionResult 对象位于 0x2b0e9c250>

幸运的是,SAHI 结果对象有一个to_fiftyone_detections()方法,可以将结果转换为 FiftyOneDetection对象的列表:

打印(result.to_fiftyone_detections())
[<检测:{ 
    'id':'661858c20ae3edf77139db7a',
    '属性':{},
    '标签':[],
    '标签':'汽车',
    'bounding_box':[ 
        0.6646394729614258,0.7850866247106482,0.06464214324951172,0.09088355170355902         ,    ],    '掩码':无,    '置信度':0.8933132290840149,    '索引':无, }         > , 
        <检测:{     'id':'661858c20ae3edf77139db7b',    '属性':{},    '标签':[],    '标签':'汽车',    'bounding_box':[         0.6196376800537109,         0.7399617513020833,         0.06670347849527995,         0.09494832356770834,    ],    '蒙版':无,    '置信度':0.8731599450111389,    '索引':无,}>,<检测:{    ....    ....    ....

这让我们的生活变得轻松,我们可以专注于数据,而不是繁琐的格式转换细节。SAHIget_sliced_prediction()函数的工作方式与 相同get_prediction(),但增加了一些超参数,让我们可以配置图像的切片方式。具体来说,我们可以指定切片高度和宽度,以及切片之间的重叠。以下是一个例子:

切片结果 = 获取切片预测(
    数据集.skip(40).first().filepath,
    检测模型,
    切片高度 = 320,
    切片宽度 = 320,
    重叠高度比 = 0.2,
    重叠宽度比 = 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} " )

未切片预测的检测结果:17使用
切片预测的检测结果: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 }

对于数据集中的样本。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 应用程序中可视化结果:

过滤视图 = 过滤所有标签 (mapped_view)
会话 = fo.launch_app (过滤视图,自动 = False )

结果看起来确实很有希望!从几个视觉示例来看,切片似乎可以提高地面实况检测的覆盖率,尤其是较小的切片似乎可以捕获更多的检测person。但我们如何才能确定呢?让我们运行一个评估程序来将检测标记为真阳性、假阳性或假阴性,以将切片预测与地面实况进行比较。我们将使用我们的过滤视图evaluate_detections()方法。

评估 SAHI 预测

坚持使用数据集的过滤视图,让我们运行一个评估程序,将每个预测标签字段的预测与真实标签进行比较。在这里,我们使用默认的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” )

让我们为每个打印一份报告:

print ( "基本模型结果:" ) 
base_results.print_report() 

print ( "-" * 50 ) 
print ( "大切片结果:" ) 
large_slice_results.print_report() 

print ( "-" * 50 ) 
print ( "小切片结果:" ) 
small_slice_results.print_report()
基础模型结果:
              精确度 召回率 f1 分数支持

         汽车 0.81 0.55 0.66 692
      人 0.94 0.16 0.28 7475
       卡车 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 分数支持

         汽车 0.67 0.71 0.69 692
      人 0.89 0.34 0.49 7475
       卡车 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 分数 支持度

         汽车 0.66 0.75 0.70 692
      人 0.84 0.42 0.56 7475
       卡车 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 分数也会显著提高。

让我们更深入地研究一下这些结果。我们之前提到,该模型在处理小物体时会遇到困难,所以让我们看看这三种方法在处理小于 32x32 像素的物体时的表现如何。我们可以使用 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 ) 

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()打印( "-" * 50 )打印( "小框 - 大切片结果:" ) small_boxes_large_slice_results.print_report()打印( "-" * 50 )打印( "小框 - 小切片结果:" ) 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 预测添加到数据中,然后严格评估切片对预测质量的影响。我们已经了解了切片辅助超推理 (SAHI) 如何提高检测的召回率和 F1 分数,尤其是对于小物体,而无需在较大的图像上训练模型。

为了最大程度地提高 SAHI 的有效性,您可能需要尝试以下操作:

  • 切片超参数,例如切片高度和宽度以及重叠
  • 基础对象检测模型,因为 SAHI 与许多模型兼容,包括 YOLOv5 和 Hugging Face Transformers 模型
  • 可能以逐类为基础进行置信度阈值处理,以减少误报的数量
  • 后处理技术,例如非最大抑制(NMS),用于减少重叠检测的数量

无论您想转动哪个旋钮,重要的是不要只关注单一数字指标。在执行小物体检测任务时,图像中的小物体越多,缺少“地面真实”标签的可能性就越大。SAHI 可以帮助您找到潜在的错误,然后您可以使用人机交互 (HITL) 工作流程来纠正这些错误。

博客原文:专业人工智能社区

  • 12
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
SAHI是一种切片辅助推理框架,旨在帮助开发人员解决现实世界中的目标检测问题。它通过将图像分成多个切片提高检测性能,从而克服了现实世界中的一些问题,例如目标尺寸变化,目标遮挡和目标密度变化等。SAHI的核心思想是将图像分成多个切片,然后对每个切片进行单独的检测,最后将检测结果合并起来得到最终的检测结果。这种方法可以提高检测性能,特别是对于小目标的检测效果更好。 下面是一个使用SAHI进行目标检测的Python代码示例: ```python import cv2 import numpy as np # 加载图像 img = cv2.imread('test.jpg') # 定义切片大小 slice_size = 512 # 获取图像大小 height, width, _ = img.shape # 计算切片数量 num_slices_h = int(np.ceil(height / slice_size)) num_slices_w = int(np.ceil(width / slice_size)) # 定义检测器 detector = cv2.dnn.readNetFromCaffe('deploy.prototxt', 'model.caffemodel') # 定义类别标签 class_labels = ['person', 'car', 'truck', 'bus'] # 定义检测结果列表 results = [] # 循环遍历每个切片 for i in range(num_slices_h): for j in range(num_slices_w): # 计算切片的坐标 x1 = j * slice_size y1 = i * slice_size x2 = min(x1 + slice_size, width) y2 = min(y1 + slice_size, height) # 提取切片 slice_img = img[y1:y2, x1:x2] # 构建输入blob blob = cv2.dnn.blobFromImage(slice_img, 1.0, (300, 300), (104.0, 177.0, 123.0)) # 进行检测 detector.setInput(blob) detections = detector.forward() # 解析检测结果 for k in range(detections.shape[2]): confidence = detections[0, 0, k, 2] class_id = int(detections[0, 0, k, 1]) # 如果置信度大于0.5,则将检测结果添加到列表中 if confidence > 0.5 and class_labels[class_id] == 'person': x = int(detections[0, 0, k, 3] * slice_size) + x1 y = int(detections[0, 0, k, 4] * slice_size) + y1 w = int(detections[0, 0, k, 5] * slice_size) - x h = int(detections[0, 0, k, 6] * slice_size) - y results.append((x, y, w, h)) # 在原始图像上绘制检测结果 for (x, y, w, h) in results: cv2.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 2) # 显示结果 cv2.imshow('result', img) cv2.waitKey(0) cv2.destroyAllWindows() ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值